[rubygems/rubygems] Allow some materialized specs to be missing

As long as some spec in the materialization is complete.

https://github.com/rubygems/rubygems/commit/9a673b0bbb
This commit is contained in:
David Rodríguez 2024-11-18 14:18:40 +01:00 committed by Hiroshi SHIBATA
parent 36fb7994fe
commit 44ad2e3f38
6 changed files with 133 additions and 26 deletions

View File

@ -668,6 +668,14 @@ module Bundler
raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}" raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}"
end end
partially_missing_specs = resolve.partially_missing_specs
if partially_missing_specs.any? && !sources.local_mode?
Bundler.ui.warn "Some locked specs have possibly been yanked (#{partially_missing_specs.map(&:full_name).join(", ")}). Ignoring them..."
resolve.delete(partially_missing_specs)
end
incomplete_specs = resolve.incomplete_specs incomplete_specs = resolve.incomplete_specs
loop do loop do
break if incomplete_specs.empty? break if incomplete_specs.empty?

View File

@ -46,7 +46,7 @@ module Bundler
end end
module_function :platform_specificity_match module_function :platform_specificity_match
def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) def select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
matching = if force_ruby matching = if force_ruby
specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! } specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! }
else else
@ -58,26 +58,40 @@ module Bundler
return locked_originally if locked_originally.any? return locked_originally if locked_originally.any?
end end
sort_best_platform_match(matching, platform) matching
end
module_function :select_all_platform_match
def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked)
sort_and_filter_best_platform_match(matching, platform)
end end
module_function :select_best_platform_match module_function :select_best_platform_match
def select_best_local_platform_match(specs, force_ruby: false) def select_best_local_platform_match(specs, force_ruby: false)
select_best_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation) matching = select_all_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation)
sort_best_platform_match(matching, local_platform)
end end
module_function :select_best_local_platform_match module_function :select_best_local_platform_match
def sort_best_platform_match(matching, platform) def sort_and_filter_best_platform_match(matching, platform)
return matching if matching.one? return matching if matching.one?
exact = matching.select {|spec| spec.platform == platform } exact = matching.select {|spec| spec.platform == platform }
return exact if exact.any? return exact if exact.any?
sorted_matching = matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) } sorted_matching = sort_best_platform_match(matching, platform)
exemplary_spec = sorted_matching.first exemplary_spec = sorted_matching.first
sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) } sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) }
end end
module_function :sort_and_filter_best_platform_match
def sort_best_platform_match(matching, platform)
matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) }
end
module_function :sort_best_platform_match module_function :sort_best_platform_match
class PlatformMatch class PlatformMatch

View File

@ -33,10 +33,16 @@ module Bundler
end end
def materialized_spec def materialized_spec
specs.first&.materialization specs.reject(&:missing?).first&.materialization
end end
def missing_specs def completely_missing_specs
return [] unless specs.all?(&:missing?)
specs
end
def partially_missing_specs
specs.select(&:missing?) specs.select(&:missing?)
end end

View File

@ -140,7 +140,11 @@ module Bundler
end end
def missing_specs def missing_specs
@materializations.flat_map(&:missing_specs) @materializations.flat_map(&:completely_missing_specs)
end
def partially_missing_specs
@materializations.flat_map(&:partially_missing_specs)
end end
def incomplete_specs def incomplete_specs

View File

@ -501,6 +501,55 @@ RSpec.describe "bundle install with install-time dependencies" do
end end
end end
context "when locked generic variant supports current Ruby, but locked specific variant does not" do
let(:original_lockfile) do
<<~L
GEM
remote: https://gem.repo4/
specs:
nokogiri (1.16.3)
nokogiri (1.16.3-x86_64-linux)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
nokogiri
BUNDLED WITH
#{Bundler::VERSION}
L
end
before do
build_repo4 do
build_gem "nokogiri", "1.16.3"
build_gem "nokogiri", "1.16.3" do |s|
s.required_ruby_version = "< #{Gem.ruby_version}"
s.platform = "x86_64-linux"
end
end
gemfile <<~G
source "https://gem.repo4"
gem "nokogiri"
G
lockfile original_lockfile
end
it "keeps both variants in the lockfile, and uses the generic one since it's compatible" do
simulate_platform "x86_64-linux" do
bundle "install --verbose"
expect(lockfile).to eq(original_lockfile)
expect(the_bundle).to include_gems("nokogiri 1.16.3")
end
end
end
it "gives a meaningful error on ruby version mismatches between dependencies" do it "gives a meaningful error on ruby version mismatches between dependencies" do
build_repo4 do build_repo4 do
build_gem "requires-old-ruby" do |s| build_gem "requires-old-ruby" do |s|

View File

@ -30,7 +30,29 @@ RSpec.context "when installing a bundle that includes yanked gems" do
expect(err).to include("Your bundle is locked to foo (10.0.0)") expect(err).to include("Your bundle is locked to foo (10.0.0)")
end end
context "when a re-resolve is necessary, and a yanked version is considered by the resolver" do context "when a platform specific yanked version is included in the lockfile, and a generic variant is available remotely" do
let(:original_lockfile) do
<<~L
GEM
remote: https://gem.repo4/
specs:
actiontext (6.1.6)
nokogiri (>= 1.8)
foo (1.0.0)
nokogiri (1.13.8-#{Bundler.local_platform})
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
actiontext (= 6.1.6)
foo (= 1.0.0)
BUNDLED WITH
#{Bundler::VERSION}
L
end
before do before do
skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform? skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform?
@ -51,29 +73,33 @@ RSpec.context "when installing a bundle that includes yanked gems" do
gemfile <<~G gemfile <<~G
source "https://gem.repo4" source "https://gem.repo4"
gem "foo", "1.0.1" gem "foo", "1.0.0"
gem "actiontext", "6.1.6" gem "actiontext", "6.1.6"
G G
lockfile <<~L lockfile original_lockfile
GEM end
remote: https://gem.repo4/
specs:
actiontext (6.1.6)
nokogiri (>= 1.8)
foo (1.0.0)
nokogiri (1.13.8-#{Bundler.local_platform})
PLATFORMS context "and a re-resolve is necessary" do
#{lockfile_platforms} before do
gemfile gemfile.sub('"foo", "1.0.0"', '"foo", "1.0.1"')
end
DEPENDENCIES it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the old index is used" do
actiontext (= 6.1.6) bundle "install", artifice: "endpoint", verbose: true
foo (= 1.0.0)
BUNDLED WITH expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1")
#{Bundler::VERSION} expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1"))
L expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...")
end
it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the compact index API is used" do
bundle "install", artifice: "compact_index", verbose: true
expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1")
expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1"))
expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...")
end
end end
it "reports the yanked gem properly when the old index is used" do it "reports the yanked gem properly when the old index is used" do