diff options
author | Samuel Giddins <segiddins@segiddins.me> | 2017-07-19 18:58:09 -0500 |
---|---|---|
committer | Samuel Giddins <segiddins@segiddins.me> | 2017-07-21 09:33:00 -0500 |
commit | 01fe509b27b317dd4d640beb9140ab31331ea349 (patch) | |
tree | e3884864071b2b35e3a70693e9a25eb7c3184751 | |
parent | 365b4301baedba9b5cf4f1e47a42f65ed028c51b (diff) | |
download | bundler-01fe509b27b317dd4d640beb9140ab31331ea349.tar.gz |
Add a special bundler binstub that ensures the correct version is activated
-rw-r--r-- | lib/bundler/cli/binstubs.rb | 5 | ||||
-rw-r--r-- | lib/bundler/installer.rb | 9 | ||||
-rw-r--r-- | lib/bundler/rubygems_integration.rb | 4 | ||||
-rw-r--r-- | lib/bundler/source/metadata.rb | 2 | ||||
-rwxr-xr-x | lib/bundler/templates/Executable | 3 | ||||
-rw-r--r-- | lib/bundler/templates/Executable.bundler | 71 | ||||
-rw-r--r-- | spec/commands/binstubs_spec.rb | 117 |
7 files changed, 196 insertions, 15 deletions
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index 38863c5e77..acec5741b7 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -31,9 +31,8 @@ module Bundler ) end - if spec.name == "bundler" - Bundler.ui.warn "Sorry, Bundler can only be run via RubyGems." - elsif options[:standalone] + if options[:standalone] + next Bundler.ui.warn("Sorry, Bundler can only be run via RubyGems.") if gem_name == "bundler" installer.generate_standalone_bundler_executable_stubs(spec) else installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true) diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 5996d185da..9b648b0dd0 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -114,14 +114,17 @@ module Bundler # double-assignment to avoid warnings about variables that will be used by ERB bin_path = bin_path = Bundler.bin_path - template = template = File.read(File.expand_path("../templates/Executable", __FILE__)) relative_gemfile_path = relative_gemfile_path = Bundler.default_gemfile.relative_path_from(bin_path) ruby_command = ruby_command = Thor::Util.ruby_command + template_path = File.expand_path("../templates/Executable", __FILE__) + if spec.name == "bundler" + template_path += ".bundler" + spec.executables = %(bundle) + end + template = File.read(template_path) exists = [] spec.executables.each do |executable| - next if executable == "bundle" - binstub_path = "#{bin_path}/#{executable}" if File.exist?(binstub_path) && !options[:force] exists << executable diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index 38374ae0b6..6654c292e6 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -450,9 +450,9 @@ module Bundler raise Gem::Exception, "no default executable for #{spec.full_name}" unless exec_name ||= spec.default_executable - unless spec.name == name + unless spec.name == exec_name Bundler::SharedHelpers.major_deprecation \ - "Bundler is using a binstub that was created for a different gem.\n" \ + "Bundler is using a binstub that was created for a different gem (#{spec.name}).\n" \ "You should run `bundle binstub #{gem_name}` " \ "to work around a system/bundle conflict." end diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb index 1d62a1f76a..93909002c7 100644 --- a/lib/bundler/source/metadata.rb +++ b/lib/bundler/source/metadata.rb @@ -14,6 +14,8 @@ module Bundler s.platform = Gem::Platform::RUBY s.source = self s.authors = ["bundler team"] + s.bindir = "exe" + s.executables = %w[bundle] # can't point to the actual gemspec or else the require paths will be wrong s.loaded_from = File.expand_path("..", __FILE__) end diff --git a/lib/bundler/templates/Executable b/lib/bundler/templates/Executable index 86e73fbbc3..9289debc26 100755 --- a/lib/bundler/templates/Executable +++ b/lib/bundler/templates/Executable @@ -8,6 +8,9 @@ # this file is here to facilitate running it. # +bundle_binstub = File.expand_path("../bundle", __FILE__) +load(bundle_binstub) if File.file?(bundle_binstub) + require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../<%= relative_gemfile_path %>", Pathname.new(__FILE__).realpath) diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler new file mode 100644 index 0000000000..351eca7427 --- /dev/null +++ b/lib/bundler/templates/Executable.bundler @@ -0,0 +1,71 @@ +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application '<%= executable %>' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def env_var_version + ENV['BUNDLER_VERSION'] + end + + def cli_arg_version + update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") } + update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a" + end + + def gemfile + gemfile = ENV['BUNDLE_GEMFILE'] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../<%= relative_gemfile_path %>", __FILE__) + end + + def lockfile + File.expand_path case File.basename(gemfile) + when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= begin + env_var_version || cli_arg_version || + lockfile_version || "#{Gem::Requirement.default}.a" + end + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler(bundler_version) + end + + def activate_bundler(bundler_version) + gem "bundler", bundler_version + rescue StandardError, LoadError => e + warn "Activating bundler (#{bundler_version}) failed:\n#{e.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" + exit 42 + end +end + +m.load_bundler! + +if File.expand_path($0) == File.expand_path(__FILE__) + load Gem.bin_path("<%= spec.name %>", "<%= executable %>") +end diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb index 89fc3866f7..eb14db4f6f 100644 --- a/spec/commands/binstubs_spec.rb +++ b/spec/commands/binstubs_spec.rb @@ -50,15 +50,118 @@ RSpec.describe "bundle binstubs <gem>" do expect(out).to include("`bundle binstubs` needs at least one gem to run.") end - it "does not bundle the bundler binary" do - install_gemfile <<-G - source "file://#{gem_repo1}" - G + context "the bundle binstub" do + before do + if system_bundler_version == :bundler + system_gems :bundler + elsif system_bundler_version + build_repo4 do + build_gem "bundler", system_bundler_version do |s| + s.executables = "bundle" + s.bindir = "exe" + s.write "exe/bundle", "puts %(system bundler #{system_bundler_version}\\n\#{ARGV.inspect})" + end + end + system_gems "bundler-#{system_bundler_version}", :gem_repo => gem_repo4 + end + build_repo2 do + build_gem "prints_loaded_gems", "1.0" do |s| + s.executables = "print_loaded_gems" + s.write "bin/print_loaded_gems", 'puts "#{Gem.loaded_specs.values.reject(&:default_gem?).map(&:full_name).sort.inspect}"' + end + end + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rack" + gem "prints_loaded_gems" + G + bundle! "binstubs bundler rack prints_loaded_gems" + end + + let(:system_bundler_version) { Bundler::VERSION } + + it "runs bundler" do + sys_exec! "#{bundled_app("bin/bundle")} install" + expect(out).to eq %(system bundler #{system_bundler_version}\n["install"]) + end + + context "when BUNDLER_VERSION is set" do + it "runs the correct version of bundler" do + sys_exec "BUNDLER_VERSION=999.999.999 #{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end + + context "when a lockfile exists with a locked bundler version" do + it "runs the correct version of bundler when the version is newer" do + lockfile lockfile.gsub(system_bundler_version, "999.999.999") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + + it "runs the correct version of bundler when the version is older" do + lockfile lockfile.gsub(system_bundler_version, "1.0") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (1.0) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '1.0'`") + end + + it "runs the correct version of bundler when the version is a pre-release" do + lockfile lockfile.gsub(system_bundler_version, "1.12.0.a") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (1.12.0.a) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '1.12.0.a'`") + end + end + + context "when update --bundler is called" do + before { lockfile.gsub(system_bundler_version, "1.1.1") } - bundle "binstubs bundler" + it "calls through to the latest bundler version" do + sys_exec! "#{bundled_app("bin/bundle")} update --bundler" + expect(last_command.stdout).to eq %(system bundler #{system_bundler_version}\n["update", "--bundler"]) + end + + it "calls through to the explicit bundler version" do + sys_exec "#{bundled_app("bin/bundle")} update --bundler=999.999.999" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end - expect(bundled_app("bin/bundle")).not_to exist - expect(out).to include("Sorry, Bundler can only be run via RubyGems.") + context "without a lockfile" do + it "falls back to the latest installed bundler" do + FileUtils.rm bundled_app("Gemfile.lock") + sys_exec! bundled_app("bin/bundle").to_s + expect(out).to eq "system bundler #{system_bundler_version}\n[]" + end + end + + context "using another binstub" do + let(:system_bundler_version) { :bundler } + it "loads all gems" do + sys_exec! bundled_app("bin/print_loaded_gems").to_s + expect(out).to eq %(["bundler-#{Bundler::VERSION}", "prints_loaded_gems-1.0", "rack-1.2"]) + end + + context "when requesting a different bundler version" do + before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") } + + it "attempts to load that version" do + sys_exec bundled_app("bin/rackup").to_s + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end + end end it "installs binstubs from git gems" do |