bundler 4.0.18 → 4.0.20

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce6bf9b503cf8fbe56890d80055e7b66389f1454eb93c13b4fb18dfc62158cd1
4
- data.tar.gz: e1f51fdb265c4717df5797824a7a4b89693fa259655f95e33d04a3596157b886
3
+ metadata.gz: b47ebbcd326a80d96d63eb16bc6743f1869f9ad1f37d83fac4dc9e1ea805c82c
4
+ data.tar.gz: c4903d7995ee2fa13381891ea105a1960ed5fb2ed69255f9fab09b4a7457f89e
5
5
  SHA512:
6
- metadata.gz: 9382209cbfdbddee4c21dcb2d9f22039ebcc2079b0998ff3cf133b14efa93ae146ebcc5570a9d157ce1cd383bcd6b0192ea89ff7590b990e21490f0c874aff8e
7
- data.tar.gz: 9889c94bcbf7c3eb0d91aeaf650c1bfdbcd3f42c0386c3a603bbc777552b95cd973511d700814e1ecceb881a931c2b1045da9059d3b1eb33bbf88babda3b5a81
6
+ metadata.gz: 2bdee0e45396bc93bf1837856a137f30ac3fa2a6bd4cc805ada1c0326e25309da64571d048edcd230f4f2143e1878ca4628d525fadea65e89333255946d2ead8
7
+ data.tar.gz: a8f333636a584612db1e4ae36ad93265e22873c38b3ff6cab66bb0e935a84126eeeaef21626fa429fe3229a4f194881be7d8bfed25ebc7675719644d538f0d2e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.20 / 2026-09-02
4
+
5
+ ### Enhancements:
6
+
7
+ * Avoid duplicate relative path prefix in bundle exec. Pull request [#9596](https://github.com/ruby/rubygems/pull/9596) by samuel-williams-shopify
8
+ * Follow renamed default branch in git sources. Pull request [#9813](https://github.com/ruby/rubygems/pull/9813) by hsbt
9
+ * Bundler::Source::Git: avoid re-fetching the repo. Pull request [#9806](https://github.com/ruby/rubygems/pull/9806) by byroot
10
+ * Show the newest out-of-cooldown version in bundle outdated. Pull request [#9803](https://github.com/ruby/rubygems/pull/9803) by hsbt
11
+
12
+ ### Bug fixes:
13
+
14
+ * Error out when `bundle init --gemspec` gets no specification. Pull request [#9818](https://github.com/ruby/rubygems/pull/9818) by hsbt
15
+ * Fix relative gemspec paths being expanded twice. Pull request [#9814](https://github.com/ruby/rubygems/pull/9814) by hsbt
16
+ * Fix gemspec evaluation under Ruby::Box. Pull request [#9809](https://github.com/ruby/rubygems/pull/9809) by hsbt
17
+ * Fix release date extraction for the current changelog header format. Pull request [#9767](https://github.com/ruby/rubygems/pull/9767) by meganemura
18
+ * Resolve globbed entries literally in Gem::Util.glob_files_in_dir. Pull request [#9802](https://github.com/ruby/rubygems/pull/9802) by hsbt
19
+ * Preserve native extensions during implicit clean. Pull request [#9800](https://github.com/ruby/rubygems/pull/9800) by sjh9714
20
+
21
+ ### Documentation:
22
+
23
+ * Clarify :path expectations and cross-link platform docs in man pages. Pull request [#9724](https://github.com/ruby/rubygems/pull/9724) by hsbt
24
+
25
+ ## 4.0.19 / 2026-08-20
26
+
27
+ ### Enhancements:
28
+
29
+ * Validate the platform field in Gem::Installer#verify_spec. Pull request [#9780](https://github.com/ruby/rubygems/pull/9780) by hsbt
30
+ * Restrict UserDefined handling to explicit class set for Gem::SafeMashal. Pull request [#9770](https://github.com/ruby/rubygems/pull/9770) by jackorp
31
+ * Filter git command output against the configured URI. Pull request [#9778](https://github.com/ruby/rubygems/pull/9778) by hsbt
32
+ * Summarize cooldown-skipped versions at the end of install, update, and lock. Pull request [#9762](https://github.com/ruby/rubygems/pull/9762) by hsbt
33
+
34
+ ### Bug fixes:
35
+
36
+ * Make repeated bundle lock options accumulate. Pull request [#9748](https://github.com/ruby/rubygems/pull/9748) by hsbt
37
+ * Fail instead of warning when frozen mode can't update the lockfile. Pull request [#9750](https://github.com/ruby/rubygems/pull/9750) by hsbt
38
+ * Fix wrong $LOAD_PATH for gems installed in the same process. Pull request [#9784](https://github.com/ruby/rubygems/pull/9784) by hsbt
39
+
40
+ ### Documentation:
41
+
42
+ * Fix auto-clean default version in bundle config docs. Pull request [#9783](https://github.com/ruby/rubygems/pull/9783) by hsbt
43
+ * Correct Bundler 5 default path documentation. Pull request [#9771](https://github.com/ruby/rubygems/pull/9771) by alloutflo
44
+
3
45
  ## 4.0.18 / 2026-08-05
4
46
 
5
47
  ### Enhancements:
@@ -4,8 +4,8 @@ module Bundler
4
4
  # Represents metadata from when the Bundler gem was built.
5
5
  module BuildMetadata
6
6
  # begin ivars
7
- @built_at = nil
8
- @git_commit_sha = "7e934435ff".freeze
7
+ @built_at = "2026-09-02".freeze
8
+ @git_commit_sha = "7f2502dc32b".freeze
9
9
  # end ivars
10
10
 
11
11
  # A hash representation of the build metadata.
@@ -20,6 +20,17 @@ module Bundler
20
20
  Bundler.ui.info msg
21
21
  end
22
22
 
23
+ def self.output_cooldown_skipped_summary(definition = Bundler.definition)
24
+ skipped = definition.cooldown_skipped
25
+ return if skipped.empty?
26
+
27
+ Bundler.ui.info "The following gem versions were skipped by the cooldown setting:"
28
+ skipped.each do |entry|
29
+ days = entry[:available_in_days]
30
+ Bundler.ui.info " * #{entry[:name]} #{entry[:version]} (available in #{days} #{days == 1 ? "day" : "days"}), resolved #{entry[:resolved]} instead"
31
+ end
32
+ end
33
+
23
34
  def self.output_fund_metadata_summary
24
35
  return if Bundler.settings["ignore_funding_requests"]
25
36
  definition = Bundler.definition
@@ -53,7 +53,7 @@ module Bundler
53
53
  end
54
54
 
55
55
  def bundles_for_gem(spec)
56
- Dir.glob("#{spec.full_gem_path}/**/*.bundle")
56
+ SharedHelpers.glob_files_in_dir("**/*.bundle", spec.full_gem_path)
57
57
  end
58
58
 
59
59
  def lookup_with_fiddle(path)
@@ -23,7 +23,7 @@ module Bundler
23
23
  bin_path.delete_suffix!(".bat") if Gem.win_platform?
24
24
  kernel_load(bin_path, *args)
25
25
  else
26
- bin_path = "./" + bin_path unless File.absolute_path?(bin_path)
26
+ bin_path = explicit_path(bin_path)
27
27
  kernel_exec(bin_path, *args)
28
28
  end
29
29
  else
@@ -71,6 +71,20 @@ module Bundler
71
71
  "#{file} #{args.join(" ")}".strip
72
72
  end
73
73
 
74
+ def explicit_path(path)
75
+ if File.absolute_path?(path) || explicit_relative_path?(path)
76
+ path
77
+ else
78
+ ".#{File::SEPARATOR}#{path}"
79
+ end
80
+ end
81
+
82
+ def explicit_relative_path?(path)
83
+ [File::SEPARATOR, File::ALT_SEPARATOR].compact.any? do |separator|
84
+ path.start_with?(".#{separator}", "..#{separator}")
85
+ end
86
+ end
87
+
74
88
  def directly_loadable?(file)
75
89
  if Gem.win_platform?
76
90
  script_wrapper?(file)
@@ -26,6 +26,10 @@ module Bundler
26
26
  end
27
27
 
28
28
  spec = Bundler.load_gemspec_uncached(gemspec)
29
+ unless spec
30
+ Bundler.ui.error "Gem specification #{gemspec} did not produce a specification"
31
+ exit 1
32
+ end
29
33
 
30
34
  File.open(gemfile, "wb") do |file|
31
35
  file << "# Generated from #{gemspec}\n"
@@ -64,6 +64,7 @@ module Bundler
64
64
  end
65
65
 
66
66
  Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
67
+ Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
67
68
 
68
69
  if CLI::Common.clean_after_install?
69
70
  require_relative "clean"
@@ -26,6 +26,9 @@ module Bundler
26
26
  Bundler::Fetcher.disable_endpoint = options["full-index"]
27
27
 
28
28
  update = options[:update]
29
+ # --update is repeatable, so it parses as an array with one entry per
30
+ # occurrence, where a bare `--update` produces a `true` entry
31
+ update = update.include?(true) ? true : update.flatten if update.is_a?(Array)
29
32
  conservative = options[:conservative]
30
33
  bundler = options[:bundler]
31
34
 
@@ -44,12 +47,12 @@ module Bundler
44
47
 
45
48
  Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
46
49
 
47
- options["remove-platform"].each do |platform_string|
50
+ options["remove-platform"].flatten.each do |platform_string|
48
51
  platform = Gem::Platform.new(platform_string)
49
52
  definition.remove_platform(platform)
50
53
  end
51
54
 
52
- options["add-platform"].each do |platform_string|
55
+ options["add-platform"].flatten.each do |platform_string|
53
56
  platform = Gem::Platform.new(platform_string)
54
57
  if platform.to_s == "unknown"
55
58
  Bundler.ui.error "The platform `#{platform_string}` is unknown to RubyGems and can't be added to the lockfile."
@@ -77,6 +80,8 @@ module Bundler
77
80
  puts "Writing lockfile to #{file}"
78
81
  definition.write_lock(file, false)
79
82
  end
83
+
84
+ Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
80
85
  end
81
86
 
82
87
  Bundler.ui.output_stream = previous_output_stream
@@ -158,11 +158,29 @@ module Bundler
158
158
 
159
159
  return active_spec if strict
160
160
 
161
- active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
162
- if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
163
- active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
161
+ matching_specs(active_spec, current_spec).last
162
+ end
163
+
164
+ def matching_specs(active_spec, current_spec)
165
+ @matching_specs ||= {}
166
+ @matching_specs[[active_spec.source, current_spec.name, current_spec.platform]] ||= begin
167
+ active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
168
+ if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
169
+ active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
170
+ end
171
+ active_specs
164
172
  end
165
- active_specs.last
173
+ end
174
+
175
+ # The newest version the cooldown setting would let bundler adopt right
176
+ # now, when the newest overall version is still inside the window. Only a
177
+ # version strictly between the installed one and the newest one is an
178
+ # adoptable update worth showing.
179
+ def newest_out_of_cooldown(active_spec, current_spec)
180
+ newest = matching_specs(active_spec, current_spec).reverse_each.find {|spec| cooldown_days_remaining(spec).nil? }
181
+ return unless newest
182
+ return if newest.version >= active_spec.version || newest.version <= current_spec.version
183
+ newest
166
184
  end
167
185
 
168
186
  def print_gems(gems_list)
@@ -208,7 +226,11 @@ module Bundler
208
226
  spec_outdated_info += ", released #{release_date}" unless release_date.empty?
209
227
 
210
228
  remaining = cooldown_days_remaining(active_spec)
211
- spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}" if remaining
229
+ if remaining
230
+ spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}"
231
+ adoptable = newest_out_of_cooldown(active_spec, current_spec)
232
+ spec_outdated_info += ", newest out of cooldown #{adoptable.version}" if adoptable
233
+ end
212
234
 
213
235
  spec_outdated_info += ")"
214
236
 
@@ -227,7 +249,11 @@ module Bundler
227
249
  current_version = "#{current_spec.version}#{current_spec.git_version}"
228
250
  spec_version = "#{active_spec.version}#{active_spec.git_version}"
229
251
  remaining = cooldown_days_remaining(active_spec)
230
- spec_version += " (cooldown #{remaining}d)" if remaining
252
+ if remaining
253
+ adoptable = newest_out_of_cooldown(active_spec, current_spec)
254
+ adoptable_note = adoptable ? ", #{adoptable.version} out of cooldown" : ""
255
+ spec_version += " (cooldown #{remaining}d#{adoptable_note})"
256
+ end
231
257
  dependency = dependency.requirement if dependency
232
258
 
233
259
  ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
@@ -236,7 +262,7 @@ module Bundler
236
262
  ret_val
237
263
  end
238
264
 
239
- def cooldown_days_remaining(spec, now = Time.now)
265
+ def cooldown_days_remaining(spec, now = cooldown_now)
240
266
  return nil unless spec.respond_to?(:created_at) && spec.created_at
241
267
  return nil unless spec.respond_to?(:remote) && spec.remote
242
268
  days = spec.remote.effective_cooldown
@@ -245,6 +271,10 @@ module Bundler
245
271
  remaining > 0 ? remaining.ceil : nil
246
272
  end
247
273
 
274
+ def cooldown_now
275
+ @cooldown_now ||= Time.now
276
+ end
277
+
248
278
  def check_for_deployment_mode!
249
279
  return unless Bundler.frozen_bundle?
250
280
  suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
@@ -118,6 +118,7 @@ module Bundler
118
118
  Bundler.ui.confirm "Bundle updated!"
119
119
  Bundler::CLI::Common.output_without_groups_message(:update)
120
120
  Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
121
+ Bundler::CLI::Common.output_cooldown_skipped_summary
121
122
 
122
123
  Bundler::CLI::Common.output_fund_metadata_summary
123
124
  end
data/lib/bundler/cli.rb CHANGED
@@ -626,15 +626,15 @@ module Bundler
626
626
  end
627
627
 
628
628
  desc "lock", "Creates a lockfile without installing"
629
- method_option "update", type: :array, lazy_default: true, banner: "ignore the existing lockfile, update all gems by default, or update list of given gems"
629
+ method_option "update", type: :array, lazy_default: true, repeatable: true, banner: "ignore the existing lockfile, update all gems by default, or update list of given gems"
630
630
  method_option "local", type: :boolean, default: false, banner: "do not attempt to fetch remote gemspecs and use the local gem cache only"
631
631
  method_option "print", type: :boolean, default: false, banner: "print the lockfile to STDOUT instead of writing to the file system"
632
632
  method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
633
633
  method_option "lockfile", type: :string, default: nil, banner: "the path the lockfile should be written to"
634
634
  method_option "full-index", type: :boolean, default: false, banner: "Fall back to using the single-file index of all gems"
635
635
  method_option "add-checksums", type: :boolean, default: false, banner: "Adds checksums to the lockfile"
636
- method_option "add-platform", type: :array, default: [], banner: "Add a new platform to the lockfile"
637
- method_option "remove-platform", type: :array, default: [], banner: "Remove a platform from the lockfile"
636
+ method_option "add-platform", type: :array, default: [], repeatable: true, banner: "Add a new platform to the lockfile"
637
+ method_option "remove-platform", type: :array, default: [], repeatable: true, banner: "Remove a platform from the lockfile"
638
638
  method_option "normalize-platforms", type: :boolean, default: false, banner: "Normalize lockfile platforms"
639
639
  method_option "patch", type: :boolean, banner: "If updating, prefer updating only to next patch version"
640
640
  method_option "minor", type: :boolean, banner: "If updating, prefer updating only to next minor version"
@@ -366,6 +366,12 @@ module Bundler
366
366
  sources.git_sources.filter_map {|s| File.realpath(s.path) if File.exist?(s.path) }
367
367
  end
368
368
 
369
+ # Versions excluded by cooldown during the last resolution, one entry per
370
+ # gem with the newest skipped version. Empty when no resolution ran.
371
+ def cooldown_skipped
372
+ @cooldown_skipped || []
373
+ end
374
+
369
375
  def groups
370
376
  dependencies.flat_map(&:groups).uniq
371
377
  end
@@ -404,7 +410,7 @@ module Bundler
404
410
  updating_major = locked_major < current_major
405
411
  end
406
412
 
407
- preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
413
+ preserve_unknown_sections ||= Bundler.frozen_bundle? || (!updating_major && !(unlocking? || @unlocking_bundler))
408
414
 
409
415
  if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
410
416
  return if Bundler.frozen_bundle?
@@ -413,8 +419,10 @@ module Bundler
413
419
  end
414
420
 
415
421
  if Bundler.frozen_bundle?
416
- Bundler.ui.error "Cannot write a changed lockfile while frozen."
417
- return
422
+ msg = lockfile_changes_summary("frozen mode is set") ||
423
+ "Your lockfile needs to be updated, but it can't be because frozen mode is set.\n\n" \
424
+ "Run `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control."
425
+ raise ProductionError, msg
418
426
  end
419
427
 
420
428
  # Convert to \r\n if the existing lock has them, i.e., Windows with
@@ -770,6 +778,8 @@ module Bundler
770
778
 
771
779
  result = SpecSet.new(resolver.start)
772
780
 
781
+ @cooldown_skipped = resolver.cooldown_skipped
782
+
773
783
  @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version
774
784
 
775
785
  @new_platforms.each do |platform|
data/lib/bundler/dsl.rb CHANGED
@@ -68,7 +68,7 @@ module Bundler
68
68
  development_group = opts[:development_group] || :development
69
69
  expanded_path = gemfile_root.join(path)
70
70
 
71
- gemspecs = Gem::Util.glob_files_in_dir("{,*}.gemspec", expanded_path).filter_map {|g| Bundler.load_gemspec(g) }
71
+ gemspecs = SharedHelpers.glob_files_in_dir("{,*}.gemspec", expanded_path).filter_map {|g| Bundler.load_gemspec(g) }
72
72
  gemspecs.reject! {|s| s.name != name } if name
73
73
  specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] }
74
74
 
@@ -50,6 +50,19 @@ module Bundler
50
50
  end
51
51
  end
52
52
 
53
+ # `require_paths` is overridden above, but `full_require_paths` (and so
54
+ # `load_paths`) is computed from `raw_require_paths`, which would otherwise
55
+ # report the default `lib` for every gem
56
+ def raw_require_paths
57
+ if @remote_specification
58
+ @remote_specification.raw_require_paths
59
+ elsif _local_specification
60
+ _local_specification.raw_require_paths
61
+ else
62
+ super
63
+ end
64
+ end
65
+
53
66
  # needed for inline
54
67
  def load_paths
55
68
  # remote specs aren't installed, and can't have load_paths
@@ -32,7 +32,7 @@ module Bundler
32
32
 
33
33
  def initialize(base = nil, name = nil)
34
34
  @base = File.expand_path(base || SharedHelpers.pwd)
35
- gemspecs = name ? [File.join(@base, "#{name}.gemspec")] : Gem::Util.glob_files_in_dir("{,*}.gemspec", @base)
35
+ gemspecs = name ? [File.join(@base, "#{name}.gemspec")] : SharedHelpers.glob_files_in_dir("{,*}.gemspec", @base)
36
36
  raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
37
37
  @spec_path = gemspecs.first
38
38
  @gemspec = Bundler.load_gemspec(@spec_path)
@@ -124,7 +124,7 @@ module Bundler
124
124
  end
125
125
 
126
126
  def built_gem_path
127
- Gem::Util.glob_files_in_dir("#{name}-*.gem", base).sort_by {|f| File.mtime(f) }.last
127
+ SharedHelpers.glob_files_in_dir("#{name}-*.gem", base).sort_by {|f| File.mtime(f) }.last
128
128
  end
129
129
 
130
130
  def git_push(remote = nil)
@@ -83,6 +83,12 @@ module Bundler
83
83
  install(options)
84
84
 
85
85
  Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available
86
+ # Drop the source caches too, since releasing resolution memory dropped
87
+ # the source indexes. A resolution happening after this point would
88
+ # otherwise rebuild them around a snapshot of installed gems taken
89
+ # before the install, and materialize against remote specs that don't
90
+ # know where the gems they stand for ended up.
91
+ @definition.sources.clear_cache
86
92
 
87
93
  lock
88
94
  Standalone.new(options[:standalone], @definition).generate if options[:standalone]
@@ -103,6 +103,11 @@ module Bundler
103
103
  end
104
104
 
105
105
  def bundler_checksum
106
+ # In frozen mode the lockfile can't change, so reproduce whatever bundler
107
+ # entry is already locked instead of recording one for the running bundler
108
+ # version, which may legitimately differ from the locked one.
109
+ return locked_bundler_checksum if Bundler.frozen_bundle?
110
+
106
111
  # `.dev` versions and `SKIP_BUNDLER_CHECKSUM` are deliberate opt-outs (used
107
112
  # by Bundler/RubyGems' own development and release tasks): never record a
108
113
  # checksum for Bundler itself in those cases.
@@ -138,5 +143,16 @@ module Bundler
138
143
 
139
144
  locked_gems.bundler_version != definition.bundler_version_to_lock
140
145
  end
146
+
147
+ def locked_bundler_checksum
148
+ locked_version = definition.locked_gems&.bundler_version
149
+ return [] unless locked_version
150
+
151
+ metadata_source = definition.sources.metadata_source
152
+ locked_spec = LazySpecification.new("bundler", locked_version, Gem::Platform::RUBY, metadata_source)
153
+ return [] if metadata_source.checksum_store.missing?(locked_spec)
154
+
155
+ [metadata_source.checksum_store.to_lock(locked_spec)]
156
+ end
141
157
  end
142
158
  end
@@ -82,7 +82,7 @@ The following is a list of all configuration keys and their purpose\. You can le
82
82
  .IP "\(bu" 4
83
83
  \fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
84
84
  .IP "\(bu" 4
85
- \fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\.
85
+ \fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 5, as long as \fBpath\fR is not explicitly configured\.
86
86
  .IP "\(bu" 4
87
87
  \fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
88
88
  .IP "\(bu" 4
@@ -147,7 +147,7 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated
147
147
  .IP "\(bu" 4
148
148
  \fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\.
149
149
  .IP "\(bu" 4
150
- \fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. That means that before Bundler 4, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 4\.
150
+ \fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 5, and to the default system path (\fBGem\.dir\fR) before Bundler 5\. That means that before Bundler 5, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 5\.
151
151
  .IP "\(bu" 4
152
152
  \fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
153
153
  .IP "\(bu" 4
@@ -133,7 +133,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
133
133
  Defaults to `vendor/cache`.
134
134
  * `clean` (`BUNDLE_CLEAN`):
135
135
  Whether Bundler should run `bundle clean` automatically after
136
- `bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not
136
+ `bundle install`. Defaults to `true` in Bundler 5, as long as `path` is not
137
137
  explicitly configured.
138
138
  * `console` (`BUNDLE_CONSOLE`):
139
139
  The console that `bundle console` starts. Defaults to `irb`.
@@ -245,12 +245,12 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
245
245
  The location on disk where all gems in your bundle will be located regardless
246
246
  of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
247
247
  will be installed by `bundle install`. When not set, Bundler install by
248
- default to a `.bundle` directory relative to repository root in Bundler 4,
249
- and to the default system path (`Gem.dir`) before Bundler 4. That means that
250
- before Bundler 4, Bundler shares this location with Rubygems, and `gem
248
+ default to a `.bundle` directory relative to repository root in Bundler 5,
249
+ and to the default system path (`Gem.dir`) before Bundler 5. That means that
250
+ before Bundler 5, Bundler shares this location with Rubygems, and `gem
251
251
  install ...` will have gems installed in the same location and therefore,
252
252
  gems installed without `path` set will show up by calling `gem list`. This
253
- will not be the case in Bundler 4.
253
+ will not be the case in Bundler 5.
254
254
  * `path.system` (`BUNDLE_PATH__SYSTEM`):
255
255
  Whether Bundler will install gems into the default system path (`Gem.dir`).
256
256
  * `plugins` (`BUNDLE_PLUGINS`):
@@ -55,7 +55,7 @@ Only list patch newer versions\.
55
55
  Only list gems specified in your Gemfile, not their dependencies\.
56
56
  .TP
57
57
  \fB\-\-cooldown=<number>\fR
58
- Annotate (rather than hide) versions that are still inside the cooldown window of \fInumber\fR days\. The prose output appends "in cooldown for Nd more days" and the table form adds "(cooldown Nd)" to the Latest column\. See \fBcooldown\fR in bundle\-config(1)\.
58
+ Annotate (rather than hide) versions that are still inside the cooldown window of \fInumber\fR days\. The prose output appends "in cooldown for Nd more days" and the table form adds "(cooldown Nd)" to the Latest column\. When a version outside the window is newer than the installed one, the prose output also appends "newest out of cooldown X" and the table form becomes "(cooldown Nd, X out of cooldown)"\. With \fB\-\-filter\-strict\fR (or \fB\-\-patch\fR, \fB\-\-minor\fR, \fB\-\-major\fR, which imply it), "newest" is already the cooldown\-filtered resolved version, so none of these annotations appear\. See \fBcooldown\fR in bundle\-config(1)\.
59
59
  .SH "PATCH LEVEL OPTIONS"
60
60
  See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
61
61
  .SH "FILTERING OUTPUT"
@@ -76,7 +76,13 @@ are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
76
76
  Annotate (rather than hide) versions that are still inside the
77
77
  cooldown window of <number> days. The prose output appends "in
78
78
  cooldown for Nd more days" and the table form adds "(cooldown Nd)" to
79
- the Latest column. See `cooldown` in bundle-config(1).
79
+ the Latest column. When a version outside the window is newer than the
80
+ installed one, the prose output also appends "newest out of cooldown
81
+ X" and the table form becomes "(cooldown Nd, X out of cooldown)".
82
+ With `--filter-strict` (or `--patch`, `--minor`, `--major`, which
83
+ imply it), "newest" is already the cooldown-filtered resolved version,
84
+ so none of these annotations appear. See `cooldown` in
85
+ bundle-config(1).
80
86
 
81
87
  ## PATCH LEVEL OPTIONS
82
88
 
@@ -38,6 +38,8 @@ Your current platform satisfies the Ruby version requirement\.
38
38
  .IP "" 0
39
39
  .P
40
40
  \fBplatform\fR lists all the platforms in your \fBGemfile\.lock\fR as well as the \fBruby\fR directive if applicable from your Gemfile(5)\. It also lets you know if the \fBruby\fR directive requirement has been met\. If \fBruby\fR directive doesn't match the running Ruby VM, it tells you what part does not\.
41
+ .P
42
+ For a detailed explanation of how bundler handles platforms, see the \fBPLATFORMS\fR section in Gemfile(5)\.
41
43
  .SH "OPTIONS"
42
44
  .TP
43
45
  \fB\-\-ruby\fR
@@ -45,5 +47,7 @@ It will display the ruby directive information, so you don't have to parse it fr
45
47
  .SH "SEE ALSO"
46
48
  .IP "\(bu" 4
47
49
  bundle\-lock(1) \fIbundle\-lock\.1\.html\fR
50
+ .IP "\(bu" 4
51
+ Gemfile(5) \fIgemfile\.5\.html\fR
48
52
  .IP "" 0
49
53
 
@@ -38,6 +38,9 @@ If you run `bundle platform` on Ruby 3.1.2, it displays the following output:
38
38
  if the `ruby` directive requirement has been met. If `ruby` directive doesn't
39
39
  match the running Ruby VM, it tells you what part does not.
40
40
 
41
+ For a detailed explanation of how bundler handles platforms, see the
42
+ `PLATFORMS` section in Gemfile(5).
43
+
41
44
  ## OPTIONS
42
45
 
43
46
  * `--ruby`:
@@ -47,3 +50,4 @@ match the running Ruby VM, it tells you what part does not.
47
50
  ## SEE ALSO
48
51
 
49
52
  * [bundle-lock(1)](bundle-lock.1.html)
53
+ * [Gemfile(5)](gemfile.5.html)
@@ -392,7 +392,7 @@ gem "rails", git: "https://rails@bitbucket\.org/rails/rails\.git"
392
392
  .P
393
393
  Since the \fBbitbucket\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
394
394
  .SS "PATH"
395
- You can specify that a gem is located in a particular location on the file system\. Relative paths are resolved relative to the directory containing the \fBGemfile\fR\.
395
+ You can specify that a gem is located in a particular location on the file system\. Relative paths are resolved relative to the directory containing the \fBGemfile\fR\. The path must point to a directory containing the gem's unpacked source code, not to a packaged \fB\.gem\fR file\.
396
396
  .P
397
397
  Similar to the semantics of the \fB:git\fR option, the \fB:path\fR option requires that the directory in question either contains a \fB\.gemspec\fR for the gem, or that you specify an explicit version that bundler should use\.
398
398
  .P
@@ -451,7 +451,9 @@ Since the `bitbucket` method is a specialization of `git_source`, it accepts a `
451
451
 
452
452
  You can specify that a gem is located in a particular location
453
453
  on the file system. Relative paths are resolved relative to the
454
- directory containing the `Gemfile`.
454
+ directory containing the `Gemfile`. The path must point to a
455
+ directory containing the gem's unpacked source code, not to a
456
+ packaged `.gem` file.
455
457
 
456
458
  Similar to the semantics of the `:git` option, the `:path`
457
459
  option requires that the directory in question either contains
@@ -14,11 +14,15 @@ module Bundler
14
14
  require_relative "resolver/root"
15
15
  require_relative "resolver/strategy"
16
16
 
17
+ attr_reader :cooldown_skipped
18
+
17
19
  def initialize(base, gem_version_promoter, most_specific_locked_platform = nil)
18
20
  @source_requirements = base.source_requirements
19
21
  @base = base
20
22
  @gem_version_promoter = gem_version_promoter
21
23
  @most_specific_locked_platform = most_specific_locked_platform
24
+ @cooldown_skipped = []
25
+ @cooldown_skipped_specs = {}
22
26
  end
23
27
 
24
28
  def start
@@ -36,6 +40,8 @@ module Bundler
36
40
  root = Resolver::Root.new(name_for_explicit_dependency_source)
37
41
  root_version = Resolver::Candidate.new(0)
38
42
 
43
+ @cooldown_skipped_specs = {}
44
+
39
45
  @all_specs = Hash.new do |specs, name|
40
46
  source = source_for(name)
41
47
  matches = source.specs.search(name)
@@ -80,7 +86,9 @@ module Bundler
80
86
  solver = PubGrub::VersionSolver.new(source: self, root: root, strategy: Strategy.new(self), logger: logger)
81
87
  result = solver.solve
82
88
  resolved_specs = result.flat_map {|package, version| version.to_specs(package, @most_specific_locked_platform) }
83
- SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs)
89
+ spec_set = SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs)
90
+ @cooldown_skipped = cooldown_skipped_summary(spec_set)
91
+ spec_set
84
92
  rescue PubGrub::SolveFailure => e
85
93
  incompatibility = e.incompatibility
86
94
 
@@ -424,6 +432,7 @@ module Bundler
424
432
  specs.each do |spec|
425
433
  next unless cooldown_excluded?(spec)
426
434
  excluded[[spec.name, spec.version]] = true
435
+ (@cooldown_skipped_specs ||= {})[[spec.name, spec.version]] ||= spec
427
436
  end
428
437
  excluded
429
438
  end
@@ -466,6 +475,50 @@ module Bundler
466
475
  @cooldown_now ||= Time.now
467
476
  end
468
477
 
478
+ # Reports, per gem, the newest version that cooldown kept out of a
479
+ # successful resolution. A skipped version is only worth reporting when it
480
+ # is newer than the resolved version and satisfies every requirement the
481
+ # final resolution places on that gem, so we don't claim a version the
482
+ # resolver could never have picked anyway.
483
+ def cooldown_skipped_summary(spec_set)
484
+ return [] if @cooldown_skipped_specs.empty?
485
+
486
+ requirements = Hash.new {|h, name| h[name] = [] }
487
+ @requirements.each {|dep| requirements[dep.name] << dep.requirement }
488
+ spec_set.each do |spec|
489
+ spec.dependencies.each {|dep| requirements[dep.name] << dep.requirement }
490
+ end
491
+
492
+ resolved_versions = {}
493
+ spec_set.each do |spec|
494
+ version = resolved_versions[spec.name]
495
+ resolved_versions[spec.name] = spec.version if version.nil? || spec.version > version
496
+ end
497
+
498
+ newest_skipped = {}
499
+ @cooldown_skipped_specs.each do |(name, version), spec|
500
+ resolved = resolved_versions[name]
501
+ next unless resolved && version > resolved
502
+ next unless requirements[name].all? {|req| req.satisfied_by?(version) }
503
+ newest = newest_skipped[name]
504
+ newest_skipped[name] = spec if newest.nil? || version > newest.version
505
+ end
506
+
507
+ newest_skipped.values.sort_by(&:name).map do |spec|
508
+ {
509
+ name: spec.name,
510
+ version: spec.version,
511
+ resolved: resolved_versions[spec.name],
512
+ available_in_days: remaining_cooldown_days(spec),
513
+ }
514
+ end
515
+ end
516
+
517
+ def remaining_cooldown_days(spec)
518
+ remaining = (spec.remote.effective_cooldown * 86_400) - (cooldown_now - spec.created_at)
519
+ [(remaining / 86_400.0).ceil, 1].max
520
+ end
521
+
469
522
  def filter_remote_specs(specs, package)
470
523
  if package.prefer_local?
471
524
  local_specs = specs.select {|s| s.is_a?(StubSpecification) }
@@ -141,7 +141,7 @@ module Bundler
141
141
  end
142
142
  end
143
143
 
144
- Gem::Util.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir|
144
+ SharedHelpers.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir|
145
145
  FileUtils.rm_rf(git_dir)
146
146
  FileUtils.touch(File.expand_path("../.bundlecache", git_dir))
147
147
  end
@@ -159,13 +159,13 @@ module Bundler
159
159
  end
160
160
 
161
161
  def clean(dry_run = false)
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)
162
+ gem_bins = SharedHelpers.glob_files_in_dir("bin/*", Gem.dir)
163
+ git_dirs = SharedHelpers.glob_files_in_dir("bundler/gems/*", Gem.dir)
164
+ git_cache_dirs = SharedHelpers.glob_files_in_dir("cache/bundler/git/*", Gem.dir)
165
+ gem_dirs = SharedHelpers.glob_files_in_dir("gems/*", Gem.dir)
166
+ gem_files = SharedHelpers.glob_files_in_dir("cache/*.gem", Gem.dir)
167
+ gemspec_files = SharedHelpers.glob_files_in_dir("specifications/*.gemspec", Gem.dir)
168
+ extension_dirs = SharedHelpers.glob_files_in_dir("extensions/*/*/*", Gem.dir) + SharedHelpers.glob_files_in_dir("bundler/gems/extensions/*/*/*", Gem.dir)
169
169
  spec_gem_paths = []
170
170
  # need to keep git sources around
171
171
  spec_git_paths = @definition.spec_git_paths
@@ -232,7 +232,7 @@ module Bundler
232
232
  private
233
233
 
234
234
  def prune_gem_cache(resolve, cache_path)
235
- cached = Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s)
235
+ cached = SharedHelpers.glob_files_in_dir("*.gem", cache_path.to_s)
236
236
 
237
237
  cached = cached.delete_if do |path|
238
238
  spec = Bundler.rubygems.spec_from_gem path
@@ -257,7 +257,7 @@ module Bundler
257
257
  end
258
258
 
259
259
  def prune_git_and_path_cache(resolve, cache_path)
260
- cached = Gem::Util.glob_files_in_dir("*/.bundlecache", cache_path.to_s)
260
+ cached = SharedHelpers.glob_files_in_dir("*/.bundlecache", cache_path.to_s)
261
261
 
262
262
  cached = cached.delete_if do |path|
263
263
  name = File.basename(File.dirname(path))
@@ -283,7 +283,7 @@ module Bundler
283
283
  # Add man/ subdirectories from activated bundles to MANPATH for man(1)
284
284
  manuals = $LOAD_PATH.filter_map do |path|
285
285
  man_subdir = path.sub(/lib$/, "man")
286
- man_subdir unless Dir.glob("man?/", base: man_subdir).empty?
286
+ man_subdir unless SharedHelpers.glob_files_in_dir("man?/", man_subdir).empty?
287
287
  end
288
288
 
289
289
  return if manuals.empty?
@@ -231,6 +231,21 @@ module Bundler
231
231
  destination
232
232
  end
233
233
 
234
+ # Globs for entries matching +glob+ inside of +base_path+, returning
235
+ # absolute paths. Glob metacharacters in +base_path+ are not treated as
236
+ # part of the pattern, and matched entries are joined literally, so an
237
+ # entry starting with `~` is not expanded into the home directory.
238
+ #
239
+ # Bundler runs against whatever RubyGems the host provides, so this cannot
240
+ # delegate to Gem::Util.glob_files_in_dir, which only gained the literal
241
+ # join in RubyGems 4.1.
242
+ def glob_files_in_dir(glob, base_path)
243
+ expanded_path = nil
244
+ Dir.glob(glob, base: base_path).map! do |f|
245
+ File.join(expanded_path ||= File.expand_path(base_path), f)
246
+ end
247
+ end
248
+
234
249
  private
235
250
 
236
251
  def validate_bundle_path
@@ -193,7 +193,13 @@ module Bundler
193
193
  return out if status.success?
194
194
 
195
195
  if err.include?("couldn't find remote ref") || err.include?("not our ref")
196
- raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref, credential_filtered_uri)
196
+ default_branch = renamed_remote_default_branch if tracking_remote_default_branch?
197
+ if default_branch
198
+ out = follow_remote_default_branch(args, default_branch)
199
+ return out if out
200
+ end
201
+
202
+ raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref || current_branch, credential_filtered_uri)
197
203
  else
198
204
  if shallow?
199
205
  args -= depth_args
@@ -298,6 +304,61 @@ module Bundler
298
304
  branch_option || ref.nil?
299
305
  end
300
306
 
307
+ def tracking_remote_default_branch?
308
+ explicit_ref.nil? && commit.nil?
309
+ end
310
+
311
+ # Returns nil on any failure, leaving HEAD untouched so the caller reports
312
+ # the original fetch failure. Runs inside the retry block, so it must not
313
+ # touch the caller's command locals.
314
+ def follow_remote_default_branch(args, default_branch)
315
+ reference = "refs/heads/#{default_branch}"
316
+ command = fetch_command(args, "#{reference}:#{reference}")
317
+ check_allowed(command)
318
+
319
+ out, err, status = capture(command, path)
320
+ unless status.success?
321
+ Bundler.ui.debug "Could not fetch #{reference} from #{credential_filtered_uri}: #{err}"
322
+ return
323
+ end
324
+
325
+ previous_branch = current_branch
326
+ begin
327
+ git "symbolic-ref", "HEAD", reference, dir: path
328
+ rescue GitError => e
329
+ Bundler.ui.debug "Could not repoint the cached clone at #{reference}: #{e.message}"
330
+ return
331
+ end
332
+ @current_branch = nil
333
+ Bundler.ui.warn "#{credential_filtered_uri} no longer has #{previous_branch}, " \
334
+ "now following its default branch #{default_branch}"
335
+ out
336
+ end
337
+
338
+ # The cached clone's HEAD branch is gone from the remote, so the remote's
339
+ # own idea of its default branch is the only thing left to follow.
340
+ def renamed_remote_default_branch
341
+ default_branch = remote_default_branch
342
+ return if default_branch.nil? || default_branch == current_branch
343
+
344
+ default_branch
345
+ end
346
+
347
+ def remote_default_branch
348
+ command = ["ls-remote", "--symref", "--", configured_uri, "HEAD"]
349
+ check_allowed(command)
350
+
351
+ out, err, status = capture(command, path)
352
+ unless status.success?
353
+ Bundler.ui.debug "Could not ask #{credential_filtered_uri} for its default branch: #{err}"
354
+ return
355
+ end
356
+
357
+ # A remote is free to advertise a ref name that is not valid UTF-8, and
358
+ # matching that as text raises out of the GitError family.
359
+ out.b[%r{^ref:\s+refs/heads/(.+?)\s+HEAD}, 1]
360
+ end
361
+
301
362
  def pinned_to_full_sha?
302
363
  full_sha_revision?(ref)
303
364
  end
@@ -364,9 +425,10 @@ module Bundler
364
425
  git("rev-parse", "--verify", reference, dir: path).strip
365
426
  end
366
427
 
367
- # Adds credentials to the URI
428
+ # Adds credentials to the URI. This is the URI given to git, so it's
429
+ # also the one command output must be filtered against.
368
430
  def configured_uri
369
- if /https?:/.match?(uri)
431
+ @configured_uri ||= if /https?:/.match?(uri)
370
432
  remote = Gem::URI(uri)
371
433
  config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
372
434
  remote.userinfo ||= config_auth
@@ -409,7 +471,7 @@ module Bundler
409
471
  raise GitNotInstalledError.new unless Bundler.git_present?
410
472
 
411
473
  require "shellwords"
412
- URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri)
474
+ URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", configured_uri)
413
475
  end
414
476
 
415
477
  def run_command(*command, dir: nil)
@@ -429,10 +491,10 @@ module Bundler
429
491
  require "open3"
430
492
  out, err, status = Open3.capture3(*capture3_args_for(cmd, dir))
431
493
 
432
- filtered_out = URICredentialsFilter.credential_filtered_string(out, uri)
494
+ filtered_out = URICredentialsFilter.credential_filtered_string(out, configured_uri)
433
495
  return [filtered_out, status] if ignore_err
434
496
 
435
- filtered_err = URICredentialsFilter.credential_filtered_string(err, uri)
497
+ filtered_err = URICredentialsFilter.credential_filtered_string(err, configured_uri)
436
498
  [filtered_out, filtered_err, status]
437
499
  end
438
500
  end
@@ -464,8 +526,8 @@ module Bundler
464
526
  args
465
527
  end
466
528
 
467
- def fetch_command(args)
468
- ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact
529
+ def fetch_command(args, spec = refspec)
530
+ ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, spec].compact
469
531
  end
470
532
 
471
533
  def clone_command(args)
@@ -91,9 +91,9 @@ module Bundler
91
91
 
92
92
  def to_s
93
93
  begin
94
- at = humanized_ref || current_branch
95
-
96
- rev = "at #{at}@#{shortref_for_display(revision)}"
94
+ at = humanized_ref
95
+ at = "#{at}@" if at
96
+ rev = "at #{at}#{shortref_for_display(revision)}"
97
97
  rescue GitError
98
98
  ""
99
99
  end
@@ -320,7 +320,7 @@ module Bundler
320
320
 
321
321
  def serialize_gemspecs_in(destination)
322
322
  destination = destination.expand_path(Bundler.root) if destination.relative?
323
- Gem::Util.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
323
+ SharedHelpers.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
324
324
  # Evaluate gemspecs and cache the result. Gemspecs
325
325
  # in git might require git or other dependencies.
326
326
  # The gemspecs we cache should already be evaluated.
@@ -161,7 +161,7 @@ module Bundler
161
161
 
162
162
  if File.directory?(expanded_path)
163
163
  # We sort depth-first since `<<` will override the earlier-found specs
164
- Gem::Util.glob_files_in_dir(@glob, expanded_path).sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file|
164
+ SharedHelpers.glob_files_in_dir(@glob, expanded_path).sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file|
165
165
  next unless spec = load_gemspec(file)
166
166
  spec.source = self
167
167
 
@@ -406,7 +406,7 @@ module Bundler
406
406
  @cached_specs ||= begin
407
407
  idx = Index.new
408
408
 
409
- Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
409
+ SharedHelpers.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
410
410
  s ||= Bundler.rubygems.spec_from_gem(gemfile)
411
411
  s.source = self
412
412
  idx << s
@@ -126,7 +126,9 @@ module Bundler
126
126
  def _remote_specification
127
127
  @_remote_specification ||= begin
128
128
  rs = stub.to_spec
129
- if rs.equal?(self) # happens when to_spec gets the spec from Gem.loaded_specs
129
+ # Gem::StubSpecification#to_spec may return an activated specification
130
+ # with the same name and version from a different gem installation.
131
+ if rs.equal?(self) || rs.loaded_from != loaded_from
130
132
  rs = Gem::Specification.load(loaded_from)
131
133
  Bundler.rubygems.stub_set_spec(stub, rs)
132
134
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  module Bundler
4
- VERSION = "4.0.18".freeze
4
+ VERSION = "4.0.20".freeze
5
5
 
6
6
  def self.bundler_major_version
7
7
  @bundler_major_version ||= gem_version.segments.first
data/lib/bundler.rb CHANGED
@@ -527,11 +527,11 @@ module Bundler
527
527
  end
528
528
 
529
529
  def load_gemspec_uncached(file, validate = false)
530
- path = Pathname.new(file)
531
- contents = read_file(file)
530
+ path = Pathname.new(file).expand_path
531
+ contents = read_file(path.to_s)
532
532
  spec = eval_gemspec(path, contents)
533
533
  return unless spec
534
- spec.loaded_from = path.expand_path.to_s
534
+ spec.loaded_from = path.to_s
535
535
  Bundler.rubygems.validate(spec) if validate
536
536
  spec
537
537
  end
@@ -646,9 +646,19 @@ module Bundler
646
646
  eval_yaml_gemspec(path, contents)
647
647
  else
648
648
  # Eval the gemspec from its parent directory, because some gemspecs
649
- # depend on "./" relative paths.
649
+ # depend on "./" relative paths. The caller expands `path` first, since
650
+ # expanding it here would resolve against the gemspec directory once
651
+ # the chdir is active.
650
652
  SharedHelpers.chdir(path.dirname.to_s) do
651
- eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s)
653
+ # TOPLEVEL_BINDING always belongs to the main box, so inside a
654
+ # Ruby::Box use a binding from the box Bundler is loaded in, where
655
+ # Gem::Specification carries Bundler's own monkey patches.
656
+ eval_binding = if defined?(Ruby::Box) && Ruby::Box.enabled?
657
+ Ruby::Box.current.eval("binding")
658
+ else
659
+ TOPLEVEL_BINDING.dup
660
+ end
661
+ eval(contents, eval_binding, path.to_s)
652
662
  end
653
663
  end
654
664
  rescue ScriptError, StandardError => e
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.18
4
+ version: 4.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Arko
@@ -401,7 +401,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
401
401
  - !ruby/object:Gem::Version
402
402
  version: 3.4.1
403
403
  requirements: []
404
- rubygems_version: 3.6.9
404
+ rubygems_version: 4.0.16
405
405
  specification_version: 4
406
406
  summary: The best way to manage your application's dependencies
407
407
  test_files: []