diff options
Diffstat (limited to 'lib/rubygems/installer.rb')
| -rw-r--r-- | lib/rubygems/installer.rb | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb new file mode 100644 index 0000000000..15d6aac0fd --- /dev/null +++ b/lib/rubygems/installer.rb @@ -0,0 +1,1030 @@ +# 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 "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 + ## + # 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 + ## + # 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\.activate_and_load_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 + spec.loaded_from = spec_file + + # 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 + + extract_files + + build_extensions + write_build_info_file + run_post_build_hooks + + generate_bin + if options[:install_plugin] == false + remove_stale_plugins + warn_skipped_plugins + else + generate_plugins + end + + write_spec + write_cache_file + + 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 unless options[:install_plugin] == false + + 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 + + ## + # The location of the spec file that is installed. + # + + def spec_file + File.join gem_home, "specifications", "#{spec.full_name}.gemspec" + end + + def default_spec_dir + dir = File.join(gem_home, "specifications", "default") + FileUtils.mkdir_p dir + dir + end + + ## + # The location of the default spec file for default gems. + # + + def default_spec_file + File.join default_spec_dir, "#{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] + @build_jobs = options[:build_jobs] + + @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_and_load_bin_path) + Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) + else + load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', 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 + if options[:build_extension] == false + warn_skipped_extensions + return + end + + builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs + + builder.build_extensions + end + + def warn_skipped_extensions # :nodoc: + return if spec.extensions.empty? + + alert_warning "#{spec.full_name} contains native extensions that were not built.\n" \ + "To build extensions, run: gem pristine #{spec.name} --extensions" + end + + def warn_skipped_plugins # :nodoc: + return if spec.plugins.empty? + + alert_warning "#{spec.full_name} contains plugins that were not installed.\n" \ + "To install plugins, run: gem pristine #{spec.name} --only-plugins" + end + + def remove_stale_plugins # :nodoc: + return unless spec.plugins.empty? + + ensure_writable_dir @plugins_dir + remove_plugins_for(spec, @plugins_dir) + 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 + + Gem.ensure_gem_subdirectories gem_home + + 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: + require "fileutils" + FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755 + + 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 build_jobs + @build_jobs ||= begin + require "etc" + Etc.nprocessors + 1 + rescue LoadError + 1 + 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? + <<~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 + + 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.filter_map do |plugin| + path = File.join(@plugins_dir, "#{spec.name}_plugin#{File.extname(plugin)}") + path if File.exist?(path) + end + Gem.load_plugin_files(plugin_files) unless plugin_files.empty? + end +end |
