Improve Gem spec selection when resolving

When computing the solution set, if a gem is already loaded, make sure
to use the specification of the loaded one instead of the first
available as otherwise there is a risk that when multiple matches are
available the specification for the wrong version may be picked.

When this happens an error message will be triggered that looks like

  can't activate json-2.3.0, already activated json-2.5.1

This can occur for distribution packaged vagrants as well as installs
for development purposes where the ruby install may contain a default
gem spec of an older version than is needed.

Fixes: #12521
Fixes: vagrant-libvirt/vagrant-libvirt#1390
This commit is contained in:
Darragh Bailey 2021-11-01 17:31:48 +00:00
parent 79881d671e
commit 0c6d6d8e9d
2 changed files with 34 additions and 1 deletions

View File

@ -258,7 +258,12 @@ module Vagrant
if solution_file&.valid?
@logger.debug("loading cached solution set")
solution = solution_file.dependency_list.map do |dep|
spec = composed_set.find_all(dep).first
spec = composed_set.find_all(dep).select do |dep_spec|
next(true) unless Gem.loaded_specs.has_key?(dep_spec.name)
Gem.loaded_specs[dep_spec.name].version.eql?(dep_spec.version)
end.first
if !spec
@logger.warn("failed to locate specification for dependency - #{dep}")
@logger.warn("invalidating solution file - #{solution_file}")

View File

@ -644,6 +644,34 @@ describe Vagrant::Bundler do
expect(Gem.sources.sources.first.uri.to_s).to eq(described_class.const_get(:HASHICORP_GEMSTORE))
end
end
context "multiple specs" do
let(:solution_file) { double('solution_file') }
let(:vagrant_set) { double('vagrant_set') }
before do
allow(subject).to receive(:load_solution_file).and_return(solution_file)
allow(subject).to receive(:generate_vagrant_set).and_return(vagrant_set)
allow(solution_file).to receive(:valid?).and_return(true)
end
it "should activate spec of deps already loaded" do
spec = Gem.loaded_specs.first
deps = [spec[0]]
specs = [spec[1].dup, spec[1].dup]
specs[0].version = Gem::Version::new('0.0.1')
# make sure haven't accidentally modified both
expect(specs[0].version).to_not eq(specs[1].version)
expect(solution_file).to receive(:dependency_list).and_return(deps)
expect(vagrant_set).to receive(:find_all).and_return(specs)
expect(subject).to receive(:activate_solution) do |activate_specs|
expect(activate_specs.length()).to eq(1)
expect(activate_specs[0].full_spec()).to eq(specs[1])
end
subject.init!([])
end
end
end
describe "#install" do