#-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require 'rubygems/command' require 'rubygems/exceptions' require 'rubygems/package' require 'rubygems/ext' require 'rubygems/user_interaction' require 'fileutils' ## # 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] ## # Deprecated in favor of Gem::Ext::BuildError ExtensionBuildError = Gem::Ext::BuildError # :nodoc: include Gem::UserInteraction # DOC: Missing docs or :nodoc:. attr_reader :gem ## # The directory a gem's executables will be installed into attr_reader :bin_dir ## # 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 @path_warning = false class << self ## # True if we've warned about PATH not including Gem.bindir attr_accessor :path_warning # DOC: Missing docs or :nodoc:. attr_writer :exec_format # Defaults to use Ruby's program prefix and suffix. def exec_format @exec_format ||= Gem.default_exec_format end end ## # Constructs an Installer instance that will install the gem located at # +gem+. +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 def initialize(gem, options={}) require 'fileutils' @gem = gem @options = options @package = Gem::Package.new @gem process_options @package.security_policy = @security_policy if options[:user_install] and not options[:unpack] then @gem_home = Gem.user_dir @bin_dir = Gem.bindir gem_home unless options[:bin_dir] check_that_user_bin_dir_is_in_path end end ## # Checks if +filename+ exists in +@bin_dir+. # # If +@force+ is set +filename+ is overwritten. # # If +filename+ exists and is a RubyGems wrapper for different gem 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 open generated_bin, 'rb' do |io| next unless io.gets =~ /^#!/ # shebang io.gets # blankline # TODO detect a specially formatted comment instead of trying # to run a regexp against Ruby code. next unless io.gets =~ /This file was generated by RubyGems/ ruby_executable = true existing = io.read.slice(/^gem (['"])(.*?)(\1),/, 2) end return if spec.name == existing # somebody has written to RubyGems' directory, overwrite, too bad return if Gem.default_bindir != @bin_dir and not ruby_executable question = "#{spec.name}'s executable \"#{filename}\" conflicts with " if ruby_executable then question << existing 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 @spec ||= @package.spec rescue Gem::Package::Error => e raise Gem::InstallError, "invalid gem: #{e.message}" 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 #=> a cached copy of the installed gem # gems//... #=> extracted files # specifications/.gemspec #=> the Gem::Specification def install pre_install_checks run_pre_install_hooks # Completely remove any previous gem files FileUtils.rm_rf gem_dir FileUtils.mkdir_p gem_dir spec.loaded_from = spec_file if @options[:install_as_default] extract_bin write_default_spec else extract_files build_extensions write_build_info_file run_post_build_hooks generate_bin write_spec write_cache_file end say spec.post_install_message unless spec.post_install_message.nil? Gem::Specification.add_spec spec unless Gem::Specification.include? spec run_post_install_hooks spec # TODO This rescue is in the wrong place. What is raising this exception? # move this rescue to around the code that actually might raise it. rescue Zlib::GzipFile::Error raise Gem::InstallError, "gzip error installing #{gem}" end def run_pre_install_hooks # :nodoc: Gem.pre_install_hooks.each do |hook| if hook.call(self) == false then location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/ message = "pre-install hook#{location} failed for #{spec.full_name}" raise Gem::InstallError, message end end end def run_post_build_hooks # :nodoc: Gem.post_build_hooks.each do |hook| if hook.call(self) == false then 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 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 @specs ||= begin specs = [] Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path| spec = Gem::Specification.load path.untaint 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 then 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 installed_specs.detect { |s| dependency.matches_spec? s } return false if @only_install_dir not dependency.matching_specs.empty? end ## # Unpacks the gem into the given directory. def unpack(directory) @gem_dir = directory extract_files end ## # The location of of the spec file that is installed. # def spec_file File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end ## # The location of 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 open spec_file, 'w' do |file| spec.installed_by_version = Gem.rubygems_version file.puts spec.to_ruby_for_cache file.fsync rescue nil # for filesystems without fsync(2) end end ## # Writes the full .gemspec specification (in Ruby) to the gem home's # specifications/default directory. def write_default_spec File.open(default_spec_file, "w") do |file| file.puts spec.to_ruby end end ## # Creates windows .bat files for easy running of commands def generate_windows_script(filename, bindir) if Gem.win_platform? then script_name = 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 say script_path if Gem.configuration.really_verbose end end # DOC: Missing docs or :nodoc:. def generate_bin return if spec.executables.nil? or spec.executables.empty? Dir.mkdir @bin_dir unless File.exist? @bin_dir raise Gem::FilePermissionError.new(@bin_dir) unless File.writable? @bin_dir spec.executables.each do |filename| filename.untaint bin_path = File.join gem_dir, spec.bindir, filename unless File.exist? bin_path then # TODO change this to a more useful warning warn "#{bin_path} maybe `gem pristine #{spec.name}` will fix it?" next end mode = File.stat(bin_path).mode | 0111 FileUtils.chmod mode, bin_path check_executable_overwrite filename if @wrappers then generate_bin_script filename, @bin_dir else generate_bin_symlink filename, @bin_dir end end 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 # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/193379 def generate_bin_script(filename, bindir) bin_script_path = File.join bindir, formatted_program_filename(filename) FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers File.open bin_script_path, 'wb', 0755 do |file| file.print app_script_text(filename) end say bin_script_path if Gem.configuration.really_verbose 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) if Gem.win_platform? then alert_warning "Unable to use symlinks on Windows, installing wrapper" generate_bin_script filename, bindir return end src = File.join gem_dir, spec.bindir, filename dst = File.join bindir, formatted_program_filename(filename) if File.exist? dst then if File.symlink? dst then 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 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) ruby_name = Gem::ConfigMap[:ruby_install_name] if @env_shebang path = File.join gem_dir, spec.bindir, bin_file_name first_line = File.open(path, "rb") {|file| file.gets} if /\A#!/ =~ first_line then # 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}$/," #{Gem::ConfigMap[: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 not ruby_name then "#!#{Gem.ruby}#{opts}" elsif opts then "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}" else # Create a plain shebang line. @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_name}" 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 ruby.untaint begin eval ruby rescue StandardError, SyntaxError => e raise Gem::InstallError, "The specification for #{spec.full_name} is corrupt (#{e.class})" end end # DOC: Missing docs or :nodoc:. def ensure_required_ruby_version_met if rrv = spec.required_ruby_version then unless rrv.satisfied_by? Gem.ruby_version then raise Gem::InstallError, "#{spec.name} requires Ruby version #{rrv}." end end end # DOC: Missing docs or :nodoc:. def ensure_required_rubygems_version_met if rrgv = spec.required_rubygems_version then unless rrgv.satisfied_by? Gem.rubygems_version then raise Gem::InstallError, "#{spec.name} requires RubyGems version #{rrgv}. " + "Try 'gem update --system' to update RubyGems itself." end end end # DOC: Missing docs or :nodoc:. def ensure_dependencies_met deps = spec.runtime_dependencies deps |= spec.development_dependencies if @development deps.each do |dep_gem| ensure_dependency spec, dep_gem end end # DOC: Missing docs or :nodoc:. def process_options @options = { :bin_dir => nil, :env_shebang => false, :force => false, :only_install_dir => false }.merge options @env_shebang = options[:env_shebang] @force = options[:force] @install_dir = options[:install_dir] @gem_home = options[:install_dir] || Gem.dir @ignore_dependencies = options[:ignore_dependencies] @format_executable = options[:format_executable] @security_policy = options[:security_policy] @wrappers = options[:wrappers] @only_install_dir = options[:only_install_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 = options[:bin_dir] || Gem.bindir(gem_home) @development = options[:development] @build_args = options[:build_args] || Gem::Command.build_args end # DOC: Missing docs or :nodoc:. def check_that_user_bin_dir_is_in_path user_bin_dir = @bin_dir || Gem.bindir(gem_home) user_bin_dir = user_bin_dir.gsub(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR path = ENV['PATH'] if Gem.win_platform? then path = path.downcase user_bin_dir = user_bin_dir.downcase end unless path.split(File::PATH_SEPARATOR).include? user_bin_dir then unless self.class.path_warning then alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables will not run." self.class.path_warning = true end end end # DOC: Missing docs or :nodoc:. def verify_gem_home(unpack = false) FileUtils.mkdir_p gem_home raise Gem::FilePermissionError, gem_home unless unpack or File.writable?(gem_home) end ## # Return the text for an application file. def app_script_text(bin_file_name) return <<-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' version = "#{Gem::Requirement.default}" if ARGV.first str = ARGV.first str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding if str =~ /\\A_(.*)_\\z/ version = $1 ARGV.shift end end gem '#{spec.name}', version load Gem.bin_path('#{spec.name}', '#{bin_file_name}', version) TEXT end ## # return the stub script text used to launch the true Ruby script def windows_stub_script(bindir, bin_file_name) ruby = File.basename(Gem.ruby).chomp('"') return <<-TEXT @ECHO OFF IF NOT "%~f0" == "~f0" GOTO :WinNT @"#{ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9 GOTO :EOF :WinNT @"#{ruby}" "%~dpn0" %* TEXT 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 builder.build_extensions end ## # Logs the build +output+ in +build_dir+, then raises Gem::Ext::BuildError. # # TODO: Delete this for RubyGems 3. It remains for API compatibility def extension_build_error(build_dir, output, backtrace = nil) # :nodoc: builder = Gem::Ext::Builder.new spec, @build_args builder.build_error build_dir, output, backtrace 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, "bin/*" end ## # Prefix and suffix the program filename the same as ruby. def formatted_program_filename(filename) if @format_executable then 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 ## # 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 this install is ignoring dependencies. def pre_install_checks verify_gem_home options[:unpack] # If we're forcing the install then disable security unless the security # policy says that we only install signed gems. @security_policy = nil if @force and @security_policy and not @security_policy.only_signed 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_required_ruby_version_met ensure_required_rubygems_version_met 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' FileUtils.mkdir_p build_info_dir build_info_file = File.join build_info_dir, "#{spec.full_name}.info" open build_info_file, 'w' do |io| @build_args.each do |arg| io.puts arg end end end ## # Writes the .gem file to the cache directory def write_cache_file cache_file = File.join gem_home, 'cache', spec.file_name FileUtils.cp @gem, cache_file unless File.exist? cache_file end end