diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-11-29 06:52:18 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-11-29 06:52:18 +0000 |
commit | 9694bb8cac12969300692dac5a1cf7aa4e3a46cd (patch) | |
tree | c3cb423d701f7049ba9382de052e2a937cd1302d /lib/rubygems/installer.rb | |
parent | 3f606b7063fc7a8b191556365ad343a314719a8d (diff) | |
download | ruby-9694bb8cac12969300692dac5a1cf7aa4e3a46cd.tar.gz |
* lib/rubygems*: Updated to RubyGems 2.0
* test/rubygems*: ditto.
* common.mk (prelude): Updated for RubyGems 2.0 source rearrangement.
* tool/change_maker.rb: Allow invalid UTF-8 characters in source
files.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37976 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems/installer.rb')
-rw-r--r-- | lib/rubygems/installer.rb | 372 |
1 files changed, 242 insertions, 130 deletions
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 514316f099..2b7c821727 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -4,15 +4,13 @@ # See LICENSE.txt for permissions. #++ -require 'rubygems/format' require 'rubygems/exceptions' +require 'rubygems/package' require 'rubygems/ext' -require 'rubygems/require_paths_builder' require 'rubygems/user_interaction' ## -# The installer class processes RubyGem .gem files and installs the files -# contained in the .gem into the Gem.path. +# 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 @@ -39,8 +37,7 @@ class Gem::Installer include Gem::UserInteraction - include Gem::RequirePathsBuilder if Gem::QUICKLOADER_SUCKAGE - + # DOC: Missing docs or :nodoc:. attr_reader :gem ## @@ -67,6 +64,7 @@ class Gem::Installer attr_accessor :path_warning + # DOC: Missing docs or :nodoc:. attr_writer :exec_format # Defaults to use Ruby's program prefix and suffix. @@ -80,24 +78,36 @@ class Gem::Installer # 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. - # :ignore_dependencies:: Don't raise if a dependency is missing. - # :install_dir:: The directory to install the gem into. # :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 check_that_user_bin_dir_is_in_path @@ -105,28 +115,79 @@ class Gem::Installer end ## - # Lazy accessor for the spec's gem directory. + # 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 gem_dir - @gem_dir ||= spec.gem_dir.dup.untaint + def check_executable_overwrite filename # :nodoc: + return if @force + + generated_bin = File.join @bin_dir, 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 installer's Gem::Format instance. + # Lazy accessor for the spec's gem directory. - def format - begin - @format ||= Gem::Format.from_file_by_path gem, @security_policy - rescue Gem::Package::FormatError - raise Gem::InstallError, "invalid gem format for #{gem}" - end + def gem_dir + @gem_dir ||= File.join(gem_home, "gems", spec.full_name) end ## # Lazy accessor for the installer's spec. def spec - @spec ||= format.spec + @spec ||= @package.spec + rescue Gem::Package::Error => e + raise Gem::InstallError, "invalid gem: #{e.message}" end ## @@ -141,11 +202,7 @@ class Gem::Installer # specifications/<gem-version>.gemspec #=> the Gem::Specification def install - current_home = Gem.dir - current_path = Gem.paths.path - verify_gem_home(options[:unpack]) - Gem.use_paths gem_home, current_path # HACK: shouldn't need Gem.paths.path # If we're forcing the install then disable security unless the security # policy says that we only install signed gems. @@ -158,65 +215,96 @@ class Gem::Installer ensure_dependencies_met unless @ignore_dependencies end - Gem.pre_install_hooks.each do |hook| - result = hook.call self - - if result == 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 + run_pre_install_hooks Gem.ensure_gem_subdirectories gem_home # Completely remove any previous gem files - FileUtils.rm_rf(gem_dir) if File.exist? gem_dir + FileUtils.rm_rf(gem_dir) FileUtils.mkdir_p gem_dir extract_files build_extensions - Gem.post_build_hooks.each do |hook| - result = hook.call self - - if result == false then - FileUtils.rm_rf gem_dir + run_post_build_hooks - location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/ + generate_bin + write_spec - message = "post-build hook#{location} failed for #{spec.full_name}" - raise Gem::InstallError, message + unless @build_args.empty? + File.open spec.build_info_file, "w" do |f| + @build_args.each { |a| f.puts a } end end - generate_bin - write_spec - - write_require_paths_file_if_needed if Gem::QUICKLOADER_SUCKAGE + # TODO should be always cache the file? Other classes have options + # to controls if caching is done. + cache_file = File.join(gem_home, "cache", "#{spec.full_name}.gem") - cache_file = spec.cache_file FileUtils.cp gem, cache_file unless File.exist? cache_file say spec.post_install_message unless spec.post_install_message.nil? - spec.loaded_from = spec.spec_file + spec.loaded_from = spec_file 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 arround 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 spec - rescue Zlib::GzipFile::Error - raise Gem::InstallError, "gzip error installing #{gem}" - ensure - # conditional since we might be here because we're erroring out early. - if current_path - Gem.use_paths current_home, current_path + ## + # + # 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 @@ -235,9 +323,11 @@ class Gem::Installer end ## - # True if the gems in the source_index satisfy +dependency+. + # 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 @@ -246,18 +336,23 @@ class Gem::Installer def unpack(directory) @gem_dir = directory - @format = Gem::Format.from_file_by_path gem, @security_policy 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 + + ## # Writes the .gemspec specification (in Ruby) to the gem home's # specifications directory. def write_spec - file_name = spec.spec_file.untaint - - File.open(file_name, "w") do |file| + File.open(spec_file, "w") do |file| file.puts spec.to_ruby_for_cache end end @@ -277,34 +372,34 @@ class Gem::Installer end end + # DOC: Missing docs or :nodoc:. def generate_bin return if spec.executables.nil? or spec.executables.empty? - # 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. - bindir = @bin_dir || Gem.bindir(gem_home) - - Dir.mkdir bindir unless File.exist? bindir - raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir + 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.expand_path File.join(gem_dir, spec.bindir, filename) + bin_path = File.join gem_dir, spec.bindir, filename - unless File.exist? bin_path - warn "Hey?!?! Where did #{bin_path} go??" + 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, bindir + generate_bin_script filename, @bin_dir else - generate_bin_symlink filename, bindir + generate_bin_symlink filename, @bin_dir end + end end @@ -358,10 +453,21 @@ class Gem::Installer ## # 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 = spec.bin_file bin_file_name + path = File.join gem_dir, spec.bindir, bin_file_name first_line = File.open(path, "rb") {|file| file.gets} if /\A#!/ =~ first_line then @@ -371,7 +477,25 @@ class Gem::Installer shebang.strip! # Avoid nasty ^M issues. end - if not ruby_name then + 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}" @@ -382,6 +506,7 @@ class Gem::Installer 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 @@ -390,9 +515,10 @@ class Gem::Installer 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::Version.new(Gem::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." @@ -400,6 +526,7 @@ class Gem::Installer end end + # DOC: Missing docs or :nodoc:. def ensure_dependencies_met deps = spec.runtime_dependencies deps |= spec.development_dependencies if @development @@ -409,13 +536,14 @@ class Gem::Installer end end + # DOC: Missing docs or :nodoc:. def process_options @options = { :bin_dir => nil, :env_shebang => false, - :exec_format => false, :force => false, :install_dir => Gem.dir, + :only_install_dir => false }.merge options @env_shebang = options[:env_shebang] @@ -425,13 +553,18 @@ class Gem::Installer @format_executable = options[:format_executable] @security_policy = options[:security_policy] @wrappers = options[:wrappers] - @bin_dir = options[:bin_dir] + @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] - raise "NOTE: Installer option :source_index is dead" if - options[:source_index] + @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.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR @@ -449,6 +582,7 @@ class Gem::Installer end end + # DOC: Missing docs or :nodoc:. def verify_gem_home(unpack = false) FileUtils.mkdir_p gem_home raise Gem::FilePermissionError, gem_home unless @@ -499,7 +633,6 @@ GOTO :EOF :WinNT @"#{ruby}" "%~dpn0" %* TEXT - end ## @@ -508,7 +641,14 @@ TEXT def build_extensions return if spec.extensions.empty? - say "Building native extensions. This could take a while..." + + if @build_args.empty? + say "Building native extensions. This could take a while..." + else + say "Building native extensions with: '#{@build_args.join(' ')}'" + say "This could take a while..." + end + dest_path = File.join gem_dir, spec.require_paths.first ran_rake = false # only run rake once @@ -516,6 +656,9 @@ TEXT break if ran_rake results = [] + extension ||= "" + extension_dir = File.join gem_dir, File.dirname(extension) + builder = case extension when /extconf/ then Gem::Ext::ExtConfBuilder @@ -525,43 +668,41 @@ TEXT ran_rake = true Gem::Ext::RakeBuilder else - results = ["No builder for extension '#{extension}'"] - nil + message = "No builder for extension '#{extension}'" + extension_build_error extension_dir, message end - - extension_dir = begin - File.join gem_dir, File.dirname(extension) - rescue TypeError # extension == nil - gem_dir - end - - begin Dir.chdir extension_dir do - results = builder.build(extension, gem_dir, dest_path, results) + results = builder.build(extension, gem_dir, dest_path, + results, @build_args) say results.join("\n") if Gem.configuration.really_verbose end rescue - results = results.join "\n" + extension_build_error(extension_dir, results.join("\n")) + end + end + end - gem_make_out = File.join extension_dir, 'gem_make.out' + ## + # Logs the build +output+ in +build_dir+, then raises ExtensionBuildError. + + def extension_build_error(build_dir, output) + gem_make_out = File.join build_dir, 'gem_make.out' - open gem_make_out, 'wb' do |io| io.puts results end + open gem_make_out, 'wb' do |io| io.puts output end - message = <<-EOF + message = <<-EOF ERROR: Failed to build gem native extension. - #{results} + #{output} Gem files will remain installed in #{gem_dir} for inspection. Results logged to #{gem_make_out} EOF - raise ExtensionBuildError, message - end - end + raise ExtensionBuildError, message end ## @@ -570,36 +711,7 @@ EOF # Ensures that files can't be installed outside the gem directory. def extract_files - raise ArgumentError, "format required to extract from" if @format.nil? - - @format.file_entries.each do |entry, file_data| - path = entry['path'].untaint - - if path.start_with? "/" then # for extra sanity - raise Gem::InstallError, "attempt to install file into #{entry['path']}" - end - - path = File.expand_path File.join(gem_dir, path) - - unless path.start_with? gem_dir then - msg = "attempt to install file into %p under %s" % - [entry['path'], gem_dir] - raise Gem::InstallError, msg - end - - FileUtils.rm_rf(path) if File.exist? path - - dir = File.dirname path - FileUtils.mkdir_p dir unless File.exist? dir - - File.open(path, "wb") do |out| - out.write file_data - end - - FileUtils.chmod entry['mode'], path - - say path if Gem.configuration.really_verbose - end + @package.extract_files gem_dir end ## |