bundler 2.4.19 → 2.4.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -2
  3. data/lib/bundler/build_metadata.rb +3 -3
  4. data/lib/bundler/cli/check.rb +1 -1
  5. data/lib/bundler/cli/gem.rb +1 -3
  6. data/lib/bundler/cli/info.rb +1 -1
  7. data/lib/bundler/cli/install.rb +2 -2
  8. data/lib/bundler/cli/lock.rb +26 -23
  9. data/lib/bundler/cli/open.rb +5 -7
  10. data/lib/bundler/cli/update.rb +1 -0
  11. data/lib/bundler/definition.rb +42 -25
  12. data/lib/bundler/env.rb +2 -2
  13. data/lib/bundler/fetcher/base.rb +2 -2
  14. data/lib/bundler/fetcher/compact_index.rb +1 -5
  15. data/lib/bundler/fetcher/dependency.rb +1 -1
  16. data/lib/bundler/fetcher.rb +31 -30
  17. data/lib/bundler/gem_version_promoter.rb +2 -2
  18. data/lib/bundler/index.rb +62 -31
  19. data/lib/bundler/injector.rb +1 -1
  20. data/lib/bundler/installer/parallel_installer.rb +0 -26
  21. data/lib/bundler/installer/standalone.rb +15 -1
  22. data/lib/bundler/lockfile_parser.rb +32 -39
  23. data/lib/bundler/man/bundle-add.1 +1 -1
  24. data/lib/bundler/man/bundle-binstubs.1 +1 -1
  25. data/lib/bundler/man/bundle-cache.1 +1 -1
  26. data/lib/bundler/man/bundle-check.1 +1 -1
  27. data/lib/bundler/man/bundle-clean.1 +1 -1
  28. data/lib/bundler/man/bundle-config.1 +1 -1
  29. data/lib/bundler/man/bundle-console.1 +1 -1
  30. data/lib/bundler/man/bundle-doctor.1 +1 -1
  31. data/lib/bundler/man/bundle-exec.1 +2 -2
  32. data/lib/bundler/man/bundle-exec.1.ronn +2 -3
  33. data/lib/bundler/man/bundle-gem.1 +1 -1
  34. data/lib/bundler/man/bundle-help.1 +1 -1
  35. data/lib/bundler/man/bundle-info.1 +1 -1
  36. data/lib/bundler/man/bundle-init.1 +1 -1
  37. data/lib/bundler/man/bundle-inject.1 +1 -1
  38. data/lib/bundler/man/bundle-install.1 +1 -1
  39. data/lib/bundler/man/bundle-list.1 +1 -1
  40. data/lib/bundler/man/bundle-lock.1 +1 -1
  41. data/lib/bundler/man/bundle-open.1 +1 -1
  42. data/lib/bundler/man/bundle-outdated.1 +1 -1
  43. data/lib/bundler/man/bundle-platform.1 +1 -1
  44. data/lib/bundler/man/bundle-plugin.1 +17 -17
  45. data/lib/bundler/man/bundle-plugin.1.ronn +5 -5
  46. data/lib/bundler/man/bundle-pristine.1 +1 -1
  47. data/lib/bundler/man/bundle-remove.1 +1 -1
  48. data/lib/bundler/man/bundle-show.1 +1 -1
  49. data/lib/bundler/man/bundle-update.1 +1 -1
  50. data/lib/bundler/man/bundle-version.1 +1 -1
  51. data/lib/bundler/man/bundle-viz.1 +1 -1
  52. data/lib/bundler/man/bundle.1 +1 -1
  53. data/lib/bundler/man/gemfile.5 +12 -1
  54. data/lib/bundler/man/gemfile.5.ronn +5 -0
  55. data/lib/bundler/plugin.rb +1 -1
  56. data/lib/bundler/resolver/package.rb +5 -0
  57. data/lib/bundler/resolver.rb +45 -10
  58. data/lib/bundler/retry.rb +1 -1
  59. data/lib/bundler/ruby_dsl.rb +23 -2
  60. data/lib/bundler/ruby_version.rb +8 -1
  61. data/lib/bundler/self_manager.rb +2 -0
  62. data/lib/bundler/settings.rb +86 -25
  63. data/lib/bundler/shared_helpers.rb +16 -1
  64. data/lib/bundler/source/git/git_proxy.rb +27 -6
  65. data/lib/bundler/source/rubygems.rb +22 -25
  66. data/lib/bundler/spec_set.rb +2 -2
  67. data/lib/bundler/stub_specification.rb +4 -2
  68. data/lib/bundler/templates/newgem/Rakefile.tt +6 -2
  69. data/lib/bundler/templates/newgem/github/workflows/main.yml.tt +1 -1
  70. data/lib/bundler/version.rb +1 -1
  71. data/lib/bundler/yaml_serializer.rb +6 -7
  72. metadata +3 -3
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-PLUGIN" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-PLUGIN" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-plugin\fR \- Manage Bundler plugins
@@ -26,37 +26,37 @@ You can install, uninstall, and list plugin(s) with this command to extend funct
26
26
  .SS "install"
27
27
  Install the given plugin(s)\.
28
28
  .
29
- .IP "\(bu" 4
30
- \fBbundle plugin install bundler\-graph\fR: Install bundler\-graph gem from RubyGems\.org\. The global source, specified in source in Gemfile is ignored\.
29
+ .TP
30
+ \fBbundle plugin install bundler\-graph\fR
31
+ Install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. The global source, specified in source in Gemfile is ignored\.
31
32
  .
32
- .IP "\(bu" 4
33
- \fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR: Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\.
33
+ .TP
34
+ \fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR
35
+ Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\.
34
36
  .
35
- .IP "\(bu" 4
36
- \fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR: You can specify the version of the gem via \fB\-\-version\fR\.
37
+ .TP
38
+ \fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR
39
+ You can specify the version of the gem via \fB\-\-version\fR\.
37
40
  .
38
- .IP "\(bu" 4
39
- \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR: Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced with \fB\-\-local\-git\fR\. You cannot use both \fB\-\-git\fR and \fB\-\-local\-git\fR\. You can use standard Git URLs like:
41
+ .TP
42
+ \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR
43
+ Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced with \fB\-\-local\-git\fR\. You cannot use both \fB\-\-git\fR and \fB\-\-local\-git\fR\. You can use standard Git URLs like:
40
44
  .
41
- .IP "\(bu" 4
45
+ .IP
42
46
  \fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR
43
47
  .
44
- .IP "\(bu" 4
48
+ .br
45
49
  \fBhttp[s]://host\.xz[:port]/path/to/repo\.git\fR
46
50
  .
47
- .IP "\(bu" 4
51
+ .br
48
52
  \fB/path/to/repo\fR
49
53
  .
50
- .IP "\(bu" 4
54
+ .br
51
55
  \fBfile:///path/to/repo\fR
52
56
  .
53
- .IP "" 0
54
- .
55
57
  .IP
56
58
  When you specify \fB\-\-git\fR/\fB\-\-local\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. When you specify both, only the latter is used\.
57
59
  .
58
- .IP "" 0
59
- .
60
60
  .SS "uninstall"
61
61
  Uninstall the plugin(s) specified in PLUGINS\.
62
62
  .
@@ -20,7 +20,7 @@ You can install, uninstall, and list plugin(s) with this command to extend funct
20
20
  Install the given plugin(s).
21
21
 
22
22
  * `bundle plugin install bundler-graph`:
23
- Install bundler-graph gem from RubyGems.org. The global source, specified in source in Gemfile is ignored.
23
+ Install bundler-graph gem from globally configured sources (defaults to RubyGems.org). The global source, specified in source in Gemfile is ignored.
24
24
 
25
25
  * `bundle plugin install bundler-graph --source https://example.com`:
26
26
  Install bundler-graph gem from example.com. The global source, specified in source in Gemfile is not considered.
@@ -31,10 +31,10 @@ Install the given plugin(s).
31
31
  * `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`:
32
32
  Install bundler-graph gem from Git repository. `--git` can be replaced with `--local-git`. You cannot use both `--git` and `--local-git`. You can use standard Git URLs like:
33
33
 
34
- * `ssh://[user@]host.xz[:port]/path/to/repo.git`
35
- * `http[s]://host.xz[:port]/path/to/repo.git`
36
- * `/path/to/repo`
37
- * `file:///path/to/repo`
34
+ `ssh://[user@]host.xz[:port]/path/to/repo.git`<br>
35
+ `http[s]://host.xz[:port]/path/to/repo.git`<br>
36
+ `/path/to/repo`<br>
37
+ `file:///path/to/repo`
38
38
 
39
39
  When you specify `--git`/`--local-git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. When you specify both, only the latter is used.
40
40
 
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-PRISTINE" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-PRISTINE" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-REMOVE" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-REMOVE" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-remove\fR \- Removes gems from the Gemfile
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-SHOW" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-SHOW" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-UPDATE" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-UPDATE" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-update\fR \- Update your gems to the latest available versions
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-VERSION" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-VERSION" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-version\fR \- Prints Bundler version information
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE\-VIZ" "1" "August 2023" "" ""
4
+ .TH "BUNDLE\-VIZ" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "BUNDLE" "1" "August 2023" "" ""
4
+ .TH "BUNDLE" "1" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBbundle\fR \- Ruby Dependency Management
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "GEMFILE" "5" "August 2023" "" ""
4
+ .TH "GEMFILE" "5" "October 2023" "" ""
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
@@ -98,6 +98,17 @@ ruby file: "\.ruby\-version"
98
98
  .
99
99
  .IP "" 0
100
100
  .
101
+ .P
102
+ The version file should conform to any of the following formats:
103
+ .
104
+ .IP "\(bu" 4
105
+ \fB3\.1\.2\fR (\.ruby\-version)
106
+ .
107
+ .IP "\(bu" 4
108
+ \fBruby 3\.1\.2\fR (\.tool\-versions, read: https://asdf\-vm\.com/manage/configuration\.html#tool\-versions)
109
+ .
110
+ .IP "" 0
111
+ .
101
112
  .SS "ENGINE"
102
113
  Each application \fImay\fR specify a Ruby engine\. If an engine is specified, an engine version \fImust\fR also be specified\.
103
114
  .
@@ -74,6 +74,11 @@ you can use the `file` option instead.
74
74
 
75
75
  ruby file: ".ruby-version"
76
76
 
77
+ The version file should conform to any of the following formats:
78
+
79
+ - `3.1.2` (.ruby-version)
80
+ - `ruby 3.1.2` (.tool-versions, read: https://asdf-vm.com/manage/configuration.html#tool-versions)
81
+
77
82
  ### ENGINE
78
83
 
79
84
  Each application _may_ specify a Ruby engine. If an engine is specified, an
@@ -197,7 +197,7 @@ module Bundler
197
197
  # @param [Hash] The options that are present in the lock file
198
198
  # @return [API::Source] the instance of the class that handles the source
199
199
  # type passed in locked_opts
200
- def source_from_lock(locked_opts)
200
+ def from_lock(locked_opts)
201
201
  src = source(locked_opts["type"])
202
202
 
203
203
  src.new(locked_opts.merge("uri" => locked_opts["remote"]))
@@ -21,6 +21,7 @@ module Bundler
21
21
  @locked_version = locked_specs[name].first&.version
22
22
  @unlock = unlock
23
23
  @dependency = dependency || Dependency.new(name, @locked_version)
24
+ @top_level = !dependency.nil?
24
25
  @prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore
25
26
  end
26
27
 
@@ -32,6 +33,10 @@ module Bundler
32
33
  false
33
34
  end
34
35
 
36
+ def top_level?
37
+ @top_level
38
+ end
39
+
35
40
  def meta?
36
41
  @name.end_with?("\0")
37
42
  end
@@ -37,9 +37,17 @@ module Bundler
37
37
  root_version = Resolver::Candidate.new(0)
38
38
 
39
39
  @all_specs = Hash.new do |specs, name|
40
- specs[name] = source_for(name).specs.search(name).reject do |s|
41
- s.dependencies.any? {|d| d.name == name && !d.requirement.satisfied_by?(s.version) } # ignore versions that depend on themselves incorrectly
42
- end.sort_by {|s| [s.version, s.platform.to_s] }
40
+ source = source_for(name)
41
+ matches = source.specs.search(name)
42
+
43
+ # Don't bother to check for circular deps when no dependency API are
44
+ # available, since it's too slow to be usable. That edge case won't work
45
+ # but resolution other than that should work fine and reasonably fast.
46
+ if source.respond_to?(:dependency_api_available?) && source.dependency_api_available?
47
+ matches = filter_invalid_self_dependencies(matches, name)
48
+ end
49
+
50
+ specs[name] = matches.sort_by {|s| [s.version, s.platform.to_s] }
43
51
  end
44
52
 
45
53
  @sorted_versions = Hash.new do |candidates, package|
@@ -123,7 +131,7 @@ module Bundler
123
131
 
124
132
  if base_requirements[name]
125
133
  names_to_unlock << name
126
- elsif package.ignores_prereleases?
134
+ elsif package.ignores_prereleases? && @all_specs[name].any? {|s| s.version.prerelease? }
127
135
  names_to_allow_prereleases_for << name
128
136
  end
129
137
 
@@ -240,8 +248,22 @@ module Bundler
240
248
  results = filter_matching_specs(results, locked_requirement) if locked_requirement
241
249
 
242
250
  versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)|
243
- platform_specs = package.platforms.flat_map {|platform| select_best_platform_match(specs, platform) }
244
- next groups if platform_specs.empty?
251
+ platform_specs = package.platforms.map {|platform| select_best_platform_match(specs, platform) }
252
+
253
+ # If package is a top-level dependency,
254
+ # candidate is only valid if there are matching versions for all resolution platforms.
255
+ #
256
+ # If package is not a top-level deependency,
257
+ # then it's not necessary that it has matching versions for all platforms, since it may have been introduced only as
258
+ # a dependency for a platform specific variant, so it will only need to have a valid version for that platform.
259
+ #
260
+ if package.top_level?
261
+ next groups if platform_specs.any?(&:empty?)
262
+ else
263
+ next groups if platform_specs.all?(&:empty?)
264
+ end
265
+
266
+ platform_specs.flatten!
245
267
 
246
268
  ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY)
247
269
  groups << Resolver::Candidate.new(version, :specs => ruby_specs) if ruby_specs.any?
@@ -287,15 +309,21 @@ module Bundler
287
309
  end
288
310
  specs_matching_requirement = filter_matching_specs(specs, package.dependency.requirement)
289
311
 
290
- if specs_matching_requirement.any?
312
+ not_found_message = if specs_matching_requirement.any?
291
313
  specs = specs_matching_requirement
292
314
  matching_part = requirement_label
293
315
  platforms = package.platforms
294
- platform_label = platforms.size == 1 ? "platform '#{platforms.first}" : "platforms '#{platforms.join("', '")}"
295
- requirement_label = "#{requirement_label}' with #{platform_label}"
316
+
317
+ if platforms.size == 1
318
+ "Could not find gem '#{requirement_label}' with platform '#{platforms.first}'"
319
+ else
320
+ "Could not find gems matching '#{requirement_label}' valid for all resolution platforms (#{platforms.join(", ")})"
321
+ end
322
+ else
323
+ "Could not find gem '#{requirement_label}'"
296
324
  end
297
325
 
298
- message = String.new("Could not find gem '#{requirement_label}' in #{source}#{cache_message}.\n")
326
+ message = String.new("#{not_found_message} in #{source}#{cache_message}.\n")
299
327
 
300
328
  if specs.any?
301
329
  message << "\n#{other_specs_matching_message(specs, matching_part)}"
@@ -318,6 +346,13 @@ module Bundler
318
346
  specs.reject {|s| s.version.prerelease? }
319
347
  end
320
348
 
349
+ # Ignore versions that depend on themselves incorrectly
350
+ def filter_invalid_self_dependencies(specs, name)
351
+ specs.reject do |s|
352
+ s.dependencies.any? {|d| d.name == name && !d.requirement.satisfied_by?(s.version) }
353
+ end
354
+ end
355
+
321
356
  def requirement_satisfied_by?(requirement, spec)
322
357
  requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
323
358
  end
data/lib/bundler/retry.rb CHANGED
@@ -56,7 +56,7 @@ module Bundler
56
56
  def keep_trying?
57
57
  return true if current_run.zero?
58
58
  return false if last_attempt?
59
- return true if @failed
59
+ true if @failed
60
60
  end
61
61
 
62
62
  def last_attempt?
@@ -10,8 +10,8 @@ module Bundler
10
10
  raise GemfileError, "Please define :engine" if options[:engine_version] && options[:engine].nil?
11
11
 
12
12
  if options[:file]
13
- raise GemfileError, "Cannot specify version when using the file option" if ruby_version.any?
14
- ruby_version << Bundler.read_file(options[:file]).strip
13
+ raise GemfileError, "Do not pass version argument when using :file option" unless ruby_version.empty?
14
+ ruby_version << normalize_ruby_file(options[:file])
15
15
  end
16
16
 
17
17
  if options[:engine] == "ruby" && options[:engine_version] &&
@@ -20,5 +20,26 @@ module Bundler
20
20
  end
21
21
  @ruby_version = RubyVersion.new(ruby_version, options[:patchlevel], options[:engine], options[:engine_version])
22
22
  end
23
+
24
+ # Support the various file formats found in .ruby-version files.
25
+ #
26
+ # 3.2.2
27
+ # ruby-3.2.2
28
+ #
29
+ # Also supports .tool-versions files for asdf. Lines not starting with "ruby" are ignored.
30
+ #
31
+ # ruby 2.5.1 # comment is ignored
32
+ # ruby 2.5.1# close comment and extra spaces doesn't confuse
33
+ #
34
+ # Intentionally does not support `3.2.1@gemset` since rvm recommends using .ruby-gemset instead
35
+ def normalize_ruby_file(filename)
36
+ file_content = Bundler.read_file(Bundler.root.join(filename))
37
+ # match "ruby-3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment
38
+ if /^ruby(-|\s+)([^\s#]+)/.match(file_content)
39
+ $2
40
+ else
41
+ file_content.strip
42
+ end
43
+ end
23
44
  end
24
45
  end
@@ -23,7 +23,7 @@ module Bundler
23
23
  # specified must match the version.
24
24
 
25
25
  @versions = Array(versions).map do |v|
26
- op, v = Gem::Requirement.parse(v)
26
+ op, v = Gem::Requirement.parse(normalize_version(v))
27
27
  op == "=" ? v.to_s : "#{op} #{v}"
28
28
  end
29
29
 
@@ -112,6 +112,13 @@ module Bundler
112
112
 
113
113
  private
114
114
 
115
+ # Ruby's official preview version format uses a `-`: Example: 3.3.0-preview2
116
+ # However, RubyGems recognizes preview version format with a `.`: Example: 3.3.0.preview2
117
+ # Returns version string after replacing `-` with `.`
118
+ def normalize_version(version)
119
+ version.tr("-", ".")
120
+ end
121
+
115
122
  def matches?(requirements, version)
116
123
  # Handles RUBY_PATCHLEVEL of -1 for instances like ruby-head
117
124
  return requirements == version if requirements.to_s == "-1" || version.to_s == "-1"
@@ -163,6 +163,8 @@ module Bundler
163
163
 
164
164
  parsed_version = Bundler::LockfileParser.bundled_with
165
165
  @lockfile_version = parsed_version ? Gem::Version.new(parsed_version) : nil
166
+ rescue ArgumentError
167
+ @lockfile_version = nil
166
168
  end
167
169
  end
168
170
  end
@@ -88,14 +88,26 @@ module Bundler
88
88
  def initialize(root = nil)
89
89
  @root = root
90
90
  @local_config = load_config(local_config_file)
91
- @env_config = ENV.to_h.select {|key, _value| key =~ /\ABUNDLE_.+/ }
91
+
92
+ @env_config = ENV.to_h
93
+ @env_config.select! {|key, _value| key.start_with?("BUNDLE_") }
94
+ @env_config.delete("BUNDLE_")
95
+
92
96
  @global_config = load_config(global_config_file)
93
97
  @temporary = {}
98
+
99
+ @key_cache = {}
94
100
  end
95
101
 
96
102
  def [](name)
97
103
  key = key_for(name)
98
- value = configs.values.map {|config| config[key] }.compact.first
104
+
105
+ value = nil
106
+ configs.each do |_, config|
107
+ value = config[key]
108
+ next if value.nil?
109
+ break
110
+ end
99
111
 
100
112
  converted_value(value, name)
101
113
  end
@@ -138,17 +150,22 @@ module Bundler
138
150
  end
139
151
 
140
152
  def all
141
- keys = @temporary.keys | @global_config.keys | @local_config.keys | @env_config.keys
153
+ keys = @temporary.keys.union(@global_config.keys, @local_config.keys, @env_config.keys)
142
154
 
143
- keys.map do |key|
144
- key.sub(/^BUNDLE_/, "").gsub(/___/, "-").gsub(/__/, ".").downcase
145
- end.sort
155
+ keys.map! do |key|
156
+ key = key.delete_prefix("BUNDLE_")
157
+ key.gsub!("___", "-")
158
+ key.gsub!("__", ".")
159
+ key.downcase!
160
+ key
161
+ end.sort!
162
+ keys
146
163
  end
147
164
 
148
165
  def local_overrides
149
166
  repos = {}
150
167
  all.each do |k|
151
- repos[$'] = self[k] if k =~ /^local\./
168
+ repos[k.delete_prefix("local.")] = self[k] if k.start_with?("local.")
152
169
  end
153
170
  repos
154
171
  end
@@ -295,13 +312,13 @@ module Bundler
295
312
  end
296
313
 
297
314
  def key_for(key)
298
- self.class.key_for(key)
315
+ @key_cache[key] ||= self.class.key_for(key)
299
316
  end
300
317
 
301
318
  private
302
319
 
303
320
  def configs
304
- {
321
+ @configs ||= {
305
322
  :temporary => @temporary,
306
323
  :local => @local_config,
307
324
  :env => @env_config,
@@ -327,16 +344,20 @@ module Bundler
327
344
  end
328
345
 
329
346
  def is_bool(name)
330
- BOOL_KEYS.include?(name.to_s) || BOOL_KEYS.include?(parent_setting_for(name.to_s))
347
+ name = self.class.key_to_s(name)
348
+ BOOL_KEYS.include?(name) || BOOL_KEYS.include?(parent_setting_for(name))
331
349
  end
332
350
 
333
351
  def is_string(name)
334
- STRING_KEYS.include?(name.to_s) || name.to_s.start_with?("local.") || name.to_s.start_with?("mirror.") || name.to_s.start_with?("build.")
352
+ name = self.class.key_to_s(name)
353
+ STRING_KEYS.include?(name) || name.start_with?("local.") || name.start_with?("mirror.") || name.start_with?("build.")
335
354
  end
336
355
 
337
356
  def to_bool(value)
338
357
  case value
339
- when nil, /\A(false|f|no|n|0|)\z/i, false
358
+ when String
359
+ value.match?(/\A(false|f|no|n|0|)\z/i) ? false : true
360
+ when nil, false
340
361
  false
341
362
  else
342
363
  true
@@ -344,11 +365,11 @@ module Bundler
344
365
  end
345
366
 
346
367
  def is_num(key)
347
- NUMBER_KEYS.include?(key.to_s)
368
+ NUMBER_KEYS.include?(self.class.key_to_s(key))
348
369
  end
349
370
 
350
371
  def is_array(key)
351
- ARRAY_KEYS.include?(key.to_s)
372
+ ARRAY_KEYS.include?(self.class.key_to_s(key))
352
373
  end
353
374
 
354
375
  def is_credential(key)
@@ -371,7 +392,7 @@ module Bundler
371
392
  end
372
393
 
373
394
  def set_key(raw_key, value, hash, file)
374
- raw_key = raw_key.to_s
395
+ raw_key = self.class.key_to_s(raw_key)
375
396
  value = array_to_s(value) if is_array(raw_key)
376
397
 
377
398
  key = key_for(raw_key)
@@ -386,12 +407,13 @@ module Bundler
386
407
  return unless file
387
408
  SharedHelpers.filesystem_access(file) do |p|
388
409
  FileUtils.mkdir_p(p.dirname)
389
- require_relative "yaml_serializer"
390
- p.open("w") {|f| f.write(YAMLSerializer.dump(hash)) }
410
+ p.open("w") {|f| f.write(serializer_class.dump(hash)) }
391
411
  end
392
412
  end
393
413
 
394
414
  def converted_value(value, key)
415
+ key = self.class.key_to_s(key)
416
+
395
417
  if is_array(key)
396
418
  to_array(value)
397
419
  elsif value.nil?
@@ -449,24 +471,31 @@ module Bundler
449
471
  SharedHelpers.filesystem_access(config_file, :read) do |file|
450
472
  valid_file = file.exist? && !file.size.zero?
451
473
  return {} unless valid_file
452
- require_relative "yaml_serializer"
453
- YAMLSerializer.load(file.read).inject({}) do |config, (k, v)|
454
- new_k = k
455
-
474
+ serializer_class.load(file.read).inject({}) do |config, (k, v)|
456
475
  if k.include?("-")
457
476
  Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \
458
477
  "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \
459
478
  "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)."
460
479
 
461
- new_k = k.gsub("-", "___")
480
+ # string hash keys are frozen
481
+ k = k.gsub("-", "___")
462
482
  end
463
483
 
464
- config[new_k] = v
484
+ config[k] = v
465
485
  config
466
486
  end
467
487
  end
468
488
  end
469
489
 
490
+ def serializer_class
491
+ require "rubygems/yaml_serializer"
492
+ Gem::YAMLSerializer
493
+ rescue LoadError
494
+ # TODO: Remove this when RubyGems 3.4 is EOL
495
+ require_relative "yaml_serializer"
496
+ YAMLSerializer
497
+ end
498
+
470
499
  PER_URI_OPTIONS = %w[
471
500
  fallback_timeout
472
501
  ].freeze
@@ -482,8 +511,11 @@ module Bundler
482
511
 
483
512
  def self.key_for(key)
484
513
  key = normalize_uri(key).to_s if key.is_a?(String) && key.start_with?("http", "mirror.http")
485
- key = key.to_s.gsub(".", "__").gsub("-", "___").upcase
486
- "BUNDLE_#{key}"
514
+ key = key_to_s(key).gsub(".", "__")
515
+ key.gsub!("-", "___")
516
+ key.upcase!
517
+
518
+ key.prepend("BUNDLE_")
487
519
  end
488
520
 
489
521
  # TODO: duplicates Rubygems#normalize_uri
@@ -503,5 +535,34 @@ module Bundler
503
535
  end
504
536
  "#{prefix}#{uri}#{suffix}"
505
537
  end
538
+
539
+ # This is a hot method, so avoid respond_to? checks on every invocation
540
+ if :read.respond_to?(:name)
541
+ def self.key_to_s(key)
542
+ case key
543
+ when String
544
+ key
545
+ when Symbol
546
+ key.name
547
+ when Bundler::URI::HTTP
548
+ key.to_s
549
+ else
550
+ raise ArgumentError, "Invalid key: #{key.inspect}"
551
+ end
552
+ end
553
+ else
554
+ def self.key_to_s(key)
555
+ case key
556
+ when String
557
+ key
558
+ when Symbol
559
+ key.to_s
560
+ when Bundler::URI::HTTP
561
+ key.to_s
562
+ else
563
+ raise ArgumentError, "Invalid key: #{key.inspect}"
564
+ end
565
+ end
566
+ end
506
567
  end
507
568
  end
@@ -197,6 +197,21 @@ module Bundler
197
197
  filesystem_access(gemfile_path) {|g| File.open(g, "w") {|file| file.puts contents } }
198
198
  end
199
199
 
200
+ def relative_gemfile_path
201
+ relative_path_to(Bundler.default_gemfile)
202
+ end
203
+
204
+ def relative_lockfile_path
205
+ relative_path_to(Bundler.default_lockfile)
206
+ end
207
+
208
+ def relative_path_to(destination, from: pwd)
209
+ Pathname.new(destination).relative_path_from(from).to_s
210
+ rescue ArgumentError
211
+ # on Windows, if source and destination are on different drivers, there's no relative path from one to the other
212
+ destination
213
+ end
214
+
200
215
  private
201
216
 
202
217
  def validate_bundle_path
@@ -297,7 +312,7 @@ module Bundler
297
312
  def set_rubyopt
298
313
  rubyopt = [ENV["RUBYOPT"]].compact
299
314
  setup_require = "-r#{File.expand_path("setup", __dir__)}"
300
- return if !rubyopt.empty? && rubyopt.first =~ /#{setup_require}/
315
+ return if !rubyopt.empty? && rubyopt.first =~ /#{Regexp.escape(setup_require)}/
301
316
  rubyopt.unshift setup_require
302
317
  Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
303
318
  end