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_install
和 Gem.post_install
。
常量
- ENV_PATHS
env(1) 可能存在的路径。有些系统坏了,把它放在 /bin 中
属性
覆盖可执行文件的格式。
这是一个带有 “%s” 的 sprintf 格式,它将被替换为可执行文件的名称。它基于 ruby 可执行文件的名称与 “ruby” 的差异。
将安装 gem 的可执行文件的目录
将安装 gem 的 gem 仓库
实例化 Gem::Installer
时传递的选项。
gem 包实例。
公共类方法
为位于 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
默认为使用 Ruby 的程序前缀和后缀。
# File rubygems/installer.rb, line 97 def exec_format @exec_format ||= Gem.default_exec_format end
为临时 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
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
构造一个 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
公共实例方法
返回应用程序文件的文本。
# 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
构建扩展。有效的扩展类型是 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
默认 gem 的默认 spec 文件的位置。
# File rubygems/installer.rb, line 434 def default_spec_file File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" end
返回要安装 gem 的目标目录。不保证此目录已填充。
# File rubygems/installer.rb, line 882 def dir gem_dir.to_s end
确保当前安装的 gem 满足依赖项。如果不是,则会引发异常。
- spec
- 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
确保为此 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
# 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
仅将 gem 中的 bin/ 文件提取到 gem 目录中。默认 gem 使用此方法,以允许 gem 感知的存根在没有安装完整 gem 的情况下运行。
# File rubygems/installer.rb, line 861 def extract_bin @package.extract_files gem_dir, "#{spec.bindir}/*" end
读取文件索引并将每个文件提取到 gem 目录中。
确保文件不能安装在 gem 目录之外。
# File rubygems/installer.rb, line 852 def extract_files @package.extract_files gem_dir end
为程序文件名添加与 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 的文件名。
# File rubygems/installer.rb, line 889 def gem @package.gem.path end
spec 的 gem 目录的延迟访问器。
# File rubygems/installer.rb, line 267 def gem_dir @gem_dir ||= File.join(gem_home, "gems", spec.full_name) end
# File rubygems/installer.rb, line 783 def gemdeps_load(name) return "" if name == "bundler" <<-TEXT Gem.use_gemdeps TEXT end
创建用于运行 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
创建用于运行 gem 中应用程序的符号链接。如果正在安装的 gem 具有较新版本,则移动符号链接。
# File rubygems/installer.rb, line 554 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
创建 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
安装 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
如果系统中的 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
返回 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
在安装 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
为 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 的延迟访问器。
# File rubygems/installer.rb, line 274 def spec @package.spec end
安装的 spec 文件的位置。
# File rubygems/installer.rb, line 426 def spec_file File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end
将 gem 解压到给定的目录中。
# File rubygems/installer.rb, line 416 def unpack(directory) @gem_dir = directory extract_files end
# 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
返回用于启动真正 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
写入包含用于构建此 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
将 .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
将完整的 .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
将 .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
私有实例方法
# 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
# File rubygems/installer.rb, line 980 def build_args @build_args ||= begin require_relative "command" Gem::Command.build_args end end
# 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
# File rubygems/installer.rb, line 995 def load_relative_enabled? rb_config["LIBRUBY_RELATIVE"] == "yes" end
# File rubygems/installer.rb, line 987 def rb_config Gem.target_rbconfig end
# File rubygems/installer.rb, line 991 def ruby_install_name rb_config["ruby_install_name"] end
# 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