Skip to content

Commit

Permalink
Fix wrong version of gem activation for bin stub.
Browse files Browse the repository at this point in the history
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 13afe08
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 deletions.
24 changes: 23 additions & 1 deletion lib/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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

Expand Down
5 changes: 3 additions & 2 deletions lib/rubygems/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions lib/rubygems/installer_test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 50 additions & 1 deletion test/rubygems/test_gem_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 13afe08

Please sign in to comment.