From cbf2e133c10b87bb7d1f858471e600af6c6e62d1 Mon Sep 17 00:00:00 2001 From: David Rodriguez Date: Sat, 30 Sep 2023 21:01:36 +0200 Subject: [PATCH] Re-resolve when lockfile is invalid Move the check for unmet dependencies in lockfile just in time to be able to re-resolve if unmet dependencies are found. --- lib/bundler/definition.rb | 37 ++++++++++----- lib/bundler/installer/parallel_installer.rb | 26 ----------- .../installer/parallel_installer_spec.rb | 46 ------------------- spec/bundler/commands/install_spec.rb | 43 +++++++++++++++++ 4 files changed, 69 insertions(+), 83 deletions(-) delete mode 100644 spec/bundler/bundler/installer/parallel_installer_spec.rb diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 3c18ce7139..cbdcde40b5 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -149,7 +149,7 @@ module Bundler @dependency_changes = converge_dependencies @local_changes = converge_locals - @missing_lockfile_dep = check_missing_lockfile_dep + check_lockfile end def gem_version_promoter @@ -478,7 +478,7 @@ module Bundler private :sources def nothing_changed? - !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@missing_lockfile_dep && !@unlocking_bundler + !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@missing_lockfile_dep && !@unlocking_bundler && !@invalid_lockfile_dep end def no_resolve_needed? @@ -630,6 +630,7 @@ module Bundler [@local_changes, "the gemspecs for git local gems changed"], [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""], [@unlocking_bundler, "an update to the version of Bundler itself was requested"], + [@invalid_lockfile_dep, "your lock file has an invalid dependency \"#{@invalid_lockfile_dep}\""], ].select(&:first).map(&:last).join(", ") end @@ -684,24 +685,38 @@ module Bundler !sources_with_changes.each {|source| @unlock[:sources] << source.name }.empty? end - def check_missing_lockfile_dep - all_locked_specs = @locked_specs.map(&:name) << "bundler" + def check_lockfile + @invalid_lockfile_dep = nil + @missing_lockfile_dep = nil - missing = @locked_specs.select do |s| - s.dependencies.any? {|dep| !all_locked_specs.include?(dep.name) } + locked_names = @locked_specs.map(&:name) + missing = [] + invalid = [] + + @locked_specs.each do |s| + s.dependencies.each do |dep| + next if dep.name == "bundler" + + missing << s unless locked_names.include?(dep.name) + invalid << s if @locked_specs.none? {|spec| dep.matches_spec?(spec) } + end end if missing.any? @locked_specs.delete(missing) - return missing.first.name + @missing_lockfile_dep = missing.first.name + elsif !@dependency_changes + @missing_lockfile_dep = current_dependencies.find do |d| + @locked_specs[d.name].empty? && d.name != "bundler" + end&.name end - return if @dependency_changes + if invalid.any? + @locked_specs.delete(invalid) - current_dependencies.find do |d| - @locked_specs[d.name].empty? && d.name != "bundler" - end&.name + @invalid_lockfile_dep = invalid.first.name + end end def converge_paths diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb index 83a381f592..11a90b36cb 100644 --- a/lib/bundler/installer/parallel_installer.rb +++ b/lib/bundler/installer/parallel_installer.rb @@ -91,38 +91,12 @@ module Bundler install_serially end - check_for_unmet_dependencies - handle_error if failed_specs.any? @specs ensure worker_pool&.stop end - def check_for_unmet_dependencies - unmet_dependencies = @specs.map do |s| - [ - s, - s.dependencies.reject {|dep| @specs.any? {|spec| dep.matches_spec?(spec.spec) } }, - ] - end.reject {|a| a.last.empty? } - return if unmet_dependencies.empty? - - warning = [] - warning << "Your lockfile doesn't include a valid resolution." - warning << "You can fix this by regenerating your lockfile or manually editing the bad locked gems to a version that satisfies all dependencies." - warning << "The unmet dependencies are:" - - unmet_dependencies.each do |spec, unmet_spec_dependencies| - unmet_spec_dependencies.each do |unmet_spec_dependency| - found = @specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) } - warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name}, unsatisfied by #{found.full_name}" - end - end - - Bundler.ui.warn(warning.join("\n")) - end - private def failed_specs diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb deleted file mode 100644 index c8403a2e38..0000000000 --- a/spec/bundler/bundler/installer/parallel_installer_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require "bundler/installer/parallel_installer" - -RSpec.describe Bundler::ParallelInstaller do - let(:installer) { instance_double("Installer") } - let(:all_specs) { [] } - let(:size) { 1 } - let(:standalone) { false } - let(:force) { false } - - subject { described_class.new(installer, all_specs, size, standalone, force) } - - context "when the spec set is not a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.4.4"), - ].flatten - end - - it "prints a warning" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) -Your lockfile doesn't include a valid resolution. -You can fix this by regenerating your lockfile or manually editing the bad locked gems to a version that satisfies all dependencies. -The unmet dependencies are: -* diff-lcs (< 1.4), dependency of cucumber-4.1.0, unsatisfied by diff-lcs-1.4.4 - W - subject.check_for_unmet_dependencies - end - end - - context "when the spec set is a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.3"), - ].flatten - end - - it "doesn't print a warning" do - expect(Bundler.ui).not_to receive(:warn) - subject.check_for_unmet_dependencies - end - end -end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 5271977ed9..92415bd9d5 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -1101,4 +1101,47 @@ RSpec.describe "bundle install with gem sources" do expect(err).to include("Could not find compatible versions") end end + + context "when a lockfile has unmet dependencies, and the Gemfile has no resolution" do + before do + build_repo4 do + build_gem "aaa", "0.2.0" do |s| + s.add_dependency "zzz", "< 0.2.0" + end + + build_gem "zzz", "0.2.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "aaa" + gem "zzz" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + aaa (0.2.0) + zzz (< 0.2.0) + zzz (0.2.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + aaa! + zzz! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not install, but raises a resolution error" do + bundle "install", :raise_on_error => false + expect(err).to include("Could not find compatible versions") + end + end end