summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Bundler Bot <bot@bundler.io>2017-07-24 21:08:04 +0000
committerThe Bundler Bot <bot@bundler.io>2017-07-24 21:08:04 +0000
commit631231dc1fe8d8ca557a386afd10cb2fda83b959 (patch)
treedc3f91a96aeb0a368616c8d4d38c40400145104e
parenta0b16613040382b5db2c3c14b4fdf04f9091e641 (diff)
parentad7fbf26c995440f7bf3b295cbd68be3855f394a (diff)
downloadbundler-631231dc1fe8d8ca557a386afd10cb2fda83b959.tar.gz
Auto merge of #5878 - bundler:seg-bundler-binstubs, r=indirect
Add a special bundler binstub that ensures the correct version is activated ### What was the end-user problem that led to this PR? Closes https://github.com/bundler/bundler/issues/5876. > Once Bundler 2 ships, each application will depend on a 1.x or 2.x version of Bundler. We want to make sure that the user always has an _easy_ and _convenient_ way to activate the version of Bundler their app is depending upon. ### What was your diagnosis of the problem? > One way to ensure that the application will always get the version of Bundler that it needs is with an application-and-bundler-version specific binstub. Let's make it so that bundle binstubs bundler will generate a binstub for the correct version of Bundler, and simply running bin/bundle will always provide the correct version of Bundler. ### What is your fix for the problem, implemented in this PR? My fix implements a `bundle` binstub when `bundle binstubs bundler` is run ### Why did you choose this fix out of the possible options? I chose this fix because it allows us to dynamically select the correct Bundler version (with appropriate overrides, such as `$BUNDLER_VERSION` and `bundle update --bundler`), both when invoking `bin/bundle`, and also when invoking other bundler-generated binstubs.
-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.bundler93
-rw-r--r--spec/commands/binstubs_spec.rb120
-rw-r--r--spec/install/binstubs_spec.rb2
-rw-r--r--spec/runtime/executable_spec.rb6
9 files changed, 225 insertions, 19 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..085955c7a9
--- /dev/null
+++ b/lib/bundler/templates/Executable.bundler
@@ -0,0 +1,93 @@
+#!/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 invoked_as_script?
+ File.expand_path($0) == File.expand_path(__FILE__)
+ end
+
+ def env_var_version
+ ENV["BUNDLER_VERSION"]
+ end
+
+ def cli_arg_version
+ return unless invoked_as_script? # don't want to hijack other binstubs
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
+ return unless update = ARGV.find {|a| a.start_with?("--bundler") } # must have a --bundler arg
+ return unless 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
+ lockfile =
+ case File.basename(gemfile)
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
+ else "#{gemfile}.lock"
+ end
+ File.expand_path(lockfile)
+ 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_error = activation_error_handling do
+ gem "bundler", bundler_version
+ end
+ return if gem_error.nil?
+ require_error = activation_error_handling do
+ require "bundler/version"
+ end
+ return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
+ warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
+ exit 42
+ end
+
+ def activation_error_handling
+ yield
+ nil
+ rescue StandardError, LoadError => e
+ e
+ end
+end
+
+m.load_bundler!
+
+if m.invoked_as_script?
+ load Gem.bin_path("<%= spec.name %>", "<%= executable %>")
+end
diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb
index 89fc3866f7..fa44928625 100644
--- a/spec/commands/binstubs_spec.rb
+++ b/spec/commands/binstubs_spec.rb
@@ -50,15 +50,121 @@ 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", <<-R
+ specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) }
+ puts specs.map(&:full_name).sort.inspect
+ R
+ 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
diff --git a/spec/install/binstubs_spec.rb b/spec/install/binstubs_spec.rb
index e5acc84e6a..9f361035e0 100644
--- a/spec/install/binstubs_spec.rb
+++ b/spec/install/binstubs_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe "bundle install" do
it "prints a deprecation notice" do
bundle "config major_deprecations true"
gembin("rackup")
- expect(out).to include("Bundler is using a binstub that was created for a different gem.")
+ expect(out).to include("Bundler is using a binstub that was created for a different gem (rack).")
end
it "loads the correct spec's executable" do
diff --git a/spec/runtime/executable_spec.rb b/spec/runtime/executable_spec.rb
index 545fc97330..388ee049d0 100644
--- a/spec/runtime/executable_spec.rb
+++ b/spec/runtime/executable_spec.rb
@@ -78,7 +78,7 @@ RSpec.describe "Running bin/* commands" do
expect(out).to eq("1.0")
end
- it "don't bundle da bundla" do
+ it "creates a bundle binstub" do
build_gem "bundler", Bundler::VERSION, :to_system => true do |s|
s.executables = "bundle"
end
@@ -88,9 +88,9 @@ RSpec.describe "Running bin/* commands" do
gem "bundler"
G
- bundle "binstubs bundler"
+ bundle! "binstubs bundler"
- expect(bundled_app("bin/bundle")).not_to exist
+ expect(bundled_app("bin/bundle")).to exist
end
it "does not generate bin stubs if the option was not specified" do