Skip to content
This repository has been archived by the owner on Apr 14, 2021. It is now read-only.

[ParallelInstaller] Allow installing with corrupted lockfiles #5267

Merged
merged 3 commits into from Dec 27, 2016
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
45 changes: 36 additions & 9 deletions lib/bundler/installer/parallel_installer.rb
Expand Up @@ -47,23 +47,22 @@ def ignorable_dependency?(dep)
# sure needed dependencies have been installed.
def dependencies_installed?(all_specs)
installed_specs = all_specs.select(&:installed?).map(&:name)
dependencies(all_specs.map(&:name)).all? {|d| installed_specs.include? d.name }
dependencies.all? {|d| installed_specs.include? d.name }
end

# Represents only the non-development dependencies, the ones that are
# itself and are in the total list.
def dependencies(all_spec_names)
def dependencies
@dependencies ||= begin
deps = all_dependencies.reject {|dep| ignorable_dependency? dep }
missing = deps.reject {|dep| all_spec_names.include? dep.name }
unless missing.empty?
raise Bundler::LockfileError, "Your Gemfile.lock is corrupt. The following #{missing.size > 1 ? "gems are" : "gem is"} missing " \
"from the DEPENDENCIES section: '#{missing.map(&:name).join('\' \'')}'"
end
deps
all_dependencies.reject {|dep| ignorable_dependency? dep }
end
end

def missing_lockfile_dependencies(all_spec_names)
deps = all_dependencies.reject {|dep| ignorable_dependency? dep }
deps.reject {|dep| all_spec_names.include? dep.name }
end

# Represents all dependencies
def all_dependencies
@spec.dependencies
Expand All @@ -79,6 +78,8 @@ def self.max_threads
[Bundler.settings[:jobs].to_i - 1, 1].max
end

attr_reader :size

def initialize(installer, all_specs, size, standalone, force)
@installer = installer
@size = size
Expand All @@ -92,6 +93,7 @@ def call
# TODO: remove in bundler 2.0
require "bundler/gem_remote_fetcher" if RUBY_VERSION < "1.9"

check_for_corrupt_lockfile
enqueue_specs
process_specs until @specs.all?(&:installed?) || @specs.any?(&:failed?)
handle_error if @specs.any?(&:failed?)
Expand Down Expand Up @@ -135,6 +137,31 @@ def handle_error
raise Bundler::InstallError, errors.map(&:to_s).join("\n\n")
end

def check_for_corrupt_lockfile
missing_dependencies = @specs.map do |s|
[
s,
s.missing_lockfile_dependencies(@specs.map(&:name)),
]
end.reject { |a| a.last.empty? }
return if missing_dependencies.empty?

warning = []
warning << "Your lockfile was created by an old Bundler that left some things out."
if @size != 1
warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time."
@size = 1
end
warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile."
warning << "The missing gems are:"

missing_dependencies.each do |spec, missing|
warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}"
end

Bundler.ui.warn(warning.join("\n"))
end

# Keys in the remains hash represent uninstalled gems specs.
# We enqueue all gem specs that do not have any dependencies.
# Later we call this lambda again to install specs that depended on
Expand Down
47 changes: 47 additions & 0 deletions spec/bundler/installer/parallel_installer_spec.rb
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require "spec_helper"
require "bundler/installer/parallel_installer"

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 dependencies that are not on the overall installation list are the only ones not installed" do
let(:all_specs) do
[
build_spec("alpha", "1.0") {|s| s.runtime "a", "1" },
].flatten
end

it "prints a warning" do
expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
Your lockfile was created by an old Bundler that left some things out.
You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
The missing gems are:
* a depended upon by alpha
W
subject.check_for_corrupt_lockfile
end

context "when size > 1" do
let(:size) { 500 }

it "prints a warning and sets size to 1" do
expect(Bundler.ui).to receive(:warn).with(<<-W.strip)
Your lockfile was created by an old Bundler that left some things out.
Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing 500 at a time.
You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile.
The missing gems are:
* a depended upon by alpha
W
subject.check_for_corrupt_lockfile
expect(subject.size).to eq(1)
end
end
end
end
Expand Up @@ -58,20 +58,5 @@ def a_spec.name
expect(spec.dependencies_installed?(all_specs)).to be_falsey
end
end

context "when dependencies that are not on the overall installation list are the only ones not installed" do
it "raises an error" do
dependencies = []
dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => true, :all_dependencies => [], :type => :production)
all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)]
# Add dependency which is not in all_specs
dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => false, :all_dependencies => [], :type => :production)
dependencies << instance_double("SpecInstallation", :spec => "delta", :name => "delta", :installed? => false, :all_dependencies => [], :type => :production)
spec = described_class.new(dep)
allow(spec).to receive(:all_dependencies).and_return(dependencies)
expect { spec.dependencies_installed?(all_specs) }.
to raise_error(Bundler::LockfileError, /Your Gemfile.lock is corrupt\. The following.*'beta' 'delta'/)
end
end
end
end