summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Giddins <segiddins@segiddins.me>2017-07-19 18:58:09 -0500
committerSamuel Giddins <segiddins@segiddins.me>2017-07-21 09:33:00 -0500
commit01fe509b27b317dd4d640beb9140ab31331ea349 (patch)
treee3884864071b2b35e3a70693e9a25eb7c3184751
parent365b4301baedba9b5cf4f1e47a42f65ed028c51b (diff)
downloadbundler-01fe509b27b317dd4d640beb9140ab31331ea349.tar.gz
Add a special bundler binstub that ensures the correct version is activated
-rw-r--r--lib/bundler/cli/binstubs.rb5
-rw-r--r--lib/bundler/installer.rb9
-rw-r--r--lib/bundler/rubygems_integration.rb4
-rw-r--r--lib/bundler/source/metadata.rb2
-rwxr-xr-xlib/bundler/templates/Executable3
-rw-r--r--lib/bundler/templates/Executable.bundler71
-rw-r--r--spec/commands/binstubs_spec.rb117
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