diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb3c2dc6..654ad6dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ ## 1.0.0 (unreleased) - + - `vagrant gem` should now be used to install Vagrant plugins that are + gems. This installs the gems to a private gem folder that Vagrant adds + to its own load path. This isolates Vagrant-related gems from system + gems. + - Plugin loading no longer happens right when Vagrant is loaded, but when + a Vagrant environment is loaded. I don't anticipate this causing any + problems but it is a backwards incompatible change should a plugin + depend on this (but I don't see any reason why they would). ## 0.9.6 (February 7, 2012) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 867324b55..eb7c86b29 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -50,6 +50,9 @@ require 'i18n' # there are issues with ciphers not being properly loaded. require 'openssl' +# Always make the version available +require 'vagrant/version' + module Vagrant autoload :Action, 'vagrant/action' autoload :Box, 'vagrant/box' @@ -136,6 +139,7 @@ I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_ro # Register the built-in commands Vagrant.commands.register(:box) { Vagrant::Command::Box } Vagrant.commands.register(:destroy) { Vagrant::Command::Destroy } +Vagrant.commands.register(:gem) { Vagrant::Command::Gem } Vagrant.commands.register(:halt) { Vagrant::Command::Halt } Vagrant.commands.register(:init) { Vagrant::Command::Init } Vagrant.commands.register(:package) { Vagrant::Command::Package } @@ -186,7 +190,3 @@ Vagrant.provisioners.register(:shell) { Vagrant::Provisioners::Shell } Vagrant.config_keys.register(:freebsd) { Vagrant::Guest::FreeBSD::FreeBSDConfig } Vagrant.config_keys.register(:linux) { Vagrant::Guest::Linux::LinuxConfig } Vagrant.config_keys.register(:solaris) { Vagrant::Guest::Solaris::SolarisConfig } - -# Load the things which must be loaded before anything else. -require 'vagrant/version' -Vagrant::Plugin.load! diff --git a/lib/vagrant/command.rb b/lib/vagrant/command.rb index 2524a1646..417450d6c 100644 --- a/lib/vagrant/command.rb +++ b/lib/vagrant/command.rb @@ -8,6 +8,7 @@ module Vagrant autoload :BoxRepackage, 'vagrant/command/box_repackage' autoload :BoxList, 'vagrant/command/box_list' autoload :Destroy, 'vagrant/command/destroy' + autoload :Gem, 'vagrant/command/gem' autoload :Halt, 'vagrant/command/halt' autoload :Init, 'vagrant/command/init' autoload :Package, 'vagrant/command/package' diff --git a/lib/vagrant/command/gem.rb b/lib/vagrant/command/gem.rb new file mode 100644 index 000000000..4a135acac --- /dev/null +++ b/lib/vagrant/command/gem.rb @@ -0,0 +1,35 @@ +require "rubygems" +require "rubygems/gem_runner" + +module Vagrant + module Command + class Gem < Base + def execute + # Bundler sets up its own custom gem load paths such that our + # own gems are never loaded. Therefore, give an error if a user + # tries to install gems while within a Bundler-managed environment. + if defined?(Bundler) + require 'bundler/shared_helpers' + if Bundler::SharedHelpers.in_bundle? + raise Errors::GemCommandInBundler + end + end + + # If the user needs some help, we add our own little message at the + # top so that they're aware of what `vagrant gem` is doing, really. + if @argv.empty? || @argv.include?("-h") || @argv.include?("--help") + @env.ui.info(I18n.t("vagrant.commands.gem.help_preamble"), + :prefix => false) + puts + end + + # We just proxy the arguments onto a real RubyGems command + # but change `GEM_HOME` so that the gems are installed into + # our own private gem folder. + ENV["GEM_HOME"] = @env.gems_path.to_s + ::Gem.clear_paths + ::Gem::GemRunner.new.run(@argv.dup) + end + end + end +end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index b92c1af5d..d74adb69e 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -11,7 +11,7 @@ module Vagrant # defined as basically a folder with a "Vagrantfile." This class allows # access to the VMs, CLI, etc. all in the scope of this environment. class Environment - HOME_SUBDIRS = ["tmp", "boxes"] + HOME_SUBDIRS = ["tmp", "boxes", "gems"] DEFAULT_VM = :default DEFAULT_HOME = "~/.vagrant.d" @@ -34,6 +34,9 @@ module Vagrant # The directory where boxes are stored. attr_reader :boxes_path + # The path where the plugins are stored (gems) + attr_reader :gems_path + # The path to the default private key attr_reader :default_private_key_path @@ -80,10 +83,14 @@ module Vagrant setup_home_path @tmp_path = @home_path.join("tmp") @boxes_path = @home_path.join("boxes") + @gems_path = @home_path.join("gems") # Setup the default private key @default_private_key_path = @home_path.join("insecure_private_key") copy_insecure_private_key + + # Load the plugins + load_plugins end #--------------------------------------------------------------- @@ -490,5 +497,17 @@ module Vagrant nil end + + # Loads the Vagrant plugins by properly setting up RubyGems so that + # our private gem repository is on the path. + def load_plugins + # Add our private gem path to the gem path and reset the paths + # that Rubygems knows about. + ENV["GEM_PATH"] = "#{@gems_path}:#{ENV["GEM_PATH"]}" + ::Gem.clear_paths + + # Load the plugins + Plugin.load! + end end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 5e928f07b..71b98ceb5 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -173,6 +173,11 @@ module Vagrant error_key(:environment_locked) end + class GemCommandInBundler < VagrantError + status_code(71) + error_key(:gem_command_in_bundler) + end + class HomeDirectoryMigrationFailed < VagrantError status_code(53) error_key(:home_dir_migration_failed) diff --git a/lib/vagrant/plugin.rb b/lib/vagrant/plugin.rb index 79f597016..62da439c2 100644 --- a/lib/vagrant/plugin.rb +++ b/lib/vagrant/plugin.rb @@ -1,5 +1,7 @@ require "rubygems" +require "log4r" + module Vagrant # Represents a single plugin and also manages loading plugins from # RubyGems. If a plugin has a `vagrant_init.rb` file somewhere on its @@ -7,20 +9,17 @@ module Vagrant # (for debugging), the list of loaded plugins is stored in the {plugins} # array. class Plugin - # The array of loaded plugins. + # The array of gem specifications that were loaded as plugins. @@plugins = [] - # The gemspec of this plugin. This is an actual gemspec object. - attr_reader :gemspec - - # The path to the `vagrant_init.rb` file which was loaded for this plugin. - attr_reader :file - # Loads all the plugins for Vagrant. Plugins are currently # gems which have a "vagrant_init.rb" somewhere on their # load path. This file is loaded to kick off the load sequence # for that plugin. def self.load! + logger = Log4r::Logger.new("vagrant::plugin") + logger.info("Searching and loading any available plugins...") + # Our version is used for checking dependencies our_version = Gem::Version.create(Vagrant::VERSION) @@ -41,10 +40,18 @@ module Vagrant specs = Gem::VERSION >= "1.6.0" ? source.latest_specs(true) : source.latest_specs specs.each do |spec| + if @@plugins.include?(spec) + logger.debug("Plugin already loaded, not loading again: #{spec.name}") + next + end + # If this gem depends on Vagrant, verify this is a valid release of # Vagrant for this gem to load into. vagrant_dep = spec.dependencies.find { |d| d.name == "vagrant" } - next if vagrant_dep && !vagrant_dep.requirement.satisfied_by?(our_version) + if vagrant_dep && !vagrant_dep.requirement.satisfied_by?(our_version) + logger.debug("Plugin Vagrant dependency mismatch: #{spec.name} (#{spec.version})") + next + end # Find a vagrant_init.rb to verify if this is a plugin file = nil @@ -55,8 +62,13 @@ module Vagrant end next if !file - @@plugins << new(spec, file) + + logger.info("Loading plugin: #{spec.name} (#{spec.version})") + @@plugins << spec + load file end + + logger.info("Loaded #{@@plugins.length} plugins.") end end @@ -65,15 +77,5 @@ module Vagrant # # @return [Array] def self.plugins; @@plugins; end - - # Initializes a new plugin, given a Gemspec and the path to the - # gem's `vagrant_init.rb` file. This should never be called manually. - # Instead {load!} creates all the instances. - def initialize(spec, file) - @gemspec = spec - @file = file - - load file - end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 95ecb8a71..d870cfd76 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -47,6 +47,12 @@ en: may run at any given time to avoid problems with VirtualBox inconsistencies occurring. Please wait for the other instance of Vagrant to end and then try again. + gem_command_in_bundler: |- + You cannot run the `vagrant gem` command while in a bundler environment. + Bundler messes around quite a bit with the RubyGem load paths and gems + installed via `vagrant gem` are excluded by Bundler. + + Instead, please include your Vagrant plugins in your Gemfile itself. guest: invalid_class: |- The specified guest class does not inherit from `Vagrant::Guest::Base`. @@ -225,6 +231,13 @@ en: vm_not_running: "VM is not currently running. Please bring it up to run this command." box: no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some." + gem: + help_preamble: |- + `vagrant gem` is used to install Vagrant plugins via the RubyGems + system. In fact, `vagrant gem` is just a frontend to the actual `gem` + interface, with the difference being that Vagrant sets up a custom + directory where gems are installed so that they are isolated from your + system gems. init: success: |- A `Vagrantfile` has been placed in this directory. You are now