diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 5280e72aa24b..d821dc022b6e 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -141,7 +141,7 @@ def cache(custom_path = nil, local = false) end end - Dir[cache_path.join("*/.git")].each do |git_dir| + Gem::Util.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir| FileUtils.rm_rf(git_dir) FileUtils.touch(File.expand_path("../.bundlecache", git_dir)) end @@ -159,13 +159,13 @@ def prune_cache(cache_path) end def clean(dry_run = false) - gem_bins = Dir["#{Gem.dir}/bin/*"] - git_dirs = Dir["#{Gem.dir}/bundler/gems/*"] - git_cache_dirs = Dir["#{Gem.dir}/cache/bundler/git/*"] - gem_dirs = Dir["#{Gem.dir}/gems/*"] - gem_files = Dir["#{Gem.dir}/cache/*.gem"] - gemspec_files = Dir["#{Gem.dir}/specifications/*.gemspec"] - extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"] + gem_bins = Gem::Util.glob_files_in_dir("bin/*", Gem.dir) + git_dirs = Gem::Util.glob_files_in_dir("bundler/gems/*", Gem.dir) + git_cache_dirs = Gem::Util.glob_files_in_dir("cache/bundler/git/*", Gem.dir) + gem_dirs = Gem::Util.glob_files_in_dir("gems/*", Gem.dir) + gem_files = Gem::Util.glob_files_in_dir("cache/*.gem", Gem.dir) + gemspec_files = Gem::Util.glob_files_in_dir("specifications/*.gemspec", Gem.dir) + extension_dirs = Gem::Util.glob_files_in_dir("extensions/*/*/*", Gem.dir) + Gem::Util.glob_files_in_dir("bundler/gems/extensions/*/*/*", Gem.dir) spec_gem_paths = [] # need to keep git sources around spec_git_paths = @definition.spec_git_paths @@ -232,7 +232,7 @@ def clean(dry_run = false) private def prune_gem_cache(resolve, cache_path) - cached = Dir["#{cache_path}/*.gem"] + cached = Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s) cached = cached.delete_if do |path| spec = Bundler.rubygems.spec_from_gem path @@ -257,7 +257,7 @@ def prune_gem_cache(resolve, cache_path) end def prune_git_and_path_cache(resolve, cache_path) - cached = Dir["#{cache_path}/*/.bundlecache"] + cached = Gem::Util.glob_files_in_dir("*/.bundlecache", cache_path.to_s) cached = cached.delete_if do |path| name = File.basename(File.dirname(path)) @@ -283,7 +283,7 @@ def setup_manpath # Add man/ subdirectories from activated bundles to MANPATH for man(1) manuals = $LOAD_PATH.filter_map do |path| man_subdir = path.sub(/lib$/, "man") - man_subdir unless Dir[man_subdir + "/man?/"].empty? + man_subdir unless Dir.glob("man?/", base: man_subdir).empty? end return if manuals.empty? diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index a002a2570a8a..ae6fd5ecdda4 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -325,7 +325,7 @@ def humanized_ref def serialize_gemspecs_in(destination) destination = destination.expand_path(Bundler.root) if destination.relative? - Dir["#{destination}/#{@glob}"].each do |spec_path| + Gem::Util.glob_files_in_dir(@glob, destination.to_s).each do |spec_path| # Evaluate gemspecs and cache the result. Gemspecs # in git might require git or other dependencies. # The gemspecs we cache should already be evaluated. diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 0a0914f6c6e3..8c21864b0828 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -406,7 +406,7 @@ def cached_specs @cached_specs ||= begin idx = Index.new - Dir["#{cache_path}/*.gem"].each do |gemfile| + Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile| s ||= Bundler.rubygems.spec_from_gem(gemfile) s.source = self idx << s diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 0ed7fc60bbe4..61d35307f4ea 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -306,9 +306,7 @@ def source_paths # Return all files in this gem that match for +glob+. def matches_for_glob(glob) # TODO: rename? - glob = File.join(lib_dirs_glob, glob) - - Dir[glob] + Gem::Util.glob_files_in_dir(File.join(lib_dirs, glob), full_gem_path) end ## @@ -323,17 +321,7 @@ def plugins # for this spec. def lib_dirs_glob - dirs = if raw_require_paths - if raw_require_paths.size > 1 - "{#{raw_require_paths.join(",")}}" - else - raw_require_paths.first - end - else - "lib" # default value for require_paths for bundler/inline - end - - "#{full_gem_path}/#{dirs}" + "#{full_gem_path}/#{lib_dirs}" end ## @@ -364,6 +352,22 @@ def this private + ## + # Returns the require_paths of this gem as a string usable in Dir.glob, + # relative to full_gem_path. + + def lib_dirs + if raw_require_paths + if raw_require_paths.size > 1 + "{#{raw_require_paths.join(",")}}" + else + raw_require_paths.first + end + else + "lib" # default value for require_paths for bundler/inline + end + end + def have_extensions? !extensions.empty? end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index ee4106c6cea0..526c612b40c8 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -76,7 +76,9 @@ def self.traverse_parents(directory, &block) ## # Globs for files matching +pattern+ inside of +directory+, - # returning absolute paths to the matching files. + # returning absolute paths to the matching files. Unlike a plain + # Dir.glob with an interpolated path, glob metacharacters in + # +base_path+ are not treated as part of the pattern. def self.glob_files_in_dir(glob, base_path) Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) } diff --git a/spec/commands/clean_spec.rb b/spec/commands/clean_spec.rb index c77859d37812..9d8bfa3b59af 100644 --- a/spec/commands/clean_spec.rb +++ b/spec/commands/clean_spec.rb @@ -46,6 +46,34 @@ def should_not_have_gems(*gems) expect(vendored_gems("bin/myrackup")).to exist end + it "removes unused gems when the bundle path contains glob metacharacters" do + gemfile <<-G + source "https://gem.repo1" + + gem "thin" + gem "foo" + G + + bundle_config "path vendor/dir[1]" + bundle_config "clean false" + bundle "install" + + gemfile <<-G + source "https://gem.repo1" + + gem "thin" + G + bundle "install" + + bundle :clean + + expect(out).to include("Removing foo (1.0)") + + metachar_vendored_gems = scoped_gem_path(bundled_app("vendor/dir[1]")) + expect(metachar_vendored_gems.join("gems/foo-1.0")).not_to exist + expect(metachar_vendored_gems.join("gems/thin-1.0")).to exist + end + it "removes old version of gem if unused" do gemfile <<-G source "https://gem.repo1" diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb index ee4c91a4f2b4..af01734f5e20 100644 --- a/spec/install/gemfile/git_spec.rb +++ b/spec/install/gemfile/git_spec.rb @@ -119,6 +119,26 @@ expect(file_code).to eq(ruby_code) end + it "caches the evaluated gemspec when the bundle path contains glob metacharacters" do + bundle_config "path vendor/dir[1]" + install_base_gemfile + git = update_git "foo" do |s| + s.executables = ["foobar"] # we added this the first time, so keep it now + s.files = ["bin/foobar"] # updating git nukes the files list + foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")') + s.write "foo.gemspec", foospec + end + + bundle "update foo" + + sha = git.ref_for("main", 11) + spec_file = scoped_gem_path(bundled_app("vendor/dir[1]")).join("bundler/gems/foo-1.0-#{sha}/foo.gemspec") + expect(spec_file).to exist + ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby + file_code = File.read(spec_file) + expect(file_code).to eq(ruby_code) + end + it "does not update the git source implicitly" do install_base_gemfile update_git "foo" diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb index 6c07480c7fd4..744ffa7d059e 100644 --- a/test/rubygems/test_gem_stub_specification.rb +++ b/test/rubygems/test_gem_stub_specification.rb @@ -118,6 +118,31 @@ def test_matches_for_glob assert_equal code_rb, stub.matches_for_glob("code*").first end + def test_matches_for_glob_with_glob_metacharacters_in_gem_dir + base_dir = File.join @tempdir, "dir[1]" + gems_dir = File.join base_dir, "gems" + spec_dir = File.join base_dir, "specifications" + FileUtils.mkdir_p spec_dir + + spec = File.join spec_dir, "stub-2.gemspec" + File.write spec, <<~STUB + # -*- encoding: utf-8 -*- + # stub: stub 2 ruby lib + + Gem::Specification.new do |s| + s.name = 'stub' + s.version = Gem::Version.new '2' + end + STUB + + stub = Gem::StubSpecification.gemspec_stub spec, base_dir, gems_dir + code_rb = File.join stub.gem_dir, "lib", "code.rb" + FileUtils.mkdir_p File.dirname code_rb + FileUtils.touch code_rb + + assert_equal code_rb, stub.matches_for_glob("code*").first + end + def test_matches_for_glob_with_bundler_inline stub = stub_with_extension code_rb = File.join stub.gem_dir, "lib", "code.rb"