[rubygems/rubygems] Auto-heal on corrupted lockfile with missing deps

Following up on https://github.com/rubygems/rubygems/pull/6355, which
turned a crash into a nicer error message, this commit auto-heals the
corrupt lockfile instead.

In this particular case (a corrupt Gemfile.lock with missing
dependencies) the LazySpecification will not have accurate dependency
information, we have to materialize the SpecSet to determine there are
missing dependencies. We've already got a way to handle this, via
`SpecSet#incomplete_specs`, but it wasn't quite working for this case
because we'd get to `@incomplete_specs += lookup[name]` and
`lookup[name]` would be empty for the dependency.

With this commit we catch it a bit earlier, marking the parent spec
containing the missing dependency as incomplete.

https://github.com/rubygems/rubygems/commit/486ecb8f20
This commit is contained in:
Daniel Colson 2023-02-21 16:27:54 -05:00 committed by git
parent c43fbe4ebd
commit 62b3bcba5e
3 changed files with 26 additions and 20 deletions

View File

@ -47,13 +47,6 @@ module Bundler
dependencies.all? {|d| installed_specs.include? d.name } dependencies.all? {|d| installed_specs.include? d.name }
end end
# Check whether spec's dependencies are missing, which can indicate a
# corrupted lockfile
def dependencies_missing?(all_specs)
spec_names = all_specs.map(&:name)
dependencies.any? {|d| !spec_names.include? d.name }
end
# Represents only the non-development dependencies, the ones that are # Represents only the non-development dependencies, the ones that are
# itself and are in the total list. # itself and are in the total list.
def dependencies def dependencies
@ -123,11 +116,7 @@ module Bundler
unmet_dependencies.each do |spec, unmet_spec_dependencies| unmet_dependencies.each do |spec, unmet_spec_dependencies|
unmet_spec_dependencies.each do |unmet_spec_dependency| 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) } found = @specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) }
if found warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name}, unsatisfied by #{found.full_name}"
warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name}, unsatisfied by #{found.full_name}"
else
warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name} but missing from lockfile"
end
end end
end end
@ -224,8 +213,6 @@ module Bundler
if spec.dependencies_installed? @specs if spec.dependencies_installed? @specs
spec.state = :enqueued spec.state = :enqueued
worker_pool.enq spec worker_pool.enq spec
elsif spec.dependencies_missing? @specs
spec.state = :failed
end end
end end
end end

View File

@ -24,6 +24,7 @@ module Bundler
name = dep[0].name name = dep[0].name
platform = dep[1] platform = dep[1]
incomplete = false
key = [name, platform] key = [name, platform]
next if handled.key?(key) next if handled.key?(key)
@ -36,11 +37,14 @@ module Bundler
specs_for_dep.first.dependencies.each do |d| specs_for_dep.first.dependencies.each do |d|
next if d.type == :development next if d.type == :development
incomplete = true if d.name != "bundler" && lookup[d.name].empty?
deps << [d, dep[1]] deps << [d, dep[1]]
end end
elsif check else
@incomplete_specs += lookup[name] incomplete = true
end end
@incomplete_specs += lookup[name] if incomplete && check
end end
specs specs

View File

@ -1221,7 +1221,7 @@ RSpec.describe "the lockfile format" do
and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.") and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.")
end end
it "errors gracefully on a corrupt lockfile" do it "auto-heals when the lockfile is missing dependent specs" do
build_repo4 do build_repo4 do
build_gem "minitest-bisect", "1.6.0" do |s| build_gem "minitest-bisect", "1.6.0" do |s|
s.add_dependency "path_expander", "~> 1.1" s.add_dependency "path_expander", "~> 1.1"
@ -1253,10 +1253,25 @@ RSpec.describe "the lockfile format" do
L L
cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", :gem_repo => gem_repo4 cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", :gem_repo => gem_repo4
bundle :install, :raise_on_error => false bundle :install
expect(err).not_to include("ERROR REPORT TEMPLATE") expect(lockfile).to eq <<~L
expect(err).to include("path_expander (~> 1.1), dependency of minitest-bisect-1.6.0 but missing from lockfile") GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
minitest-bisect (1.6.0)
path_expander (~> 1.1)
path_expander (1.1.1)
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
minitest-bisect
BUNDLED WITH
#{Bundler::VERSION}
L
end end
it "auto-heals when the lockfile is missing specs" do it "auto-heals when the lockfile is missing specs" do