1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
|
# frozen_string_literal: true
require_relative "../command"
require_relative "../dependency_installer"
require_relative "../gem_runner"
require_relative "../package"
require_relative "../version_option"
class Gem::Commands::ExecCommand < Gem::Command
include Gem::VersionOption
def initialize
super "exec", "Run a command from a gem", {
version: Gem::Requirement.default,
}
add_version_option
add_prerelease_option "to be installed"
add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options|
options[:gem_name] = value
end
add_option(:"Install/Update", "--conservative",
"Prefer the most recent installed version, ",
"rather than the latest version overall") do |_value, options|
options[:conservative] = true
end
end
def arguments # :nodoc:
"COMMAND the executable command to run"
end
def defaults_str # :nodoc:
"--version '#{Gem::Requirement.default}'"
end
def description # :nodoc:
<<-EOF
The exec command handles installing (if necessary) and running an executable
from a gem, regardless of whether that gem is currently installed.
The exec command can be thought of as a shortcut to running `gem install` and
then the executable from the installed gem.
For example, `gem exec rails new .` will run `rails new .` in the current
directory, without having to manually run `gem install rails`.
Additionally, the exec command ensures the most recent version of the gem
is used (unless run with `--conservative`), and that the gem is not installed
to the same gem path as user-installed gems.
EOF
end
def usage # :nodoc:
"#{program_name} [options --] COMMAND [args]"
end
def execute
check_executable
print_command
if options[:gem_name] == "gem" && options[:executable] == "gem"
set_gem_exec_install_paths
Gem::GemRunner.new.run options[:args]
return
elsif options[:conservative]
install_if_needed
else
install
activate!
end
load!
end
private
def handle_options(args)
args = add_extra_args(args)
check_deprecated_options(args)
@options = Marshal.load Marshal.dump @defaults # deep copy
parser.order!(args) do |v|
# put the non-option back at the front of the list of arguments
args.unshift(v)
# stop parsing once we hit the first non-option,
# so you can call `gem exec rails --version` and it prints the rails
# version rather than rubygem's
break
end
@options[:args] = args
options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift)
options[:gem_name] ||= options[:executable]
if gem_version
if options[:version].none?
options[:version] = Gem::Requirement.new(gem_version)
else
options[:version].concat [gem_version]
end
end
if options[:prerelease] && !options[:version].prerelease?
if options[:version].none?
options[:version] = Gem::Requirement.default_prerelease
else
options[:version].concat [Gem::Requirement.default_prerelease]
end
end
end
def check_executable
if options[:executable].nil?
raise Gem::CommandLineError,
"Please specify an executable to run (e.g. #{program_name} COMMAND)"
end
end
def print_command
verbose "running #{program_name} with:\n"
opts = options.reject {|_, v| v.nil? || Array(v).empty? }
max_length = opts.map {|k, _| k.size }.max
opts.each do |k, v|
next if v.nil?
verbose "\t#{k.to_s.rjust(max_length)}: #{v}"
end
verbose ""
end
def install_if_needed
activate!
rescue Gem::MissingSpecError
verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote"
install
activate!
end
def set_gem_exec_install_paths
home = Gem.dir
ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR)
ENV["GEM_HOME"] = home
Gem.clear_paths
end
def install
set_gem_exec_install_paths
gem_name = options[:gem_name]
gem_version = options[:version]
install_options = options.merge(
minimal_deps: false,
wrappers: true
)
suppress_always_install do
dep_installer = Gem::DependencyInstaller.new install_options
request_set = dep_installer.resolve_dependencies gem_name, gem_version
verbose "Gems to install:"
request_set.sorted_requests.each do |activation_request|
verbose "\t#{activation_request.full_name}"
end
request_set.install install_options
end
Gem::Specification.reset
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
terminate_interaction 1
rescue Gem::GemNotFoundException => e
show_lookup_failure e.name, e.version, e.errors, false
terminate_interaction 2
rescue Gem::UnsatisfiableDependencyError => e
show_lookup_failure e.name, e.version, e.errors, false,
"'#{gem_name}' (#{gem_version})"
terminate_interaction 2
end
def activate!
gem(options[:gem_name], options[:version])
Gem.finish_resolve
verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})"
end
def load!
argv = ARGV.clone
ARGV.replace options[:args]
exe = executable = options[:executable]
contains_executable = Gem.loaded_specs.values.select do |spec|
spec.executables.include?(executable)
end
if contains_executable.any? {|s| s.name == executable }
contains_executable.select! {|s| s.name == executable }
end
if contains_executable.empty?
if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable)
contains_executable << spec
else
alert_error "Failed to load executable `#{executable}`," \
" are you sure the gem `#{options[:gem_name]}` contains it?"
terminate_interaction 1
end
end
if contains_executable.size > 1
alert_error "Ambiguous which gem `#{executable}` should come from: " \
"the options are #{contains_executable.map(&:name)}, " \
"specify one via `-g`"
terminate_interaction 1
end
load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a")
ensure
ARGV.replace argv
end
def suppress_always_install
name = :always_install
cls = ::Gem::Resolver::InstallerSet
method = cls.instance_method(name)
cls.remove_method(name)
cls.define_method(name) { [] }
begin
yield
ensure
cls.remove_method(name)
cls.define_method(name, method)
end
end
end
|