bundler 4.0.22 → 4.1.0.beta2

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 (200) hide show
  1. checksums.yaml +4 -4
  2. data/{lib/bundler/vendor/connection_pool/LICENSE → MIT.txt} +8 -6
  3. data/bundler.gemspec +23 -6
  4. data/lib/bundler/build_metadata.rb +2 -2
  5. data/lib/bundler/checksum.rb +40 -25
  6. data/lib/bundler/ci_detector.rb +1 -1
  7. data/lib/bundler/cli/add.rb +3 -4
  8. data/lib/bundler/cli/cache.rb +4 -0
  9. data/lib/bundler/cli/clean.rb +5 -0
  10. data/lib/bundler/cli/common.rb +41 -1
  11. data/lib/bundler/cli/config.rb +4 -2
  12. data/lib/bundler/cli/doctor/ssl.rb +4 -8
  13. data/lib/bundler/cli/gem.rb +0 -17
  14. data/lib/bundler/cli/info.rb +1 -1
  15. data/lib/bundler/cli/install.rb +4 -5
  16. data/lib/bundler/cli/lock.rb +15 -5
  17. data/lib/bundler/cli/open.rb +7 -1
  18. data/lib/bundler/cli/outdated.rb +16 -13
  19. data/lib/bundler/cli/pristine.rb +2 -1
  20. data/lib/bundler/cli/show.rb +5 -1
  21. data/lib/bundler/cli/update.rb +20 -11
  22. data/lib/bundler/cli.rb +15 -9
  23. data/lib/bundler/constants.rb +1 -1
  24. data/lib/bundler/definition.rb +102 -15
  25. data/lib/bundler/dependency.rb +4 -0
  26. data/lib/bundler/dsl.rb +137 -13
  27. data/lib/bundler/endpoint_specification.rb +51 -14
  28. data/lib/bundler/env.rb +7 -24
  29. data/lib/bundler/errors.rb +34 -5
  30. data/lib/bundler/fetcher/compact_index.rb +14 -5
  31. data/lib/bundler/fetcher/connection_pools.rb +150 -0
  32. data/lib/bundler/fetcher/downloader.rb +35 -16
  33. data/lib/bundler/fetcher.rb +8 -47
  34. data/lib/bundler/gem_helper.rb +1 -1
  35. data/lib/bundler/injector.rb +3 -3
  36. data/lib/bundler/inline.rb +44 -4
  37. data/lib/bundler/installer/parallel_installer.rb +27 -31
  38. data/lib/bundler/installer.rb +21 -7
  39. data/lib/bundler/lazy_specification.rb +45 -18
  40. data/lib/bundler/lockfile_generator.rb +24 -2
  41. data/lib/bundler/lockfile_parser.rb +44 -6
  42. data/lib/bundler/man/bundle-add.1 +7 -4
  43. data/lib/bundler/man/bundle-add.1.ronn +8 -4
  44. data/lib/bundler/man/bundle-binstubs.1 +1 -1
  45. data/lib/bundler/man/bundle-cache.1 +2 -2
  46. data/lib/bundler/man/bundle-cache.1.ronn +2 -2
  47. data/lib/bundler/man/bundle-check.1 +1 -1
  48. data/lib/bundler/man/bundle-clean.1 +1 -1
  49. data/lib/bundler/man/bundle-config.1 +38 -6
  50. data/lib/bundler/man/bundle-config.1.ronn +143 -10
  51. data/lib/bundler/man/bundle-console.1 +1 -1
  52. data/lib/bundler/man/bundle-doctor.1 +1 -1
  53. data/lib/bundler/man/bundle-env.1 +1 -1
  54. data/lib/bundler/man/bundle-exec.1 +1 -1
  55. data/lib/bundler/man/bundle-fund.1 +1 -1
  56. data/lib/bundler/man/bundle-gem.1 +1 -1
  57. data/lib/bundler/man/bundle-help.1 +1 -1
  58. data/lib/bundler/man/bundle-info.1 +5 -2
  59. data/lib/bundler/man/bundle-info.1.ronn +4 -0
  60. data/lib/bundler/man/bundle-init.1 +1 -1
  61. data/lib/bundler/man/bundle-install.1 +2 -2
  62. data/lib/bundler/man/bundle-install.1.ronn +4 -4
  63. data/lib/bundler/man/bundle-issue.1 +1 -1
  64. data/lib/bundler/man/bundle-licenses.1 +1 -1
  65. data/lib/bundler/man/bundle-list.1 +1 -1
  66. data/lib/bundler/man/bundle-lock.1 +2 -2
  67. data/lib/bundler/man/bundle-lock.1.ronn +3 -2
  68. data/lib/bundler/man/bundle-open.1 +5 -2
  69. data/lib/bundler/man/bundle-open.1.ronn +4 -1
  70. data/lib/bundler/man/bundle-outdated.1 +1 -1
  71. data/lib/bundler/man/bundle-platform.1 +1 -1
  72. data/lib/bundler/man/bundle-plugin.1 +1 -1
  73. data/lib/bundler/man/bundle-pristine.1 +1 -1
  74. data/lib/bundler/man/bundle-remove.1 +1 -1
  75. data/lib/bundler/man/bundle-show.1 +5 -2
  76. data/lib/bundler/man/bundle-show.1.ronn +4 -0
  77. data/lib/bundler/man/bundle-update.1 +2 -2
  78. data/lib/bundler/man/bundle-update.1.ronn +2 -2
  79. data/lib/bundler/man/bundle-version.1 +1 -1
  80. data/lib/bundler/man/bundle.1 +1 -1
  81. data/lib/bundler/man/gemfile.5 +77 -1
  82. data/lib/bundler/man/gemfile.5.ronn +104 -0
  83. data/lib/bundler/match_metadata.rb +20 -0
  84. data/lib/bundler/match_platform.rb +25 -2
  85. data/lib/bundler/match_remote_metadata.rb +21 -6
  86. data/lib/bundler/materialization.rb +5 -2
  87. data/lib/bundler/override.rb +97 -0
  88. data/lib/bundler/plugin/dsl.rb +3 -5
  89. data/lib/bundler/plugin/events.rb +55 -13
  90. data/lib/bundler/plugin/index.rb +87 -11
  91. data/lib/bundler/plugin/installer/path.rb +1 -1
  92. data/lib/bundler/plugin/installer.rb +13 -5
  93. data/lib/bundler/plugin.rb +58 -30
  94. data/lib/bundler/remote_specification.rb +6 -3
  95. data/lib/bundler/resolver/base.rb +2 -1
  96. data/lib/bundler/resolver/incompatibility.rb +1 -1
  97. data/lib/bundler/resolver.rb +97 -23
  98. data/lib/bundler/ruby_version.rb +0 -2
  99. data/lib/bundler/rubygems_ext.rb +71 -1
  100. data/lib/bundler/rubygems_gem_installer.rb +41 -2
  101. data/lib/bundler/rubygems_integration.rb +7 -2
  102. data/lib/bundler/runtime.rb +69 -2
  103. data/lib/bundler/settings.rb +383 -56
  104. data/lib/bundler/shared_helpers.rb +2 -1
  105. data/lib/bundler/source/git/git_proxy.rb +31 -2
  106. data/lib/bundler/source/git.rb +49 -13
  107. data/lib/bundler/source/path/installer.rb +1 -1
  108. data/lib/bundler/source/rubygems/remote.rb +10 -5
  109. data/lib/bundler/source/rubygems.rb +48 -12
  110. data/lib/bundler/source/rubygems_aggregate.rb +1 -1
  111. data/lib/bundler/source_map.rb +7 -3
  112. data/lib/bundler/spec_set.rb +23 -7
  113. data/lib/bundler/stub_specification.rb +2 -1
  114. data/lib/bundler/templates/newgem/Gemfile.tt +4 -4
  115. data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -3
  116. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
  117. data/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +1 -1
  118. data/lib/bundler/ui/shell.rb +0 -4
  119. data/lib/bundler/ui/silent.rb +0 -4
  120. data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +35 -2
  121. data/lib/bundler/vendor/thor/lib/thor/base.rb +2 -1
  122. data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +14 -6
  123. data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +2 -0
  124. data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
  125. data/lib/bundler/vendor/thor/lib/thor.rb +34 -1
  126. data/lib/bundler/vendored_pub_grub.rb +5 -2
  127. data/lib/bundler/vendored_securerandom.rb +11 -8
  128. data/lib/bundler/vendored_uri.rb +14 -14
  129. data/lib/bundler/version.rb +1 -1
  130. data/lib/bundler/worker.rb +3 -2
  131. data/lib/bundler.rb +1 -1
  132. data/lib/rubygems/compact_index_client/cache.rb +122 -0
  133. data/lib/rubygems/compact_index_client/cache_file.rb +147 -0
  134. data/lib/rubygems/compact_index_client/http_fetcher.rb +90 -0
  135. data/lib/rubygems/compact_index_client/parser.rb +90 -0
  136. data/lib/rubygems/compact_index_client/updater.rb +113 -0
  137. data/lib/rubygems/compact_index_client.rb +111 -0
  138. data/lib/rubygems/cooldown_settings.rb +57 -0
  139. data/lib/rubygems/credential_store/native/linux.rb +97 -0
  140. data/lib/rubygems/credential_store/native/macos.rb +88 -0
  141. data/lib/rubygems/credential_store/native/windows.rb +130 -0
  142. data/lib/rubygems/credential_store.rb +332 -0
  143. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/assignment.rb +1 -1
  144. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +7 -7
  145. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/failure_writer.rb +1 -1
  146. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/incompatibility.rb +4 -4
  147. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/package.rb +1 -1
  148. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/partial_solution.rb +1 -1
  149. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/rubygems.rb +3 -3
  150. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/solve_failure.rb +1 -1
  151. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/static_package_source.rb +3 -3
  152. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/strategy.rb +2 -2
  153. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/term.rb +1 -1
  154. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version.rb +1 -1
  155. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_constraint.rb +3 -3
  156. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_range.rb +1 -1
  157. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_solver.rb +2 -2
  158. data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_union.rb +1 -1
  159. data/lib/rubygems/vendor/pub_grub/lib/pub_grub.rb +57 -0
  160. data/lib/{bundler → rubygems}/vendor/securerandom/lib/securerandom.rb +10 -6
  161. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/common.rb +119 -119
  162. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/file.rb +13 -13
  163. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ftp.rb +24 -24
  164. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/generic.rb +132 -132
  165. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/http.rb +18 -18
  166. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/https.rb +4 -4
  167. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldap.rb +14 -14
  168. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldaps.rb +4 -4
  169. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/mailto.rb +21 -21
  170. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc2396_parser.rb +39 -39
  171. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc3986_parser.rb +14 -14
  172. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/version.rb +1 -1
  173. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ws.rb +12 -12
  174. data/lib/{bundler → rubygems}/vendor/uri/lib/uri/wss.rb +4 -4
  175. data/lib/{bundler → rubygems}/vendor/uri/lib/uri.rb +35 -31
  176. data/lib/rubygems/yaml_serializer.rb +880 -0
  177. metadata +391 -381
  178. data/CHANGELOG.md +0 -5801
  179. data/LICENSE.md +0 -22
  180. data/README.md +0 -58
  181. data/lib/bundler/compact_index_client/cache.rb +0 -107
  182. data/lib/bundler/compact_index_client/cache_file.rb +0 -148
  183. data/lib/bundler/compact_index_client/parser.rb +0 -87
  184. data/lib/bundler/compact_index_client/updater.rb +0 -105
  185. data/lib/bundler/compact_index_client.rb +0 -92
  186. data/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +0 -227
  187. data/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +0 -3
  188. data/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +0 -56
  189. data/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +0 -230
  190. data/lib/bundler/vendor/net-http-persistent/README.rdoc +0 -82
  191. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +0 -41
  192. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +0 -65
  193. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +0 -80
  194. data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +0 -1153
  195. data/lib/bundler/vendor/pub_grub/lib/pub_grub.rb +0 -31
  196. data/lib/bundler/vendored_persistent.rb +0 -11
  197. data/lib/bundler/yaml_serializer.rb +0 -98
  198. /data/lib/{bundler → rubygems}/vendor/pub_grub/LICENSE.txt +0 -0
  199. /data/lib/{bundler → rubygems}/vendor/securerandom/COPYING +0 -0
  200. /data/lib/{bundler → rubygems}/vendor/uri/COPYING +0 -0
@@ -62,13 +62,52 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
62
62
  definition.validate_runtime!
63
63
 
64
64
  if force_latest_compatible || definition.missing_specs?
65
- Bundler.settings.temporary(inline: true, no_install: false) do
66
- installer = Bundler::Installer.install(Bundler.root, definition, system: true)
67
- installer.post_install_messages.each do |name, message|
68
- Bundler.ui.info "Post-install message from #{name}:\n#{message}"
65
+ do_install = -> do
66
+ Bundler.settings.temporary(inline: true, no_install: false) do
67
+ installer = Bundler::Installer.install(Bundler.root, definition, system: true)
68
+ installer.post_install_messages.each do |name, message|
69
+ Bundler.ui.info "Post-install message from #{name}:\n#{message}"
70
+ end
69
71
  end
70
72
  end
73
+
74
+ # When possible we do the install in a subprocess because to install
75
+ # gems we need to require some default gems like `securerandom` etc
76
+ # which may later conflict with the Gemfile requirements.
77
+ installed_in_fork = false
78
+ if Process.respond_to?(:fork)
79
+ Gem.load_yaml # Avoid NameError on Ruby 3.2's safe_yaml.rb after Gem::Specification.reset
80
+ _, status = Process.waitpid2(Process.fork do
81
+ $VERBOSE = nil
82
+ do_install.call end)
83
+ exit(status.exitstatus || status.to_i) unless status.success?
84
+
85
+ installed_in_fork = true
86
+
87
+ Bundler.reset!
88
+ Gem::Specification.reset
89
+ Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir))
90
+
91
+ builder = Bundler::Dsl.new
92
+ builder.instance_eval(&gemfile)
93
+ builder.check_primary_source_safety
94
+
95
+ definition = builder.to_definition(nil, true)
96
+ definition.validate_runtime!
97
+ else
98
+ do_install.call
99
+ end
100
+ end
101
+
102
+ configure_forked_definition = ->(d) do
103
+ d.sources.rubygems_sources.each(&:remote!)
104
+ d.sources.git_sources.each do |source|
105
+ source.cached!
106
+ source.instance_variable_set(:@copied, true)
107
+ end
108
+ def d.lock(*); end
71
109
  end
110
+ configure_forked_definition.call(definition) if installed_in_fork
72
111
 
73
112
  begin
74
113
  runtime = Bundler::Runtime.new(nil, definition).setup
@@ -84,6 +123,7 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
84
123
  builder.dependencies.delete_if {|d| d.name == name }
85
124
  builder.instance_eval { gem name, activated_version }
86
125
  definition = builder.to_definition(nil, true)
126
+ configure_forked_definition.call(definition) if installed_in_fork
87
127
 
88
128
  retry
89
129
  end
@@ -20,10 +20,6 @@ module Bundler
20
20
  state == :installed
21
21
  end
22
22
 
23
- def enqueued?
24
- state == :enqueued
25
- end
26
-
27
23
  def enqueue_with_priority?
28
24
  state == :installable && spec.extensions.any?
29
25
  end
@@ -73,7 +69,8 @@ module Bundler
73
69
  @specs = all_specs.map {|s| SpecInstallation.new(s) }
74
70
  specs_by_name = @specs.to_h {|s| [s.name, s] }
75
71
  @specs.each do |spec_install|
76
- spec_install.dependencies = spec_install.spec.dependencies.filter_map do |dep|
72
+ dependencies = Override.rewrite_dependencies(installer.definition.overrides, spec_install.name, spec_install.spec.dependencies)
73
+ spec_install.dependencies = dependencies.filter_map do |dep|
77
74
  specs_by_name[dep.name] unless dep.type == :development || dep.name == spec_install.name
78
75
  end
79
76
  end
@@ -100,7 +97,8 @@ module Bundler
100
97
  handle_error if failed_specs.any?
101
98
  @specs
102
99
  ensure
103
- worker_pool&.stop
100
+ @worker_pool&.stop
101
+ @download_worker_pool&.stop
104
102
  end
105
103
 
106
104
  private
@@ -170,17 +168,18 @@ module Bundler
170
168
  end
171
169
  end
172
170
 
171
+ def download_worker_pool
172
+ @download_worker_pool ||= Bundler::Worker.new(@size, "Gem Downloader",
173
+ ->(spec_install, worker_num) { do_download(spec_install, worker_num) }, response_queue: response_queue)
174
+ end
175
+
173
176
  def worker_pool
174
- @worker_pool ||= Bundler::Worker.new @size, "Parallel Installer", lambda {|spec_install, worker_num|
175
- case spec_install.state
176
- when :enqueued
177
- do_download(spec_install, worker_num)
178
- when :installable
179
- do_install(spec_install, worker_num)
180
- else
181
- spec_install
182
- end
183
- }
177
+ @worker_pool ||= Bundler::Worker.new(@size, "Parallel Installer",
178
+ ->(spec_install, worker_num) { do_install(spec_install, worker_num) }, response_queue: response_queue)
179
+ end
180
+
181
+ def response_queue
182
+ @response_queue ||= Thread::Queue.new
184
183
  end
185
184
 
186
185
  def do_download(spec_install, worker_num)
@@ -218,24 +217,24 @@ module Bundler
218
217
  spec_install
219
218
  end
220
219
 
221
- # Dequeue a spec and save its post-install message and then enqueue the
222
- # remaining specs.
223
- # Some specs might've had to wait til this spec was installed to be
224
- # processed so the call to `enqueue_specs` is important after every
225
- # dequeue.
220
+ # Process one completed download or installation. Downloads can finish
221
+ # before their dependencies are installed, so check all downloaded specs
222
+ # after each completion and enqueue any that are now installable.
226
223
  def process_specs(installed_specs)
227
224
  spec = worker_pool.deq
228
225
 
229
226
  if spec.installed?
230
227
  installed_specs[spec.name] = true
231
- return
232
228
  elsif spec.failed?
233
229
  return
234
- elsif spec.ready_to_install?(installed_specs)
235
- spec.state = :installable
236
230
  end
237
231
 
238
- worker_pool.enq(spec, priority: spec.enqueue_with_priority?)
232
+ @specs.each do |candidate|
233
+ next unless candidate.ready_to_install?(installed_specs)
234
+
235
+ candidate.state = :installable
236
+ worker_pool.enq(candidate, priority: candidate.enqueue_with_priority?)
237
+ end
239
238
  end
240
239
 
241
240
  def finished_installing?
@@ -274,11 +273,8 @@ module Bundler
274
273
  t
275
274
  end
276
275
 
277
- # Keys in the remains hash represent uninstalled gems specs.
278
- # We enqueue all gem specs that do not have any dependencies.
279
- # Later we call this lambda again to install specs that depended on
280
- # previously installed specifications. We continue until all specs
281
- # are installed.
276
+ # Queue every missing spec for download. `process_specs` schedules each
277
+ # downloaded spec for installation once its dependencies are installed.
282
278
  def enqueue_specs(installed_specs)
283
279
  @specs.each do |spec|
284
280
  if spec.installed?
@@ -287,7 +283,7 @@ module Bundler
287
283
  end
288
284
 
289
285
  spec.state = :enqueued
290
- worker_pool.enq spec
286
+ download_worker_pool.enq spec
291
287
  end
292
288
  end
293
289
  end
@@ -103,7 +103,7 @@ module Bundler
103
103
 
104
104
  if options[:binstubs_cmd] && spec.executables.empty?
105
105
  options = {}
106
- spec.runtime_dependencies.each do |dep|
106
+ Override.rewrite_dependencies(@definition.overrides, spec.name, spec.runtime_dependencies).each do |dep|
107
107
  bins = @definition.specs[dep].first.executables
108
108
  options[dep.name] = bins unless bins.empty?
109
109
  end
@@ -232,18 +232,32 @@ module Bundler
232
232
  end
233
233
 
234
234
  def ensure_specs_are_compatible!
235
+ overrides = @definition.overrides
235
236
  @definition.specs.each do |spec|
236
- unless spec.matches_current_ruby?
237
- raise InstallError, "#{spec.full_name} requires ruby version #{spec.required_ruby_version}, " \
238
- "which is incompatible with the current version, #{Gem.ruby_version}"
237
+ unless spec.matches_current_ruby_with_overrides?(overrides)
238
+ raise InstallError, incompatibility_message(spec, "requires ruby version #{spec.required_ruby_version}, " \
239
+ "which is incompatible with the current version, #{Gem.ruby_version}")
239
240
  end
240
- unless spec.matches_current_rubygems?
241
- raise InstallError, "#{spec.full_name} requires rubygems version #{spec.required_rubygems_version}, " \
242
- "which is incompatible with the current version, #{Gem.rubygems_version}"
241
+ unless spec.matches_current_rubygems_with_overrides?(overrides)
242
+ raise InstallError, incompatibility_message(spec, "requires rubygems version #{spec.required_rubygems_version}, " \
243
+ "which is incompatible with the current version, #{Gem.rubygems_version}")
243
244
  end
244
245
  end
245
246
  end
246
247
 
248
+ # Build the error raised when a locked spec can't run on the current Ruby or
249
+ # RubyGems. When the offending spec is a platform-specific variant locked in
250
+ # frozen mode, point users at the fix: re-resolving outside frozen mode adds
251
+ # the Ruby (non platform-specific) fallback variant to the lockfile.
252
+ def incompatibility_message(spec, reason)
253
+ message = "#{spec.full_name} #{reason}"
254
+ return message unless Bundler.frozen_bundle? && spec.platform != Gem::Platform::RUBY
255
+
256
+ message + "\n\nThe lockfile only includes the #{spec.platform} variant of #{spec.name}. " \
257
+ "If a compatible Ruby variant is available, run `bundle install` outside of frozen/deployment mode " \
258
+ "to add it to the lockfile as a fallback, then commit the updated #{SharedHelpers.relative_lockfile_path}."
259
+ end
260
+
247
261
  def lock
248
262
  @definition.lock
249
263
  end
@@ -8,8 +8,9 @@ module Bundler
8
8
  include MatchPlatform
9
9
  include ForcePlatform
10
10
 
11
- attr_reader :name, :version, :platform, :materialization
11
+ attr_reader :name, :version, :platform, :materialization, :content_address
12
12
  attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version
13
+ attr_accessor :overrides
13
14
 
14
15
  #
15
16
  # For backwards compatibility with existing lockfiles, if the most specific
@@ -26,20 +27,22 @@ module Bundler
26
27
  alias_method :runtime_dependencies, :dependencies
27
28
 
28
29
  def self.from_spec(s)
29
- lazy_spec = new(s.name, s.version, s.platform, s.source)
30
+ lazy_spec = new(s.name, s.version, s.platform, s.source, content_address: s.content_address)
30
31
  lazy_spec.dependencies = s.runtime_dependencies
31
32
  lazy_spec.required_ruby_version = s.required_ruby_version
32
33
  lazy_spec.required_rubygems_version = s.required_rubygems_version
34
+ lazy_spec.overrides = s.overrides if s.is_a?(LazySpecification)
33
35
  lazy_spec
34
36
  end
35
37
 
36
- def initialize(name, version, platform, source = nil, **materialization_options)
38
+ def initialize(name, version, platform, source = nil, content_address: nil, **materialization_options)
37
39
  @name = name
38
40
  @version = version
39
41
  @dependencies = []
40
42
  @required_ruby_version = Gem::Requirement.default
41
43
  @required_rubygems_version = Gem::Requirement.default
42
44
  @platform = platform || Gem::Platform::RUBY
45
+ @content_address = content_address
43
46
 
44
47
  @original_source = source
45
48
  @source = source
@@ -63,19 +66,25 @@ module Bundler
63
66
  end
64
67
 
65
68
  def full_name
66
- @full_name ||= if platform == Gem::Platform::RUBY
69
+ @full_name ||= if Gem::ContentAddress.content_addressed?(self, validate_ruby_abi: false)
70
+ "#{@name}-#{@version}-#{@content_address}"
71
+ elsif platform == Gem::Platform::RUBY
67
72
  "#{@name}-#{@version}"
68
73
  else
69
74
  "#{@name}-#{@version}-#{platform}"
70
75
  end
71
76
  end
72
77
 
78
+ def file_name
79
+ "#{full_name}.gem"
80
+ end
81
+
73
82
  def lock_name
74
83
  @lock_name ||= name_tuple.lock_name
75
84
  end
76
85
 
77
86
  def name_tuple
78
- Gem::NameTuple.new(@name, @version, @platform)
87
+ Gem::NameTuple.new(@name, @version, @platform, content_address: @content_address)
79
88
  end
80
89
 
81
90
  def ==(other)
@@ -110,6 +119,16 @@ module Bundler
110
119
  @name == dependency.name && effective_requirement.satisfied_by?(Gem::Version.new(@version))
111
120
  end
112
121
 
122
+ ##
123
+ # Assigns the content address parsed from the lockfile's CONTENT
124
+ # ADDRESSES section. The full name embeds the content address, so its
125
+ # memoization must be invalidated.
126
+
127
+ def content_address=(value)
128
+ @content_address = value
129
+ @full_name = nil
130
+ end
131
+
113
132
  def to_lock
114
133
  out = String.new
115
134
  out << " #{lock_name}\n"
@@ -128,13 +147,13 @@ module Bundler
128
147
  materialize(self, &:first)
129
148
  end
130
149
 
131
- def materialized_for_installation
132
- @materialization = materialize_for_installation
150
+ def materialized_for_installation(locked_platforms = nil)
151
+ @materialization = materialize_for_installation(locked_platforms)
133
152
 
134
153
  self unless incomplete?
135
154
  end
136
155
 
137
- def materialize_for_installation
156
+ def materialize_for_installation(locked_platforms)
138
157
  source.local!
139
158
 
140
159
  if use_exact_resolved_specifications?
@@ -144,7 +163,7 @@ module Bundler
144
163
  # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant
145
164
  # In non-frozen mode, return nil to trigger re-resolution and lockfile update
146
165
  if Bundler.frozen_bundle?
147
- materialize([name, version]) {|specs| resolve_best_platform(specs) }
166
+ materialize([name, version]) {|specs| resolve_best_platform(specs, locked_platforms: locked_platforms) }
148
167
  end
149
168
  else
150
169
  materialize([name, version]) {|specs| resolve_best_platform(specs) }
@@ -185,12 +204,14 @@ module Bundler
185
204
  # Try platforms in order of preference until finding a compatible spec.
186
205
  # Used for legacy lockfiles and as a fallback when the exact locked spec
187
206
  # is incompatible. Falls back to frozen bundle behavior if none match.
188
- def resolve_best_platform(specs)
189
- find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs)
207
+ def resolve_best_platform(specs, locked_platforms: nil)
208
+ specs = MatchPlatform.select_all_content_address_match(specs, content_address)
209
+
210
+ find_compatible_platform_spec(specs, locked_platforms: locked_platforms) || frozen_bundle_fallback(specs)
190
211
  end
191
212
 
192
- def find_compatible_platform_spec(specs)
193
- candidate_platforms.each do |plat|
213
+ def find_compatible_platform_spec(specs, locked_platforms: nil)
214
+ candidate_platforms(locked_platforms: locked_platforms).each do |plat|
194
215
  candidates = MatchPlatform.select_best_platform_match(specs, plat)
195
216
  spec = choose_compatible(candidates, fallback_to_non_installable: false)
196
217
  return spec if spec
@@ -200,9 +221,14 @@ module Bundler
200
221
 
201
222
  # Platforms to try in order of preference. Ruby platform is last since it
202
223
  # requires compilation, but works when precompiled gems are incompatible.
203
- def candidate_platforms
224
+ # When a set of locked platforms is given (frozen mode), restrict the
225
+ # candidates to those, so we never materialize a platform that wasn't locked.
226
+ def candidate_platforms(locked_platforms: nil)
204
227
  target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
205
- [target, platform, Gem::Platform::RUBY].uniq
228
+ platforms = [target, platform, Gem::Platform::RUBY].uniq
229
+ return platforms unless locked_platforms
230
+
231
+ platforms & locked_platforms
206
232
  end
207
233
 
208
234
  # In frozen mode, accept any candidate. Will error at install time.
@@ -234,8 +260,9 @@ module Bundler
234
260
  # about the mismatch higher up the stack, right before trying to install the
235
261
  # bad gem.
236
262
  def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?)
263
+ override_list = overrides || []
237
264
  search = candidates.reverse.find do |spec|
238
- spec.is_a?(StubSpecification) || spec.matches_current_metadata?
265
+ spec.is_a?(StubSpecification) || spec.matches_current_metadata_with_overrides?(override_list)
239
266
  end
240
267
  if search.nil? && fallback_to_non_installable
241
268
  search = candidates.last
@@ -259,10 +286,10 @@ module Bundler
259
286
  # which would affect `bundler/setup` performance.
260
287
  def validate_dependencies(spec)
261
288
  if !@materialization_options[:strict] && spec.is_a?(StubSpecification)
262
- spec.dependencies = dependencies
289
+ spec.dependencies = Override.rewrite_dependencies(overrides, name, dependencies)
263
290
  else
264
291
  if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort
265
- raise IncorrectLockfileDependencies.new(self)
292
+ raise IncorrectLockfileDependencies.new(self, spec.runtime_dependencies, dependencies)
266
293
  end
267
294
  end
268
295
  end
@@ -19,6 +19,7 @@ module Bundler
19
19
  add_sources
20
20
  add_platforms
21
21
  add_dependencies
22
+ add_content_addresses
22
23
  add_checksums
23
24
  add_locked_ruby_version
24
25
  add_bundled_with
@@ -66,10 +67,31 @@ module Bundler
66
67
  end
67
68
  end
68
69
 
70
+ def add_content_addresses
71
+ content_addresses = definition.resolve.filter_map do |spec|
72
+ next unless Gem::ContentAddress.content_addressed?(spec, validate_ruby_abi: false)
73
+
74
+ line = "#{spec.lock_name} #{spec.content_address}"
75
+
76
+ if definition.locked_checksums
77
+ checksums = spec.source.checksum_store.checksums_to_lock(spec.full_name)
78
+ line += " #{checksums}" if checksums
79
+ end
80
+
81
+ line
82
+ end
83
+
84
+ add_section("CONTENT ADDRESSES", content_addresses) unless content_addresses.empty?
85
+ end
86
+
69
87
  def add_checksums
70
88
  return unless definition.locked_checksums
71
- checksums = definition.resolve.map do |spec|
72
- spec.source.checksum_store.to_lock(spec)
89
+ checksums = definition.resolve.filter_map do |spec|
90
+ line = spec.source.checksum_store.to_lock(spec)
91
+
92
+ next if line == spec.lock_name && Gem::ContentAddress.content_addressed?(spec, validate_ruby_abi: false)
93
+
94
+ line
73
95
  end
74
96
 
75
97
  add_section("CHECKSUMS", checksums + bundler_checksum)
@@ -41,6 +41,7 @@ module Bundler
41
41
  BUNDLED = "BUNDLED WITH"
42
42
  DEPENDENCIES = "DEPENDENCIES"
43
43
  CHECKSUMS = "CHECKSUMS"
44
+ CONTENT_ADDRESSES = "CONTENT ADDRESSES"
44
45
  PLATFORMS = "PLATFORMS"
45
46
  RUBY = "RUBY VERSION"
46
47
  GIT = "GIT"
@@ -48,7 +49,7 @@ module Bundler
48
49
  PATH = "PATH"
49
50
  PLUGIN = "PLUGIN SOURCE"
50
51
  SPECS = " specs:"
51
- OPTIONS = /^ ([a-z]+): (.*)$/i
52
+ OPTIONS = /^ ([a-z_]+): (.*)$/i
52
53
  SOURCE = [GIT, GEM, PATH, PLUGIN].freeze
53
54
 
54
55
  SECTIONS_BY_VERSION_INTRODUCED = {
@@ -57,6 +58,7 @@ module Bundler
57
58
  Gem::Version.create("1.12") => [RUBY].freeze,
58
59
  Gem::Version.create("1.13") => [PLUGIN].freeze,
59
60
  Gem::Version.create("2.5.0") => [CHECKSUMS].freeze,
61
+ Gem::Version.create("4.1.0") => [CONTENT_ADDRESSES].freeze,
60
62
  }.freeze
61
63
 
62
64
  KNOWN_SECTIONS = SECTIONS_BY_VERSION_INTRODUCED.values.flatten!.freeze
@@ -95,14 +97,14 @@ module Bundler
95
97
  lockfile_contents.split(BUNDLED).last.strip
96
98
  end
97
99
 
98
- def initialize(lockfile, strict: false)
100
+ def initialize(lockfile, strict: false, lockfile_path: nil)
99
101
  @platforms = []
100
102
  @sources = []
101
103
  @metadata_source = Source::Metadata.new
102
104
  @dependencies = {}
103
105
  @parse_method = nil
104
106
  @specs = {}
105
- @lockfile_path = begin
107
+ @lockfile_path = lockfile_path || begin
106
108
  SharedHelpers.relative_lockfile_path
107
109
  rescue GemfileNotFound
108
110
  "Gemfile.lock"
@@ -140,6 +142,8 @@ module Bundler
140
142
  # for all gemfiles that don't already explicitly include the feature.
141
143
  @checksums = true
142
144
  @parse_method = :parse_checksum
145
+ elsif line == CONTENT_ADDRESSES
146
+ @parse_method = :parse_content_address
143
147
  elsif line == PLATFORMS
144
148
  @parse_method = :parse_platform
145
149
  elsif line == RUBY
@@ -227,6 +231,16 @@ module Bundler
227
231
  $ # Line end
228
232
  /xo
229
233
 
234
+ NAME_VERSION_CONTENT_ADDRESS = /
235
+ ^#{space}{2}(?!#{space}) # Exactly 2 spaces at the start of the line
236
+ (.*?) # Name
237
+ #{space}\(([^-]*) # Space, followed by version
238
+ -(.*)\) # Platform, always present for content-addressable gems
239
+ #{space}([0-9a-f]{8,64}) # Content address
240
+ (?:#{space}([^ ]+))? # Optional checksums
241
+ $ # Line end
242
+ /xo
243
+
230
244
  def parse_dependency(line)
231
245
  return unless line =~ NAME_VERSION
232
246
  spaces = $1
@@ -268,7 +282,8 @@ module Bundler
268
282
 
269
283
  version = Gem::Version.new(version)
270
284
  platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
271
- full_name = Gem::NameTuple.new(name, version, platform).full_name
285
+ name_tuple = Gem::NameTuple.new(name, version, platform)
286
+ full_name = name_tuple.full_name
272
287
  spec = @specs[full_name]
273
288
 
274
289
  if name == "bundler"
@@ -280,10 +295,33 @@ module Bundler
280
295
  checksums.split(",") do |lock_checksum|
281
296
  column = line.index(lock_checksum) + 1
282
297
  checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}")
283
- spec.source.checksum_store.register(spec, checksum)
298
+ spec.source.checksum_store.register(name_tuple, checksum)
284
299
  end
285
300
  else
286
- spec.source.checksum_store.register(spec, nil)
301
+ spec.source.checksum_store.register(name_tuple, nil)
302
+ end
303
+ end
304
+
305
+ def parse_content_address(line)
306
+ return unless line =~ NAME_VERSION_CONTENT_ADDRESS
307
+
308
+ name = -$1
309
+ version = Gem::Version.new($2)
310
+ platform = Gem::Platform.new($3)
311
+ content_address = $4
312
+ checksums = $5
313
+
314
+ spec = @specs[Gem::NameTuple.new(name, version, platform).full_name]
315
+ return unless spec
316
+
317
+ spec.content_address = content_address
318
+
319
+ return unless checksums
320
+
321
+ checksums.split(",") do |lock_checksum|
322
+ column = line.index(lock_checksum) + 1
323
+ checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}")
324
+ spec.source.checksum_store.register(spec, checksum)
287
325
  end
288
326
  end
289
327
 
@@ -1,6 +1,6 @@
1
1
  .\" generated with Ronn-NG/v0.10.1
2
2
  .\" http://github.com/apjanke/ronn-ng/tree/0.10.1
3
- .TH "BUNDLE\-ADD" "1" "April 2026" ""
3
+ .TH "BUNDLE\-ADD" "1" "May 2026" ""
4
4
  .SH "NAME"
5
5
  \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
6
6
  .SH "SYNOPSIS"
@@ -46,13 +46,16 @@ Do not print progress information to the standard output\.
46
46
  Adds the gem to the Gemfile but does not install it\.
47
47
  .TP
48
48
  \fB\-\-optimistic\fR
49
- Adds optimistic declaration of version\.
49
+ Ignored (now default behavior)
50
+ .TP
51
+ \fB\-\-pessimistic\fR
52
+ Adds pessimistic declaration of version\.
50
53
  .TP
51
54
  \fB\-\-strict\fR
52
55
  Adds strict declaration of version\.
53
56
  .TP
54
57
  \fB\-\-cooldown=<number>\fR
55
- Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
58
+ Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
56
59
  .SH "EXAMPLES"
57
60
  .IP "1." 4
58
61
  You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.
@@ -65,7 +68,7 @@ You can add the \fBrails\fR gem with version greater than 1\.1 (not including 1\
65
68
  .IP "3." 4
66
69
  You can use the \fBhttps://gems\.example\.com\fR custom source and assign the gem to a group\.
67
70
  .IP
68
- \fBbundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
71
+ \fBbundle add rails \-\-version ">= 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
69
72
  .IP "4." 4
70
73
  The following adds the \fBgem\fR entry to the Gemfile without installing the gem\. You can install gems later via \fBbundle install\fR\.
71
74
  .IP
@@ -51,15 +51,19 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
51
51
  Adds the gem to the Gemfile but does not install it.
52
52
 
53
53
  * `--optimistic`:
54
- Adds optimistic declaration of version.
54
+ Ignored (now default behavior)
55
+
56
+ * `--pessimistic`:
57
+ Adds pessimistic declaration of version.
55
58
 
56
59
  * `--strict`:
57
60
  Adds strict declaration of version.
58
61
 
59
62
  * `--cooldown=<number>`:
60
63
  Only consider gem versions published at least <number> days ago when
61
- resolving. Pass `0` to disable cooldown for this run. See `cooldown`
62
- in bundle-config(1) for precedence rules.
64
+ resolving. Pass `0` to disable cooldown for this run, overriding every
65
+ other cooldown setting, RubyGems' own included. See `cooldown` in
66
+ bundle-config(1) for precedence rules.
63
67
 
64
68
  ## EXAMPLES
65
69
 
@@ -75,7 +79,7 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
75
79
  3. You can use the `https://gems.example.com` custom source and assign the gem
76
80
  to a group.
77
81
 
78
- `bundle add rails --version "~> 5.0.0" --source "https://gems.example.com" --group "development"`
82
+ `bundle add rails --version ">= 5.0.0" --source "https://gems.example.com" --group "development"`
79
83
 
80
84
  4. The following adds the `gem` entry to the Gemfile without installing the
81
85
  gem. You can install gems later via `bundle install`.
@@ -1,6 +1,6 @@
1
1
  .\" generated with Ronn-NG/v0.10.1
2
2
  .\" http://github.com/apjanke/ronn-ng/tree/0.10.1
3
- .TH "BUNDLE\-BINSTUBS" "1" "April 2026" ""
3
+ .TH "BUNDLE\-BINSTUBS" "1" "May 2026" ""
4
4
  .SH "NAME"
5
5
  \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
6
6
  .SH "SYNOPSIS"
@@ -1,6 +1,6 @@
1
1
  .\" generated with Ronn-NG/v0.10.1
2
2
  .\" http://github.com/apjanke/ronn-ng/tree/0.10.1
3
- .TH "BUNDLE\-CACHE" "1" "April 2026" ""
3
+ .TH "BUNDLE\-CACHE" "1" "May 2026" ""
4
4
  .SH "NAME"
5
5
  \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
6
6
  .SH "SYNOPSIS"
@@ -18,7 +18,7 @@ Include gems for all platforms present in the lockfile, not only the current one
18
18
  Specify a different cache path than the default (vendor/cache)\.
19
19
  .TP
20
20
  \fB\-\-cooldown=<number>\fR
21
- Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1)\.
21
+ Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1)\.
22
22
  .TP
23
23
  \fB\-\-gemfile=GEMFILE\fR
24
24
  Use the specified gemfile instead of Gemfile\.
@@ -24,8 +24,8 @@ use the gems in the cache in preference to the ones on `rubygems.org`.
24
24
  * `--cooldown=<number>`:
25
25
  Only consider gem versions published at least <number> days ago when
26
26
  resolving before caching. Pass `0` to disable cooldown for this run,
27
- overriding any per-source or global configuration. See `cooldown` in
28
- bundle-config(1).
27
+ overriding every other cooldown setting, RubyGems' own included. See
28
+ `cooldown` in bundle-config(1).
29
29
 
30
30
  * `--gemfile=GEMFILE`:
31
31
  Use the specified gemfile instead of Gemfile.
@@ -1,6 +1,6 @@
1
1
  .\" generated with Ronn-NG/v0.10.1
2
2
  .\" http://github.com/apjanke/ronn-ng/tree/0.10.1
3
- .TH "BUNDLE\-CHECK" "1" "April 2026" ""
3
+ .TH "BUNDLE\-CHECK" "1" "May 2026" ""
4
4
  .SH "NAME"
5
5
  \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
6
6
  .SH "SYNOPSIS"