зеркало из https://github.com/github/ruby.git
1033 строки
28 KiB
Ruby
1033 строки
28 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#--
|
|
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
|
|
# All rights reserved.
|
|
# See LICENSE.txt for permissions.
|
|
#++
|
|
|
|
require_relative "installer_uninstaller_utils"
|
|
require_relative "exceptions"
|
|
require_relative "deprecate"
|
|
require_relative "package"
|
|
require_relative "ext"
|
|
require_relative "user_interaction"
|
|
|
|
##
|
|
# The installer installs the files contained in the .gem into the Gem.home.
|
|
#
|
|
# Gem::Installer does the work of putting files in all the right places on the
|
|
# filesystem including unpacking the gem into its gem dir, installing the
|
|
# gemspec in the specifications dir, storing the cached gem in the cache dir,
|
|
# and installing either wrappers or symlinks for executables.
|
|
#
|
|
# The installer invokes pre and post install hooks. Hooks can be added either
|
|
# through a rubygems_plugin.rb file in an installed gem or via a
|
|
# rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb
|
|
# file. See Gem.pre_install and Gem.post_install for details.
|
|
|
|
class Gem::Installer
|
|
extend Gem::Deprecate
|
|
|
|
##
|
|
# Paths where env(1) might live. Some systems are broken and have it in
|
|
# /bin
|
|
|
|
ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
|
|
|
|
##
|
|
# Deprecated in favor of Gem::Ext::BuildError
|
|
|
|
ExtensionBuildError = Gem::Ext::BuildError # :nodoc:
|
|
|
|
include Gem::UserInteraction
|
|
|
|
include Gem::InstallerUninstallerUtils
|
|
|
|
##
|
|
# The directory a gem's executables will be installed into
|
|
|
|
attr_reader :bin_dir
|
|
|
|
attr_reader :build_root # :nodoc:
|
|
|
|
##
|
|
# The gem repository the gem will be installed into
|
|
|
|
attr_reader :gem_home
|
|
|
|
##
|
|
# The options passed when the Gem::Installer was instantiated.
|
|
|
|
attr_reader :options
|
|
|
|
##
|
|
# The gem package instance.
|
|
|
|
attr_reader :package
|
|
|
|
class << self
|
|
#
|
|
# Changes in rubygems to lazily loading `rubygems/command` (in order to
|
|
# lazily load `optparse` as a side effect) affect bundler's custom installer
|
|
# which uses `Gem::Command` without requiring it (up until bundler 2.2.29).
|
|
# This hook is to compensate for that missing require.
|
|
#
|
|
# TODO: Remove when rubygems no longer supports running on bundler older
|
|
# than 2.2.29.
|
|
|
|
def inherited(klass)
|
|
if klass.name == "Bundler::RubyGemsGemInstaller"
|
|
require "rubygems/command"
|
|
end
|
|
|
|
super(klass)
|
|
end
|
|
|
|
##
|
|
# Overrides the executable format.
|
|
#
|
|
# This is a sprintf format with a "%s" which will be replaced with the
|
|
# executable name. It is based off the ruby executable name's difference
|
|
# from "ruby".
|
|
|
|
attr_writer :exec_format
|
|
|
|
# Defaults to use Ruby's program prefix and suffix.
|
|
def exec_format
|
|
@exec_format ||= Gem.default_exec_format
|
|
end
|
|
end
|
|
|
|
##
|
|
# Construct an installer object for the gem file located at +path+
|
|
|
|
def self.at(path, options = {})
|
|
security_policy = options[:security_policy]
|
|
package = Gem::Package.new path, security_policy
|
|
new package, options
|
|
end
|
|
|
|
class FakePackage
|
|
attr_accessor :spec
|
|
|
|
attr_accessor :dir_mode
|
|
attr_accessor :prog_mode
|
|
attr_accessor :data_mode
|
|
|
|
def initialize(spec)
|
|
@spec = spec
|
|
end
|
|
|
|
def extract_files(destination_dir, pattern = "*")
|
|
FileUtils.mkdir_p destination_dir
|
|
|
|
spec.files.each do |file|
|
|
file = File.join destination_dir, file
|
|
next if File.exist? file
|
|
FileUtils.mkdir_p File.dirname(file)
|
|
File.open file, "w" do |fp|
|
|
fp.puts "# #{file}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def copy_to(path)
|
|
end
|
|
end
|
|
|
|
##
|
|
# Construct an installer object for an ephemeral gem (one where we don't
|
|
# actually have a .gem file, just a spec)
|
|
|
|
def self.for_spec(spec, options = {})
|
|
# FIXME: we should have a real Package class for this
|
|
new FakePackage.new(spec), options
|
|
end
|
|
|
|
##
|
|
# Constructs an Installer instance that will install the gem at +package+ which
|
|
# can either be a path or an instance of Gem::Package. +options+ is a Hash
|
|
# with the following keys:
|
|
#
|
|
# :bin_dir:: Where to put a bin wrapper if needed.
|
|
# :development:: Whether or not development dependencies should be installed.
|
|
# :env_shebang:: Use /usr/bin/env in bin wrappers.
|
|
# :force:: Overrides all version checks and security policy checks, except
|
|
# for a signed-gems-only policy.
|
|
# :format_executable:: Format the executable the same as the Ruby executable.
|
|
# If your Ruby is ruby18, foo_exec will be installed as
|
|
# foo_exec18.
|
|
# :ignore_dependencies:: Don't raise if a dependency is missing.
|
|
# :install_dir:: The directory to install the gem into.
|
|
# :security_policy:: Use the specified security policy. See Gem::Security
|
|
# :user_install:: Indicate that the gem should be unpacked into the users
|
|
# personal gem directory.
|
|
# :only_install_dir:: Only validate dependencies against what is in the
|
|
# install_dir
|
|
# :wrappers:: Install wrappers if true, symlinks if false.
|
|
# :build_args:: An Array of arguments to pass to the extension builder
|
|
# process. If not set, then Gem::Command.build_args is used
|
|
# :post_install_message:: Print gem post install message if true
|
|
|
|
def initialize(package, options={})
|
|
require "fileutils"
|
|
|
|
@options = options
|
|
@package = package
|
|
|
|
process_options
|
|
|
|
@package.dir_mode = options[:dir_mode]
|
|
@package.prog_mode = options[:prog_mode]
|
|
@package.data_mode = options[:data_mode]
|
|
end
|
|
|
|
##
|
|
# Checks if +filename+ exists in +@bin_dir+.
|
|
#
|
|
# If +@force+ is set +filename+ is overwritten.
|
|
#
|
|
# If +filename+ exists and it is a RubyGems wrapper for a different gem, then
|
|
# the user is consulted.
|
|
#
|
|
# If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the
|
|
# user is consulted.
|
|
#
|
|
# Otherwise +filename+ is overwritten.
|
|
|
|
def check_executable_overwrite(filename) # :nodoc:
|
|
return if @force
|
|
|
|
generated_bin = File.join @bin_dir, formatted_program_filename(filename)
|
|
|
|
return unless File.exist? generated_bin
|
|
|
|
ruby_executable = false
|
|
existing = nil
|
|
|
|
File.open generated_bin, "rb" do |io|
|
|
line = io.gets
|
|
shebang = /^#!.*ruby/o
|
|
|
|
# TruffleRuby uses a bash prelude in default launchers
|
|
if load_relative_enabled? || RUBY_ENGINE == "truffleruby"
|
|
until line.nil? || shebang.match?(line) do
|
|
line = io.gets
|
|
end
|
|
end
|
|
|
|
next unless line&.match?(shebang)
|
|
|
|
io.gets # blankline
|
|
|
|
# TODO: detect a specially formatted comment instead of trying
|
|
# to find a string inside Ruby code.
|
|
next unless io.gets&.include?("This file was generated by RubyGems")
|
|
|
|
ruby_executable = true
|
|
existing = io.read.slice(/
|
|
^\s*(
|
|
gem \s |
|
|
load \s Gem\.bin_path\( |
|
|
load \s Gem\.activate_bin_path\(
|
|
)
|
|
(['"])(.*?)(\2),
|
|
/x, 3)
|
|
end
|
|
|
|
return if spec.name == existing
|
|
|
|
# somebody has written to RubyGems' directory, overwrite, too bad
|
|
return if Gem.default_bindir != @bin_dir && !ruby_executable
|
|
|
|
question = "#{spec.name}'s executable \"#{filename}\" conflicts with ".dup
|
|
|
|
if ruby_executable
|
|
question << (existing || "an unknown executable")
|
|
|
|
return if ask_yes_no "#{question}\nOverwrite the executable?", false
|
|
|
|
conflict = "installed executable from #{existing}"
|
|
else
|
|
question << generated_bin
|
|
|
|
return if ask_yes_no "#{question}\nOverwrite the executable?", false
|
|
|
|
conflict = generated_bin
|
|
end
|
|
|
|
raise Gem::InstallError,
|
|
"\"#{filename}\" from #{spec.name} conflicts with #{conflict}"
|
|
end
|
|
|
|
##
|
|
# Lazy accessor for the spec's gem directory.
|
|
|
|
def gem_dir
|
|
@gem_dir ||= File.join(gem_home, "gems", spec.full_name)
|
|
end
|
|
|
|
##
|
|
# Lazy accessor for the installer's spec.
|
|
|
|
def spec
|
|
@package.spec
|
|
end
|
|
|
|
##
|
|
# Installs the gem and returns a loaded Gem::Specification for the installed
|
|
# gem.
|
|
#
|
|
# The gem will be installed with the following structure:
|
|
#
|
|
# @gem_home/
|
|
# cache/<gem-version>.gem #=> a cached copy of the installed gem
|
|
# gems/<gem-version>/... #=> extracted files
|
|
# specifications/<gem-version>.gemspec #=> the Gem::Specification
|
|
|
|
def install
|
|
pre_install_checks
|
|
|
|
run_pre_install_hooks
|
|
|
|
# Set loaded_from to ensure extension_dir is correct
|
|
if @options[:install_as_default]
|
|
spec.loaded_from = default_spec_file
|
|
else
|
|
spec.loaded_from = spec_file
|
|
end
|
|
|
|
# Completely remove any previous gem files
|
|
FileUtils.rm_rf gem_dir
|
|
FileUtils.rm_rf spec.extension_dir
|
|
|
|
dir_mode = options[:dir_mode]
|
|
FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755
|
|
|
|
if @options[:install_as_default]
|
|
extract_bin
|
|
write_default_spec
|
|
else
|
|
extract_files
|
|
|
|
build_extensions
|
|
write_build_info_file
|
|
run_post_build_hooks
|
|
end
|
|
|
|
generate_bin
|
|
generate_plugins
|
|
|
|
unless @options[:install_as_default]
|
|
write_spec
|
|
write_cache_file
|
|
end
|
|
|
|
File.chmod(dir_mode, gem_dir) if dir_mode
|
|
|
|
say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?
|
|
|
|
Gem::Specification.add_spec(spec) unless @install_dir
|
|
|
|
load_plugin
|
|
|
|
run_post_install_hooks
|
|
|
|
spec
|
|
rescue Errno::EACCES => e
|
|
# Permission denied - /path/to/foo
|
|
raise Gem::FilePermissionError, e.message.split(" - ").last
|
|
end
|
|
|
|
def run_pre_install_hooks # :nodoc:
|
|
Gem.pre_install_hooks.each do |hook|
|
|
next unless hook.call(self) == false
|
|
location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
|
|
|
|
message = "pre-install hook#{location} failed for #{spec.full_name}"
|
|
raise Gem::InstallError, message
|
|
end
|
|
end
|
|
|
|
def run_post_build_hooks # :nodoc:
|
|
Gem.post_build_hooks.each do |hook|
|
|
next unless hook.call(self) == false
|
|
FileUtils.rm_rf gem_dir
|
|
|
|
location = " at #{$1}" if hook.inspect =~ /[ @](.*:\d+)/
|
|
|
|
message = "post-build hook#{location} failed for #{spec.full_name}"
|
|
raise Gem::InstallError, message
|
|
end
|
|
end
|
|
|
|
def run_post_install_hooks # :nodoc:
|
|
Gem.post_install_hooks.each do |hook|
|
|
hook.call self
|
|
end
|
|
end
|
|
|
|
##
|
|
#
|
|
# Return an Array of Specifications contained within the gem_home
|
|
# we'll be installing into.
|
|
|
|
def installed_specs
|
|
@installed_specs ||= begin
|
|
specs = []
|
|
|
|
Gem::Util.glob_files_in_dir("*.gemspec", File.join(gem_home, "specifications")).each do |path|
|
|
spec = Gem::Specification.load path
|
|
specs << spec if spec
|
|
end
|
|
|
|
specs
|
|
end
|
|
end
|
|
|
|
##
|
|
# Ensure that the dependency is satisfied by the current installation of
|
|
# gem. If it is not an exception is raised.
|
|
#
|
|
# spec :: Gem::Specification
|
|
# dependency :: Gem::Dependency
|
|
|
|
def ensure_dependency(spec, dependency)
|
|
unless installation_satisfies_dependency? dependency
|
|
raise Gem::InstallError, "#{spec.name} requires #{dependency}"
|
|
end
|
|
true
|
|
end
|
|
|
|
##
|
|
# True if the gems in the system satisfy +dependency+.
|
|
|
|
def installation_satisfies_dependency?(dependency)
|
|
return true if @options[:development] && dependency.type == :development
|
|
return true if installed_specs.detect {|s| dependency.matches_spec? s }
|
|
return false if @only_install_dir
|
|
!dependency.matching_specs.empty?
|
|
end
|
|
|
|
##
|
|
# Unpacks the gem into the given directory.
|
|
|
|
def unpack(directory)
|
|
@gem_dir = directory
|
|
extract_files
|
|
end
|
|
rubygems_deprecate :unpack
|
|
|
|
##
|
|
# The location of the spec file that is installed.
|
|
#
|
|
|
|
def spec_file
|
|
File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
|
|
end
|
|
|
|
##
|
|
# The location of the default spec file for default gems.
|
|
#
|
|
|
|
def default_spec_file
|
|
File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec"
|
|
end
|
|
|
|
##
|
|
# Writes the .gemspec specification (in Ruby) to the gem home's
|
|
# specifications directory.
|
|
|
|
def write_spec
|
|
spec.installed_by_version = Gem.rubygems_version
|
|
|
|
Gem.write_binary(spec_file, spec.to_ruby_for_cache)
|
|
end
|
|
|
|
##
|
|
# Writes the full .gemspec specification (in Ruby) to the gem home's
|
|
# specifications/default directory.
|
|
#
|
|
# In contrast to #write_spec, this keeps file lists, so the `gem contents`
|
|
# command works.
|
|
|
|
def write_default_spec
|
|
Gem.write_binary(default_spec_file, spec.to_ruby)
|
|
end
|
|
|
|
##
|
|
# Creates windows .bat files for easy running of commands
|
|
|
|
def generate_windows_script(filename, bindir)
|
|
if Gem.win_platform?
|
|
script_name = formatted_program_filename(filename) + ".bat"
|
|
script_path = File.join bindir, File.basename(script_name)
|
|
File.open script_path, "w" do |file|
|
|
file.puts windows_stub_script(bindir, filename)
|
|
end
|
|
|
|
verbose script_path
|
|
end
|
|
end
|
|
|
|
def generate_bin # :nodoc:
|
|
executables = spec.executables
|
|
return if executables.nil? || executables.empty?
|
|
|
|
if @gem_home == Gem.user_dir
|
|
# If we get here, then one of the following likely happened:
|
|
# - `--user-install` was specified
|
|
# - `Gem::PathSupport#home` fell back to `Gem.user_dir`
|
|
# - GEM_HOME was manually set to `Gem.user_dir`
|
|
|
|
check_that_user_bin_dir_is_in_path(executables)
|
|
end
|
|
|
|
ensure_writable_dir @bin_dir
|
|
|
|
executables.each do |filename|
|
|
bin_path = File.join gem_dir, spec.bindir, filename
|
|
next unless File.exist? bin_path
|
|
|
|
mode = File.stat(bin_path).mode
|
|
dir_mode = options[:prog_mode] || (mode | 0o111)
|
|
|
|
unless dir_mode == mode
|
|
File.chmod dir_mode, bin_path
|
|
end
|
|
|
|
check_executable_overwrite filename
|
|
|
|
if @wrappers
|
|
generate_bin_script filename, @bin_dir
|
|
else
|
|
generate_bin_symlink filename, @bin_dir
|
|
end
|
|
end
|
|
end
|
|
|
|
def generate_plugins # :nodoc:
|
|
latest = Gem::Specification.latest_spec_for(spec.name)
|
|
return if latest && latest.version > spec.version
|
|
|
|
ensure_writable_dir @plugins_dir
|
|
|
|
if spec.plugins.empty?
|
|
remove_plugins_for(spec, @plugins_dir)
|
|
else
|
|
regenerate_plugins_for(spec, @plugins_dir)
|
|
end
|
|
rescue ArgumentError => e
|
|
raise e, "#{latest.name} #{latest.version} #{spec.name} #{spec.version}: #{e.message}"
|
|
end
|
|
|
|
##
|
|
# Creates the scripts to run the applications in the gem.
|
|
#--
|
|
# The Windows script is generated in addition to the regular one due to a
|
|
# bug or misfeature in the Windows shell's pipe. See
|
|
# https://blade.ruby-lang.org/ruby-talk/193379
|
|
|
|
def generate_bin_script(filename, bindir)
|
|
bin_script_path = File.join bindir, formatted_program_filename(filename)
|
|
|
|
Gem.open_file_with_lock(bin_script_path) do
|
|
require "fileutils"
|
|
FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers
|
|
|
|
File.open(bin_script_path, "wb", 0o755) do |file|
|
|
file.write app_script_text(filename)
|
|
file.chmod(options[:prog_mode] || 0o755)
|
|
end
|
|
end
|
|
|
|
verbose bin_script_path
|
|
|
|
generate_windows_script filename, bindir
|
|
end
|
|
|
|
##
|
|
# Creates the symlinks to run the applications in the gem. Moves
|
|
# the symlink if the gem being installed has a newer version.
|
|
|
|
def generate_bin_symlink(filename, bindir)
|
|
src = File.join gem_dir, spec.bindir, filename
|
|
dst = File.join bindir, formatted_program_filename(filename)
|
|
|
|
if File.exist? dst
|
|
if File.symlink? dst
|
|
link = File.readlink(dst).split File::SEPARATOR
|
|
cur_version = Gem::Version.create(link[-3].sub(/^.*-/, ""))
|
|
return if spec.version < cur_version
|
|
end
|
|
File.unlink dst
|
|
end
|
|
|
|
FileUtils.symlink src, dst, verbose: Gem.configuration.really_verbose
|
|
rescue NotImplementedError, SystemCallError
|
|
alert_warning "Unable to use symlinks, installing wrapper"
|
|
generate_bin_script filename, bindir
|
|
end
|
|
|
|
##
|
|
# Generates a #! line for +bin_file_name+'s wrapper copying arguments if
|
|
# necessary.
|
|
#
|
|
# If the :custom_shebang config is set, then it is used as a template
|
|
# for how to create the shebang used for to run a gem's executables.
|
|
#
|
|
# The template supports 4 expansions:
|
|
#
|
|
# $env the path to the unix env utility
|
|
# $ruby the path to the currently running ruby interpreter
|
|
# $exec the path to the gem's executable
|
|
# $name the name of the gem the executable is for
|
|
#
|
|
|
|
def shebang(bin_file_name)
|
|
path = File.join gem_dir, spec.bindir, bin_file_name
|
|
first_line = File.open(path, "rb", &:gets) || ""
|
|
|
|
if first_line.start_with?("#!")
|
|
# Preserve extra words on shebang line, like "-w". Thanks RPA.
|
|
shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}")
|
|
opts = $1
|
|
shebang.strip! # Avoid nasty ^M issues.
|
|
end
|
|
|
|
if which = Gem.configuration[:custom_shebang]
|
|
# replace bin_file_name with "ruby" to avoid endless loops
|
|
which = which.gsub(/ #{bin_file_name}$/," #{ruby_install_name}")
|
|
|
|
which = which.gsub(/\$(\w+)/) do
|
|
case $1
|
|
when "env"
|
|
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
|
|
when "ruby"
|
|
"#{Gem.ruby}#{opts}"
|
|
when "exec"
|
|
bin_file_name
|
|
when "name"
|
|
spec.name
|
|
end
|
|
end
|
|
|
|
"#!#{which}"
|
|
elsif @env_shebang
|
|
# Create a plain shebang line.
|
|
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
|
|
"#!#{@env_path} #{ruby_install_name}"
|
|
else
|
|
"#{bash_prolog_script}#!#{Gem.ruby}#{opts}"
|
|
end
|
|
end
|
|
|
|
##
|
|
# Ensures the Gem::Specification written out for this gem is loadable upon
|
|
# installation.
|
|
|
|
def ensure_loadable_spec
|
|
ruby = spec.to_ruby_for_cache
|
|
|
|
begin
|
|
eval ruby
|
|
rescue StandardError, SyntaxError => e
|
|
raise Gem::InstallError,
|
|
"The specification for #{spec.full_name} is corrupt (#{e.class})"
|
|
end
|
|
end
|
|
|
|
def ensure_dependencies_met # :nodoc:
|
|
deps = spec.runtime_dependencies
|
|
deps |= spec.development_dependencies if @development
|
|
|
|
deps.each do |dep_gem|
|
|
ensure_dependency spec, dep_gem
|
|
end
|
|
end
|
|
|
|
def process_options # :nodoc:
|
|
@options = {
|
|
bin_dir: nil,
|
|
env_shebang: false,
|
|
force: false,
|
|
only_install_dir: false,
|
|
post_install_message: true,
|
|
}.merge options
|
|
|
|
@env_shebang = options[:env_shebang]
|
|
@force = options[:force]
|
|
@install_dir = options[:install_dir]
|
|
@user_install = options[:user_install]
|
|
@ignore_dependencies = options[:ignore_dependencies]
|
|
@format_executable = options[:format_executable]
|
|
@wrappers = options[:wrappers]
|
|
@only_install_dir = options[:only_install_dir]
|
|
|
|
@bin_dir = options[:bin_dir]
|
|
@development = options[:development]
|
|
@build_root = options[:build_root]
|
|
|
|
@build_args = options[:build_args]
|
|
|
|
@gem_home = @install_dir || user_install_dir || Gem.dir
|
|
|
|
# If the user has asked for the gem to be installed in a directory that is
|
|
# the system gem directory, then use the system bin directory, else create
|
|
# (or use) a new bin dir under the gem_home.
|
|
@bin_dir ||= Gem.bindir(@gem_home)
|
|
|
|
@plugins_dir = Gem.plugindir(@gem_home)
|
|
|
|
unless @build_root.nil?
|
|
@bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, ""))
|
|
@gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, ""))
|
|
@plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, ""))
|
|
alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}\n Plugins dir: #{@plugins_dir}"
|
|
end
|
|
end
|
|
|
|
def check_that_user_bin_dir_is_in_path(executables) # :nodoc:
|
|
user_bin_dir = @bin_dir || Gem.bindir(gem_home)
|
|
user_bin_dir = user_bin_dir.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
|
|
path = ENV["PATH"]
|
|
path = path.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
|
|
if Gem.win_platform?
|
|
path = path.downcase
|
|
user_bin_dir = user_bin_dir.downcase
|
|
end
|
|
|
|
path = path.split(File::PATH_SEPARATOR)
|
|
|
|
unless path.include? user_bin_dir
|
|
unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV["HOME"], "~"))
|
|
alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables (#{executables.join(", ")}) will not run."
|
|
end
|
|
end
|
|
end
|
|
|
|
def verify_gem_home # :nodoc:
|
|
FileUtils.mkdir_p gem_home, mode: options[:dir_mode] && 0o755
|
|
end
|
|
|
|
def verify_spec
|
|
unless Gem::Specification::VALID_NAME_PATTERN.match?(spec.name)
|
|
raise Gem::InstallError, "#{spec} has an invalid name"
|
|
end
|
|
|
|
if spec.raw_require_paths.any? {|path| path =~ /\R/ }
|
|
raise Gem::InstallError, "#{spec} has an invalid require_paths"
|
|
end
|
|
|
|
if spec.extensions.any? {|ext| ext =~ /\R/ }
|
|
raise Gem::InstallError, "#{spec} has an invalid extensions"
|
|
end
|
|
|
|
if /\R/.match?(spec.platform.to_s)
|
|
raise Gem::InstallError, "#{spec.platform} is an invalid platform"
|
|
end
|
|
|
|
unless /\A\d+\z/.match?(spec.specification_version.to_s)
|
|
raise Gem::InstallError, "#{spec} has an invalid specification_version"
|
|
end
|
|
|
|
if spec.dependencies.any? {|dep| dep.type != :runtime && dep.type != :development }
|
|
raise Gem::InstallError, "#{spec} has an invalid dependencies"
|
|
end
|
|
|
|
if spec.dependencies.any? {|dep| dep.name =~ /(?:\R|[<>])/ }
|
|
raise Gem::InstallError, "#{spec} has an invalid dependencies"
|
|
end
|
|
end
|
|
|
|
##
|
|
# Return the text for an application file.
|
|
|
|
def app_script_text(bin_file_name)
|
|
# NOTE: that the `load` lines cannot be indented, as old RG versions match
|
|
# against the beginning of the line
|
|
<<-TEXT
|
|
#{shebang bin_file_name}
|
|
#
|
|
# This file was generated by RubyGems.
|
|
#
|
|
# The application '#{spec.name}' is installed as part of a gem, and
|
|
# this file is here to facilitate running it.
|
|
#
|
|
|
|
require 'rubygems'
|
|
#{gemdeps_load(spec.name)}
|
|
version = "#{Gem::Requirement.default_prerelease}"
|
|
|
|
str = ARGV.first
|
|
if str
|
|
str = str.b[/\\A_(.*)_\\z/, 1]
|
|
if str and Gem::Version.correct?(str)
|
|
#{explicit_version_requirement(spec.name)}
|
|
ARGV.shift
|
|
end
|
|
end
|
|
|
|
if Gem.respond_to?(:activate_bin_path)
|
|
load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
|
|
else
|
|
gem #{spec.name.dump}, version
|
|
load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version)
|
|
end
|
|
TEXT
|
|
end
|
|
|
|
def gemdeps_load(name)
|
|
return "" if name == "bundler"
|
|
|
|
<<-TEXT
|
|
|
|
Gem.use_gemdeps
|
|
TEXT
|
|
end
|
|
|
|
def explicit_version_requirement(name)
|
|
code = "version = str"
|
|
return code unless name == "bundler"
|
|
|
|
code += <<-TEXT
|
|
|
|
ENV['BUNDLER_VERSION'] = str
|
|
TEXT
|
|
end
|
|
|
|
##
|
|
# return the stub script text used to launch the true Ruby script
|
|
|
|
def windows_stub_script(bindir, bin_file_name)
|
|
rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"])
|
|
|
|
# get ruby executable file name from RbConfig
|
|
ruby_exe = "#{rb_config["RUBY_INSTALL_NAME"]}#{rb_config["EXEEXT"]}"
|
|
ruby_exe = "ruby.exe" if ruby_exe.empty?
|
|
|
|
if File.exist?(File.join(bindir, ruby_exe))
|
|
# stub & ruby.exe within same folder. Portable
|
|
<<-TEXT
|
|
@ECHO OFF
|
|
@"%~dp0#{ruby_exe}" "%~dpn0" %*
|
|
TEXT
|
|
elsif bindir.downcase.start_with? rb_topdir.downcase
|
|
# stub within ruby folder, but not standard bin. Portable
|
|
require "pathname"
|
|
from = Pathname.new bindir
|
|
to = Pathname.new "#{rb_topdir}/bin"
|
|
rel = to.relative_path_from from
|
|
<<-TEXT
|
|
@ECHO OFF
|
|
@"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %*
|
|
TEXT
|
|
else
|
|
# outside ruby folder, maybe -user-install or bundler. Portable, but ruby
|
|
# is dependent on PATH
|
|
<<-TEXT
|
|
@ECHO OFF
|
|
@#{ruby_exe} "%~dpn0" %*
|
|
TEXT
|
|
end
|
|
end
|
|
##
|
|
# Builds extensions. Valid types of extensions are extconf.rb files,
|
|
# configure scripts and rakefiles or mkrf_conf files.
|
|
|
|
def build_extensions
|
|
builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig
|
|
|
|
builder.build_extensions
|
|
end
|
|
|
|
##
|
|
# Reads the file index and extracts each file into the gem directory.
|
|
#
|
|
# Ensures that files can't be installed outside the gem directory.
|
|
|
|
def extract_files
|
|
@package.extract_files gem_dir
|
|
end
|
|
|
|
##
|
|
# Extracts only the bin/ files from the gem into the gem directory.
|
|
# This is used by default gems to allow a gem-aware stub to function
|
|
# without the full gem installed.
|
|
|
|
def extract_bin
|
|
@package.extract_files gem_dir, "#{spec.bindir}/*"
|
|
end
|
|
|
|
##
|
|
# Prefix and suffix the program filename the same as ruby.
|
|
|
|
def formatted_program_filename(filename)
|
|
if @format_executable
|
|
self.class.exec_format % File.basename(filename)
|
|
else
|
|
filename
|
|
end
|
|
end
|
|
|
|
##
|
|
#
|
|
# Return the target directory where the gem is to be installed. This
|
|
# directory is not guaranteed to be populated.
|
|
#
|
|
|
|
def dir
|
|
gem_dir.to_s
|
|
end
|
|
|
|
##
|
|
# Filename of the gem being installed.
|
|
|
|
def gem
|
|
@package.gem.path
|
|
end
|
|
|
|
##
|
|
# Performs various checks before installing the gem such as the install
|
|
# repository is writable and its directories exist, required Ruby and
|
|
# rubygems versions are met and that dependencies are installed.
|
|
#
|
|
# Version and dependency checks are skipped if this install is forced.
|
|
#
|
|
# The dependent check will be skipped if the install is ignoring dependencies.
|
|
|
|
def pre_install_checks
|
|
verify_gem_home
|
|
|
|
# The name and require_paths must be verified first, since it could contain
|
|
# ruby code that would be eval'ed in #ensure_loadable_spec
|
|
verify_spec
|
|
|
|
ensure_loadable_spec
|
|
|
|
if options[:install_as_default]
|
|
Gem.ensure_default_gem_subdirectories gem_home
|
|
else
|
|
Gem.ensure_gem_subdirectories gem_home
|
|
end
|
|
|
|
return true if @force
|
|
|
|
ensure_dependencies_met unless @ignore_dependencies
|
|
|
|
true
|
|
end
|
|
|
|
##
|
|
# Writes the file containing the arguments for building this gem's
|
|
# extensions.
|
|
|
|
def write_build_info_file
|
|
return if build_args.empty?
|
|
|
|
build_info_dir = File.join gem_home, "build_info"
|
|
|
|
dir_mode = options[:dir_mode]
|
|
FileUtils.mkdir_p build_info_dir, mode: dir_mode && 0o755
|
|
|
|
build_info_file = File.join build_info_dir, "#{spec.full_name}.info"
|
|
|
|
File.open build_info_file, "w" do |io|
|
|
build_args.each do |arg|
|
|
io.puts arg
|
|
end
|
|
end
|
|
|
|
File.chmod(dir_mode, build_info_dir) if dir_mode
|
|
end
|
|
|
|
##
|
|
# Writes the .gem file to the cache directory
|
|
|
|
def write_cache_file
|
|
cache_file = File.join gem_home, "cache", spec.file_name
|
|
@package.copy_to cache_file
|
|
end
|
|
|
|
def ensure_writable_dir(dir) # :nodoc:
|
|
begin
|
|
Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact
|
|
rescue SystemCallError
|
|
raise unless File.directory? dir
|
|
end
|
|
|
|
raise Gem::FilePermissionError.new(dir) unless File.writable? dir
|
|
end
|
|
|
|
private
|
|
|
|
def user_install_dir
|
|
# never install to user home in --build-root mode
|
|
return unless @build_root.nil?
|
|
|
|
# Please note that @user_install might have three states:
|
|
# * `true`: `--user-install`
|
|
# * `false`: `--no-user-install` and
|
|
# * `nil`: option was not specified
|
|
if @user_install || (@user_install.nil? && Gem.default_user_install)
|
|
Gem.user_dir
|
|
end
|
|
end
|
|
|
|
def build_args
|
|
@build_args ||= begin
|
|
require_relative "command"
|
|
Gem::Command.build_args
|
|
end
|
|
end
|
|
|
|
def rb_config
|
|
Gem.target_rbconfig
|
|
end
|
|
|
|
def ruby_install_name
|
|
rb_config["ruby_install_name"]
|
|
end
|
|
|
|
def load_relative_enabled?
|
|
rb_config["LIBRUBY_RELATIVE"] == "yes"
|
|
end
|
|
|
|
def bash_prolog_script
|
|
if load_relative_enabled?
|
|
script = +<<~EOS
|
|
bindir="${0%/*}"
|
|
EOS
|
|
|
|
script << %(exec "$bindir/#{ruby_install_name}" "-x" "$0" "$@"\n)
|
|
|
|
<<~EOS
|
|
#!/bin/sh
|
|
# -*- ruby -*-
|
|
_=_\\
|
|
=begin
|
|
#{script.chomp}
|
|
=end
|
|
EOS
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def load_plugin
|
|
specs = Gem::Specification.find_all_by_name(spec.name)
|
|
# If old version already exists, this plugin isn't loaded
|
|
# immediately. It's for avoiding a case that multiple versions
|
|
# are loaded at the same time.
|
|
return unless specs.size == 1
|
|
|
|
plugin_files = spec.plugins.map do |plugin|
|
|
File.join(@plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}")
|
|
end
|
|
Gem.load_plugin_files(plugin_files)
|
|
end
|
|
end
|