Merge pull request #12225 from chrisroberts/resolution-isolation

Activate builtin specs on startup, provider better plugin install errors
This commit is contained in:
Chris Roberts 2021-03-09 11:38:14 -08:00 committed by GitHub
commit 8a69d0c4da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 124 additions and 39 deletions

View file

@ -23,9 +23,9 @@ if idx = argv.index("--")
argv = argv.slice(0, idx)
end
require_relative "../lib/vagrant/version"
# Fast path the version of Vagrant
if argv.include?("-v") || argv.include?("--version")
require_relative "../lib/vagrant/version"
puts "Vagrant #{Vagrant::VERSION}"
exit 0
end
@ -82,6 +82,29 @@ end
$stdout.sync = true
$stderr.sync = true
# Before we start activate all our dependencies
# so we can provide correct resolutions later
builtin_specs = []
vagrant_spec = Gem::Specification.find_all_by_name("vagrant").detect do |spec|
spec.version == Gem::Version.new(Vagrant::VERSION)
end
dep_activator = proc do |spec|
spec.runtime_dependencies.each do |dep|
gem(dep.name, *dep.requirement.as_list)
dep_spec = Gem::Specification.find_all_by_name(dep.name).detect(&:activated?)
if dep_spec
builtin_specs << dep_spec
dep_activator.call(dep_spec)
end
end
end
if vagrant_spec
dep_activator.call(vagrant_spec)
end
env = nil
begin
require 'log4r'
@ -91,6 +114,9 @@ begin
require 'vagrant/util/platform'
require 'vagrant/util/experimental'
# Set our list of builtin specs
Vagrant::Bundler.instance.builtin_specs = builtin_specs
# Schedule the cleanup of things
at_exit(&Vagrant::Bundler.instance.method(:deinit))

View file

@ -81,7 +81,7 @@ if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != ""
# See https://github.com/rest-client/rest-client/issues/34#issuecomment-290858
# for more information
class VagrantLogger < Log4r::Logger
def << (msg)
def << msg
debug(msg.strip)
end
end

View file

@ -189,8 +189,11 @@ module Vagrant
attr_reader :env_plugin_gem_path
# @return [Pathname] Vagrant environment data path
attr_reader :environment_data_path
# @return [Array<Gem::Specification>, nil] List of builtin specs
attr_accessor :builtin_specs
def initialize
@builtin_specs = []
@plugin_gem_path = Vagrant.user_data_path.join("gems", RUBY_VERSION).freeze
@logger = Log4r::Logger.new("vagrant::bundler")
end
@ -287,7 +290,6 @@ module Vagrant
# Never allow dependencies to be remotely satisfied during init
request_set.remote = false
repair_result = nil
begin
@logger.debug("resolving solution from available specification set")
# Resolve the request set to ensure proper activation order
@ -647,7 +649,6 @@ module Vagrant
self_spec.activate
@logger.info("Activated vagrant specification version - #{self_spec.version}")
end
self_spec.runtime_dependencies.each { |d| gem d.name, *d.requirement.as_list }
# discover all the gems we have available
list = {}
if Gem.respond_to?(:default_specifications_dir)
@ -656,10 +657,16 @@ module Vagrant
spec_dir = Gem::Specification.default_specifications_dir
end
directories = [spec_dir]
Gem::Specification.find_all{true}.each do |spec|
list[spec.full_name] = spec
if Vagrant.in_bundler?
Gem::Specification.find_all{true}.each do |spec|
list[spec.full_name] = spec
end
else
builtin_specs.each do |spec|
list[spec.full_name] = spec
end
end
if(!Object.const_defined?(:Bundler))
if Vagrant.in_installer?
directories += Gem::Specification.dirs.find_all do |path|
!path.start_with?(Gem.user_dir)
end

View file

@ -636,6 +636,18 @@ module Vagrant
error_key(:provisioner_winrm_unsupported)
end
class PluginNeedsDeveloperTools < VagrantError
error_key(:plugin_needs_developer_tools)
end
class PluginMissingLibrary < VagrantError
error_key(:plugin_missing_library)
end
class PluginMissingRubyDev < VagrantError
error_key(:plugin_missing_ruby_dev)
end
class PluginGemNotFound < VagrantError
error_key(:plugin_gem_not_found)
end

View file

@ -179,8 +179,26 @@ module Vagrant
result
rescue Gem::GemNotFoundException
raise Errors::PluginGemNotFound, name: name
rescue Gem::Exception => e
raise Errors::BundlerError, message: e.to_s
rescue Gem::Exception => err
@logger.warn("Failed to install plugin: #{err}")
@logger.debug("#{err.class}: #{err}\n#{err.backtrace.join("\n")}")
# Try and determine a cause for the failure
case err.message
when /install development tools first/
raise Errors::PluginNeedsDeveloperTools
when /library not found in default locations/
lib = err.message.match(/(\w+) library not found in default locations/)
if lib.nil?
raise Errors::BundlerError, message: err.message
end
raise Errors::PluginMissingLibrary,
library: lib.captures.first,
name: name
when /find header files for ruby/
raise Errors::PluginMissingRubyDev
else
raise Errors::BundlerError, message: err.message
end
end
# Uninstalls the plugin with the given name.

View file

@ -794,9 +794,9 @@ en:
matching this provider. For example, if you're using VirtualBox,
the clone environment must also be using VirtualBox.
cloud_init_not_found: |-
cloud-init is not found. Please ensure that cloud-init is installed and
cloud-init is not found. Please ensure that cloud-init is installed and
available on path for guest '%{guest_name}'.
cloud_init_command_failed: |-
cloud_init_command_failed: |-
cloud init command '%{cmd}' failed on guest '%{guest_name}'.
command_deprecated: |-
The command 'vagrant %{name}' has been deprecated and is no longer functional
@ -1238,6 +1238,23 @@ en:
following command:
vagrant plugin install --local
plugin_needs_developer_tools: |-
Vagrant failed to install the requested plugin because development tools
are required for installation but are not currently installed on this
machine. Please install development tools and then try this command
again.
plugin_missing_library: |-
Vagrant failed to install the requested plugin because it depends
on a library which is not currently installed on this system. The
following library is required by the '%{name}' plugin:
%{library}
Please install the library and then run the command again.
plugin_missing_ruby_dev: |-
Vagrant failed to install the requested plugin because the Ruby header
files could not be found. Install the ruby development package for your
system and then run this command again.
powershell_not_found: |-
Failed to locate the powershell executable on the available PATH. Please
ensure powershell is installed and available on the local PATH, then
@ -2998,7 +3015,7 @@ en:
pushes:
file:
no_destination: "File destination must be specified."
autocomplete:
installed: |-
Autocomplete installed at paths:

View file

@ -30,6 +30,7 @@ describe "vagrant bin" do
allow(Kernel).to receive(:exit)
allow(Vagrant::Environment).to receive(:new).and_return(env)
allow(Vagrant).to receive(:in_installer?).and_return(true)
allow(self).to receive(:require_relative)
end
after { expect(run_vagrant).to eq(exit_code) }

View file

@ -778,42 +778,46 @@ describe Vagrant::Bundler do
end
end
context "when run time dependencies are defined" do
let(:vagrant_dep_specs) { [double("spec", name: "vagrant-dep", requirement: double("spec-req", as_list: []))] }
it "should call #gem to activate the dependencies" do
expect(subject).to receive(:gem).with("vagrant-dep", any_args)
subject.send(:vagrant_internal_specs)
end
end
context "when bundler is not defined" do
before { expect(Object).to receive(:const_defined?).with(:Bundler).and_return(false) }
before { expect(Vagrant).to receive(:in_bundler?).and_return(false) }
it "should load gem specification directories" do
expect(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
subject.send(:vagrant_internal_specs)
end
context "when running inside the installer" do
before { expect(Vagrant).to receive(:in_installer?).and_return(true) }
context "when checking paths" do
let(:spec_dirs) { [double("spec-dir", start_with?: in_user_dir)] }
let(:in_user_dir) { true }
let(:user_dir) { double("user-dir") }
before { allow(Gem).to receive(:user_dir).and_return(user_dir) }
it "should check if path is within local user directory" do
expect(spec_dirs.first).to receive(:start_with?).with(user_dir).and_return(false)
it "should load gem specification directories" do
expect(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
subject.send(:vagrant_internal_specs)
end
context "when path is not within user directory" do
let(:in_user_dir) { false }
context "when checking paths" do
let(:spec_dirs) { [double("spec-dir", start_with?: in_user_dir)] }
let(:in_user_dir) { true }
let(:user_dir) { double("user-dir") }
it "should use path when loading specs" do
expect(Gem::Specification).to receive(:each_spec) { |arg| expect(arg).to include(spec_dirs.first) }
before { allow(Gem).to receive(:user_dir).and_return(user_dir) }
it "should check if path is within local user directory" do
expect(spec_dirs.first).to receive(:start_with?).with(user_dir).and_return(false)
subject.send(:vagrant_internal_specs)
end
context "when path is not within user directory" do
let(:in_user_dir) { false }
it "should use path when loading specs" do
expect(Gem::Specification).to receive(:each_spec) { |arg| expect(arg).to include(spec_dirs.first) }
subject.send(:vagrant_internal_specs)
end
end
end
end
context "when running outside the installer" do
before { expect(Vagrant).to receive(:in_installer?).and_return(false) }
it "should not load gem specification directories" do
expect(Gem::Specification).not_to receive(:dirs)
subject.send(:vagrant_internal_specs)
end
end
end