Skip to content
This repository was archived by the owner on Apr 14, 2021. It is now read-only.

Add a special bundler binstub that ensures the correct version is activated #5878

Merged
merged 5 commits into from
Jul 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions lib/bundler/cli/binstubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ def run
)
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)
Expand Down
9 changes: 6 additions & 3 deletions lib/bundler/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,17 @@ def generate_bundler_executable_stubs(spec, options = {})

# 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
Expand Down
4 changes: 2 additions & 2 deletions lib/bundler/rubygems_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -450,9 +450,9 @@ def replace_bin_path(specs, specs_by_name)

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
Expand Down
2 changes: 2 additions & 0 deletions lib/bundler/source/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def specs
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
Expand Down
3 changes: 3 additions & 0 deletions lib/bundler/templates/Executable
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
93 changes: 93 additions & 0 deletions lib/bundler/templates/Executable.bundler
Original file line number Diff line number Diff line change
@@ -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
120 changes: 113 additions & 7 deletions spec/commands/binstubs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,121 @@
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
Expand Down
2 changes: 1 addition & 1 deletion spec/install/binstubs_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def Gem.bindir; "/usr/bin"; end
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
Expand Down
6 changes: 3 additions & 3 deletions spec/runtime/executable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
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
Expand All @@ -88,9 +88,9 @@
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
Expand Down