From f3d69bed626e3326959dd62dbcc9ac4510e42b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 16 Mar 2023 17:45:54 +0100 Subject: [PATCH] [rubygems/rubygems] Fix resolver hangs when dealing with an incomplete lockfile While working on locking multiple platforms by default, I got an infinite resolution loop in one of our resolver specs. The culprit ended up being that when dealing with lockfile specs with incomplete dependencies (spec appears in lockfile, but its dependencies don't), those specs were not being properly expired and that tripped up resolution. The issue for some reason only manifests when dealing with multiple lockfile platforms, that's why it only manifested when working on locking multiple platforms by default. https://github.com/rubygems/rubygems/commit/4ca72913bb --- lib/bundler/definition.rb | 10 +++++++++- lib/bundler/resolver/base.rb | 4 +--- lib/bundler/spec_set.rb | 4 ++-- spec/bundler/commands/lock_spec.rb | 23 +++++++++++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 9467d312fa..3249fb09dc 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -668,9 +668,17 @@ module Bundler def check_missing_lockfile_specs all_locked_specs = @locked_specs.map(&:name) << "bundler" - @locked_specs.any? do |s| + missing = @locked_specs.select do |s| s.dependencies.any? {|dep| !all_locked_specs.include?(dep.name) } end + + if missing.any? + @locked_specs.delete(missing) + + true + else + false + end end def converge_paths diff --git a/lib/bundler/resolver/base.rb b/lib/bundler/resolver/base.rb index c6afa82056..e5c3763c3f 100644 --- a/lib/bundler/resolver/base.rb +++ b/lib/bundler/resolver/base.rb @@ -35,9 +35,7 @@ module Bundler end def delete(specs) - specs.each do |spec| - @base.delete(spec) - end + @base.delete(specs) end def get_package(name) diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index cf63c16a70..21630e3a3e 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -63,8 +63,8 @@ module Bundler @sorted = nil end - def delete(spec) - @specs.delete(spec) + def delete(specs) + specs.each {|spec| @specs.delete(spec) } @lookup = nil @sorted = nil end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 126853e35f..85b3d4a075 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -1008,6 +1008,29 @@ RSpec.describe "bundle lock" do So, because Gemfile depends on rails >= 7.0.2.3, version solving has failed. ERR + + lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") + + bundle "lock", :raise_on_error => false + + expect(err).to eq <<~ERR.strip + Could not find compatible versions + + Because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1 + and rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3, + rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 OR = 7.0.3.1. + And because every version of activemodel depends on activesupport = 6.0.4, + rails >= 7.0.2.3, < 7.0.4 requires activesupport = 6.0.4. + Because rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1 + and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3, + rails >= 7.0.2.3, < 7.0.4 requires activesupport = 7.0.2.3 OR = 7.0.3.1. + Thus, rails >= 7.0.2.3, < 7.0.4 cannot be used. + And because rails >= 7.0.4 depends on activemodel = 7.0.4, + rails >= 7.0.2.3 requires activemodel = 7.0.4. + So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally + and Gemfile depends on rails >= 7.0.2.3, + version solving has failed. + ERR end it "does not accidentally resolves to prereleases" do