lib/vagrant/bundler.rb in vagrant-unbundled-1.9.1.1 vs lib/vagrant/bundler.rb in vagrant-unbundled-1.9.5.1
- old
+ new
@@ -1,10 +1,11 @@
require "monitor"
require "pathname"
require "set"
require "tempfile"
require "fileutils"
+require "uri"
require "rubygems/package"
require "rubygems/uninstaller"
require "rubygems/name_tuple"
@@ -16,12 +17,19 @@
# 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.
class Bundler
- HASHICORP_GEMSTORE = 'https://gems.hashicorp.com'.freeze
+ # Location of HashiCorp gem repository
+ HASHICORP_GEMSTORE = "https://gems.hashicorp.com/".freeze
+ # Default gem repositories
+ DEFAULT_GEM_SOURCES = [
+ "https://rubygems.org/".freeze,
+ HASHICORP_GEMSTORE
+ ].freeze
+
def self.instance
@bundler ||= self.new
end
attr_reader :plugin_gem_path
@@ -104,12 +112,13 @@
# @return [Gem::Specification]
def install_local(path, opts={})
plugin_source = Gem::Source::SpecificFile.new(path)
plugin_info = {
plugin_source.spec.name => {
+ "gem_version" => plugin_source.spec.version.to_s,
"local_source" => plugin_source,
- "sources" => opts.fetch(:sources, Gem.sources.map(&:to_s))
+ "sources" => opts.fetch(:sources, [])
}
}
@logger.debug("Installing local plugin - #{plugin_info}")
internal_install(plugin_info, {})
plugin_source.spec
@@ -201,39 +210,70 @@
end
protected
def internal_install(plugins, update, **extra)
- # Only allow defined Gem sources
- Gem.sources.clear
-
update = {} if !update.is_a?(Hash)
skips = []
- installer_set = Gem::Resolver::InstallerSet.new(:both)
+ source_list = {}
+ installer_set = VagrantSet.new(:both)
# Generate all required plugin deps
plugin_deps = plugins.map do |name, info|
if update[:gems] == true || (update[:gems].respond_to?(:include?) && update[:gems].include?(name))
gem_version = '> 0'
skips << name
else
gem_version = info['gem_version'].to_s.empty? ? '> 0' : info['gem_version']
end
+ source_list[name] ||= []
if plugin_source = info.delete("local_source")
installer_set.add_local(plugin_source.spec.name, plugin_source.spec, plugin_source)
+ source_list[name] << plugin_source.path
end
Array(info["sources"]).each do |source|
- if !Gem.sources.include?(source)
- @logger.debug("Adding RubyGems source for plugin install: #{source}")
- Gem.sources << source
+ if !source.end_with?("/")
+ source = source + "/"
end
+ source_list[name] << source
end
Gem::Dependency.new(name, gem_version)
end
@logger.debug("Dependency list for installation: #{plugin_deps}")
+ all_sources = source_list.values.flatten.uniq
+ default_sources = DEFAULT_GEM_SOURCES & all_sources
+ all_sources -= DEFAULT_GEM_SOURCES
+
+ # Only allow defined Gem sources
+ Gem.sources.clear
+
+ @logger.debug("Enabling user defined remote RubyGems sources")
+ all_sources.each do |src|
+ begin
+ next if File.file?(src) || URI.parse(src).scheme.nil?
+ rescue URI::InvalidURIError
+ next
+ end
+ @logger.debug("Adding RubyGems source #{src}")
+ Gem.sources << src
+ end
+
+ @logger.debug("Enabling default remote RubyGems sources")
+ default_sources.each do |src|
+ @logger.debug("Adding source - #{src}")
+ Gem.sources << src
+ end
+
+ validate_configured_sources!
+
+ source_list.values.each{|srcs| srcs.delete_if{|src| default_sources.include?(src)}}
+ installer_set.prefer_sources = source_list
+
+ @logger.debug("Current source list for install: #{Gem.sources.to_a}")
+
# Create the request set for the new plugins
request_set = Gem::RequestSet.new(*plugin_deps)
installer_set = Gem::Resolver.compose_sets(
installer_set,
@@ -251,11 +291,15 @@
# Install all remote gems into plugin path. Set the installer to ignore dependencies
# as we know the dependencies are satisfied and it will attempt to validate a gem's
# dependencies are satisified by gems in the install directory (which will likely not
# be true)
- result = request_set.install_into(plugin_gem_path.to_s, true, ignore_dependencies: true)
+ result = request_set.install_into(plugin_gem_path.to_s, true,
+ ignore_dependencies: true,
+ prerelease: Vagrant.prerelease?,
+ wrappers: true
+ )
result = result.map(&:full_spec)
result
end
# Generate the composite resolver set totally all of vagrant (builtin + plugin set)
@@ -281,10 +325,31 @@
end
end
list.values
end
+ # Iterates each configured RubyGem source to validate that it is properly
+ # available. If source is unavailable an exception is raised.
+ def validate_configured_sources!
+ Gem.sources.each_source do |src|
+ begin
+ src.load_specs(:released)
+ rescue Gem::Exception => source_error
+ if ENV["VAGRANT_ALLOW_PLUGIN_SOURCE_ERRORS"]
+ @logger.warn("Failed to load configured plugin source: #{src}!")
+ @logger.warn("Error received attempting to load source (#{src}): #{source_error}")
+ @logger.warn("Ignoring plugin source load failure due user request via env variable")
+ else
+ @logger.error("Failed to load configured plugin source `#{src}`: #{source_error}")
+ raise Vagrant::Errors::PluginSourceError,
+ source: src.uri.to_s,
+ error_msg: source_error.message
+ end
+ end
+ end
+ end
+
# Generate the builtin resolver set
def generate_builtin_set
builtin_set = BuiltinSet.new
@logger.debug("Generating new builtin set instance.")
vagrant_internal_specs.each do |spec|
@@ -356,10 +421,41 @@
retry
end
end
end
+ # This is a custom Gem::Resolver::InstallerSet. It will prefer sources which are
+ # explicitly provided over default sources when matches are found. This is generally
+ # the entire set used for performing full resolutions on install.
+ class VagrantSet < Gem::Resolver::InstallerSet
+ attr_accessor :prefer_sources
+
+ def initialize(domain, defined_sources={})
+ @prefer_sources = defined_sources
+ super(domain)
+ end
+
+ # Allow InstallerSet to find matching specs, then filter
+ # for preferred sources
+ def find_all(req)
+ result = super
+ subset = result.find_all do |idx_spec|
+ preferred = false
+ if prefer_sources[req.name]
+ if idx_spec.source.respond_to?(:path)
+ preferred = prefer_sources[req.name].include?(idx_spec.source.path.to_s)
+ end
+ if !preferred
+ preferred = prefer_sources[req.name].include?(idx_spec.source.uri.to_s)
+ end
+ end
+ preferred
+ end
+ subset.empty? ? result : subset
+ end
+ end
+
# This is a custom Gem::Resolver::Set for use with vagrant "system" gems. It
# allows the installed set of gems to be used for providing a solution while
# enforcing strict constraints. This ensures that plugins cannot "upgrade"
# gems that are builtin to vagrant itself.
class BuiltinSet < Gem::Resolver::Set
@@ -373,11 +469,12 @@
@specs.push(spec).uniq!
end
def find_all(req)
@specs.select do |spec|
- req.match?(spec)
+ allow_prerelease = spec.name == "vagrant" && Vagrant.prerelease?
+ req.match?(spec, allow_prerelease)
end.map do |spec|
Gem::Resolver::InstalledSpecification.new(self, spec)
end
end
end
@@ -396,10 +493,11 @@
raise Gem::GemNotFoundException,
"unable to find #{gemspec} for gem #{name}"
end
spec.full_gem_path = File.expand_path(directory)
+ spec.base_dir = File.dirname(spec.base_dir)
@specs[spec.name] ||= []
@specs[spec.name] << spec
@directories[spec] = directory
@@ -421,10 +519,10 @@
##
# Loads a spec with the given +name+. +version+, +platform+ and +source+ are
# ignored.
def load_spec (name, version, platform, source)
version = Gem::Version.new(version) if !version.is_a?(Gem::Version)
- @specs.fetch(name, []).detect{|s| s.name == name && s.version = version}
+ @specs.fetch(name, []).detect{|s| s.name == name && s.version == version}
end
end
end
end