From 01c0bf34f0c6515e1b8cf2f6aaeb2f2ed71d0d0c Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Wed, 27 Mar 2024 17:20:52 +0100 Subject: [PATCH] Make sure to force latest resolvable version explicitly To make sure we can always update to the latest resolvable version for each gem explicitly requested for update, we first run a full update, and then add explicit exact requirements to the resolved versions. This may lead into conflicts, but our resolver already automatically parses those and unlocks additional gems to fix them. --- bundler/lib/bundler/definition.rb | 28 ++++++- bundler/spec/commands/lock_spec.rb | 122 +++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index 3fe783acb53a..22070b6b1771 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -570,7 +570,9 @@ def resolution_packages last_resolve = converge_locked_specs remove_invalid_platforms!(current_dependencies) packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @gems_to_unlock, prerelease: gem_version_promoter.pre?) - additional_base_requirements_for_resolve(packages, last_resolve) + packages = additional_base_requirements_to_prevent_downgrades(packages, last_resolve) + packages = additional_base_requirements_to_force_updates(packages) + packages end end @@ -1015,7 +1017,7 @@ def lockfiles_equal?(current, proposed, preserve_unknown_sections) current == proposed end - def additional_base_requirements_for_resolve(resolution_packages, last_resolve) + def additional_base_requirements_to_prevent_downgrades(resolution_packages, last_resolve) return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) converge_specs(@originally_locked_specs - last_resolve).each do |locked_spec| next if locked_spec.source.is_a?(Source::Path) @@ -1024,6 +1026,28 @@ def additional_base_requirements_for_resolve(resolution_packages, last_resolve) resolution_packages end + def additional_base_requirements_to_force_updates(resolution_packages) + return resolution_packages if @explicit_unlocks.empty? + full_update = dup_for_full_unlock.resolve + @explicit_unlocks.each do |name| + version = full_update[name].first&.version + resolution_packages.base_requirements[name] = Gem::Requirement.new("= #{version}") if version + end + resolution_packages + end + + def dup_for_full_unlock + unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles) + unlocked_definition.resolution_mode = { "local" => !@remote } + unlocked_definition.setup_sources_for_resolve + unlocked_definition.gem_version_promoter.tap do |gvp| + gvp.level = gem_version_promoter.level + gvp.strict = gem_version_promoter.strict + gvp.pre = gem_version_promoter.pre + end + unlocked_definition + end + def remove_invalid_platforms!(dependencies) return if Bundler.frozen_bundle? diff --git a/bundler/spec/commands/lock_spec.rb b/bundler/spec/commands/lock_spec.rb index f6793d393ba8..c6bb0f58afe0 100644 --- a/bundler/spec/commands/lock_spec.rb +++ b/bundler/spec/commands/lock_spec.rb @@ -252,6 +252,128 @@ expect(read_lockfile).to eq(remove_checksums_from_lockfile(@lockfile, "(2.3.2)", "(#{rake_version})")) end + it "updates specific gems using --update, even if that requires unlocking other top level gems" do + build_repo4 do + build_gem "prism", "0.15.1" + build_gem "prism", "0.24.0" + + build_gem "ruby-lsp", "0.12.0" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "ruby-lsp", "0.16.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "tapioca", "0.11.10" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "tapioca", "0.13.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "tapioca" + gem "ruby-lsp" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + prism (0.15.1) + ruby-lsp (0.12.0) + prism (< 0.24.0) + tapioca (0.11.10) + prism (< 0.24.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp + tapioca + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update tapioca --verbose" + + expect(lockfile).to include("tapioca (0.13.1)") + end + + it "updates specific gems using --update, even if that requires unlocking other top level gems, but only as few as possible" do + build_repo4 do + build_gem "prism", "0.15.1" + build_gem "prism", "0.24.0" + + build_gem "ruby-lsp", "0.12.0" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "ruby-lsp", "0.16.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "tapioca", "0.11.10" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "tapioca", "0.13.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "other-prism-dependent", "1.0.0" do |s| + s.add_dependency "prism", ">= 0.15.1" + end + + build_gem "other-prism-dependent", "1.1.0" do |s| + s.add_dependency "prism", ">= 0.15.1" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "tapioca" + gem "ruby-lsp" + gem "other-prism-dependent" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + other-prism-dependent (1.0.0) + prism (>= 0.15.1) + prism (0.15.1) + ruby-lsp (0.12.0) + prism (< 0.24.0) + tapioca (0.11.10) + prism (< 0.24.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp + tapioca + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update tapioca" + + expect(lockfile).to include("tapioca (0.13.1)") + expect(lockfile).to include("other-prism-dependent (1.0.0)") + end + it "preserves unknown checksum algorithms" do lockfile @lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123")