class Vagrant::Bundler

This class manages Vagrant's interaction with Bundler. Vagrant uses Bundler as a way to properly resolve all dependencies of Vagrant and all Vagrant-installed plugins.

Public Class Methods

instance() click to toggle source
# File lib/vagrant/bundler.rb, line 16
def self.instance
  @bundler ||= self.new
end
new() click to toggle source
# File lib/vagrant/bundler.rb, line 20
def initialize
  @enabled = true if ENV["VAGRANT_INSTALLER_ENV"] ||
    ENV["VAGRANT_FORCE_BUNDLER"]
  @enabled  = !::Bundler::SharedHelpers.in_bundle? if !@enabled
  @monitor  = Monitor.new

  @gem_home = ENV["GEM_HOME"]
  @gem_path = ENV["GEM_PATH"]
end

Public Instance Methods

clean(plugins) click to toggle source

Clean removes any unused gems.

# File lib/vagrant/bundler.rb, line 129
def clean(plugins)
  gemfile    = build_gemfile(plugins)
  lockfile   = "#{gemfile.path}.lock"
  definition = ::Bundler::Definition.build(gemfile, lockfile, nil)
  root       = File.dirname(gemfile.path)

  with_isolated_gem do
    runtime = ::Bundler::Runtime.new(root, definition)
    runtime.clean
  end
end
init!(plugins) click to toggle source

Initializes Bundler and the various gem paths so that we can begin loading gems. This must only be called once.

# File lib/vagrant/bundler.rb, line 32
def init!(plugins)
  # If we're not enabled, then we don't do anything.
  return if !@enabled

  # Set the Bundler UI to be a silent UI. We have to add the
  # `silence` method to it because Bundler UI doesn't have it.
  ::Bundler.ui = ::Bundler::UI.new
  if !::Bundler.ui.respond_to?(:silence)
    ui = ::Bundler.ui
    def ui.silence(*args)
      yield
    end
  end

  bundle_path = Vagrant.user_data_path.join("gems")

  # Setup the "local" Bundler configuration. We need to set BUNDLE_PATH
  # because the existence of this actually suppresses `sudo`.
  @appconfigpath = Dir.mktmpdir
  File.open(File.join(@appconfigpath, "config"), "w+") do |f|
    f.write("BUNDLE_PATH: \"#{bundle_path}\"")
  end

  # Setup the Bundler configuration
  @configfile = File.open(Tempfile.new("vagrant").path + "1", "w+")
  @configfile.close

  # Build up the Gemfile for our Bundler context. We make sure to
  # lock Vagrant to our current Vagrant version. In addition to that,
  # we add all our plugin dependencies.
  @gemfile = build_gemfile(plugins)

  # Set the environmental variables for Bundler
  ENV["BUNDLE_APP_CONFIG"] = @appconfigpath
  ENV["BUNDLE_CONFIG"]  = @configfile.path
  ENV["BUNDLE_GEMFILE"] = @gemfile.path
  ENV["GEM_PATH"] =
    "#{bundle_path}#{::File::PATH_SEPARATOR}#{@gem_path}"
  Gem.clear_paths
end
install(plugins, local=false) click to toggle source

Installs the list of plugins.

@param [Hash] plugins @return [Array<Gem::Specification>]

# File lib/vagrant/bundler.rb, line 77
def install(plugins, local=false)
  internal_install(plugins, nil, local: local)
end
install_local(path) click to toggle source

Installs a local '*.gem' file so that Bundler can find it.

@param [String] path Path to a local gem file. @return [Gem::Specification]

# File lib/vagrant/bundler.rb, line 85
def install_local(path)
  # We have to do this load here because this file can be loaded
  # before RubyGems is actually loaded.
  require "rubygems/dependency_installer"
  begin
    require "rubygems/format"
  rescue LoadError
    # rubygems 2.x
  end

  # If we're installing from a gem file, determine the name
  # based on the spec in the file.
  pkg = if defined?(Gem::Format)
          # RubyGems 1.x
          Gem::Format.from_file_by_path(path)
        else
          # RubyGems 2.x
          Gem::Package.new(path)
        end

  # Install the gem manually. If the gem exists locally, then
  # Bundler shouldn't attempt to get it remotely.
  with_isolated_gem do
    installer = Gem::DependencyInstaller.new(
      :document => [], :prerelease => false)
    installer.install(path, "= #{pkg.spec.version}")
  end

  pkg.spec
end
update(plugins, specific) click to toggle source

Update updates the given plugins, or every plugin if none is given.

@param [Hash] plugins @param [Array<String>] specific Specific plugin names to update. If

empty or nil, all plugins will be updated.
# File lib/vagrant/bundler.rb, line 121
def update(plugins, specific)
  specific ||= []
  update = true
  update = { gems: specific } if !specific.empty?
  internal_install(plugins, update)
end
verbose() { || ... } click to toggle source

During the duration of the yielded block, Bundler loud output is enabled.

# File lib/vagrant/bundler.rb, line 143
def verbose
  @monitor.synchronize do
    begin
      old_ui = ::Bundler.ui
      require 'bundler/vendored_thor'
      ::Bundler.ui = ::Bundler::UI::Shell.new
      yield
    ensure
      ::Bundler.ui = old_ui
    end
  end
end

Protected Instance Methods

build_gemfile(plugins) click to toggle source

Builds a valid Gemfile for use with Bundler given the list of plugins.

@return [Tempfile]

# File lib/vagrant/bundler.rb, line 162
def build_gemfile(plugins)
  f = File.open(Tempfile.new("vagrant").path + "2", "w+")
  f.tap do |gemfile|
    gemfile.puts(%Q[source "https://rubygems.org"])
    gemfile.puts(%Q[source "http://gems.hashicorp.com"])
    sources = plugins.values.map { |p| p["sources"] }.flatten.compact.uniq
    sources.each do |source|
      next if source == ""
      gemfile.puts(%Q[source "#{source}"])
    end

    gemfile.puts(%Q[gem "vagrant", "= #{VERSION}"])

    gemfile.puts("group :plugins do")
    plugins.each do |name, plugin|
      version = plugin["gem_version"]
      version = nil if version == ""

      opts = {}
      if plugin["require"] && plugin["require"] != ""
        opts[:require] = plugin["require"]
      end

      gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}])
    end
    gemfile.puts("end")

    gemfile.close
  end
end
internal_install(plugins, update, **extra) click to toggle source

This installs a set of plugins and optionally updates those gems.

@param [Hash] plugins @param [Hash, Boolean] update If true, updates all plugins, otherwise

can be a hash of options. See Bundler.definition.

@return [Array<Gem::Specification>]

# File lib/vagrant/bundler.rb, line 199
def internal_install(plugins, update, **extra)
  gemfile    = build_gemfile(plugins)
  lockfile   = "#{gemfile.path}.lock"
  definition = ::Bundler::Definition.build(gemfile, lockfile, update)
  root       = File.dirname(gemfile.path)
  opts       = {}
  opts["local"] = true if extra[:local]

  with_isolated_gem do
    ::Bundler::Installer.install(root, definition, opts)
  end

  # TODO(mitchellh): clean gems here... for some reason when I put
  # it in on install, we get a GemNotFound exception. Gotta investigate.

  definition.specs
rescue ::Bundler::VersionConflict => e
  raise Errors::PluginInstallVersionConflict,
    conflicts: e.to_s.gsub("Bundler", "Vagrant")
end
with_isolated_gem() { || ... } click to toggle source
# File lib/vagrant/bundler.rb, line 220
def with_isolated_gem
  raise Errors::BundlerDisabled if !@enabled

  # Remove bundler settings so that Bundler isn't loaded when building
  # native extensions because it causes all sorts of problems.
  old_rubyopt = ENV["RUBYOPT"]
  old_gemfile = ENV["BUNDLE_GEMFILE"]
  ENV["BUNDLE_GEMFILE"] = Tempfile.new("vagrant-gemfile").path
  ENV["RUBYOPT"] = (ENV["RUBYOPT"] || "").gsub(/-rbundler\/setup\s*/, "")

  # Set the GEM_HOME so gems are installed only to our local gem dir
  ENV["GEM_HOME"] = Vagrant.user_data_path.join("gems").to_s

  # Clear paths so that it reads the new GEM_HOME setting
  Gem.paths = ENV

  # Reset the all specs override that Bundler does
  old_all = Gem::Specification._all
  Gem::Specification.all = nil

  # /etc/gemrc and so on.
  old_config = nil
  begin
    old_config = Gem.configuration
  rescue Psych::SyntaxError
    # Just ignore this. This means that the ".gemrc" file has
    # an invalid syntax and can't be loaded. We don't care, because
    # when we set Gem.configuration to nil later, it'll force a reload
    # if it is needed.
  end
  Gem.configuration = NilGemConfig.new

  # Use a silent UI so that we have no output
  Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do
    return yield
  end
ensure
  ENV["BUNDLE_GEMFILE"] = old_gemfile
  ENV["GEM_HOME"] = @gem_home
  ENV["RUBYOPT"]  = old_rubyopt

  Gem.configuration = old_config
  Gem.paths = ENV
  Gem::Specification.all = old_all
end