From d19f7a44e51d6bc30ae688a8d7490bd4eff6450c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:13:19 -0500 Subject: [PATCH 1/6] Starting on the `vagrant gem` command. --- lib/vagrant.rb | 1 + lib/vagrant/command.rb | 1 + lib/vagrant/command/gem.rb | 17 +++++++++++++++++ lib/vagrant/environment.rb | 6 +++++- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 lib/vagrant/command/gem.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 2008d8785..63645f06a 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -136,6 +136,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 } 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..3eec3b2e3 --- /dev/null +++ b/lib/vagrant/command/gem.rb @@ -0,0 +1,17 @@ +require "rubygems" +require "rubygems/gem_runner" + +module Vagrant + module Command + class Gem < Base + def execute + # 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..fc6503951 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,6 +83,7 @@ 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") From 4444e7150ce0b1828e984f73a9da862758223d44 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:21:01 -0500 Subject: [PATCH 2/6] `vagarnt gem` cannot be called within a bundler env --- lib/vagrant/command/gem.rb | 10 ++++++++++ lib/vagrant/errors.rb | 5 +++++ templates/locales/en.yml | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/lib/vagrant/command/gem.rb b/lib/vagrant/command/gem.rb index 3eec3b2e3..fe558b1e5 100644 --- a/lib/vagrant/command/gem.rb +++ b/lib/vagrant/command/gem.rb @@ -5,6 +5,16 @@ 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 + # 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. 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/templates/locales/en.yml b/templates/locales/en.yml index 95ecb8a71..1789821df 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`. From 261a83d60f5ca7d1b4ab3257832477e0823d7bf9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:31:45 -0500 Subject: [PATCH 3/6] Logging statements in plugin loading --- lib/vagrant/plugin.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/plugin.rb b/lib/vagrant/plugin.rb index 79f597016..abdbccd73 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 @@ -21,6 +23,9 @@ module Vagrant # 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) @@ -44,7 +49,10 @@ module Vagrant # 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 +63,12 @@ module Vagrant end next if !file + + logger.info("Loading plugin: #{spec.name} (#{spec.version})") @@plugins << new(spec, file) end + + logger.info("Loaded #{@@plugins.length} plugins.") end end From 13fddfa6f9766ee85f9494c9a12580411f48fa07 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:39:35 -0500 Subject: [PATCH 4/6] Load plugins with the private gem path. This changed plugin loading semantics a tiny bit, since they are no longer loaded when Vagrant is loaded but instead when the Vagrant::Environment is initialized. I'll note this in the CHANGELOG. --- lib/vagrant.rb | 7 +++---- lib/vagrant/environment.rb | 15 +++++++++++++++ lib/vagrant/plugin.rb | 26 ++++++++------------------ 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 63645f06a..778009f98 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' @@ -186,7 +189,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/environment.rb b/lib/vagrant/environment.rb index fc6503951..d74adb69e 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -88,6 +88,9 @@ module Vagrant # 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 #--------------------------------------------------------------- @@ -494,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/plugin.rb b/lib/vagrant/plugin.rb index abdbccd73..62da439c2 100644 --- a/lib/vagrant/plugin.rb +++ b/lib/vagrant/plugin.rb @@ -9,15 +9,9 @@ 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 @@ -46,6 +40,11 @@ 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" } @@ -65,7 +64,8 @@ module Vagrant next if !file logger.info("Loading plugin: #{spec.name} (#{spec.version})") - @@plugins << new(spec, file) + @@plugins << spec + load file end logger.info("Loaded #{@@plugins.length} plugins.") @@ -77,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 From 2fdc2f314c3288d393ea6d715d4781e2fbc2f223 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:43:38 -0500 Subject: [PATCH 5/6] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3663487..3fe380c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ - `vagrant ssh` does a direct `exec()` syscall now instead of going through the shell. This makes it so things like shell expansion oddities no longer cause problems. [GH-715] + - `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.5 (February 5, 2012) From a3d9615a13cfb86072b2bed376ec93a1974a2e54 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Feb 2012 22:52:34 -0500 Subject: [PATCH 6/6] Custom help for `vagrant gem` on top of RubyGems help --- lib/vagrant/command/gem.rb | 8 ++++++++ templates/locales/en.yml | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/lib/vagrant/command/gem.rb b/lib/vagrant/command/gem.rb index fe558b1e5..4a135acac 100644 --- a/lib/vagrant/command/gem.rb +++ b/lib/vagrant/command/gem.rb @@ -15,6 +15,14 @@ module Vagrant 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. diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1789821df..d870cfd76 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -231,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