Skip to content

Commit 666bca3

Browse files
authored
Merge pull request #9687 from ruby/glob-metachar-install-paths
Escape glob metacharacters in install paths when globbing
2 parents 4bc3116 + bbf936e commit 666bca3

8 files changed

Lines changed: 107 additions & 28 deletions

File tree

lib/bundler/runtime.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def cache(custom_path = nil, local = false)
141141
end
142142
end
143143

144-
Dir[cache_path.join("*/.git")].each do |git_dir|
144+
Gem::Util.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir|
145145
FileUtils.rm_rf(git_dir)
146146
FileUtils.touch(File.expand_path("../.bundlecache", git_dir))
147147
end
@@ -159,13 +159,13 @@ def prune_cache(cache_path)
159159
end
160160

161161
def clean(dry_run = false)
162-
gem_bins = Dir["#{Gem.dir}/bin/*"]
163-
git_dirs = Dir["#{Gem.dir}/bundler/gems/*"]
164-
git_cache_dirs = Dir["#{Gem.dir}/cache/bundler/git/*"]
165-
gem_dirs = Dir["#{Gem.dir}/gems/*"]
166-
gem_files = Dir["#{Gem.dir}/cache/*.gem"]
167-
gemspec_files = Dir["#{Gem.dir}/specifications/*.gemspec"]
168-
extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]
162+
gem_bins = Gem::Util.glob_files_in_dir("bin/*", Gem.dir)
163+
git_dirs = Gem::Util.glob_files_in_dir("bundler/gems/*", Gem.dir)
164+
git_cache_dirs = Gem::Util.glob_files_in_dir("cache/bundler/git/*", Gem.dir)
165+
gem_dirs = Gem::Util.glob_files_in_dir("gems/*", Gem.dir)
166+
gem_files = Gem::Util.glob_files_in_dir("cache/*.gem", Gem.dir)
167+
gemspec_files = Gem::Util.glob_files_in_dir("specifications/*.gemspec", Gem.dir)
168+
extension_dirs = Gem::Util.glob_files_in_dir("extensions/*/*/*", Gem.dir) + Gem::Util.glob_files_in_dir("bundler/gems/extensions/*/*/*", Gem.dir)
169169
spec_gem_paths = []
170170
# need to keep git sources around
171171
spec_git_paths = @definition.spec_git_paths
@@ -232,7 +232,7 @@ def clean(dry_run = false)
232232
private
233233

234234
def prune_gem_cache(resolve, cache_path)
235-
cached = Dir["#{cache_path}/*.gem"]
235+
cached = Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s)
236236

237237
cached = cached.delete_if do |path|
238238
spec = Bundler.rubygems.spec_from_gem path
@@ -257,7 +257,7 @@ def prune_gem_cache(resolve, cache_path)
257257
end
258258

259259
def prune_git_and_path_cache(resolve, cache_path)
260-
cached = Dir["#{cache_path}/*/.bundlecache"]
260+
cached = Gem::Util.glob_files_in_dir("*/.bundlecache", cache_path.to_s)
261261

262262
cached = cached.delete_if do |path|
263263
name = File.basename(File.dirname(path))
@@ -283,7 +283,7 @@ def setup_manpath
283283
# Add man/ subdirectories from activated bundles to MANPATH for man(1)
284284
manuals = $LOAD_PATH.filter_map do |path|
285285
man_subdir = path.sub(/lib$/, "man")
286-
man_subdir unless Dir[man_subdir + "/man?/"].empty?
286+
man_subdir unless Dir.glob("man?/", base: man_subdir).empty?
287287
end
288288

289289
return if manuals.empty?

lib/bundler/source/git.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def humanized_ref
325325

326326
def serialize_gemspecs_in(destination)
327327
destination = destination.expand_path(Bundler.root) if destination.relative?
328-
Dir["#{destination}/#{@glob}"].each do |spec_path|
328+
Gem::Util.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
329329
# Evaluate gemspecs and cache the result. Gemspecs
330330
# in git might require git or other dependencies.
331331
# The gemspecs we cache should already be evaluated.

lib/bundler/source/rubygems.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def cached_specs
406406
@cached_specs ||= begin
407407
idx = Index.new
408408

409-
Dir["#{cache_path}/*.gem"].each do |gemfile|
409+
Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
410410
s ||= Bundler.rubygems.spec_from_gem(gemfile)
411411
s.source = self
412412
idx << s

lib/rubygems/basic_specification.rb

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,7 @@ def source_paths
306306
# Return all files in this gem that match for +glob+.
307307

308308
def matches_for_glob(glob) # TODO: rename?
309-
glob = File.join(lib_dirs_glob, glob)
310-
311-
Dir[glob]
309+
Gem::Util.glob_files_in_dir(File.join(lib_dirs, glob), full_gem_path)
312310
end
313311

314312
##
@@ -323,17 +321,7 @@ def plugins
323321
# for this spec.
324322

325323
def lib_dirs_glob
326-
dirs = if raw_require_paths
327-
if raw_require_paths.size > 1
328-
"{#{raw_require_paths.join(",")}}"
329-
else
330-
raw_require_paths.first
331-
end
332-
else
333-
"lib" # default value for require_paths for bundler/inline
334-
end
335-
336-
"#{full_gem_path}/#{dirs}"
324+
"#{full_gem_path}/#{lib_dirs}"
337325
end
338326

339327
##
@@ -364,6 +352,22 @@ def this
364352

365353
private
366354

355+
##
356+
# Returns the require_paths of this gem as a string usable in Dir.glob,
357+
# relative to full_gem_path.
358+
359+
def lib_dirs
360+
if raw_require_paths
361+
if raw_require_paths.size > 1
362+
"{#{raw_require_paths.join(",")}}"
363+
else
364+
raw_require_paths.first
365+
end
366+
else
367+
"lib" # default value for require_paths for bundler/inline
368+
end
369+
end
370+
367371
def have_extensions?
368372
!extensions.empty?
369373
end

lib/rubygems/util.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def self.traverse_parents(directory, &block)
7676

7777
##
7878
# Globs for files matching +pattern+ inside of +directory+,
79-
# returning absolute paths to the matching files.
79+
# returning absolute paths to the matching files. Unlike a plain
80+
# Dir.glob with an interpolated path, glob metacharacters in
81+
# +base_path+ are not treated as part of the pattern.
8082

8183
def self.glob_files_in_dir(glob, base_path)
8284
Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) }

spec/commands/clean_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ def should_not_have_gems(*gems)
4646
expect(vendored_gems("bin/myrackup")).to exist
4747
end
4848

49+
it "removes unused gems when the bundle path contains glob metacharacters" do
50+
gemfile <<-G
51+
source "https://gem.repo1"
52+
53+
gem "thin"
54+
gem "foo"
55+
G
56+
57+
bundle_config "path vendor/dir[1]"
58+
bundle_config "clean false"
59+
bundle "install"
60+
61+
gemfile <<-G
62+
source "https://gem.repo1"
63+
64+
gem "thin"
65+
G
66+
bundle "install"
67+
68+
bundle :clean
69+
70+
expect(out).to include("Removing foo (1.0)")
71+
72+
metachar_vendored_gems = scoped_gem_path(bundled_app("vendor/dir[1]"))
73+
expect(metachar_vendored_gems.join("gems/foo-1.0")).not_to exist
74+
expect(metachar_vendored_gems.join("gems/thin-1.0")).to exist
75+
end
76+
4977
it "removes old version of gem if unused" do
5078
gemfile <<-G
5179
source "https://gem.repo1"

spec/install/gemfile/git_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,26 @@
122122
expect(file_code.strip).to eq(ruby_code)
123123
end
124124

125+
it "caches the evaluated gemspec when the bundle path contains glob metacharacters" do
126+
bundle_config "path vendor/dir[1]"
127+
install_base_gemfile
128+
git = update_git "foo" do |s|
129+
s.executables = ["foobar"] # we added this the first time, so keep it now
130+
s.files = ["bin/foobar"] # updating git nukes the files list
131+
foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")')
132+
s.write "foo.gemspec", foospec
133+
end
134+
135+
bundle "update foo"
136+
137+
sha = git.ref_for("main", 11)
138+
spec_file = scoped_gem_path(bundled_app("vendor/dir[1]")).join("bundler/gems/foo-1.0-#{sha}/foo.gemspec")
139+
expect(spec_file).to exist
140+
ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby
141+
file_code = File.read(spec_file)
142+
expect(file_code).to eq(ruby_code)
143+
end
144+
125145
it "does not update the git source implicitly" do
126146
install_base_gemfile
127147
update_git "foo"

test/rubygems/test_gem_stub_specification.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,31 @@ def test_matches_for_glob
118118
assert_equal code_rb, stub.matches_for_glob("code*").first
119119
end
120120

121+
def test_matches_for_glob_with_glob_metacharacters_in_gem_dir
122+
base_dir = File.join @tempdir, "dir[1]"
123+
gems_dir = File.join base_dir, "gems"
124+
spec_dir = File.join base_dir, "specifications"
125+
FileUtils.mkdir_p spec_dir
126+
127+
spec = File.join spec_dir, "stub-2.gemspec"
128+
File.write spec, <<~STUB
129+
# -*- encoding: utf-8 -*-
130+
# stub: stub 2 ruby lib
131+
132+
Gem::Specification.new do |s|
133+
s.name = 'stub'
134+
s.version = Gem::Version.new '2'
135+
end
136+
STUB
137+
138+
stub = Gem::StubSpecification.gemspec_stub spec, base_dir, gems_dir
139+
code_rb = File.join stub.gem_dir, "lib", "code.rb"
140+
FileUtils.mkdir_p File.dirname code_rb
141+
FileUtils.touch code_rb
142+
143+
assert_equal code_rb, stub.matches_for_glob("code*").first
144+
end
145+
121146
def test_matches_for_glob_with_bundler_inline
122147
stub = stub_with_extension
123148
code_rb = File.join stub.gem_dir, "lib", "code.rb"

0 commit comments

Comments
 (0)