--- bin/vagrant +++ bin/vagrant @@ -69,6 +69,11 @@ require "bundler" begin $vagrant_bundler_runtime = Bundler.setup(:default, :plugins) +# Invalidate the cached Gemfile.lock if necessary and try again +rescue Bundler::GemNotFound + FileUtils.rm File.expand_path("~/.vagrant.d/Gemfile") if File.exists? File.expand_path("~/.vagrant.d/Gemfile") + FileUtils.rm File.expand_path("~/.vagrant.d/Gemfile.lock") if File.exists? File.expand_path("~/.vagrant.d/Gemfile.lock") + $vagrant_bundler_runtime = Bundler.setup(:default, :plugins) rescue Bundler::GemNotFound $stderr.puts "Bundler, the underlying system used to manage Vagrant plugins," $stderr.puts "is reporting that a plugin or its dependency can't be found." --- lib/vagrant/bundler.rb 2016-06-13 10:21:40.000000000 +0200 +++ lib/vagrant/bundler.rb 2016-06-23 08:19:22.466039136 +0200 @@ -65,6 +65,13 @@ @configfile = tempfile("vagrant-configfile") @configfile.close + # Ensure the path to user's Gemfile exists + gemfile = Vagrant.user_data_path.join("Gemfile") + unless File.exists? gemfile + FileUtils.mkdir_p(File.dirname(gemfile)) + File.open(gemfile, 'w') {} + end + # 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. @@ -151,7 +158,7 @@ # Clean removes any unused gems. def clean(plugins) - gemfile = build_gemfile(plugins) + gemfile = build_gemfile(plugins, false, true) lockfile = "#{gemfile.path}.lock" definition = ::Bundler::Definition.build(gemfile, lockfile, nil) root = File.dirname(gemfile.path) @@ -182,11 +189,23 @@ # Builds a valid Gemfile for use with Bundler given the list of # plugins. # + # @param [Hash|Bool] update Hash of gems to update or true for all + # @param [Bool] invalidate Invalidate Gemfile.lock # @return [Tempfile] - def build_gemfile(plugins) + def build_gemfile(plugins, update = false, invalidate = false) sources = plugins.values.map { |p| p["sources"] }.flatten.compact.uniq - f = tempfile("vagrant-gemfile") + # Determine what gems to update + if update.is_a? Hash + update_gems = update[:gems] + elsif update === true + update_gems = plugins.map{ |p| p[0] } + else + update_gems = [] + end + + gemfile = Vagrant.user_data_path.join("Gemfile") + f = File.open(gemfile, "w+") f.tap do |gemfile| sources.each do |source| next if source == "" @@ -195,6 +214,19 @@ gemfile.puts(%Q[gem "vagrant", "= #{VERSION}"]) + locked_gems = [] + + # Use Gemfile.lock to lock the gem versions + if ENV["VAGRANT_INTERNAL_BUNDLERIZED"] && File.exist?("#{gemfile.path}.lock") && !invalidate + lockfile = ::Bundler::LockfileParser.new(::Bundler.read_file("#{gemfile.path}.lock")) + lockfile.specs.each do |s| + if s.name != 'vagrant' && !(update_gems.include? s.name) + gemfile.puts(%Q[gem "#{s.name}", "#{s.version.to_s}"]) + end + end + locked_gems = lockfile.specs.map(&:name) - update_gems + end + gemfile.puts("group :plugins do") plugins.each do |name, plugin| version = plugin["gem_version"] @@ -205,10 +237,18 @@ opts[:require] = plugin["require"] end - gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) + gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) unless locked_gems.include? name end gemfile.puts("end") gemfile.close + + # Create Gemfile.lock if missing and re-generate Gemfile + if !File.exist?("#{f.path}.lock") && File.exist?(f.path) + lockfile = "#{f.path}.lock" + ENV['BUNDLE_GEMFILE'] = f.path + definition = ::Bundler::Definition.build(f.path, lockfile, false) + end + f end end @@ -219,7 +259,7 @@ # can be a hash of options. See Bundler.definition. # @return [Array] def internal_install(plugins, update, **extra) - gemfile = build_gemfile(plugins) + gemfile = build_gemfile(plugins, update) lockfile = "#{gemfile.path}.lock" definition = ::Bundler::Definition.build(gemfile, lockfile, update) root = File.dirname(gemfile.path)