summaryrefslogtreecommitdiff
path: root/lib/rubygems/installer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/installer.rb')
-rw-r--r--lib/rubygems/installer.rb372
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
##