diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 82e08291a7..9ec39d5a35 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -248,8 +248,22 @@ module Bundler results = filter_matching_specs(results, locked_requirement) if locked_requirement versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)| - platform_specs = package.platforms.flat_map {|platform| select_best_platform_match(specs, platform) } - next groups if platform_specs.empty? + platform_specs = package.platforms.map {|platform| select_best_platform_match(specs, platform) } + + # If package is a top-level dependency, + # candidate is only valid if there are matching versions for all resolution platforms. + # + # If package is not a top-level deependency, + # then it's not necessary that it has matching versions for all platforms, since it may have been introduced only as + # a dependency for a platform specific variant, so it will only need to have a valid version for that platform. + # + if package.top_level? + next groups if platform_specs.any?(&:empty?) + else + next groups if platform_specs.all?(&:empty?) + end + + platform_specs.flatten! ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY) groups << Resolver::Candidate.new(version, :specs => ruby_specs) if ruby_specs.any? diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb index 7499a75006..0461328683 100644 --- a/lib/bundler/resolver/package.rb +++ b/lib/bundler/resolver/package.rb @@ -21,6 +21,7 @@ module Bundler @locked_version = locked_specs[name].first&.version @unlock = unlock @dependency = dependency || Dependency.new(name, @locked_version) + @top_level = !dependency.nil? @prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore end @@ -32,6 +33,10 @@ module Bundler false end + def top_level? + @top_level + end + def meta? @name.end_with?("\0") end diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index 0980293590..cf42b8a979 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -425,6 +425,62 @@ RSpec.describe "bundle install with install-time dependencies" do end end + context "when adding a new gem that does not resolve under all locked platforms" do + before do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + bundle "lock", :raise_on_error => false + end + end + + it "raises a proper error" do + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + + The source contains the following gems matching 'sorbet-static': + * sorbet-static-0.5.10696-x86_64-linux + E + expect(err).to end_with(nice_error) + end + end + it "gives a meaningful error on ruby version mismatches between dependencies" do build_repo4 do build_gem "requires-old-ruby" do |s|