Permalink
Browse files

Fix wrong version of gem activation for bin stub.

Gem FOO version 1 is installed with a bin file.  The bin file requires
"FOO.rb"

Gem FOO version 2 is installed, no longer has the bin file, but still
contains "FOO.rb"

If the user has FOO gem version 1 and 2 installed, the bin file for
version 1 will erroneously require "FOO.rb" from version 2.

This commit changes the bin file to find the executable and activate the
gem spec that contains that executable.  To do this, I introduced a new
function called `activate_bin_path` which should only be used in bin
stubs.  This function is exactly the same as `bin_path`, but as a side
effect activates the gemspec that contains the binfile.

`bin_path` is public and documented, so I didn't want to change it to
activate gem specifications.
  • Loading branch information...
tenderlove committed Mar 2, 2016
1 parent 6395126 commit 13afe08ec7dfcbbc09e7d76aedb476c0694c05fd
Showing with 78 additions and 4 deletions.
  1. +23 −1 lib/rubygems.rb
  2. +3 −2 lib/rubygems/installer.rb
  3. +2 −0 lib/rubygems/installer_test_case.rb
  4. +50 −1 test/rubygems/test_gem_installer.rb
View
@@ -243,11 +243,15 @@ def self.bin_path(name, exec_name = nil, *requirements)
requirements = Gem::Requirement.default if
requirements.empty?
find_spec_for_exe(name, exec_name, requirements).bin_file exec_name
end
def self.find_spec_for_exe name, exec_name, requirements
dep = Gem::Dependency.new name, requirements
loaded = Gem.loaded_specs[name]
return loaded.bin_file exec_name if loaded && dep.matches_spec?(loaded)
return loaded if loaded && dep.matches_spec?(loaded)
specs = dep.matching_specs(true)
@@ -263,6 +267,24 @@ def self.bin_path(name, exec_name = nil, *requirements)
raise Gem::GemNotFoundException, msg
end
spec
end
private_class_method :find_spec_for_exe
##
# Find the full path to the executable for gem +name+. If the +exec_name+
# is not given, the gem's default_executable is chosen, otherwise the
# specified executable's path is returned. +requirements+ allows
# you to specify specific gem versions.
#
# A side effect of this method is that it will activate the gem that
# contains the executable.
#
# This method should *only* be used in bin stub files.
def self.activate_bin_path name, exec_name, requirement # :nodoc:
spec = find_spec_for_exe name, exec_name, [requirement]
Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate }
spec.bin_file exec_name
end
@@ -216,7 +216,8 @@ def check_executable_overwrite filename # :nodoc:
existing = io.read.slice(%r{
^(
gem \s |
load \s Gem\.bin_path\(
load \s Gem\.bin_path\( |
load \s Gem\.activate_bin_path\(
)
(['"])(.*?)(\2),
}x, 3)
@@ -719,7 +720,7 @@ def app_script_text(bin_file_name)
end
end
load Gem.bin_path('#{spec.name}', '#{bin_file_name}', version)
load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
TEXT
end
@@ -170,6 +170,8 @@ def util_setup_gem(ui = @ui) # HACK fix use_ui to make this automatic
EOF
end
yield @spec if block_given?
use_ui ui do
FileUtils.rm_f @gem
@@ -48,7 +48,7 @@ def test_app_script_text
end
end
load Gem.bin_path('a', 'executable', version)
load Gem.activate_bin_path('a', 'executable', version)
EOF
wrapper = @installer.app_script_text 'executable'
@@ -781,6 +781,55 @@ def test_install_creates_working_binstub
assert_match(/ran executable/, e.message)
end
def test_conflicting_binstubs
Dir.mkdir util_inst_bindir
util_clear_gems
# build old version that has a bin file
util_setup_gem do |spec|
File.open File.join('bin', 'executable'), 'w' do |f|
f.puts "require 'code'"
end
File.open File.join('lib', 'code.rb'), 'w' do |f|
f.puts 'raise "I have an executable"'
end
end
@installer.wrappers = true
build_rake_in do
use_ui @ui do
@newspec = @installer.install
end
end
old_bin_file = File.join @installer.bin_dir, 'executable'
# build new version that doesn't have a bin file
util_setup_gem do |spec|
FileUtils.rm File.join('bin', 'executable')
spec.files.delete File.join('bin', 'executable')
spec.executables.delete 'executable'
spec.version = @spec.version.bump
File.open File.join('lib', 'code.rb'), 'w' do |f|
f.puts 'raise "I do not have an executable"'
end
end
build_rake_in do
use_ui @ui do
@newspec = @installer.install
end
end
e = assert_raises RuntimeError do
instance_eval File.read(old_bin_file)
end
# We expect the bin stub to activate the version that actually contains
# the binstub.
assert_match('I have an executable', e.message)
end
def test_install_creates_binstub_that_understand_version
Dir.mkdir util_inst_bindir
util_setup_gem

0 comments on commit 13afe08

Please sign in to comment.