Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid crashing when installing from a corrupted lockfile #6355

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion bundler/lib/bundler/installer/parallel_installer.rb
Expand Up @@ -47,6 +47,13 @@ def dependencies_installed?(all_specs)
dependencies.all? {|d| installed_specs.include? d.name }
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
# itself and are in the total list.
def dependencies
Expand Down Expand Up @@ -115,7 +122,12 @@ def check_for_unmet_dependencies

unmet_dependencies.each do |spec, unmet_spec_dependencies|
unmet_spec_dependencies.each do |unmet_spec_dependency|
warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name}, unsatisfied by #{@specs.find {|s| s.name == unmet_spec_dependency.name && !unmet_spec_dependency.matches_spec?(s.spec) }.full_name}"
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}"
else
warning << "* #{unmet_spec_dependency}, dependency of #{spec.full_name} but missing from lockfile"
end
end
end

Expand Down Expand Up @@ -212,6 +224,8 @@ def enqueue_specs
if spec.dependencies_installed? @specs
spec.state = :enqueued
worker_pool.enq spec
elsif spec.dependencies_missing? @specs
spec.state = :failed
end
end
end
Expand Down
38 changes: 38 additions & 0 deletions bundler/spec/lock/lockfile_spec.rb
Expand Up @@ -1221,6 +1221,44 @@
and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.")
end

it "errors gracefully on a corrupt lockfile" do
build_repo4 do
build_gem "minitest-bisect", "1.6.0" do |s|
s.add_dependency "path_expander", "~> 1.1"
end

build_gem "path_expander", "1.1.1"
end

gemfile <<~G
source "#{file_uri_for(gem_repo4)}"
gem "minitest-bisect"
G

# Corrupt lockfile (completely missing path_expander)
lockfile <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
minitest-bisect (1.6.0)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
minitest-bisect

BUNDLED WITH
#{Bundler::VERSION}
L

cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", :gem_repo => gem_repo4
bundle :install, :raise_on_error => false

expect(err).not_to include("ERROR REPORT TEMPLATE")
expect(err).to include("path_expander (~> 1.1), dependency of minitest-bisect-1.6.0 but missing from lockfile")
end

it "auto-heals when the lockfile is missing specs" do
build_repo4 do
build_gem "minitest-bisect", "1.6.0" do |s|
Expand Down
4 changes: 2 additions & 2 deletions bundler/spec/support/helpers.rb
Expand Up @@ -414,14 +414,14 @@ def realworld_system_gems(*gems)
end
end

def cache_gems(*gems)
def cache_gems(*gems, gem_repo: gem_repo1)
gems = gems.flatten

FileUtils.rm_rf("#{bundled_app}/vendor/cache")
FileUtils.mkdir_p("#{bundled_app}/vendor/cache")

gems.each do |g|
path = "#{gem_repo1}/gems/#{g}.gem"
path = "#{gem_repo}/gems/#{g}.gem"
raise "OMG `#{path}` does not exist!" unless File.exist?(path)
FileUtils.cp(path, "#{bundled_app}/vendor/cache")
end
Expand Down