rubygems-update 4.1.0.beta2 → 4.1.0.beta3

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/Manifest.txt +1 -1
  4. data/lib/bundler/build_metadata.rb +2 -2
  5. data/lib/bundler/cli/lock.rb +5 -1
  6. data/lib/bundler/definition.rb +45 -9
  7. data/lib/bundler/dsl.rb +34 -21
  8. data/lib/bundler/fetcher/downloader.rb +9 -3
  9. data/lib/bundler/fetcher.rb +4 -1
  10. data/lib/bundler/lockfile_generator.rb +6 -0
  11. data/lib/bundler/lockfile_parser.rb +17 -1
  12. data/lib/bundler/man/gemfile.5 +18 -9
  13. data/lib/bundler/man/gemfile.5.ronn +28 -14
  14. data/lib/bundler/match_platform.rb +12 -3
  15. data/lib/bundler/override.rb +60 -7
  16. data/lib/bundler/resolver.rb +1 -1
  17. data/lib/bundler/rubygems_ext.rb +15 -4
  18. data/lib/bundler/source/rubygems.rb +2 -2
  19. data/lib/bundler/source_map.rb +1 -1
  20. data/lib/bundler/vendor/fileutils/lib/fileutils.rb +54 -29
  21. data/lib/bundler/version.rb +1 -1
  22. data/lib/rubygems/compact_index_client/parser.rb +1 -0
  23. data/lib/rubygems/ext/builder.rb +7 -3
  24. data/lib/rubygems/specification_policy.rb +12 -10
  25. data/lib/rubygems/uninstaller.rb +14 -2
  26. data/lib/rubygems/user_interaction.rb +6 -9
  27. data/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +10 -1
  28. data/lib/rubygems/vendor/net-http/lib/net/http/header.rb +56 -15
  29. data/lib/rubygems/vendor/net-http/lib/net/http/request.rb +1 -0
  30. data/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +59 -24
  31. data/lib/rubygems/vendor/net-http/lib/net/http/response.rb +40 -15
  32. data/lib/rubygems/vendor/net-http/lib/net/http.rb +73 -14
  33. data/lib/rubygems/vendor/net-protocol/COPYING +56 -0
  34. data/lib/rubygems/vendor/net-protocol/lib/net/protocol.rb +95 -26
  35. data/lib/rubygems/vendor/uri/lib/uri/common.rb +1 -1
  36. data/lib/rubygems/vendor/uri/lib/uri/generic.rb +15 -3
  37. data/lib/rubygems/vendor/uri/lib/uri/version.rb +1 -1
  38. data/lib/rubygems/yaml_serializer.rb +131 -103
  39. data/lib/rubygems.rb +23 -7
  40. metadata +3 -3
  41. data/lib/rubygems/vendor/net-protocol/LICENSE.txt +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92ef5aaf37fa34e9c54d4ccb621998b24ad8a17dacf8fd40c4d3431b1deadf5f
4
- data.tar.gz: b7f8ac5ccc71b284957ce258ce02c6930926da7e32d15ac1295220fd7caa6dbc
3
+ metadata.gz: 6318a5162408d17e02ca7534ba15f8701fe7eeeeaf7d891f4331588d0e595472
4
+ data.tar.gz: c044abf170bb96482454ed885860c11aa96cda0618cdaac5177009bdbb1d814d
5
5
  SHA512:
6
- metadata.gz: f27352dfa7e4d9f164444c96ee7ce41ff17dd8288ce4a7e96f3f5c4ca3452fee4427a73abb7c82f753165488880039fcef77e6d3870addf9a57d908cfe8f3810
7
- data.tar.gz: 1dcf75db4af1ac4206836b95283658481f5d70a3a049e06959a642efbcb5e7a4945067417b43daa8ac648f72aa6d7124907c31546ba82f1102fa334237426bc5
6
+ metadata.gz: 3c0e7401e36950ef3563213d1489dacccf2cdabf370c63bdb789a0ac0f0bd68987d3ac558dcb9054bbd9dc6ab0003853878d4563f43a43fb995d77d6ecfd6ddb
7
+ data.tar.gz: 0677d5d8ec93ba1e7d6698c7b480d5f496cc2d42a37aea380e981133da66ef3b49bece38b90aab61425a30c5b7126e6f2354eea48614b508ec6ac72e6225e856
data/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  Bundler's changelog before 4.0.0, when it was versioned separately from RubyGems, is in [doc/CHANGELOG-bundler-2.x.md](https://github.com/ruby/rubygems/blob/master/doc/CHANGELOG-bundler-2.x.md).
4
4
 
5
+ ## 4.1.0.beta3 / 2026-10-28
6
+
7
+ ### RubyGems
8
+
9
+ #### Enhancements:
10
+
11
+ * Installs bundler 4.1.0.beta3 as a default gem.
12
+
13
+ #### Bug fixes:
14
+
15
+ * Skip executable removal gracefully when the bin directory is missing. Pull request [#9963](https://github.com/ruby/rubygems/pull/9963) by Evan Li and Hiroshi SHIBATA
16
+ * Fix file locks letting multiple processes in when lock files are removed. Pull request [#9930](https://github.com/ruby/rubygems/pull/9930) by Richard Janita and Hiroshi SHIBATA
17
+ * Turn off echo before printing the password prompt. Pull request [#9969](https://github.com/ruby/rubygems/pull/9969) by Mike Dalessio
18
+
19
+ ### Bundler
20
+
21
+ Changes already released in 4.0.x are not repeated here.
22
+
23
+ #### Features:
24
+
25
+ * Rename the `from:` and `to:` keys of `override` to `in:` and `with:`. Pull request [#9979](https://github.com/ruby/rubygems/pull/9979) by Hiroshi SHIBATA
26
+ * Record overrides in Gemfile.lock (`OVERRIDES` section). Pull request [#9958](https://github.com/ruby/rubygems/pull/9958) by Ufuk Kayserilioglu
27
+
28
+ #### Bug fixes:
29
+
30
+ * Make `Bundler::Source::Rubygems#hash` agree with `eql?`. Pull request [#9983](https://github.com/ruby/rubygems/pull/9983) by Hiroshi SHIBATA
31
+ * Add checksums after platform changes in `bundle lock`. Pull request [#9960](https://github.com/ruby/rubygems/pull/9960) by Farid Zakaria
32
+ * Apply BUNDLE_RETRY to lazy gemspec downloads. Pull request [#9868](https://github.com/ruby/rubygems/pull/9868) by Islam Elsayed
33
+ * Fix file locks letting multiple processes in when lock files are removed. Pull request [#9930](https://github.com/ruby/rubygems/pull/9930) by Richard Janita and Hiroshi SHIBATA
34
+ * Preload git sources that share a cache one after another. Pull request [#9951](https://github.com/ruby/rubygems/pull/9951) by Shai
35
+
36
+ ## 4.0.23 / 2026-10-14
37
+
38
+ ### RubyGems
39
+
40
+ #### Enhancements:
41
+
42
+ * Update vendored gems to latest versions. Pull request [#9948](https://github.com/ruby/rubygems/pull/9948) by Hiroshi SHIBATA
43
+ * Drop setuid, setgid and sticky bits when extracting gems. Pull request [#9929](https://github.com/ruby/rubygems/pull/9929) by Hiroshi SHIBATA
44
+ * Improve handling of paths with invalid Windows characters. Pull request [#9276](https://github.com/ruby/rubygems/pull/9276) by Said Kaldybaev and Hiroshi SHIBATA
45
+ * Installs bundler 4.0.23 as a default gem.
46
+
47
+ ### Bundler
48
+
49
+ #### Enhancements:
50
+
51
+ * Update vendored gems to latest versions. Pull request [#9948](https://github.com/ruby/rubygems/pull/9948) by Hiroshi SHIBATA
52
+
53
+ #### Bug fixes:
54
+
55
+ * Stop retrying a 416 response to a request without Range. Pull request [#9938](https://github.com/ruby/rubygems/pull/9938) by Hiroshi SHIBATA
56
+ * Respect Gemfile lockfile in `bundle lock`. Pull request [#9661](https://github.com/ruby/rubygems/pull/9661) by David Stosik and Hiroshi SHIBATA
57
+
5
58
  ## 4.1.0.beta2 / 2026-10-08
6
59
 
7
60
  ### RubyGems
data/Manifest.txt CHANGED
@@ -503,7 +503,7 @@ lib/rubygems/vendor/net-http/lib/net/http/response.rb
503
503
  lib/rubygems/vendor/net-http/lib/net/http/responses.rb
504
504
  lib/rubygems/vendor/net-http/lib/net/http/status.rb
505
505
  lib/rubygems/vendor/net-http/lib/net/https.rb
506
- lib/rubygems/vendor/net-protocol/LICENSE.txt
506
+ lib/rubygems/vendor/net-protocol/COPYING
507
507
  lib/rubygems/vendor/net-protocol/lib/net/protocol.rb
508
508
  lib/rubygems/vendor/optparse/COPYING
509
509
  lib/rubygems/vendor/optparse/lib/optionparser.rb
@@ -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 = "2026-10-08".freeze
8
- @git_commit_sha = "f8b8b02".freeze
7
+ @built_at = "2026-10-28".freeze
8
+ @git_commit_sha = "cd7536a".freeze
9
9
  # end ivars
10
10
 
11
11
  # A hash representation of the build metadata.
@@ -42,7 +42,6 @@ module Bundler
42
42
 
43
43
  Bundler.settings.temporary(frozen: false) do
44
44
  definition = Bundler.definition(update, Bundler.default_lockfile)
45
- definition.add_checksums if options["add-checksums"]
46
45
 
47
46
  Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
48
47
 
@@ -64,6 +63,11 @@ module Bundler
64
63
  raise InvalidOption, "Removing all platforms from the bundle is not allowed"
65
64
  end
66
65
 
66
+ # Adding checksums resolves the bundle to fetch them, so it has to
67
+ # run after every platform change above has been recorded, or the
68
+ # new platforms are listed in the lockfile without their specs.
69
+ definition.add_checksums if options["add-checksums"]
70
+
67
71
  definition.remotely! unless options[:local]
68
72
 
69
73
  if options["normalize-platforms"]
@@ -167,6 +167,7 @@ module Bundler
167
167
 
168
168
  @dependency_changes = converge_dependencies
169
169
  @local_changes = converge_locals
170
+ @override_changes = converge_overrides
170
171
 
171
172
  check_lockfile
172
173
  end
@@ -477,6 +478,11 @@ module Bundler
477
478
  @resolved_bundler_version || Bundler.gem_version
478
479
  end
479
480
 
481
+ # The lines for the OVERRIDES section of Gemfile.lock.
482
+ def overrides_to_lock
483
+ @overrides.map(&:to_lock).uniq
484
+ end
485
+
480
486
  def to_lock
481
487
  require_relative "lockfile_generator"
482
488
  LockfileGenerator.generate(self)
@@ -599,6 +605,11 @@ module Bundler
599
605
  added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
600
606
  deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any?
601
607
 
608
+ if @override_changes
609
+ added.concat((overrides_to_lock - locked_overrides_to_lock).sort.map {|o| "* override #{o}" })
610
+ deleted.concat((locked_overrides_to_lock - overrides_to_lock).sort.map {|o| "* override #{o}" })
611
+ end
612
+
602
613
  both_sources = Hash.new {|h, k| h[k] = [] }
603
614
  current_dependencies.each {|d| both_sources[d.name][0] = d }
604
615
  current_locked_dependencies.each {|d| both_sources[d.name][1] = d }
@@ -908,6 +919,8 @@ module Bundler
908
919
  else
909
920
  lockfile_changed_reason
910
921
  end
922
+ elsif @override_changes
923
+ "the overrides in your gemfile changed"
911
924
  else
912
925
  "some dependencies were deleted from your gemfile"
913
926
  end
@@ -943,6 +956,7 @@ module Bundler
943
956
  [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""],
944
957
  [@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"],
945
958
  [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""],
959
+ [@override_changes, "the overrides in your gemfile changed"],
946
960
  ].select(&:first).map(&:last).join(", ")
947
961
  end
948
962
 
@@ -1115,7 +1129,7 @@ module Bundler
1115
1129
  end
1116
1130
 
1117
1131
  # The lockfile keeps the upstream dependencies of every gem, so a gem that
1118
- # a `from:`/`to:` override cut loose stays locked until a resolution drops
1132
+ # a `with:`/`in:` override cut loose stays locked until a resolution drops
1119
1133
  # it.
1120
1134
  def converge_rewritten_dependencies
1121
1135
  @overrides.each do |override|
@@ -1135,7 +1149,7 @@ module Bundler
1135
1149
  end
1136
1150
  end
1137
1151
 
1138
- # A misspelled `from:` gem leaves the override with nothing to rewrite.
1152
+ # A misspelled `in:` gem leaves the override with nothing to rewrite.
1139
1153
  def warn_inert_overrides(specs)
1140
1154
  @overrides.each do |override|
1141
1155
  next unless override.from
@@ -1145,13 +1159,29 @@ module Bundler
1145
1159
  end
1146
1160
  end
1147
1161
 
1162
+ # The lockfile records the Gemfile's overrides, so a change to them is a
1163
+ # change to the lockfile even when every locked version still satisfies
1164
+ # them. Rewriting the section needs no resolution, so this stays out of
1165
+ # #something_changed?. #write_lock rewrites it because the generated
1166
+ # contents differ, and the frozen check reports it.
1167
+ def converge_overrides
1168
+ return false unless @locked_gems
1169
+
1170
+ overrides_to_lock.sort != locked_overrides_to_lock.sort
1171
+ end
1172
+
1173
+ def locked_overrides_to_lock
1174
+ Array(@locked_gems&.overrides).map(&:to_lock).uniq
1175
+ end
1176
+
1148
1177
  def converge_overrides_outside_dependencies
1149
1178
  @overrides.each do |override|
1150
- # :all and metadata overrides are intentionally not pre-unlocked. The
1151
- # lockfile does not record overrides, so unlocking for them would
1152
- # re-resolve on every run and reject every frozen install. Install-time
1153
- # compatibility checks already honor them for locked specs, and they
1154
- # affect resolution on a fresh lock or when the user runs `bundle update`.
1179
+ # :all and metadata overrides are intentionally not pre-unlocked.
1180
+ # Unlocking for them would re-resolve gems the user did not ask to
1181
+ # move. Install-time compatibility checks already honor them for locked
1182
+ # specs, and they affect resolution on a fresh lock or when the user
1183
+ # runs `bundle update`. Changing one still rewrites the OVERRIDES
1184
+ # section (see #converge_overrides).
1155
1185
  next unless override.target.is_a?(String) && override.field == :version
1156
1186
 
1157
1187
  name = override.target
@@ -1266,7 +1296,7 @@ module Bundler
1266
1296
  def preload_git_source_worker
1267
1297
  workers = Bundler.settings.installation_parallelization
1268
1298
 
1269
- @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(source, _) { source.specs })
1299
+ @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(sources, _) { sources.each(&:specs) })
1270
1300
  end
1271
1301
 
1272
1302
  def preload_git_sources
@@ -1277,7 +1307,13 @@ module Bundler
1277
1307
  end
1278
1308
 
1279
1309
  begin
1280
- needed_git_sources.each {|source| preload_git_source_worker.enq(source) }
1310
+ # Sources from the same repository share one cache directory, and
1311
+ # running git in that directory from two workers at once makes the
1312
+ # fetches collide. Preload sources that share a cache in one job, one
1313
+ # after the other, and run the separate caches in parallel.
1314
+ needed_git_sources.group_by(&:cache_path).each_value do |sources|
1315
+ preload_git_source_worker.enq(sources)
1316
+ end
1281
1317
  ensure
1282
1318
  preload_git_source_worker.stop
1283
1319
  end
data/lib/bundler/dsl.rb CHANGED
@@ -202,7 +202,12 @@ module Bundler
202
202
  validate_override_target!(target)
203
203
  source_location = caller_locations(1, 1)&.first
204
204
 
205
- if operations.key?(:from) || operations.key?(:to)
205
+ # 4.1.0.beta2 spelled `with:` and `in:` as `to:` and `from:`.
206
+ if operations.key?(:from)
207
+ raise ArgumentError, "`from:` and `to:` of `override` are now `in:` and `with:`; write `override #{target.inspect}, with: #{operations.fetch(:to, "<gem>").inspect}, in: #{operations[:from].inspect}`"
208
+ end
209
+
210
+ if operations.key?(:in) || operations.key?(:with)
206
211
  add_dependency_override(target, operations, source_location)
207
212
  return
208
213
  end
@@ -292,8 +297,16 @@ module Bundler
292
297
 
293
298
  def validate_override_target!(target)
294
299
  return if target == :all
295
- return if target.is_a?(String)
296
- raise ArgumentError, "override target must be :all or a gem name string, got #{target.inspect}"
300
+ raise ArgumentError, "override target must be :all or a gem name string, got #{target.inspect}" unless target.is_a?(String)
301
+ validate_override_gem_name!(target)
302
+ end
303
+
304
+ # Override names are recorded in the lockfile's OVERRIDES section, so they
305
+ # must be valid gem names.
306
+ def validate_override_gem_name!(name)
307
+ validate_gem_name!(name)
308
+ return if Gem::Specification::VALID_NAME_PATTERN.match?(name)
309
+ raise GemfileError, "#{name.inspect} is not a valid gem name"
297
310
  end
298
311
 
299
312
  def validate_override_field!(field)
@@ -324,40 +337,40 @@ module Bundler
324
337
  end
325
338
 
326
339
  def add_dependency_override(target, operations, source_location)
327
- unless operations.key?(:from)
328
- raise ArgumentError, "`override #{target.inspect}, to:` requires `from:` naming the gem whose dependency is rewritten"
340
+ unless operations.key?(:in)
341
+ raise ArgumentError, "`override #{target.inspect}, with:` requires `in:` naming the gem whose dependency is rewritten"
329
342
  end
330
- unless operations.key?(:to)
331
- raise ArgumentError, "`override #{target.inspect}, from:` requires `to:`; use `to: nil` to drop the dependency or `to: \"<gem>\"` to replace it"
343
+ unless operations.key?(:with)
344
+ raise ArgumentError, "`override #{target.inspect}, in:` requires `with:`; use `with: nil` to drop the dependency or `with: \"<gem>\"` to replace it"
332
345
  end
333
346
 
334
- from = operations[:from]
335
- to = operations[:to]
347
+ from = operations[:in]
348
+ with = operations[:with]
336
349
  requirement = operations[:version]
337
350
 
338
- raise ArgumentError, "`override :all` does not accept `from:`" if target == :all
339
- raise ArgumentError, "`from:` must be a gem name string, got #{from.inspect}" unless from.is_a?(String)
340
- raise ArgumentError, "`to:` must be a gem name string or nil, got #{to.inspect}" unless to.nil? || to.is_a?(String)
341
- raise ArgumentError, "`to:` must name a gem other than #{target.inspect} and #{from.inspect}" if to == target || to == from
342
- validate_gem_name!(from)
343
- validate_gem_name!(to) if to
351
+ raise ArgumentError, "`override :all` does not accept `in:`" if target == :all
352
+ raise ArgumentError, "`in:` must be a gem name string, got #{from.inspect}" unless from.is_a?(String)
353
+ raise ArgumentError, "`with:` must be a gem name string or nil, got #{with.inspect}" unless with.nil? || with.is_a?(String)
354
+ raise ArgumentError, "`with:` must name a gem other than #{target.inspect} and #{from.inspect}" if with == target || with == from
355
+ validate_override_gem_name!(from)
356
+ validate_override_gem_name!(with) if with
344
357
 
345
- unsupported = operations.keys - [:from, :to, :version]
358
+ unsupported = operations.keys - [:in, :with, :version]
346
359
  if unsupported.any?
347
- raise ArgumentError, "unsupported override field `#{unsupported.first}:` with `from:`; supported fields: `to:`, `version:`"
360
+ raise ArgumentError, "unsupported override field `#{unsupported.first}:` with `in:`; supported fields: `with:`, `version:`"
348
361
  end
349
362
 
350
363
  if operations.key?(:version)
351
- raise ArgumentError, "`version:` with `from:` requires `to:` to name a replacement gem" if to.nil?
352
- raise ArgumentError, "`version:` with `from:` must be a version requirement string, got #{requirement.inspect}" unless requirement.is_a?(String)
364
+ raise ArgumentError, "`version:` with `in:` requires `with:` to name a replacement gem" if with.nil?
365
+ raise ArgumentError, "`version:` with `in:` must be a version requirement string, got #{requirement.inspect}" unless requirement.is_a?(String)
353
366
  validate_override_operation!(requirement)
354
367
  end
355
368
 
356
369
  if @overrides.any? {|o| o.target == target && o.from == from }
357
- raise ArgumentError, "duplicate override for #{target.inspect} `from: #{from.inspect}`"
370
+ raise ArgumentError, "duplicate override for #{target.inspect} `in: #{from.inspect}`"
358
371
  end
359
372
 
360
- @overrides << Override.new(target, :to, to, from: from, requirement: requirement, source_location: source_location)
373
+ @overrides << Override.new(target, :with, with, from: from, requirement: requirement, source_location: source_location)
361
374
  end
362
375
 
363
376
  # Dropping a gem the Gemfile requires directly would leave it installed
@@ -66,6 +66,8 @@ module Bundler
66
66
  end
67
67
  fetch(new_uri, headers, counter + 1)
68
68
  when Gem::Net::HTTPRequestedRangeNotSatisfiable
69
+ raise bad_response(response) unless headers.key?("Range")
70
+
69
71
  new_headers = headers.dup
70
72
  new_headers.delete("Range")
71
73
  fetch(uri, new_headers)
@@ -81,9 +83,7 @@ module Bundler
81
83
  when Gem::Net::HTTPNotFound
82
84
  raise FallbackError, "Gem::Net::HTTPNotFound: #{filtered_uri}"
83
85
  else
84
- message = "Gem::#{response.class.name.gsub(/\AGem::/, "")}"
85
- message += ": #{response.body}" unless response.body.empty?
86
- raise HTTPError, message
86
+ raise bad_response(response)
87
87
  end
88
88
  end
89
89
 
@@ -121,6 +121,12 @@ module Bundler
121
121
 
122
122
  private
123
123
 
124
+ def bad_response(response)
125
+ message = "Gem::#{response.class.name.gsub(/\AGem::/, "")}"
126
+ message += ": #{response.body}" unless response.body.empty?
127
+ HTTPError.new(message)
128
+ end
129
+
124
130
  def network_down_error(uri, filtered_uri)
125
131
  host = uri.host
126
132
  host_port = "#{host}:#{uri.port}"
@@ -157,7 +157,10 @@ module Bundler
157
157
  elsif cached_spec_path = gemspec_cached_path(spec_file_name)
158
158
  Bundler.load_gemspec(cached_spec_path)
159
159
  else
160
- Bundler.safe_load_marshal Bundler.rubygems.inflate(downloader.fetch(uri).body)
160
+ body = Bundler::Retry.new("fetcher", FAIL_ERRORS).attempts do
161
+ downloader.fetch(uri).body
162
+ end
163
+ Bundler.safe_load_marshal Bundler.rubygems.inflate(body)
161
164
  end
162
165
  raise MarshalError, "is #{spec.inspect}" unless spec.is_a?(Gem::Specification)
163
166
  spec
@@ -19,6 +19,7 @@ module Bundler
19
19
  add_sources
20
20
  add_platforms
21
21
  add_dependencies
22
+ add_overrides
22
23
  add_content_addresses
23
24
  add_checksums
24
25
  add_locked_ruby_version
@@ -67,6 +68,11 @@ module Bundler
67
68
  end
68
69
  end
69
70
 
71
+ def add_overrides
72
+ overrides = definition.overrides_to_lock
73
+ add_section("OVERRIDES", overrides) unless overrides.empty?
74
+ end
75
+
70
76
  def add_content_addresses
71
77
  content_addresses = definition.resolve.filter_map do |spec|
72
78
  next unless Gem::ContentAddress.content_addressed?(spec, validate_ruby_abi: false)
@@ -36,12 +36,14 @@ module Bundler
36
36
  :bundler_version,
37
37
  :ruby_version,
38
38
  :checksums,
39
+ :overrides,
39
40
  )
40
41
 
41
42
  BUNDLED = "BUNDLED WITH"
42
43
  DEPENDENCIES = "DEPENDENCIES"
43
44
  CHECKSUMS = "CHECKSUMS"
44
45
  CONTENT_ADDRESSES = "CONTENT ADDRESSES"
46
+ OVERRIDES = "OVERRIDES"
45
47
  PLATFORMS = "PLATFORMS"
46
48
  RUBY = "RUBY VERSION"
47
49
  GIT = "GIT"
@@ -61,7 +63,11 @@ module Bundler
61
63
  Gem::Version.create("4.1.0") => [CONTENT_ADDRESSES].freeze,
62
64
  }.freeze
63
65
 
64
- KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze
66
+ # OVERRIDES is deliberately not in SECTIONS_BY_VERSION_INTRODUCED. Sections
67
+ # listed there are ignored when comparing a lockfile bundled with an older
68
+ # Bundler, so a lockfile still BUNDLED WITH 4.0 would never get its
69
+ # overrides recorded while the resolution stays the same.
70
+ KNOWN_SECTIONS = (SECTIONS_BY_VERSION_INTRODUCED.values.flatten + [OVERRIDES]).freeze
65
71
 
66
72
  ENVIRONMENT_VERSION_SECTIONS = [BUNDLED, RUBY].freeze
67
73
  deprecate_constant(:ENVIRONMENT_VERSION_SECTIONS)
@@ -104,6 +110,7 @@ module Bundler
104
110
  @dependencies = {}
105
111
  @parse_method = nil
106
112
  @specs = {}
113
+ @overrides = nil
107
114
  @lockfile_path = lockfile_path || begin
108
115
  SharedHelpers.relative_lockfile_path
109
116
  rescue GemfileNotFound
@@ -144,6 +151,9 @@ module Bundler
144
151
  @parse_method = :parse_checksum
145
152
  elsif line == CONTENT_ADDRESSES
146
153
  @parse_method = :parse_content_address
154
+ elsif line == OVERRIDES
155
+ @overrides = []
156
+ @parse_method = :parse_override
147
157
  elsif line == PLATFORMS
148
158
  @parse_method = :parse_platform
149
159
  elsif line == RUBY
@@ -270,6 +280,12 @@ module Bundler
270
280
  @dependencies[dep.name] = dep
271
281
  end
272
282
 
283
+ def parse_override(line)
284
+ raise ArgumentError, "invalid override in lockfile: #{line.inspect}" unless line.start_with?(" ") && line[2] != " "
285
+
286
+ @overrides << Override.from_lock(line[2..])
287
+ end
288
+
273
289
  def parse_checksum(line)
274
290
  return unless line =~ NAME_VERSION
275
291
 
@@ -521,32 +521,41 @@ gem "rails", "~> 7\.0"
521
521
  .fi
522
522
  .IP "" 0
523
523
  .P
524
- The override only affects resolution and the install\-time Ruby/RubyGems compatibility checks; \fBGemfile\.lock\fR continues to reflect the resolved versions, not the rewritten requirements\. When resolution still fails, Bundler appends the active overrides (with their Gemfile location) to the error message so it is clear which override shaped the constraint set\.
524
+ The override only affects resolution and the install\-time Ruby/RubyGems compatibility checks; the gems in \fBGemfile\.lock\fR continue to reflect the resolved versions, not the rewritten requirements\. The overrides themselves are recorded in the lockfile's \fBOVERRIDES\fR section, one line per field, sorted:
525
+ .IP "" 4
526
+ .nf
527
+ OVERRIDES
528
+ "rails", version: ">= 8\.0"
529
+ :all, required_ruby_version: :ignore_upper
530
+ .fi
531
+ .IP "" 0
532
+ .P
533
+ Requirements are recorded in normalized form, so \fB">=8\.0"\fR and \fB">= 8\.0"\fR are the same override\. A frozen install fails when the Gemfile's overrides differ from the recorded ones\. Adding or removing a \fB:all\fR or metadata override rewrites the section and keeps the locked versions\. The exception is a locked gem that is not installed and whose metadata rejects the current Ruby\. Bundler re\-resolves it, as it does without the override\. Run \fBbundle update\fR to re\-resolve under the override\. When resolution still fails, Bundler appends the active overrides (with their Gemfile location) to the error message so it is clear which override shaped the constraint set\.
525
534
  .SS "DROPPING OR REPLACING A DEPENDENCY"
526
- \fBfrom:\fR and \fBto:\fR rewrite a single dependency of a single gem:
535
+ \fBwith:\fR and \fBin:\fR rewrite a single dependency of a single gem:
527
536
  .IP "" 4
528
537
  .nf
529
- override <target>, from: <gem>, to: nil
530
- override <target>, from: <gem>, to: <replacement>, version: <requirement>
538
+ override <target>, with: nil, in: <gem>
539
+ override <target>, with: <replacement>, in: <gem>, version: <requirement>
531
540
  .fi
532
541
  .IP "" 0
533
542
  .P
534
- \fBto: nil\fR removes the dependency of \fB<gem>\fR on \fB<target>\fR, so \fB<target>\fR is not installed unless something else still requires it\. \fBto: <replacement>\fR makes \fB<gem>\fR depend on \fB<replacement>\fR instead of \fB<target>\fR\. The requirement \fB<gem>\fR put on \fB<target>\fR is discarded, and the optional \fBversion:\fR gives the replacement its own\. Both forms apply whether \fB<gem>\fR is a direct or a transitive dependency\.
543
+ \fBwith: nil\fR removes the dependency of \fB<gem>\fR on \fB<target>\fR, so \fB<target>\fR is not installed unless something else still requires it\. \fBwith: <replacement>\fR makes \fB<gem>\fR depend on \fB<replacement>\fR instead of \fB<target>\fR\. The requirement \fB<gem>\fR put on \fB<target>\fR is discarded, and the optional \fBversion:\fR gives the replacement its own\. Both forms apply whether \fB<gem>\fR is a direct or a transitive dependency\.
535
544
  .P
536
- \fBfrom:\fR is required, and so is \fBto:\fR once \fBfrom:\fR is given\. To rewrite the same dependency of several gems, write one \fBoverride\fR per gem\. It is an error to declare the same \fB<target>\fR and \fBfrom:\fR twice, to drop a gem that the Gemfile requires directly, or to replace a gem that another gem still requires, since both would then be in the bundle\.
545
+ \fBin:\fR is required, and so is \fBwith:\fR once \fBin:\fR is given\. To rewrite the same dependency of several gems, write one \fBoverride\fR per gem\. It is an error to declare the same \fB<target>\fR and \fBin:\fR twice, to drop a gem that the Gemfile requires directly, or to replace a gem that another gem still requires, since both would then be in the bundle\.
537
546
  .IP "" 4
538
547
  .nf
539
548
  # jekyll only loads em\-websocket for `jekyll serve \-\-livereload`\.
540
- override "em\-websocket", from: "jekyll", to: nil
549
+ override "em\-websocket", with: nil, in: "jekyll"
541
550
 
542
551
  # Use an API\-compatible gem that provides the same files\.
543
- override "foo", from: "bar", to: "foo\-lite"
552
+ override "foo", with: "foo\-lite", in: "bar"
544
553
  .fi
545
554
  .IP "" 0
546
555
  .P
547
556
  Bundler does not check what a gem loads at runtime\. If \fB<gem>\fR requires \fB<target>\fR unconditionally, dropping it makes that require raise \fBLoadError\fR, as with jekyll's dependency on jekyll\-sass\-converter or the adapters that faraday 1\.x requires on load\. A replacement only works when it provides the files \fB<gem>\fR requires, and it does not take over the name of \fB<target>\fR: \fBGem\.loaded_specs\fR has no entry for \fB<target>\fR, and \fBgem "<target>"\fR fails\.
548
557
  .P
549
- \fBGemfile\.lock\fR keeps listing \fB<target>\fR under \fB<gem>\fR as the gem publishes it, while the locked gems leave \fB<target>\fR out or include \fB<replacement>\fR\. A frozen install accepts that lockfile as long as the Gemfile still has the override\. Removing the override makes Bundler resolve \fB<target>\fR again\.
558
+ \fBGemfile\.lock\fR keeps listing \fB<target>\fR under \fB<gem>\fR as the gem publishes it, while the locked gems leave \fB<target>\fR out or include \fB<replacement>\fR, and the \fBOVERRIDES\fR section records the rewrite so tools that read the lockfile can apply it\. A frozen install accepts that lockfile as long as the Gemfile still has the override\. Removing the override makes Bundler resolve \fB<target>\fR again\.
550
559
  .SH "SOURCE PRIORITY"
551
560
  When attempting to locate a gem to satisfy a gem requirement, bundler uses the following priority order:
552
561
  .IP "1." 4
@@ -623,36 +623,49 @@ Declaring the same `target` and `field` twice is an error.
623
623
  gem "rails", "~> 7.0"
624
624
 
625
625
  The override only affects resolution and the install-time Ruby/RubyGems
626
- compatibility checks; `Gemfile.lock` continues to reflect the resolved
627
- versions, not the rewritten requirements. When resolution still fails,
626
+ compatibility checks; the gems in `Gemfile.lock` continue to reflect the
627
+ resolved versions, not the rewritten requirements. The overrides themselves are
628
+ recorded in the lockfile's `OVERRIDES` section, one line per field, sorted:
629
+
630
+ OVERRIDES
631
+ "rails", version: ">= 8.0"
632
+ :all, required_ruby_version: :ignore_upper
633
+
634
+ Requirements are recorded in normalized form, so `">=8.0"` and `">= 8.0"` are
635
+ the same override. A frozen install fails when the Gemfile's overrides differ
636
+ from the recorded ones. Adding or removing a `:all` or metadata override
637
+ rewrites the section and keeps the locked versions. The exception is a locked
638
+ gem that is not installed and whose metadata rejects the current Ruby. Bundler
639
+ re-resolves it, as it does without the override. Run `bundle update` to
640
+ re-resolve under the override. When resolution still fails,
628
641
  Bundler appends the active overrides (with their Gemfile location) to the
629
642
  error message so it is clear which override shaped the constraint set.
630
643
 
631
644
  ### DROPPING OR REPLACING A DEPENDENCY
632
645
 
633
- `from:` and `to:` rewrite a single dependency of a single gem:
646
+ `with:` and `in:` rewrite a single dependency of a single gem:
634
647
 
635
- override <target>, from: <gem>, to: nil
636
- override <target>, from: <gem>, to: <replacement>, version: <requirement>
648
+ override <target>, with: nil, in: <gem>
649
+ override <target>, with: <replacement>, in: <gem>, version: <requirement>
637
650
 
638
- `to: nil` removes the dependency of `<gem>` on `<target>`, so `<target>` is
639
- not installed unless something else still requires it. `to: <replacement>`
651
+ `with: nil` removes the dependency of `<gem>` on `<target>`, so `<target>` is
652
+ not installed unless something else still requires it. `with: <replacement>`
640
653
  makes `<gem>` depend on `<replacement>` instead of `<target>`. The requirement
641
654
  `<gem>` put on `<target>` is discarded, and the optional `version:` gives the
642
655
  replacement its own. Both forms apply whether `<gem>` is a direct or a
643
656
  transitive dependency.
644
657
 
645
- `from:` is required, and so is `to:` once `from:` is given. To rewrite the
658
+ `in:` is required, and so is `with:` once `in:` is given. To rewrite the
646
659
  same dependency of several gems, write one `override` per gem. It is an error
647
- to declare the same `<target>` and `from:` twice, to drop a gem that the
660
+ to declare the same `<target>` and `in:` twice, to drop a gem that the
648
661
  Gemfile requires directly, or to replace a gem that another gem still
649
662
  requires, since both would then be in the bundle.
650
663
 
651
664
  # jekyll only loads em-websocket for `jekyll serve --livereload`.
652
- override "em-websocket", from: "jekyll", to: nil
665
+ override "em-websocket", with: nil, in: "jekyll"
653
666
 
654
667
  # Use an API-compatible gem that provides the same files.
655
- override "foo", from: "bar", to: "foo-lite"
668
+ override "foo", with: "foo-lite", in: "bar"
656
669
 
657
670
  Bundler does not check what a gem loads at runtime. If `<gem>` requires
658
671
  `<target>` unconditionally, dropping it makes that require raise `LoadError`,
@@ -662,9 +675,10 @@ files `<gem>` requires, and it does not take over the name of `<target>`:
662
675
  `Gem.loaded_specs` has no entry for `<target>`, and `gem "<target>"` fails.
663
676
 
664
677
  `Gemfile.lock` keeps listing `<target>` under `<gem>` as the gem publishes it,
665
- while the locked gems leave `<target>` out or include `<replacement>`. A
666
- frozen install accepts that lockfile as long as the Gemfile still has the
667
- override. Removing the override makes Bundler resolve `<target>` again.
678
+ while the locked gems leave `<target>` out or include `<replacement>`, and the
679
+ `OVERRIDES` section records the rewrite so tools that read the lockfile can
680
+ apply it. A frozen install accepts that lockfile as long as the Gemfile still
681
+ has the override. Removing the override makes Bundler resolve `<target>` again.
668
682
 
669
683
  ## SOURCE PRIORITY
670
684
 
@@ -27,9 +27,18 @@ module Bundler
27
27
  end
28
28
 
29
29
  def self.prefer_content_addressable(matching)
30
- addressable, non_addressable = matching.partition {|s| Gem::ContentAddress.content_addressed?(s, validate_ruby_abi: false) }
31
- return matching if addressable.empty?
32
-
30
+ first = matching.index {|s| Gem::ContentAddress.content_addressed?(s, validate_ruby_abi: false) }
31
+ return matching unless first
32
+
33
+ addressable = [matching[first]]
34
+ non_addressable = matching.take(first)
35
+ matching.drop(first + 1).each do |s|
36
+ if Gem::ContentAddress.content_addressed?(s, validate_ruby_abi: false)
37
+ addressable << s
38
+ else
39
+ non_addressable << s
40
+ end
41
+ end
33
42
  compatible = addressable.select(&:matches_current_metadata?)
34
43
  return compatible if compatible.any?
35
44