diff --git a/lib/bundler/multilock.rb b/lib/bundler/multilock.rb index b5cfa59..1b5ce8e 100644 --- a/lib/bundler/multilock.rb +++ b/lib/bundler/multilock.rb @@ -202,7 +202,6 @@ def after_install_all(install: true) Bundler.ui.info("Syncing to #{relative_lockfile}...") if attempts == 1 synced_any = true - specs = lockfile_name.exist? ? cache.specs(lockfile_name) : {} parent_lockfile_name = lockfile_definition[:parent] parent_root = parent_lockfile_name.dirname parent_specs = cache.specs(parent_lockfile_name) @@ -218,7 +217,7 @@ def after_install_all(install: true) end # add a source for the current gem - gem_spec = parent_specs[[File.basename(Bundler.root), "ruby"]] + gem_spec = parent_specs.dig(File.basename(Bundler.root), "ruby") if gem_spec adjusted_parent_lockfile_contents += <<~TEXT @@ -253,27 +252,13 @@ def after_install_all(install: true) spec, parent_spec) - # look through all reverse dependencies; if any of them say it - # has to come from self, due to conflicts, then this gem has - # to come from self as well - [cache.reverse_dependencies(lockfile_name), - cache.reverse_dependencies(parent_lockfile_name)].each do |reverse_dependencies| - break if precedence == :self - - reverse_dependencies[spec.name].each do |dep_name| - precedence = check_precedence.call(specs[dep_name], parent_specs[dep_name]) - break if precedence == :self - end - end - spec_precedences[spec.name] = precedence || :parent end # replace any duplicate specs with what's in the parent lockfile lockfile.specs.map! do |spec| - parent_spec = parent_specs[[spec.name, spec.platform]] + parent_spec = cache.find_matching_spec(parent_specs, spec) next spec unless parent_spec - next spec if check_precedence.call(spec, parent_spec) == :self dependency_changes ||= spec != parent_spec diff --git a/lib/bundler/multilock/cache.rb b/lib/bundler/multilock/cache.rb index cf8665b..0d3f856 100644 --- a/lib/bundler/multilock/cache.rb +++ b/lib/bundler/multilock/cache.rb @@ -57,11 +57,27 @@ def parser(lockfile_name) end def specs(lockfile_name) - @specs[lockfile_name] ||= parser(lockfile_name).specs.to_h do |spec| - [[spec.name, spec.platform], spec] + @specs[lockfile_name] ||= begin + specs = {} + parser(lockfile_name).specs.each do |spec| + (specs[spec.name] ||= {})[spec.platform] = spec + end + specs end end + # sometimes a gem changes platforms with a new version, such as from aarch64-linux + # to aarch64-linux-gnu. we need to still sync it + def find_matching_spec(specs, spec) + specs = self.specs(specs) unless specs.is_a?(Hash) + platform_specs = specs[spec.name] + return unless platform_specs + + parent_spec = platform_specs[spec.platform] + parent_spec ||= platform_specs.find { |platform, _| platform =~ spec.platform }&.last + parent_spec || platform_specs.find { |platform, _| platform == "ruby" }&.last + end + # @param lockfile_name [Pathname] # @return [Hash>] hash of gem name to set of gem names that depend on it def reverse_dependencies(lockfile_name) diff --git a/lib/bundler/multilock/check.rb b/lib/bundler/multilock/check.rb index 2bc7b86..0180556 100644 --- a/lib/bundler/multilock/check.rb +++ b/lib/bundler/multilock/check.rb @@ -120,7 +120,7 @@ def deep_check(lockfile_definition) # check for conflicting requirements (and build list of pins, in the same loop) parser.specs.each do |spec| - parent_spec = @cache.specs(parent_lockfile_name)[[spec.name, spec.platform]] + parent_spec = @cache.find_matching_spec(parent_lockfile_name, spec) if lockfile_definition[:enforce_pinned_additional_dependencies] # look through what this spec depends on, and keep track of all pinned requirements diff --git a/spec/bundler/multilock_spec.rb b/spec/bundler/multilock_spec.rb index 1d104b1..e60bfe4 100644 --- a/spec/bundler/multilock_spec.rb +++ b/spec/bundler/multilock_spec.rb @@ -863,6 +863,35 @@ end end + it "syncs gems whose platforms changed slightly" do + if RUBY_VERSION < "3.0" + skip "The test case that triggers this requires Ruby 3.0+; " \ + "just rely on this test running on other ruby versions" + end + + with_gemfile(<<~RUBY) do + gem "sqlite3", "~> 1.7" + + lockfile("all") {} + RUBY + invoke_bundler("install") + + write_gemfile(<<~RUBY) + gem "sqlite3" + + lockfile("all") {} + RUBY + invoke_bundler("install") + + expect(invoke_bundler("info sqlite3")).to include("1.7.3") + expect(invoke_bundler("info sqlite3", env: { "BUNDLE_LOCKFILE" => "all" })).to include("1.7.3") + + invoke_bundler("update sqlite3") + expect(invoke_bundler("info sqlite3")).not_to include("1.7.3") + expect(invoke_bundler("info sqlite3", env: { "BUNDLE_LOCKFILE" => "all" })).not_to include("1.7.3") + end + end + it "syncs ruby version" do with_gemfile(<<~RUBY) do gem "concurrent-ruby", "1.2.2"