class Gem::Installer

安装程序将 .gem 文件中包含的文件安装到 Gem.home 中。

Gem::Installer 负责将文件放置在文件系统上的所有正确位置,包括将 gem 解压到其 gem 目录中,将 gemspec 安装在 specifications 目录中,将缓存的 gem 存储在 cache 目录中,以及为可执行文件安装包装器或符号链接。

安装程序会调用安装前和安装后钩子。可以通过已安装 gem 中的 rubygems_plugin.rb 文件或通过 rubygems/defaults/#{RUBY_ENGINE}.rb 或 rubygems/defaults/operating_system.rb 文件添加钩子。有关详细信息,请参阅 Gem.pre_installGem.post_install

常量

ENV_PATHS

env(1) 可能存在的路径。有些系统坏了,把它放在 /bin 中

属性

exec_format[W]

覆盖可执行文件的格式。

这是一个带有 “%s” 的 sprintf 格式,它将被替换为可执行文件的名称。它基于 ruby 可执行文件的名称与 “ruby” 的差异。

bin_dir[R]

将安装 gem 的可执行文件的目录

gem_home[R]

将安装 gem 的 gem 仓库

options[R]

实例化 Gem::Installer 时传递的选项。

package[R]

gem 包实例。

公共类方法

at(path, options = {}) 单击以切换源

为位于 path 的 gem 文件构造一个安装程序对象

# File rubygems/installer.rb, line 105
def self.at(path, options = {})
  security_policy = options[:security_policy]
  package = Gem::Package.new path, security_policy
  new package, options
end
exec_format() 单击以切换源

默认为使用 Ruby 的程序前缀和后缀。

# File rubygems/installer.rb, line 97
def exec_format
  @exec_format ||= Gem.default_exec_format
end
for_spec(spec, options = {}) 单击以切换源

为临时 gem(一个我们实际上没有 .gem 文件,只有一个 spec 的 gem)构造一个安装程序对象

# File rubygems/installer.rb, line 143
def self.for_spec(spec, options = {})
  # FIXME: we should have a real Package class for this
  new FakePackage.new(spec), options
end
inherited(klass) 单击以切换源

rubygems 中延迟加载 ‘rubygems/command’ 的更改(为了延迟加载 ‘optparse’ 作为副作用)会影响 bundler 的自定义安装程序,它在没有要求的情况下使用 ‘Gem::Command’(直到 bundler 2.2.29)。此钩子是为了补偿丢失的 require。

TODO:当 rubygems 不再支持在早于 2.2.29 的 bundler 上运行时删除。

调用超类方法
# File rubygems/installer.rb, line 79
def inherited(klass)
  if klass.name == "Bundler::RubyGemsGemInstaller"
    require "rubygems/command"
  end

  super(klass)
end
new(package, options={}) 单击以切换源

构造一个 Installer 实例,该实例将安装位于 package 的 gem,它可以是路径或 Gem::Package 的实例。options 是一个具有以下键的哈希

:bin_dir

如果需要,将 bin 包装器放在哪里。

:development

是否应安装开发依赖项。

:env_shebang

在 bin 包装器中使用 /usr/bin/env。

:force

覆盖所有版本检查和安全策略检查,除了仅限签名 gem 的策略。

:format_executable

将可执行文件的格式设置为与 Ruby 可执行文件相同。如果你的 Ruby 是 ruby18,则 foo_exec 将安装为 foo_exec18。

:ignore_dependencies

如果缺少依赖项,则不引发异常。

:install_dir

将 gem 安装到的目录。

:security_policy

使用指定的安全策略。请参阅 Gem::Security

:user_install

表示应将 gem 解压到用户的个人 gem 目录中。

:only_install_dir

仅针对 install_dir 中的内容验证依赖项

:wrappers

如果为 true,则安装包装器,如果为 false,则安装符号链接。

:build_args

传递给扩展构建器进程的参数数组。如果未设置,则使用 Gem::Command.build_args

:post_install_message

如果为 true,则打印 gem 安装后消息

# File rubygems/installer.rb, line 173
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

公共实例方法

app_script_text(bin_file_name) 单击以切换源

返回应用程序文件的文本。

# File rubygems/installer.rb, line 749
  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
build_extensions() 单击以切换源

构建扩展。有效的扩展类型是 extconf.rb 文件、configure 脚本和 rakefiles 或 mkrf_conf 文件。

# File rubygems/installer.rb, line 841
def build_extensions
  builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig

  builder.build_extensions
end
default_spec_file() 单击以切换源

默认 gem 的默认 spec 文件的位置。

# File rubygems/installer.rb, line 434
def default_spec_file
  File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec"
end
dir() 单击以切换源

返回要安装 gem 的目标目录。不保证此目录已填充。

# File rubygems/installer.rb, line 882
def dir
  gem_dir.to_s
end
ensure_dependency(spec, dependency) 单击以切换源

确保当前安装的 gem 满足依赖项。如果不是,则会引发异常。

spec

Gem::Specification

dependency

Gem::Dependency

# File rubygems/installer.rb, line 396
def ensure_dependency(spec, dependency)
  unless installation_satisfies_dependency? dependency
    raise Gem::InstallError, "#{spec.name} requires #{dependency}"
  end
  true
end
ensure_loadable_spec() 单击以切换源

确保为此 gem 写出的 Gem::Specification 在安装时可加载。

# File rubygems/installer.rb, line 630
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
explicit_version_requirement(name) 单击以切换源
# File rubygems/installer.rb, line 792
  def explicit_version_requirement(name)
    code = "version = str"
    return code unless name == "bundler"

    code += <<-TEXT

    ENV['BUNDLER_VERSION'] = str
TEXT
  end
extract_bin() 单击以切换源

仅将 gem 中的 bin/ 文件提取到 gem 目录中。默认 gem 使用此方法,以允许 gem 感知的存根在没有安装完整 gem 的情况下运行。

# File rubygems/installer.rb, line 861
def extract_bin
  @package.extract_files gem_dir, "#{spec.bindir}/*"
end
extract_files() 单击以切换源

读取文件索引并将每个文件提取到 gem 目录中。

确保文件不能安装在 gem 目录之外。

# File rubygems/installer.rb, line 852
def extract_files
  @package.extract_files gem_dir
end
formatted_program_filename(filename) 单击以切换源

为程序文件名添加与 ruby 相同的前缀和后缀。

# File rubygems/installer.rb, line 868
def formatted_program_filename(filename)
  if @format_executable
    self.class.exec_format % File.basename(filename)
  else
    filename
  end
end
gem() 单击以切换源

正在安装的 gem 的文件名。

# File rubygems/installer.rb, line 889
def gem
  @package.gem.path
end
gem_dir() 单击以切换源

spec 的 gem 目录的延迟访问器。

# File rubygems/installer.rb, line 267
def gem_dir
  @gem_dir ||= File.join(gem_home, "gems", spec.full_name)
end
gemdeps_load(name) 单击以切换源
# File rubygems/installer.rb, line 783
  def gemdeps_load(name)
    return "" if name == "bundler"

    <<-TEXT

Gem.use_gemdeps
TEXT
  end
generate_bin_script(filename, bindir) 单击以切换源

创建用于运行 gem 中应用程序的脚本。

# File rubygems/installer.rb, line 532
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
generate_windows_script(filename, bindir) 单击以切换源

创建 windows .bat 文件以便于运行命令

# File rubygems/installer.rb, line 462
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
install() 单击以切换源

安装 gem 并返回已安装 gem 的加载的 Gem::Specification

gem 将按以下结构安装

@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
# File rubygems/installer.rb, line 289
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
installation_satisfies_dependency?(dependency) 单击以切换源

如果系统中的 gem 满足 dependency,则为 True。

# File rubygems/installer.rb, line 406
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
installed_specs() 单击以切换源

返回 gem_home 中包含的 Specification 数组,我们将安装到该目录中。

# File rubygems/installer.rb, line 376
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
pre_install_checks() 单击以切换源

在安装 gem 之前执行各种检查,例如安装仓库是否可写,并且其目录存在,是否满足所需的 Ruby 和 rubygems 版本,以及是否安装了依赖项。

如果强制安装,则跳过版本和依赖项检查。

如果安装忽略依赖项,则将跳过依赖项检查。

# File rubygems/installer.rb, line 902
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
shebang(bin_file_name) 单击以切换源

bin_file_name 的包装器生成 #! 行,并在必要时复制参数。

如果设置了 :custom_shebang 配置,则将其用作如何创建用于运行 gem 的可执行文件的 shebang 的模板。

该模板支持 4 个扩展

$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
# File rubygems/installer.rb, line 588
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
spec() 单击以切换源

安装程序的 spec 的延迟访问器。

# File rubygems/installer.rb, line 274
def spec
  @package.spec
end
spec_file() 单击以切换源

安装的 spec 文件的位置。

# File rubygems/installer.rb, line 426
def spec_file
  File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
end
unpack(directory) 单击以切换源

将 gem 解压到给定的目录中。

# File rubygems/installer.rb, line 416
def unpack(directory)
  @gem_dir = directory
  extract_files
end
verify_spec() 点击以切换源代码
# File rubygems/installer.rb, line 716
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
windows_stub_script(bindir, bin_file_name) 点击以切换源代码

返回用于启动真正 Ruby 脚本的存根脚本文本

# File rubygems/installer.rb, line 805
  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
write_build_info_file() 点击以切换源代码

写入包含用于构建此 gem 扩展的参数的文件。

# File rubygems/installer.rb, line 928
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
write_cache_file() 点击以切换源代码

将 .gem 文件写入缓存目录

# File rubygems/installer.rb, line 950
def write_cache_file
  cache_file = File.join gem_home, "cache", spec.file_name
  @package.copy_to cache_file
end
write_default_spec() 点击以切换源代码

将完整的 .gemspec 规范(以 Ruby 编写)写入 gem 主目录的 specifications/default 目录。

write_spec 不同,这保留了文件列表,因此 `gem contents` 命令可以工作。

# File rubygems/installer.rb, line 455
def write_default_spec
  Gem.write_binary(default_spec_file, spec.to_ruby)
end
write_spec() 点击以切换源代码

将 .gemspec 规范(以 Ruby 编写)写入 gem 主目录的 specifications 目录。

# File rubygems/installer.rb, line 442
def write_spec
  spec.installed_by_version = Gem.rubygems_version

  Gem.write_binary(spec_file, spec.to_ruby_for_cache)
end

私有实例方法

bash_prolog_script() 点击以切换源代码
# File rubygems/installer.rb, line 999
  def bash_prolog_script
    if load_relative_enabled?
      <<~EOS
        #!/bin/sh
        # -*- ruby -*-
        _=_\\
        =begin
        bindir="${0%/*}"
        ruby="$bindir/#{ruby_install_name}"
        if [ ! -f "$ruby" ]; then
          ruby="#{ruby_install_name}"
        fi
        exec "$ruby" "-x" "$0" "$@"
        =end
      EOS
    else
      ""
    end
  end
build_args() 点击以切换源代码
# File rubygems/installer.rb, line 980
def build_args
  @build_args ||= begin
                    require_relative "command"
                    Gem::Command.build_args
                  end
end
load_plugin() 点击以切换源代码
# File rubygems/installer.rb, line 1019
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
load_relative_enabled?() 点击以切换源代码
# File rubygems/installer.rb, line 995
def load_relative_enabled?
  rb_config["LIBRUBY_RELATIVE"] == "yes"
end
rb_config() 点击以切换源代码
# File rubygems/installer.rb, line 987
def rb_config
  Gem.target_rbconfig
end
ruby_install_name() 点击以切换源代码
# File rubygems/installer.rb, line 991
def ruby_install_name
  rb_config["ruby_install_name"]
end
user_install_dir() 点击以切换源代码
# File rubygems/installer.rb, line 967
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