aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/remote_installer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/remote_installer.rb')
-rw-r--r--lib/rubygems/remote_installer.rb195
1 files changed, 195 insertions, 0 deletions
diff --git a/lib/rubygems/remote_installer.rb b/lib/rubygems/remote_installer.rb
new file mode 100644
index 0000000000..e33fd548f2
--- /dev/null
+++ b/lib/rubygems/remote_installer.rb
@@ -0,0 +1,195 @@
+#--
+# 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
+
+ # <tt>options[:http_proxy]</tt>::
+ # * [String]: explicit specification of proxy; overrides any
+ # environment variable setting
+ # * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS)
+ # * <tt>:no_proxy</tt>: ignore environment variables and _don't_
+ # use a proxy
+ #
+ # * <tt>:cache_dir</tt>: 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