require 'rubygems/exceptions'
require 'rubygems/package'
require 'rubygems/ext'
require 'rubygems/user_interaction'
require 'fileutils'
require 'thread'
class Gem::Installer
ENV_PATHS = %w[/usr/bin/env /bin/env]
CHDIR_MUTEX = Mutex.new
class ExtensionBuildError < Gem::InstallError; end
include Gem::UserInteraction
attr_reader :gem
attr_reader :bin_dir
attr_reader :gem_home
attr_reader :options
@path_warning = false
class << self
attr_accessor :path_warning
attr_writer :exec_format
def exec_format
@exec_format ||= Gem.default_exec_format
end
end
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
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 =~ /^ io.gets
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
def gem_dir
@gem_dir ||= File.join(gem_home, "gems", spec.full_name)
end
def spec
@spec ||= @package.spec
rescue Gem::Package::Error => e
raise Gem::InstallError, "invalid gem: #{e.message}"
end
def install
pre_install_checks
run_pre_install_hooks
FileUtils.rm_rf gem_dir
FileUtils.mkdir_p gem_dir
extract_files
build_extensions
write_build_info_file
run_post_build_hooks
generate_bin
write_spec
write_cache_file
say spec.post_install_message unless spec.post_install_message.nil?
spec.loaded_from = spec_file
Gem::Specification.add_spec spec unless Gem::Specification.include? spec
run_post_install_hooks
spec
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
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
def ensure_dependency(spec, dependency)
unless installation_satisfies_dependency? dependency then
raise Gem::InstallError, "#{spec.name} requires #{dependency}"
end
true
end
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
def unpack(directory)
@gem_dir = directory
extract_files
end
def spec_file
File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
end
def write_spec
open spec_file, 'w' do |file|
file.puts spec.to_ruby_for_cache
file.fsync rescue nil end
end
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
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
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
def generate_bin_script(filename, bindir)
bin_script_path = File.join bindir, formatted_program_filename(filename)
FileUtils.rm_f bin_script_path
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
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
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 shebang = first_line.sub(/\A\ opts = $1
shebang.strip! end
if which = Gem.configuration[:custom_shebang]
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
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
"#!#{@env_path} #{ruby_name}"
end
end
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
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
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
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
def process_options
@options = {
:bin_dir => nil,
:env_shebang => false,
:force => false,
:install_dir => Gem.dir,
:only_install_dir => false
}.merge options
@env_shebang = options[:env_shebang]
@force = options[:force]
@gem_home = options[:install_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]
@bin_dir = options[:bin_dir] || Gem.bindir(gem_home)
@development = options[:development]
@build_args = options[:build_args] || Gem::Command.build_args
end
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
def verify_gem_home(unpack = false)
FileUtils.mkdir_p gem_home
raise Gem::FilePermissionError, gem_home unless
unpack or File.writable?(gem_home)
end
def app_script_text(bin_file_name)
return <<-TEXT
#{shebang bin_file_name}
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
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
def build_extensions
return if spec.extensions.empty?
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
spec.extensions.each do |extension|
break if ran_rake
results = []
extension ||= ""
extension_dir = File.join gem_dir, File.dirname(extension)
builder = case extension
when /extconf/ then
Gem::Ext::ExtConfBuilder
when /configure/ then
Gem::Ext::ConfigureBuilder
when /rakefile/i, /mkrf_conf/i then
ran_rake = true
Gem::Ext::RakeBuilder
when /CMakeLists.txt/ then
Gem::Ext::CmakeBuilder
else
message = "No builder for extension '#{extension}'"
extension_build_error extension_dir, message
end
begin
FileUtils.mkdir_p dest_path
CHDIR_MUTEX.synchronize do
Dir.chdir extension_dir do
results = builder.build(extension, gem_dir, dest_path,
results, @build_args)
say results.join("\n") if Gem.configuration.really_verbose
end
end
rescue
extension_build_error(extension_dir, results.join("\n"), $@)
end
end
end
def extension_build_error(build_dir, output, backtrace = nil)
gem_make_out = File.join build_dir, 'gem_make.out'
open gem_make_out, 'wb' do |io| io.puts output end
message = <<-EOF
ERROR: Failed to build gem native extension.
#{output}
Gem files will remain installed in #{gem_dir} for inspection.
Results logged to #{gem_make_out}
EOF
raise ExtensionBuildError, message, backtrace
end
def extract_files
@package.extract_files gem_dir
end
def formatted_program_filename(filename)
if @format_executable then
self.class.exec_format % File.basename(filename)
else
filename
end
end
def dir
gem_dir.to_s
end
def pre_install_checks
verify_gem_home options[:unpack]
@security_policy = nil if
@force and @security_policy and not @security_policy.only_signed
ensure_loadable_spec
Gem.ensure_gem_subdirectories gem_home
return true if @force
ensure_required_ruby_version_met
ensure_required_rubygems_version_met
ensure_dependencies_met unless @ignore_dependencies
true
end
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
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