#-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. # See LICENSE.txt for permissions. #++ require 'fileutils' require 'rubygems' require 'rubygems/installer' require 'rubygems/source_info_cache' module Gem class RemoteInstaller include UserInteraction # options[:http_proxy]:: # * [String]: explicit specification of proxy; overrides any # environment variable setting # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS) # * :no_proxy: ignore environment variables and _don't_ # use a proxy # # * :cache_dir: override where downloaded gems are cached. def initialize(options={}) @options = options @source_index_hash = nil end # This method will install package_name onto the local system. # # gem_name:: # [String] Name of the Gem to install # # version_requirement:: # [default = ">= 0"] Gem version requirement to install # # Returns:: # an array of Gem::Specification objects, one for each gem installed. # def install(gem_name, version_requirement = Gem::Requirement.default, force = false, install_dir = Gem.dir) unless version_requirement.respond_to?(:satisfied_by?) version_requirement = Gem::Requirement.new [version_requirement] end installed_gems = [] begin spec, source = find_gem_to_install(gem_name, version_requirement) dependencies = find_dependencies_not_installed(spec.dependencies) installed_gems << install_dependencies(dependencies, force, install_dir) cache_dir = @options[:cache_dir] || File.join(install_dir, "cache") destination_file = File.join(cache_dir, spec.full_name + ".gem") download_gem(destination_file, source, spec) installer = new_installer(destination_file) installed_gems.unshift installer.install(force, install_dir) rescue RemoteInstallationSkipped => e alert_error e.message end installed_gems.flatten end # Return a hash mapping the available source names to the source # index of that source. def source_index_hash return @source_index_hash if @source_index_hash @source_index_hash = {} Gem::SourceInfoCache.cache_data.each do |source_uri, sic_entry| @source_index_hash[source_uri] = sic_entry.source_index end @source_index_hash end # Finds the Gem::Specification objects and the corresponding source URI # for gems matching +gem_name+ and +version_requirement+ def specs_n_sources_matching(gem_name, version_requirement) specs_n_sources = [] source_index_hash.each do |source_uri, source_index| specs = source_index.search(/^#{Regexp.escape gem_name}$/i, version_requirement) # TODO move to SourceIndex#search? ruby_version = Gem::Version.new RUBY_VERSION specs = specs.select do |spec| spec.required_ruby_version.nil? or spec.required_ruby_version.satisfied_by? ruby_version end specs.each { |spec| specs_n_sources << [spec, source_uri] } end if specs_n_sources.empty? then raise GemNotFoundException, "Could not find #{gem_name} (#{version_requirement}) in any repository" end specs_n_sources = specs_n_sources.sort_by { |gs,| gs.version }.reverse specs_n_sources end # Find a gem to be installed by interacting with the user. def find_gem_to_install(gem_name, version_requirement) specs_n_sources = specs_n_sources_matching gem_name, version_requirement top_3_versions = specs_n_sources.map{|gs| gs.first.version}.uniq[0..3] specs_n_sources.reject!{|gs| !top_3_versions.include?(gs.first.version)} binary_gems = specs_n_sources.reject { |item| item[0].platform.nil? || item[0].platform==Platform::RUBY } # only non-binary gems...return latest return specs_n_sources.first if binary_gems.empty? list = specs_n_sources.collect { |spec, source_uri| "#{spec.name} #{spec.version} (#{spec.platform})" } list << "Skip this gem" list << "Cancel installation" string, index = choose_from_list( "Select which gem to install for your platform (#{RUBY_PLATFORM})", list) if index.nil? or index == (list.size - 1) then raise RemoteInstallationCancelled, "Installation of #{gem_name} cancelled." end if index == (list.size - 2) then raise RemoteInstallationSkipped, "Installation of #{gem_name} skipped." end specs_n_sources[index] end def find_dependencies_not_installed(dependencies) to_install = [] dependencies.each do |dependency| srcindex = Gem::SourceIndex.from_installed_gems matches = srcindex.find_name(dependency.name, dependency.requirement_list) to_install.push dependency if matches.empty? end to_install end # Install all the given dependencies. Returns an array of # Gem::Specification objects, one for each dependency installed. # # TODO: For now, we recursively install, but this is not the right # way to do things (e.g. if a package fails to download, we # shouldn't install anything). def install_dependencies(dependencies, force, install_dir) return if @options[:ignore_dependencies] installed_gems = [] dependencies.each do |dep| if @options[:include_dependencies] || ask_yes_no("Install required dependency #{dep.name}?", true) remote_installer = RemoteInstaller.new @options installed_gems << remote_installer.install(dep.name, dep.version_requirements, force, install_dir) elsif force then # ignore else raise DependencyError, "Required dependency #{dep.name} not installed" end end installed_gems end def download_gem(destination_file, source, spec) return if File.exist? destination_file uri = source + "/gems/#{spec.full_name}.gem" response = Gem::RemoteFetcher.fetcher.fetch_path uri write_gem_to_file response, destination_file end def write_gem_to_file(body, destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) unless File.exist?(destination_file) File.open(destination_file, 'wb') do |out| out.write(body) end end def new_installer(gem) return Installer.new(gem, @options) end end end