mirror of
https://github.com/ruby/ruby.git
synced 2026-10-29 17:06:21 +08:00
When bundle exec resolves an explicit relative path like ./script and falls back to Kernel.exec, Bundler currently prepends another ./ because the resolved path is not absolute. The same issue affects Windows paths such as .\script. Only prepend ./ for non-absolute paths that do not already begin with an explicit relative path marker. Recognize the platform's alternative path separator without changing the meaning of backslashes on POSIX. Add integration coverage for the original regression and focused coverage for primary and alternative path separators. Closes https://github.com/ruby/rubygems/issues/8930 Assisted-By: devx/caa7d694-19ff-42bb-9306-686cb9d69649 https://github.com/ruby/rubygems/commit/d172a35a54
129 lines
3.4 KiB
Ruby
129 lines
3.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../current_ruby"
|
|
|
|
module Bundler
|
|
class CLI::Exec
|
|
attr_reader :options, :args, :cmd
|
|
|
|
TRAPPED_SIGNALS = %w[INT].freeze
|
|
|
|
def initialize(options, args)
|
|
@options = options
|
|
@cmd = args.shift
|
|
@args = args
|
|
@args << { close_others: !options.keep_file_descriptors? } unless Bundler.current_ruby.jruby?
|
|
end
|
|
|
|
def run
|
|
validate_cmd!
|
|
SharedHelpers.set_bundle_environment
|
|
if bin_path = Bundler.which(cmd)
|
|
if !Bundler.settings[:disable_exec_load] && directly_loadable?(bin_path)
|
|
bin_path.delete_suffix!(".bat") if Gem.win_platform?
|
|
kernel_load(bin_path, *args)
|
|
else
|
|
bin_path = explicit_path(bin_path)
|
|
kernel_exec(bin_path, *args)
|
|
end
|
|
else
|
|
# exec using the given command
|
|
kernel_exec(cmd, *args)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def validate_cmd!
|
|
return unless cmd.nil?
|
|
Bundler.ui.error "bundler: exec needs a command to run"
|
|
exit 128
|
|
end
|
|
|
|
def kernel_exec(*args)
|
|
Kernel.exec(*args)
|
|
rescue Errno::EACCES, Errno::ENOEXEC
|
|
Bundler.ui.error "bundler: not executable: #{cmd}"
|
|
exit 126
|
|
rescue Errno::ENOENT
|
|
Bundler.ui.error "bundler: command not found: #{cmd}"
|
|
Bundler.ui.warn "Install missing gem executables with `bundle install`"
|
|
exit 127
|
|
end
|
|
|
|
def kernel_load(file, *args)
|
|
args.pop if args.last.is_a?(Hash)
|
|
ARGV.replace(args)
|
|
$0 = file
|
|
Process.setproctitle(process_title(file, args)) if Process.respond_to?(:setproctitle)
|
|
require_relative "../setup"
|
|
TRAPPED_SIGNALS.each {|s| trap(s, "DEFAULT") }
|
|
Kernel.load(file)
|
|
rescue SystemExit, SignalException
|
|
raise
|
|
rescue Exception # rubocop:disable Lint/RescueException
|
|
Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
|
|
Bundler::FriendlyErrors.disable!
|
|
raise
|
|
end
|
|
|
|
def process_title(file, args)
|
|
"#{file} #{args.join(" ")}".strip
|
|
end
|
|
|
|
def explicit_path(path)
|
|
if File.absolute_path?(path) || explicit_relative_path?(path)
|
|
path
|
|
else
|
|
".#{File::SEPARATOR}#{path}"
|
|
end
|
|
end
|
|
|
|
def explicit_relative_path?(path)
|
|
[File::SEPARATOR, File::ALT_SEPARATOR].compact.any? do |separator|
|
|
path.start_with?(".#{separator}", "..#{separator}")
|
|
end
|
|
end
|
|
|
|
def directly_loadable?(file)
|
|
if Gem.win_platform?
|
|
script_wrapper?(file)
|
|
else
|
|
ruby_shebang?(file)
|
|
end
|
|
end
|
|
|
|
def script_wrapper?(file)
|
|
script_file = file.delete_suffix(".bat")
|
|
return false unless File.exist?(script_file)
|
|
|
|
if File.zero?(script_file)
|
|
Bundler.ui.warn "#{script_file} is empty"
|
|
return false
|
|
end
|
|
|
|
header = File.open(file, "r") {|f| f.read(32) }
|
|
ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}"
|
|
ruby_exe = "ruby.exe" if ruby_exe.empty?
|
|
header.include?(ruby_exe)
|
|
end
|
|
|
|
def ruby_shebang?(file)
|
|
possibilities = [
|
|
"#!/usr/bin/env ruby\n",
|
|
"#!/usr/bin/env jruby\n",
|
|
"#!/usr/bin/env truffleruby\n",
|
|
"#!#{Gem.ruby}\n",
|
|
]
|
|
|
|
if File.zero?(file)
|
|
Bundler.ui.warn "#{file} is empty"
|
|
return false
|
|
end
|
|
|
|
first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) }
|
|
possibilities.any? {|shebang| first_line.start_with?(shebang) }
|
|
end
|
|
end
|
|
end
|