diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 0ed5f74c1..ad32bd4c6 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -615,15 +615,25 @@ module Vagrant def vagrant_internal_specs # activate any dependencies up front so we can always # pin them when resolving - Gem::Specification.find { |s| s.name == "vagrant" && s.activated? }. - runtime_dependencies.each { |d| gem d.name, *d.requirement.as_list } + self_spec = Gem::Specification.find { |s| s.name == "vagrant" && s.activated? } + if !self_spec + @logger.warn("Failed to locate activated vagrant specification. Activating...") + self_spec = Gem::Specification.find { |s| s.name == "vagrant" } + if !self_spec + @logger.error("Failed to locate Vagrant RubyGem specification") + raise Vagrant::Errors::SourceSpecNotFound + end + 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 = {} directories = [Gem::Specification.default_specifications_dir] Gem::Specification.find_all{true}.each do |spec| list[spec.full_name] = spec end - if(!defined?(::Bundler)) + if(!Object.const_defined?(:Bundler)) directories += Gem::Specification.dirs.find_all do |path| !path.start_with?(Gem.user_dir) end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index ab7584eed..efc0e2d65 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -248,6 +248,10 @@ module Vagrant error_key(:bundler_error) end + class SourceSpecNotFound < BundlerError + error_key(:source_spec_not_found) + end + class CantReadMACAddresses < VagrantError error_key(:cant_read_mac_addresses) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index bf738f202..743be4761 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -711,6 +711,10 @@ en: or transient network issues. The reported error is: %{message} + source_spec_not_found: |- + Vagrant failed to properly initialize its internal library + dependencies. Please try running the command again. If this + error persists, please report a bug. cant_read_mac_addresses: |- The provider you are using ('%{provider}') doesn't support the "nic_mac_addresses" provider capability which is required diff --git a/test/unit/vagrant/bundler_test.rb b/test/unit/vagrant/bundler_test.rb index 0d7872baa..89391c013 100644 --- a/test/unit/vagrant/bundler_test.rb +++ b/test/unit/vagrant/bundler_test.rb @@ -540,6 +540,86 @@ describe Vagrant::Bundler do end end + describe "#vagrant_internal_specs" do + let(:vagrant_spec) { double("vagrant_spec", name: "vagrant", version: Gem::Version.new(Vagrant::VERSION), + activated?: vagrant_spec_activated, activate: nil, runtime_dependencies: vagrant_dep_specs) } + let(:spec_list) { [] } + let(:spec_dirs) { [] } + let(:spec_default_dir) { "/dev/null" } + let(:dir_spec_list) { [] } + let(:vagrant_spec_activated) { true } + let(:vagrant_dep_specs) { [] } + + before do + allow(Gem::Specification).to receive(:find) { |&b| vagrant_spec if b.call(vagrant_spec) } + allow(Gem::Specification).to receive(:find_all).and_return(spec_list) + allow(Gem::Specification).to receive(:dirs).and_return(spec_dirs) + allow(Gem::Specification).to receive(:default_specifications_dir).and_return(spec_default_dir) + allow(Gem::Specification).to receive(:each_spec).and_return(dir_spec_list) + end + + it "should return an empty list" do + expect(subject.send(:vagrant_internal_specs)).to eq([]) + end + + context "when vagrant specification is not activated" do + let(:vagrant_spec_activated) { false } + + it "should activate the specification" do + expect(vagrant_spec).to receive(:activate) + subject.send(:vagrant_internal_specs) + end + end + + context "when vagrant specification is not found" do + before { allow(Gem::Specification).to receive(:find).and_return(nil) } + + it "should raise not found error" do + expect { subject.send(:vagrant_internal_specs) }.to raise_error(Vagrant::Errors::SourceSpecNotFound) + 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) } + + 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 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) + 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 + end + describe Vagrant::Bundler::PluginSet do let(:name) { "test-gem" } let(:version) { "1.0.0" }