aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/rubygems.rb95
-rw-r--r--lib/rubygems/command_manager.rb1
-rw-r--r--lib/rubygems/commands/dependency_command.rb67
-rw-r--r--lib/rubygems/commands/environment_command.rb2
-rw-r--r--lib/rubygems/commands/fetch_command.rb4
-rw-r--r--lib/rubygems/commands/install_command.rb4
-rw-r--r--lib/rubygems/commands/list_command.rb58
-rw-r--r--lib/rubygems/commands/lock_command.rb2
-rw-r--r--lib/rubygems/commands/outdated_command.rb7
-rw-r--r--lib/rubygems/commands/pristine_command.rb47
-rw-r--r--lib/rubygems/commands/query_command.rb101
-rw-r--r--lib/rubygems/commands/sources_command.rb84
-rw-r--r--lib/rubygems/commands/specification_command.rb7
-rw-r--r--lib/rubygems/commands/stale_command.rb27
-rw-r--r--lib/rubygems/commands/update_command.rb57
-rw-r--r--lib/rubygems/config_file.rb40
-rwxr-xr-xlib/rubygems/custom_require.rb2
-rw-r--r--lib/rubygems/defaults.rb2
-rw-r--r--lib/rubygems/dependency.rb72
-rw-r--r--lib/rubygems/dependency_installer.rb80
-rw-r--r--lib/rubygems/dependency_list.rb2
-rw-r--r--lib/rubygems/doc_manager.rb10
-rw-r--r--lib/rubygems/indexer.rb352
-rw-r--r--lib/rubygems/install_update_options.rb6
-rw-r--r--lib/rubygems/installer.rb47
-rw-r--r--lib/rubygems/local_remote_options.rb31
-rw-r--r--lib/rubygems/platform.rb16
-rw-r--r--lib/rubygems/remote_fetcher.rb230
-rw-r--r--lib/rubygems/requirement.rb28
-rw-r--r--lib/rubygems/rubygems_version.rb2
-rw-r--r--lib/rubygems/server.rb347
-rw-r--r--lib/rubygems/source_index.rb62
-rw-r--r--lib/rubygems/spec_fetcher.rb251
-rw-r--r--lib/rubygems/specification.rb143
-rw-r--r--lib/rubygems/test_utilities.rb120
-rw-r--r--lib/rubygems/uninstaller.rb5
-rw-r--r--lib/rubygems/user_interaction.rb223
-rw-r--r--lib/rubygems/version.rb44
38 files changed, 1980 insertions, 698 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 1cb205cbc9..4f2cc94ae0 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -67,11 +67,14 @@ module Gem
:RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"],
:arch => RbConfig::CONFIG["arch"],
:bindir => RbConfig::CONFIG["bindir"],
+ :datadir => RbConfig::CONFIG["datadir"],
:libdir => RbConfig::CONFIG["libdir"],
:ruby_install_name => RbConfig::CONFIG["ruby_install_name"],
:ruby_version => RbConfig::CONFIG["ruby_version"],
:sitedir => RbConfig::CONFIG["sitedir"],
- :sitelibdir => RbConfig::CONFIG["sitelibdir"]
+ :sitelibdir => RbConfig::CONFIG["sitelibdir"],
+ :vendordir => RbConfig::CONFIG["vendordir"] ,
+ :vendorlibdir => RbConfig::CONFIG["vendorlibdir"]
)
DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES)
@@ -137,7 +140,7 @@ module Gem
unless matches.any? { |spec| spec.version == existing_spec.version } then
raise Gem::Exception,
- "can't activate #{gem}, already activated #{existing_spec.full_name}]"
+ "can't activate #{gem}, already activated #{existing_spec.full_name}"
end
return false
@@ -151,7 +154,7 @@ module Gem
@loaded_specs[spec.name] = spec
# Load dependent gems first
- spec.dependencies.each do |dep_gem|
+ spec.runtime_dependencies.each do |dep_gem|
activate dep_gem
end
@@ -204,6 +207,19 @@ module Gem
private_class_method :all_partials
##
+ # See if a given gem is available.
+
+ def self.available?(gem, *requirements)
+ requirements = Gem::Requirement.default if requirements.empty?
+
+ unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
+ gem = Gem::Dependency.new(gem, requirements)
+ end
+
+ !Gem.source_index.search(gem).empty?
+ end
+
+ ##
# The mode needed to read a file as straight binary.
def self.binary_mode
@@ -268,6 +284,13 @@ module Gem
end
##
+ # A Zlib::Deflate.deflate wrapper
+
+ def self.deflate(data)
+ Zlib::Deflate.deflate data
+ end
+
+ ##
# The path where gems are to be installed.
def self.dir
@@ -346,6 +369,33 @@ module Gem
private_class_method :find_home
##
+ # Zlib::GzipReader wrapper that unzips +data+.
+
+ def self.gunzip(data)
+ data = StringIO.new data
+
+ Zlib::GzipReader.new(data).read
+ end
+
+ ##
+ # Zlib::GzipWriter wrapper that zips +data+.
+
+ def self.gzip(data)
+ zipped = StringIO.new
+
+ Zlib::GzipWriter.wrap zipped do |io| io.write data end
+
+ zipped.string
+ end
+
+ ##
+ # A Zlib::Inflate#inflate wrapper
+
+ def self.inflate(data)
+ Zlib::Inflate.inflate data
+ end
+
+ ##
# Return a list of all possible load paths for the latest version for all
# gems in the Gem installation.
@@ -438,7 +488,11 @@ module Gem
@gem_path ||= nil
unless @gem_path then
- paths = [ENV['GEM_PATH']] || [default_path]
+ paths = if ENV['GEM_PATH'] then
+ [ENV['GEM_PATH']]
+ else
+ [default_path]
+ end
if defined?(APPLE_GEM_HOME) and not ENV['GEM_PATH'] then
paths << APPLE_GEM_HOME
@@ -459,7 +513,7 @@ module Gem
##
# Array of platforms this RubyGems supports.
-
+
def self.platforms
@platforms ||= []
if @platforms.empty?
@@ -586,13 +640,13 @@ module Gem
def self.set_paths(gpaths)
if gpaths
@gem_path = gpaths.split(File::PATH_SEPARATOR)
-
+
if File::ALT_SEPARATOR then
@gem_path.map! do |path|
path.gsub File::ALT_SEPARATOR, File::SEPARATOR
end
end
-
+
@gem_path << Gem.dir
else
@gem_path = [Gem.dir]
@@ -683,24 +737,25 @@ module Gem
end
-end
+ MARSHAL_SPEC_DIR = "quick/Marshal.#{Gem.marshal_version}/"
-# Modify the non-gem version of datadir to handle gem package names.
+ YAML_SPEC_DIR = 'quick/'
-require 'rbconfig/datadir'
+end
-module Config # :nodoc:
+module Config
+ # :stopdoc:
class << self
- alias gem_original_datadir datadir
-
# Return the path to the data directory associated with the named
# package. If the package is loaded as a gem, return the gem
# specific data directory. Otherwise return a path to the share
# area as define by "#{ConfigMap[:datadir]}/#{package_name}".
def datadir(package_name)
- Gem.datadir(package_name) || Config.gem_original_datadir(package_name)
+ Gem.datadir(package_name) ||
+ File.join(Gem::ConfigMap[:datadir], package_name)
end
end
+ # :startdoc:
end
require 'rubygems/exceptions'
@@ -712,6 +767,18 @@ require 'rubygems/source_index' # Needed for Kernel#gem
require 'rubygems/platform'
require 'rubygems/builder' # HACK: Needed for rake's package task.
+begin
+ require 'rubygems/defaults/operating_system'
+rescue LoadError
+end
+
+if defined?(RUBY_ENGINE) then
+ begin
+ require "rubygems/defaults/#{RUBY_ENGINE}"
+ rescue LoadError
+ end
+end
+
if RUBY_VERSION < '1.9' then
require 'rubygems/custom_require'
end
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index ee55d5a200..dd9a1aee15 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -46,6 +46,7 @@ module Gem
register_command :server
register_command :sources
register_command :specification
+ register_command :stale
register_command :uninstall
register_command :unpack
register_command :update
diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb
index 1a43505d7c..8fae87c90f 100644
--- a/lib/rubygems/commands/dependency_command.rb
+++ b/lib/rubygems/commands/dependency_command.rb
@@ -46,37 +46,67 @@ class Gem::Commands::DependencyCommand < Gem::Command
options[:args] << '.' if options[:args].empty?
specs = {}
- source_indexes = []
+ source_indexes = Hash.new do |h, source_uri|
+ h[source_uri] = Gem::SourceIndex.new
+ end
- if local? then
- source_indexes << Gem::SourceIndex.from_installed_gems
+ pattern = /\A#{Regexp.union(*options[:args])}/
+ dependency = Gem::Dependency.new pattern, options[:version]
+
+ if options[:reverse_dependencies] and remote? and not local? then
+ alert_error 'Only reverse dependencies for local gems are supported.'
+ terminate_interaction 1
end
- if remote? then
- Gem::SourceInfoCache.cache_data.map do |_, sice|
- source_indexes << sice.source_index
+ if local? then
+ Gem.source_index.search(dependency).each do |spec|
+ source_indexes[:local].add_spec spec
end
end
- options[:args].each do |name|
- new_specs = nil
- source_indexes.each do |source_index|
- new_specs = find_gems(name, source_index)
+ if remote? and not options[:reverse_dependencies] then
+ fetcher = Gem::SpecFetcher.fetcher
+
+ begin
+ fetcher.find_matching(dependency).each do |spec_tuple, source_uri|
+ spec = fetcher.fetch_spec spec_tuple, URI.parse(source_uri)
+
+ source_indexes[source_uri].add_spec spec
+ end
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise unless fetcher.warn_legacy e do
+ require 'rubygems/source_info_cache'
+
+ specs = Gem::SourceInfoCache.search_with_source dependency, false
+
+ specs.each do |spec, source_uri|
+ source_indexes[source_uri].add_spec spec
+ end
+ end
end
+ end
- say "No match found for #{name} (#{options[:version]})" if
- new_specs.empty?
+ if source_indexes.empty? then
+ patterns = options[:args].join ','
+ say "No gems found matching #{patterns} (#{options[:version]})" if
+ Gem.configuration.verbose
- specs = specs.merge new_specs
+ terminate_interaction 1
end
- terminate_interaction 1 if specs.empty?
+ specs = {}
+
+ source_indexes.values.each do |source_index|
+ source_index.gems.each do |name, spec|
+ specs[spec.full_name] = [source_index, spec]
+ end
+ end
reverse = Hash.new { |h, k| h[k] = [] }
if options[:reverse_dependencies] then
- specs.values.each do |source_index, spec|
- reverse[spec.full_name] = find_reverse_dependencies spec, source_index
+ specs.values.each do |_, spec|
+ reverse[spec.full_name] = find_reverse_dependencies spec
end
end
@@ -118,10 +148,10 @@ class Gem::Commands::DependencyCommand < Gem::Command
end
# Retuns list of [specification, dep] that are satisfied by spec.
- def find_reverse_dependencies(spec, source_index)
+ def find_reverse_dependencies(spec)
result = []
- source_index.each do |name, sp|
+ Gem.source_index.each do |name, sp|
sp.dependencies.each do |dep|
dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep
@@ -146,5 +176,6 @@ class Gem::Commands::DependencyCommand < Gem::Command
specs
end
+
end
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index 342f93ca54..a67c00bfd6 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -51,6 +51,8 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
out << " - RUBY EXECUTABLE: #{Gem.ruby}\n"
+ out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n"
+
out << " - RUBYGEMS PLATFORMS:\n"
Gem.platforms.each do |platform|
out << " - #{platform}\n"
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index ccedc45401..76c9924e6b 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -33,12 +33,14 @@ class Gem::Commands::FetchCommand < Gem::Command
def execute
version = options[:version] || Gem::Requirement.default
+ all = Gem::Requirement.default
gem_names = get_all_gem_names
gem_names.each do |gem_name|
dep = Gem::Dependency.new gem_name, version
- specs_and_sources = Gem::SourceInfoCache.search_with_source dep, true
+
+ specs_and_sources = Gem::SpecFetcher.fetcher.fetch dep, all
specs_and_sources.sort_by { |spec,| spec.version }
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index ce0bc6ba04..48cd3869f9 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -16,7 +16,6 @@ class Gem::Commands::InstallCommand < Gem::Command
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
:generate_rdoc => true,
:generate_ri => true,
- :install_dir => Gem.dir,
:format_executable => false,
:test => false,
:version => Gem::Requirement.default,
@@ -62,7 +61,8 @@ class Gem::Commands::InstallCommand < Gem::Command
:install_dir => options[:install_dir],
:security_policy => options[:security_policy],
:wrappers => options[:wrappers],
- :bin_dir => options[:bin_dir]
+ :bin_dir => options[:bin_dir],
+ :development => options[:development],
}
exit_code = 0
diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb
index f8b377fcde..f3e5da9551 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -1,33 +1,35 @@
require 'rubygems/command'
require 'rubygems/commands/query_command'
-module Gem
- module Commands
- class ListCommand < QueryCommand
-
- def initialize
- super 'list', 'Display gems whose name starts with STRING'
-
- remove_option('--name-matches')
- end
-
- def arguments # :nodoc:
- "STRING start of gem name to look for"
- end
-
- def defaults_str # :nodoc:
- "--local --no-details"
- end
-
- def usage # :nodoc:
- "#{program_name} [STRING]"
- end
-
- def execute
- string = get_one_optional_argument || ''
- options[:name] = /^#{string}/i
- super
- end
- end
+##
+# An alternate to Gem::Commands::QueryCommand that searches for gems starting
+# with the the supplied argument.
+
+class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
+
+ def initialize
+ super 'list', 'Display gems whose name starts with STRING'
+
+ remove_option('--name-matches')
+ end
+
+ def arguments # :nodoc:
+ "STRING start of gem name to look for"
+ end
+
+ def defaults_str # :nodoc:
+ "--local --no-details"
end
+
+ def usage # :nodoc:
+ "#{program_name} [STRING]"
+ end
+
+ def execute
+ string = get_one_optional_argument || ''
+ options[:name] = /^#{string}/i
+ super
+ end
+
end
+
diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb
index 3a3dcc0c6b..6be2774e92 100644
--- a/lib/rubygems/commands/lock_command.rb
+++ b/lib/rubygems/commands/lock_command.rb
@@ -80,7 +80,7 @@ lock it down to the exact version.
say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name]
locked[spec.name] = true
- spec.dependencies.each do |dep|
+ spec.runtime_dependencies.each do |dep|
next if locked[dep.name]
candidates = Gem.source_index.search dep.name, dep.requirement_list
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index 9c0062019b..1cd1087dd1 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -1,6 +1,6 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
-require 'rubygems/source_info_cache'
+require 'rubygems/spec_fetcher'
require 'rubygems/version_option'
class Gem::Commands::OutdatedCommand < Gem::Command
@@ -20,8 +20,11 @@ class Gem::Commands::OutdatedCommand < Gem::Command
locals.outdated.sort.each do |name|
local = locals.search(/^#{name}$/).last
- remotes = Gem::SourceInfoCache.search_with_source(/^#{name}$/, true)
+
+ dep = Gem::Dependency.new local.name, ">= #{local.version}"
+ remotes = Gem::SpecFetcher.fetcher.fetch dep
remote = remotes.last.first
+
say "#{local.name} (#{local.version} < #{remote.version})"
end
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index bbea835133..3e55a1bb30 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -82,51 +82,10 @@ revert the gem.
end
# TODO use installer options
- installer = Gem::Installer.new gem, :wrappers => true
+ installer = Gem::Installer.new gem, :wrappers => true, :force => true
+ installer.install
- gem_file = File.join install_dir, "cache", "#{spec.full_name}.gem"
-
- security_policy = nil # TODO use installer option
-
- format = Gem::Format.from_file_by_path gem_file, security_policy
-
- target_directory = File.join(install_dir, "gems", format.spec.full_name)
- target_directory.untaint
-
- pristine_files = format.file_entries.collect { |data| data[0]["path"] }
- file_map = {}
-
- format.file_entries.each do |entry, file_data|
- file_map[entry["path"]] = file_data
- end
-
- Dir.chdir target_directory do
- deployed_files = Dir.glob(File.join("**", "*")) +
- Dir.glob(File.join("**", ".*"))
-
- pristine_files = pristine_files.map { |f| File.expand_path f }
- deployed_files = deployed_files.map { |f| File.expand_path f }
-
- to_redeploy = (pristine_files - deployed_files)
- to_redeploy = to_redeploy.map { |path| path.untaint}
-
- if to_redeploy.length > 0 then
- say "Restoring #{to_redeploy.length} file#{to_redeploy.length == 1 ? "" : "s"} to #{spec.full_name}..."
-
- to_redeploy.each do |path|
- say " #{path}"
- FileUtils.mkdir_p File.dirname(path)
- File.open(path, "wb") do |out|
- out.write file_map[path]
- end
- end
- else
- say "#{spec.full_name} is in pristine condition"
- end
- end
-
- installer.generate_bin
- installer.build_extensions
+ say "Restored #{spec.full_name}"
end
end
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index ea83b93bbb..cc81f3f07e 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -1,6 +1,6 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
-require 'rubygems/source_info_cache'
+require 'rubygems/spec_fetcher'
require 'rubygems/version_option'
class Gem::Commands::QueryCommand < Gem::Command
@@ -74,7 +74,13 @@ class Gem::Commands::QueryCommand < Gem::Command
say "*** LOCAL GEMS ***"
say
- output_query_results Gem.source_index.search(name)
+ specs = Gem.source_index.search name
+
+ spec_tuples = specs.map do |spec|
+ [[spec.name, spec.version, spec.original_platform, spec], :local]
+ end
+
+ output_query_results spec_tuples
end
if remote? then
@@ -84,13 +90,26 @@ class Gem::Commands::QueryCommand < Gem::Command
all = options[:all]
+ dep = Gem::Dependency.new name, Gem::Requirement.default
begin
- Gem::SourceInfoCache.cache all
- rescue Gem::RemoteFetcher::FetchError
- # no network
+ fetcher = Gem::SpecFetcher.fetcher
+ spec_tuples = fetcher.find_matching dep, all, false
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise unless fetcher.warn_legacy e do
+ require 'rubygems/source_info_cache'
+
+ dep.name = '' if dep.name == //
+
+ specs = Gem::SourceInfoCache.search_with_source dep, false, all
+
+ spec_tuples = specs.map do |spec, source_uri|
+ [[spec.name, spec.version, spec.original_platform, spec],
+ source_uri]
+ end
+ end
end
- output_query_results Gem::SourceInfoCache.search(name, false, all)
+ output_query_results spec_tuples
end
end
@@ -104,28 +123,30 @@ class Gem::Commands::QueryCommand < Gem::Command
!Gem.source_index.search(dep).empty?
end
- def output_query_results(gemspecs)
+ def output_query_results(spec_tuples)
output = []
- gem_list_with_version = {}
+ versions = Hash.new { |h,name| h[name] = [] }
- gemspecs.flatten.each do |gemspec|
- gem_list_with_version[gemspec.name] ||= []
- gem_list_with_version[gemspec.name] << gemspec
+ spec_tuples.each do |spec_tuple, source_uri|
+ versions[spec_tuple.first] << [spec_tuple, source_uri]
end
- gem_list_with_version = gem_list_with_version.sort_by do |name, spec|
+ versions = versions.sort_by do |(name,),|
name.downcase
end
- gem_list_with_version.each do |gem_name, list_of_matching|
- list_of_matching = list_of_matching.sort_by { |x| x.version.to_ints }.reverse
- seen_versions = {}
+ versions.each do |gem_name, matching_tuples|
+ matching_tuples = matching_tuples.sort_by do |(name, version,),|
+ version
+ end.reverse
- list_of_matching.delete_if do |item|
- if seen_versions[item.version] then
+ seen = {}
+
+ matching_tuples.delete_if do |(name, version,),|
+ if seen[version] then
true
else
- seen_versions[item.version] = true
+ seen[version] = true
false
end
end
@@ -133,12 +154,50 @@ class Gem::Commands::QueryCommand < Gem::Command
entry = gem_name.dup
if options[:versions] then
- versions = list_of_matching.map { |s| s.version }.uniq
+ versions = matching_tuples.map { |(name, version,),| version }.uniq
entry << " (#{versions.join ', '})"
end
- entry << "\n" << format_text(list_of_matching[0].summary, 68, 4) if
- options[:details]
+ if options[:details] then
+ detail_tuple = matching_tuples.first
+
+ spec = if detail_tuple.first.length == 4 then
+ detail_tuple.first.last
+ else
+ uri = URI.parse detail_tuple.last
+ Gem::SpecFetcher.fetcher.fetch_spec detail_tuple.first, uri
+ end
+
+ entry << "\n"
+ authors = "Author#{spec.authors.length > 1 ? 's' : ''}: "
+ authors << spec.authors.join(', ')
+ entry << format_text(authors, 68, 4)
+
+ if spec.rubyforge_project and not spec.rubyforge_project.empty? then
+ rubyforge = "Rubyforge: http://rubyforge.org/projects/#{spec.rubyforge_project}"
+ entry << "\n" << format_text(rubyforge, 68, 4)
+ end
+
+ if spec.homepage and not spec.homepage.empty? then
+ entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4)
+ end
+
+ if spec.loaded_from then
+ if matching_tuples.length == 1 then
+ loaded_from = File.dirname File.dirname(spec.loaded_from)
+ entry << "\n" << " Installed at: #{loaded_from}"
+ else
+ label = 'Installed at'
+ matching_tuples.each do |(_,version,_,s),|
+ loaded_from = File.dirname File.dirname(s.loaded_from)
+ entry << "\n" << " #{label} (#{version}): #{loaded_from}"
+ label = ' ' * label.length
+ end
+ end
+ end
+
+ entry << "\n\n" << format_text(spec.summary, 68, 4)
+ end
output << entry
end
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index 1558d79b8b..f45438463c 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -1,7 +1,8 @@
+require 'fileutils'
require 'rubygems/command'
require 'rubygems/remote_fetcher'
require 'rubygems/source_info_cache'
-require 'rubygems/source_info_cache_entry'
+require 'rubygems/spec_fetcher'
class Gem::Commands::SourcesCommand < Gem::Command
@@ -21,14 +22,14 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:remove] = value
end
- add_option '-u', '--update', 'Update source cache' do |value, options|
- options[:update] = value
- end
-
add_option '-c', '--clear-all',
'Remove all sources (clear the cache)' do |value, options|
options[:clear_all] = value
end
+
+ add_option '-u', '--update', 'Update source cache' do |value, options|
+ options[:update] = value
+ end
end
def defaults_str
@@ -36,9 +37,23 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
def execute
- options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update])
+ options[:list] = !(options[:add] ||
+ options[:clear_all] ||
+ options[:remove] ||
+ options[:update])
if options[:clear_all] then
+ path = Gem::SpecFetcher.fetcher.dir
+ FileUtils.rm_rf path
+
+ if not File.exist?(path) then
+ say "*** Removed specs cache ***"
+ elsif not File.writable?(path) then
+ say "*** Unable to remove source cache (write protected) ***"
+ else
+ say "*** Unable to remove source cache ***"
+ end
+
sic = Gem::SourceInfoCache
remove_cache_file 'user', sic.user_cache_file
remove_cache_file 'latest user', sic.latest_user_cache_file
@@ -48,15 +63,10 @@ class Gem::Commands::SourcesCommand < Gem::Command
if options[:add] then
source_uri = options[:add]
+ uri = URI.parse source_uri
- sice = Gem::SourceInfoCacheEntry.new nil, nil
begin
- sice.refresh source_uri, true
-
- Gem::SourceInfoCache.cache_data[source_uri] = sice
- Gem::SourceInfoCache.cache.update
- Gem::SourceInfoCache.cache.flush
-
+ Gem::SpecFetcher.fetcher.load_specs uri, 'specs'
Gem.sources << source_uri
Gem.configuration.write
@@ -64,15 +74,24 @@ class Gem::Commands::SourcesCommand < Gem::Command
rescue URI::Error, ArgumentError
say "#{source_uri} is not a URI"
rescue Gem::RemoteFetcher::FetchError => e
- say "Error fetching #{source_uri}:\n\t#{e.message}"
- end
- end
+ yaml_uri = uri + 'yaml'
+ gem_repo = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri rescue false
- if options[:update] then
- Gem::SourceInfoCache.cache true
- Gem::SourceInfoCache.cache.flush
+ if e.uri =~ /specs\.#{Regexp.escape Gem.marshal_version}\.gz$/ and
+ gem_repo then
- say "source cache successfully updated"
+ alert_warning <<-EOF
+RubyGems 1.2+ index not found for:
+\t#{source_uri}
+
+Will cause RubyGems to revert to legacy indexes, degrading performance.
+ EOF
+
+ say "#{source_uri} added to sources"
+ else
+ say "Error fetching #{source_uri}:\n\t#{e.message}"
+ end
+ end
end
if options[:remove] then
@@ -81,14 +100,6 @@ class Gem::Commands::SourcesCommand < Gem::Command
unless Gem.sources.include? source_uri then
say "source #{source_uri} not present in cache"
else
- begin # HACK figure out how to get the cache w/o update
- Gem::SourceInfoCache.cache
- rescue Gem::RemoteFetcher::FetchError
- end
-
- Gem::SourceInfoCache.cache_data.delete source_uri
- Gem::SourceInfoCache.cache.update
- Gem::SourceInfoCache.cache.flush
Gem.sources.delete source_uri
Gem.configuration.write
@@ -96,6 +107,23 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
end
+ if options[:update] then
+ fetcher = Gem::SpecFetcher.fetcher
+
+ if fetcher.legacy_repos.empty? then
+ Gem.sources.each do |source_uri|
+ source_uri = URI.parse source_uri
+ fetcher.load_specs source_uri, 'specs'
+ fetcher.load_specs source_uri, 'latest_specs'
+ end
+ else
+ Gem::SourceInfoCache.cache true
+ Gem::SourceInfoCache.cache.flush
+ end
+
+ say "source cache successfully updated"
+ end
+
if options[:list] then
say "*** CURRENT SOURCES ***"
say
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index 7c8598e53b..689f2560c9 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -52,9 +52,10 @@ class Gem::Commands::SpecificationCommand < Gem::Command
end
if remote? then
- Gem::SourceInfoCache.cache_data.each do |_,sice|
- specs.push(*sice.source_index.search(gem, options[:version]))
- end
+ dep = Gem::Dependency.new gem, options[:version]
+ found = Gem::SpecFetcher.fetcher.fetch dep
+
+ specs.push(*found.map { |spec,| spec })
end
if specs.empty? then
diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb
new file mode 100644
index 0000000000..78cbdcc00a
--- /dev/null
+++ b/lib/rubygems/commands/stale_command.rb
@@ -0,0 +1,27 @@
+require 'rubygems/command'
+
+class Gem::Commands::StaleCommand < Gem::Command
+ def initialize
+ super('stale', 'List gems along with access times')
+ end
+
+ def usage # :nodoc:
+ "#{program_name}"
+ end
+
+ def execute
+ gem_to_atime = {}
+ Gem.source_index.each do |name, spec|
+ Dir["#{spec.full_gem_path}/**/*.*"].each do |file|
+ next if File.directory?(file)
+ stat = File.stat(file)
+ gem_to_atime[name] ||= stat.atime
+ gem_to_atime[name] = stat.atime if gem_to_atime[name] < stat.atime
+ end
+ end
+
+ gem_to_atime.sort_by { |_, atime| atime }.each do |name, atime|
+ say "#{name} at #{atime.strftime '%c'}"
+ end
+ end
+end
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index 31a97c4844..78baa8ba56 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -2,7 +2,7 @@ require 'rubygems/command'
require 'rubygems/command_manager'
require 'rubygems/install_update_options'
require 'rubygems/local_remote_options'
-require 'rubygems/source_info_cache'
+require 'rubygems/spec_fetcher'
require 'rubygems/version_option'
require 'rubygems/commands/install_command'
@@ -15,11 +15,10 @@ class Gem::Commands::UpdateCommand < Gem::Command
def initialize
super 'update',
'Update the named gems (or all installed gems) in the local repository',
- :generate_rdoc => true,
- :generate_ri => true,
- :force => false,
- :test => false,
- :install_dir => Gem.dir
+ :generate_rdoc => true,
+ :generate_ri => true,
+ :force => false,
+ :test => false
add_install_update_options
@@ -60,21 +59,13 @@ class Gem::Commands::UpdateCommand < Gem::Command
hig = {} # highest installed gems
- Gem::SourceIndex.from_installed_gems.each do |name, spec|
+ Gem.source_index.each do |name, spec|
if hig[spec.name].nil? or hig[spec.name].version < spec.version then
hig[spec.name] = spec
end
end
- pattern = if options[:args].empty? then
- //
- else
- Regexp.union(*options[:args])
- end
-
- remote_gemspecs = Gem::SourceInfoCache.search pattern
-
- gems_to_update = which_to_update hig, remote_gemspecs
+ gems_to_update = which_to_update hig, options[:args]
updated = []
@@ -135,20 +126,42 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
end
- def which_to_update(highest_installed_gems, remote_gemspecs)
+ def which_to_update(highest_installed_gems, gem_names)
result = []
highest_installed_gems.each do |l_name, l_spec|
- matching_gems = remote_gemspecs.select do |spec|
- spec.name == l_name and Gem.platforms.any? do |platform|
- platform == spec.platform
+ next if not gem_names.empty? and
+ gem_names.all? { |name| /#{name}/ !~ l_spec.name }
+
+ dependency = Gem::Dependency.new l_spec.name, "> #{l_spec.version}"
+
+ begin
+ fetcher = Gem::SpecFetcher.fetcher
+ spec_tuples = fetcher.find_matching dependency
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise unless fetcher.warn_legacy e do
+ require 'rubygems/source_info_cache'
+
+ dependency.name = '' if dependency.name == //
+
+ specs = Gem::SourceInfoCache.search_with_source dependency
+
+ spec_tuples = specs.map do |spec, source_uri|
+ [[spec.name, spec.version, spec.original_platform], source_uri]
+ end
end
end
- highest_remote_gem = matching_gems.sort_by { |spec| spec.version }.last
+ matching_gems = spec_tuples.select do |(name, version, platform),|
+ name == l_name and Gem::Platform.match platform
+ end
+
+ highest_remote_gem = matching_gems.sort_by do |(name, version),|
+ version
+ end.last
if highest_remote_gem and
- l_spec.version < highest_remote_gem.version then
+ l_spec.version < highest_remote_gem.first[1] then
result << l_name
end
end
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index 5bca0bd14e..c657bf7f01 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -18,6 +18,22 @@ class Gem::ConfigFile
DEFAULT_VERBOSITY = true
DEFAULT_UPDATE_SOURCES = true
+ system_config_path =
+ begin
+ require 'Win32API'
+
+ CSIDL_COMMON_APPDATA = 0x0023
+ path = 0.chr * 260
+ SHGetFolderPath = Win32API.new 'shell32', 'SHGetFolderPath', 'LLLLP', 'L'
+ SHGetFolderPath.call 0, CSIDL_COMMON_APPDATA, 0, 1, path
+
+ path.strip
+ rescue LoadError
+ '/etc'
+ end
+
+ SYSTEM_WIDE_CONFIG_FILE = File.join system_config_path, 'gemrc'
+
# List of arguments supplied to the config file object.
attr_reader :args
@@ -81,18 +97,8 @@ class Gem::ConfigFile
@verbose = DEFAULT_VERBOSITY
@update_sources = DEFAULT_UPDATE_SOURCES
- begin
- # HACK $SAFE ok?
- @hash = open(config_file_name.dup.untaint) {|f| YAML.load(f) }
- rescue ArgumentError
- warn "Failed to load #{config_file_name}"
- rescue Errno::ENOENT
- # Ignore missing config file error.
- rescue Errno::EACCES
- warn "Failed to load #{config_file_name} due to permissions problem."
- end
-
- @hash ||= {}
+ @hash = load_file(SYSTEM_WIDE_CONFIG_FILE)
+ @hash.merge!(load_file(config_file_name.dup.untaint))
# HACK these override command-line args, which is bad
@backtrace = @hash[:backtrace] if @hash.key? :backtrace
@@ -105,6 +111,16 @@ class Gem::ConfigFile
handle_arguments arg_list
end
+ def load_file(filename)
+ begin
+ YAML.load(File.read(filename)) if filename and File.exist?(filename)
+ rescue ArgumentError
+ warn "Failed to load #{config_file_name}"
+ rescue Errno::EACCES
+ warn "Failed to load #{config_file_name} due to permissions problem."
+ end or {}
+ end
+
# True if the backtrace option has been specified, or debug is on.
def backtrace
@backtrace or $DEBUG
diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb
index 5ff65afb14..90e6b53959 100755
--- a/lib/rubygems/custom_require.rb
+++ b/lib/rubygems/custom_require.rb
@@ -26,7 +26,7 @@ module Kernel
def require(path) # :nodoc:
gem_original_require path
rescue LoadError => load_error
- if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and
+ if load_error.message =~ /#{Regexp.escape path}\z/ and
spec = Gem.searcher.find(path) then
Gem.activate(spec.name, "= #{spec.version}")
gem_original_require path
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index 3864e5faca..914b9f777f 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -2,7 +2,7 @@ module Gem
# An Array of the default sources that come with RubyGems.
def self.default_sources
- %w[http://gems.rubyforge.org]
+ %w[http://gems.rubyforge.org/]
end
# Default home directory path to be used if an alternate value is not
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index be731d564e..7b9904df55 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -8,24 +8,54 @@ require 'rubygems'
##
# The Dependency class holds a Gem name and a Gem::Requirement
+
class Gem::Dependency
+ ##
+ # Valid dependency types.
+ #--
+ # When this list is updated, be sure to change
+ # Gem::Specification::CURRENT_SPECIFICATION_VERSION as well.
+
+ TYPES = [
+ :development,
+ :runtime,
+ ]
+
+ ##
+ # Dependency name or regular expression.
+
attr_accessor :name
+ ##
+ # Dependency type.
+
+ attr_reader :type
+
+ ##
+ # Dependent versions.
+
attr_writer :version_requirements
+ ##
+ # Orders dependencies by name only.
+
def <=>(other)
[@name] <=> [other.name]
end
##
- # Constructs the dependency
- #
- # name:: [String] name of the Gem
- # version_requirements:: [String Array] version requirement (e.g. ["> 1.2"])
- #
- def initialize(name, version_requirements)
+ # Constructs a dependency with +name+ and +requirements+.
+
+ def initialize(name, version_requirements, type=:runtime)
@name = name
+
+ unless TYPES.include? type
+ raise ArgumentError, "Valid types are #{TYPES.inspect}, not #{@type.inspect}"
+ end
+
+ @type = type
+
@version_requirements = Gem::Requirement.create version_requirements
@version_requirement = nil # Avoid warnings.
end
@@ -48,17 +78,41 @@ class Gem::Dependency
end
def to_s # :nodoc:
- "#{name} (#{version_requirements})"
+ "#{name} (#{version_requirements}, #{@type || :runtime})"
end
def ==(other) # :nodoc:
self.class === other &&
self.name == other.name &&
+ self.type == other.type &&
self.version_requirements == other.version_requirements
end
- def hash
- name.hash + version_requirements.hash
+ ##
+ # Uses this dependency as a pattern to compare to the dependency +other+.
+ # This dependency will match if the name matches the other's name, and other
+ # has only an equal version requirement that satisfies this dependency.
+
+ def =~(other)
+ return false unless self.class === other
+
+ pattern = @name
+ pattern = /\A#{@name}\Z/ unless Regexp === pattern
+
+ return false unless pattern =~ other.name
+
+ reqs = other.version_requirements.requirements
+
+ return false unless reqs.length == 1
+ return false unless reqs.first.first == '='
+
+ version = reqs.first.last
+
+ version_requirements.satisfied_by? version
+ end
+
+ def hash # :nodoc:
+ name.hash + type.hash + version_requirements.hash
end
end
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index 7ea2c0c317..b849d37245 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,9 +1,12 @@
require 'rubygems'
require 'rubygems/dependency_list'
require 'rubygems/installer'
-require 'rubygems/source_info_cache'
+require 'rubygems/spec_fetcher'
require 'rubygems/user_interaction'
+##
+# Installs a gem along with all its dependencies from local and remote gems.
+
class Gem::DependencyInstaller
include Gem::UserInteraction
@@ -25,36 +28,50 @@ class Gem::DependencyInstaller
# Creates a new installer instance.
#
# Options are:
- # :env_shebang:: See Gem::Installer::new.
+ # :cache_dir:: Alternate repository path to store .gem files in.
# :domain:: :local, :remote, or :both. :local only searches gems in the
# current directory. :remote searches only gems in Gem::sources.
# :both searches both.
+ # :env_shebang:: See Gem::Installer::new.
# :force:: See Gem::Installer#install.
# :format_executable:: See Gem::Installer#initialize.
- # :ignore_dependencies: Don't install any dependencies.
- # :install_dir: See Gem::Installer#install.
- # :security_policy: See Gem::Installer::new and Gem::Security.
- # :wrappers: See Gem::Installer::new
+ # :ignore_dependencies:: Don't install any dependencies.
+ # :install_dir:: See Gem::Installer#install.
+ # :security_policy:: See Gem::Installer::new and Gem::Security.
+ # :wrappers:: See Gem::Installer::new
+
def initialize(options = {})
options = DEFAULT_OPTIONS.merge options
- @env_shebang = options[:env_shebang]
+
+ @bin_dir = options[:bin_dir]
+ @development = options[:development]
@domain = options[:domain]
+ @env_shebang = options[:env_shebang]
@force = options[:force]
@format_executable = options[:format_executable]
@ignore_dependencies = options[:ignore_dependencies]
- @install_dir = options[:install_dir] || Gem.dir
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
- @bin_dir = options[:bin_dir]
@installed_gems = []
+
+ @install_dir = options[:install_dir] || Gem.dir
+ @cache_dir = options[:cache_dir] || @install_dir
+
+ if options[:install_dir] then
+ spec_dir = File.join @install_dir, 'specifications'
+ @source_index = Gem::SourceIndex.from_gems_in spec_dir
+ else
+ @source_index = Gem.source_index
+ end
end
##
# Returns a list of pairs of gemspecs and source_uris that match
# Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
- # sources. Gems are sorted with newer gems preferred over older gems, and
+ # sources. Gems are sorted with newer gems prefered over older gems, and
# local gems preferred over remote gems.
+
def find_gems_with_sources(dep)
gems_and_sources = []
@@ -74,8 +91,7 @@ class Gem::DependencyInstaller
all = requirements.length > 1 ||
(requirements.first != ">=" and requirements.first != ">")
- found = Gem::SourceInfoCache.search_with_source dep, true, all
-
+ found = Gem::SpecFetcher.fetcher.fetch dep, all
gems_and_sources.push(*found)
rescue Gem::RemoteFetcher::FetchError => e
@@ -95,6 +111,7 @@ class Gem::DependencyInstaller
##
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
+
def gather_dependencies
specs = @specs_and_sources.map { |spec,_| spec }
@@ -110,8 +127,18 @@ class Gem::DependencyInstaller
next if spec.nil? or seen[spec.name]
seen[spec.name] = true
- spec.dependencies.each do |dep|
- results = find_gems_with_sources(dep).reverse # local gems first
+ deps = spec.runtime_dependencies
+ deps |= spec.development_dependencies if @development
+
+ deps.each do |dep|
+ results = find_gems_with_sources(dep).reverse
+
+ results.reject! do |spec,|
+ @source_index.any? do |_, installed_spec|
+ dep.name == installed_spec.name and
+ dep.version_requirements.satisfied_by? installed_spec.version
+ end
+ end
results.each do |dep_spec, source_uri|
next if seen[dep_spec.name]
@@ -126,6 +153,11 @@ class Gem::DependencyInstaller
@gems_to_install = dependency_list.dependency_order.reverse
end
+ ##
+ # Finds a spec and the source_uri it came from for gem +gem_name+ and
+ # +version+. Returns an Array of specs and sources required for
+ # installation of the gem.
+
def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default
spec_and_source = nil
@@ -160,14 +192,16 @@ class Gem::DependencyInstaller
if spec_and_source.nil? then
raise Gem::GemNotFoundException,
- "could not find #{gem_name} locally or in a repository"
+ "could not find gem #{gem_name} locally or in a repository"
end
@specs_and_sources = [spec_and_source]
end
##
- # Installs the gem and all its dependencies.
+ # Installs the gem and all its dependencies. Returns an Array of installed
+ # gems specifications.
+
def install dep_or_name, version = Gem::Requirement.default
if String === dep_or_name then
find_spec_by_name_and_version dep_or_name, version
@@ -175,15 +209,14 @@ class Gem::DependencyInstaller
@specs_and_sources = [find_gems_with_sources(dep_or_name).last]
end
- gather_dependencies
+ @installed_gems = []
- spec_dir = File.join @install_dir, 'specifications'
- source_index = Gem::SourceIndex.from_gems_in spec_dir
+ gather_dependencies
@gems_to_install.each do |spec|
last = spec == @gems_to_install.last
# HACK is this test for full_name acceptable?
- next if source_index.any? { |n,_| n == spec.full_name } and not last
+ next if @source_index.any? { |n,_| n == spec.full_name } and not last
# TODO: make this sorta_verbose so other users can benefit from it
say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
@@ -191,7 +224,7 @@ class Gem::DependencyInstaller
_, source_uri = @specs_and_sources.assoc spec
begin
local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri,
- @install_dir
+ @cache_dir
rescue Gem::RemoteFetcher::FetchError
next if @force
raise
@@ -205,12 +238,15 @@ class Gem::DependencyInstaller
:install_dir => @install_dir,
:security_policy => @security_policy,
:wrappers => @wrappers,
- :bin_dir => @bin_dir
+ :bin_dir => @bin_dir,
+ :development => @development
spec = inst.install
@installed_gems << spec
end
+
+ @installed_gems
end
end
diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb
index 81aa65bfb2..a129743914 100644
--- a/lib/rubygems/dependency_list.rb
+++ b/lib/rubygems/dependency_list.rb
@@ -69,7 +69,7 @@ class Gem::DependencyList
# Are all the dependencies in the list satisfied?
def ok?
@specs.all? do |spec|
- spec.dependencies.all? do |dep|
+ spec.runtime_dependencies.all? do |dep|
@specs.find { |s| s.satisfies_requirement? dep }
end
end
diff --git a/lib/rubygems/doc_manager.rb b/lib/rubygems/doc_manager.rb
index f214269ab3..88d7964d85 100644
--- a/lib/rubygems/doc_manager.rb
+++ b/lib/rubygems/doc_manager.rb
@@ -9,9 +9,9 @@ require 'fileutils'
module Gem
class DocManager
-
+
include UserInteraction
-
+
# Create a document manager for the given gem spec.
#
# spec:: The Gem::Specification object representing the gem.
@@ -22,12 +22,12 @@ module Gem
@doc_dir = File.join(spec.installation_path, "doc", spec.full_name)
@rdoc_args = rdoc_args.nil? ? [] : rdoc_args.split
end
-
+
# Is the RDoc documentation installed?
def rdoc_installed?
return File.exist?(File.join(@doc_dir, "rdoc"))
end
-
+
# Generate the RI documents for this gem spec.
#
# Note that if both RI and RDoc documents are generated from the
@@ -102,7 +102,7 @@ module Gem
args << '--quiet'
args << @spec.require_paths.clone
args << @spec.extra_rdoc_files
- args.flatten!
+ args = args.flatten.map do |arg| arg.to_s end
r = RDoc::RDoc.new
diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb
index 9f6c0a2fc9..b45931a91d 100644
--- a/lib/rubygems/indexer.rb
+++ b/lib/rubygems/indexer.rb
@@ -1,5 +1,6 @@
require 'fileutils'
require 'tmpdir'
+require 'zlib'
require 'rubygems'
require 'rubygems/format'
@@ -40,116 +41,303 @@ class Gem::Indexer
marshal_name = "Marshal.#{Gem.marshal_version}"
- @master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory
- @marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory
- @quick_index = Gem::Indexer::QuickIndexBuilder.new 'index', @directory
+ @master_index = File.join @directory, 'yaml'
+ @marshal_index = File.join @directory, marshal_name
- quick_dir = File.join @directory, 'quick'
- @latest_index = Gem::Indexer::LatestIndexBuilder.new 'latest_index', quick_dir
+ @quick_dir = File.join @directory, 'quick'
+
+ @quick_marshal_dir = File.join @quick_dir, marshal_name
+
+ @quick_index = File.join @quick_dir, 'index'
+ @latest_index = File.join @quick_dir, 'latest_index'
+
+ @specs_index = File.join @directory, "specs.#{Gem.marshal_version}"
+ @latest_specs_index = File.join @directory,
+ "latest_specs.#{Gem.marshal_version}"
+
+ files = [
+ @specs_index,
+ "#{@specs_index}.gz",
+ @latest_specs_index,
+ "#{@latest_specs_index}.gz",
+ @quick_dir,
+ @master_index,
+ "#{@master_index}.Z",
+ @marshal_index,
+ "#{@marshal_index}.Z",
+ ]
+
+ @files = files.map do |path|
+ path.sub @directory, ''
+ end
+ end
+
+ ##
+ # Abbreviate the spec for downloading. Abbreviated specs are only used for
+ # searching, downloading and related activities and do not need deployment
+ # specific information (e.g. list of files). So we abbreviate the spec,
+ # making it much smaller for quicker downloads.
+
+ def abbreviate(spec)
+ spec.files = []
+ spec.test_files = []
+ spec.rdoc_options = []
+ spec.extra_rdoc_files = []
+ spec.cert_chain = []
+ spec
end
##
- # Build the index.
-
- def build_index
- @master_index.build do
- @quick_index.build do
- @marshal_index.build do
- @latest_index.build do
- progress = ui.progress_reporter gem_file_list.size,
- "Generating index for #{gem_file_list.size} gems in #{@dest_directory}",
- "Loaded all gems"
-
- gem_file_list.each do |gemfile|
- if File.size(gemfile.to_s) == 0 then
- alert_warning "Skipping zero-length gem: #{gemfile}"
- next
- end
-
- begin
- spec = Gem::Format.from_file_by_path(gemfile).spec
-
- unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
- alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
- next
- end
-
- abbreviate spec
- sanitize spec
-
- @master_index.add spec
- @quick_index.add spec
- @marshal_index.add spec
- @latest_index.add spec
-
- progress.updated spec.original_name
-
- rescue SignalException => e
- alert_error "Received signal, exiting"
- raise
- rescue Exception => e
- alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
- end
- end
-
- progress.done
-
- say "Generating master indexes (this may take a while)"
- end
+ # Build various indicies
+
+ def build_indicies(index)
+ progress = ui.progress_reporter index.size,
+ "Generating quick index gemspecs for #{index.size} gems",
+ "Complete"
+
+ index.each do |original_name, spec|
+ spec_file_name = "#{original_name}.gemspec.rz"
+ yaml_name = File.join @quick_dir, spec_file_name
+ marshal_name = File.join @quick_marshal_dir, spec_file_name
+
+ yaml_zipped = Gem.deflate spec.to_yaml
+ open yaml_name, 'wb' do |io| io.write yaml_zipped end
+
+ marshal_zipped = Gem.deflate Marshal.dump(spec)
+ open marshal_name, 'wb' do |io| io.write marshal_zipped end
+
+ progress.updated original_name
+ end
+
+ progress.done
+
+ say "Generating specs index"
+
+ open @specs_index, 'wb' do |io|
+ specs = index.sort.map do |_, spec|
+ platform = spec.original_platform
+ platform = Gem::Platform::RUBY if platform.nil?
+ [spec.name, spec.version, platform]
+ end
+
+ specs = compact_specs specs
+
+ Marshal.dump specs, io
+ end
+
+ say "Generating latest specs index"
+
+ open @latest_specs_index, 'wb' do |io|
+ specs = index.latest_specs.sort.map do |spec|
+ [spec.name, spec.version, spec.original_platform]
+ end
+
+ specs = compact_specs specs
+
+ Marshal.dump specs, io
+ end
+
+ say "Generating quick index"
+
+ quick_index = File.join @quick_dir, 'index'
+ open quick_index, 'wb' do |io|
+ io.puts index.sort.map { |_, spec| spec.original_name }
+ end
+
+ say "Generating latest index"
+
+ latest_index = File.join @quick_dir, 'latest_index'
+ open latest_index, 'wb' do |io|
+ io.puts index.latest_specs.sort.map { |spec| spec.original_name }
+ end
+
+ say "Generating Marshal master index"
+
+ open @marshal_index, 'wb' do |io|
+ io.write index.dump
+ end
+
+ progress = ui.progress_reporter index.size,
+ "Generating YAML master index for #{index.size} gems (this may take a while)",
+ "Complete"
+
+ open @master_index, 'wb' do |io|
+ io.puts "--- !ruby/object:#{index.class}"
+ io.puts "gems:"
+
+ gems = index.sort_by { |name, gemspec| gemspec.sort_obj }
+ gems.each do |original_name, gemspec|
+ yaml = gemspec.to_yaml.gsub(/^/, ' ')
+ yaml = yaml.sub(/\A ---/, '') # there's a needed extra ' ' here
+ io.print " #{original_name}:"
+ io.puts yaml
+
+ progress.updated original_name
+ end
+ end
+
+ progress.done
+
+ say "Compressing indicies"
+ # use gzip for future files.
+
+ compress quick_index, 'rz'
+ paranoid quick_index, 'rz'
+
+ compress latest_index, 'rz'
+ paranoid latest_index, 'rz'
+
+ compress @marshal_index, 'Z'
+ paranoid @marshal_index, 'Z'
+
+ compress @master_index, 'Z'
+ paranoid @master_index, 'Z'
+
+ gzip @specs_index
+ gzip @latest_specs_index
+ end
+
+ ##
+ # Collect specifications from .gem files from the gem directory.
+
+ def collect_specs
+ index = Gem::SourceIndex.new
+
+ progress = ui.progress_reporter gem_file_list.size,
+ "Loading #{gem_file_list.size} gems from #{@dest_directory}",
+ "Loaded all gems"
+
+ gem_file_list.each do |gemfile|
+ if File.size(gemfile.to_s) == 0 then
+ alert_warning "Skipping zero-length gem: #{gemfile}"
+ next
+ end
+
+ begin
+ spec = Gem::Format.from_file_by_path(gemfile).spec
+
+ unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
+ alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
+ next
end
+
+ abbreviate spec
+ sanitize spec
+
+ index.gems[spec.original_name] = spec
+
+ progress.updated spec.original_name
+
+ rescue SignalException => e
+ alert_error "Received signal, exiting"
+ raise
+ rescue Exception => e
+ alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
end
end
+
+ progress.done
+
+ index
end
- def install_index
- verbose = Gem.configuration.really_verbose
+ ##
+ # Compacts Marshal output for the specs index data source by using identical
+ # objects as much as possible.
- say "Moving index into production dir #{@dest_directory}" if verbose
+ def compact_specs(specs)
+ names = {}
+ versions = {}
+ platforms = {}
- files = @master_index.files + @quick_index.files + @marshal_index.files +
- @latest_index.files
+ specs.map do |(name, version, platform)|
+ names[name] = name unless names.include? name
+ versions[version] = version unless versions.include? version
+ platforms[platform] = platform unless platforms.include? platform
- files.each do |file|
- src_name = File.join @directory, file
- dst_name = File.join @dest_directory, file
+ [names[name], versions[version], platforms[platform]]
+ end
+ end
- FileUtils.rm_rf dst_name, :verbose => verbose
- FileUtils.mv src_name, @dest_directory, :verbose => verbose
+ ##
+ # Compress +filename+ with +extension+.
+
+ def compress(filename, extension)
+ data = Gem.read_binary filename
+
+ zipped = Gem.deflate data
+
+ open "#{filename}.#{extension}", 'wb' do |io|
+ io.write zipped
end
end
+ ##
+ # List of gem file names to index.
+
+ def gem_file_list
+ Dir.glob(File.join(@dest_directory, "gems", "*.gem"))
+ end
+
+ ##
+ # Builds and installs indexicies.
+
def generate_index
FileUtils.rm_rf @directory
FileUtils.mkdir_p @directory, :mode => 0700
+ FileUtils.mkdir_p @quick_marshal_dir
- build_index
- install_index
+ index = collect_specs
+ build_indicies index
+ install_indicies
rescue SignalException
ensure
FileUtils.rm_rf @directory
end
- # List of gem file names to index.
- def gem_file_list
- Dir.glob(File.join(@dest_directory, "gems", "*.gem"))
+ ##
+ # Zlib::GzipWriter wrapper that gzips +filename+ on disk.
+
+ def gzip(filename)
+ Zlib::GzipWriter.open "#{filename}.gz" do |io|
+ io.write Gem.read_binary(filename)
+ end
end
- # Abbreviate the spec for downloading. Abbreviated specs are only
- # used for searching, downloading and related activities and do not
- # need deployment specific information (e.g. list of files). So we
- # abbreviate the spec, making it much smaller for quicker downloads.
- def abbreviate(spec)
- spec.files = []
- spec.test_files = []
- spec.rdoc_options = []
- spec.extra_rdoc_files = []
- spec.cert_chain = []
- spec
+ ##
+ # Install generated indicies into the destination directory.
+
+ def install_indicies
+ verbose = Gem.configuration.really_verbose
+
+ say "Moving index into production dir #{@dest_directory}" if verbose
+
+ @files.each do |file|
+ src_name = File.join @directory, file
+ dst_name = File.join @dest_directory, file
+
+ FileUtils.rm_rf dst_name, :verbose => verbose
+ FileUtils.mv src_name, @dest_directory, :verbose => verbose
+ end
+ end
+
+ ##
+ # Ensure +path+ and path with +extension+ are identical.
+
+ def paranoid(path, extension)
+ data = Gem.read_binary path
+ compressed_data = Gem.read_binary "#{path}.#{extension}"
+
+ unless data == Gem.inflate(compressed_data) then
+ raise "Compressed file #{compressed_path} does not match uncompressed file #{path}"
+ end
end
+ ##
# Sanitize the descriptive fields in the spec. Sometimes non-ASCII
# characters will garble the site index. Non-ASCII characters will
# be replaced by their XML entity equivalent.
+
def sanitize(spec)
spec.summary = sanitize_string(spec.summary)
spec.description = sanitize_string(spec.description)
@@ -158,7 +346,9 @@ class Gem::Indexer
spec
end
+ ##
# Sanitize a single string.
+
def sanitize_string(string)
# HACK the #to_s is in here because RSpec has an Array of Arrays of
# Strings for authors. Need a way to disallow bad values on gempsec
@@ -168,9 +358,3 @@ class Gem::Indexer
end
-require 'rubygems/indexer/abstract_index_builder'
-require 'rubygems/indexer/master_index_builder'
-require 'rubygems/indexer/quick_index_builder'
-require 'rubygems/indexer/marshal_index_builder'
-require 'rubygems/indexer/latest_index_builder'
-
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index 58807be62a..5202c105db 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -89,6 +89,12 @@ module Gem::InstallUpdateOptions
'foo_exec18') do |value, options|
options[:format_executable] = value
end
+
+ add_option(:"Install/Update", "--development",
+ "Install any additional development",
+ "dependencies") do |value, options|
+ options[:development] = true
+ end
end
# Default options for the gem install command.
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 9dbbca8d08..ae699a90a0 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -56,6 +56,7 @@ class Gem::Installer
# foo_exec18.
# :security_policy:: Use the specified security policy. See Gem::Security
# :wrappers:: Install wrappers if true, symlinks if false.
+
def initialize(gem, options={})
@gem = gem
@@ -76,6 +77,7 @@ class Gem::Installer
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
@bin_dir = options[:bin_dir]
+ @development = options[:development]
begin
@format = Gem::Format.from_file_by_path @gem, @security_policy
@@ -98,6 +100,7 @@ class Gem::Installer
# cache/<gem-version>.gem #=> a cached copy of the installed gem
# gems/<gem-version>/... #=> extracted files
# specifications/<gem-version>.gemspec #=> the Gem::Specification
+
def install
# If we're forcing the install then disable security unless the security
# policy says that we only install singed gems.
@@ -119,7 +122,10 @@ class Gem::Installer
end
unless @ignore_dependencies then
- @spec.dependencies.each do |dep_gem|
+ deps = @spec.runtime_dependencies
+ deps |= @spec.development_dependencies if @development
+
+ deps.each do |dep_gem|
ensure_dependency @spec, dep_gem
end
end
@@ -150,6 +156,8 @@ class Gem::Installer
@spec.loaded_from = File.join(@gem_home, 'specifications',
"#{@spec.full_name}.gemspec")
+ Gem.source_index.add_spec @spec
+
return @spec
rescue Zlib::GzipFile::Error
raise Gem::InstallError, "gzip error installing #{@gem}"
@@ -161,6 +169,7 @@ class Gem::Installer
#
# spec :: Gem::Specification
# dependency :: Gem::Dependency
+
def ensure_dependency(spec, dependency)
unless installation_satisfies_dependency? dependency then
raise Gem::InstallError, "#{spec.name} requires #{dependency}"
@@ -170,17 +179,15 @@ class Gem::Installer
end
##
- # True if the current installed gems satisfy the given dependency.
- #
- # dependency :: Gem::Dependency
+ # True if the gems in Gem.source_index satisfy +dependency+.
+
def installation_satisfies_dependency?(dependency)
- current_index = Gem::SourceIndex.from_installed_gems
- current_index.find_name(dependency.name, dependency.version_requirements).size > 0
+ Gem.source_index.find_name(dependency.name, dependency.version_requirements).size > 0
end
##
# Unpacks the gem into the given directory.
- #
+
def unpack(directory)
@gem_dir = directory
@format = Gem::Format.from_file_by_path @gem, @security_policy
@@ -193,7 +200,7 @@ class Gem::Installer
#
# spec:: [Gem::Specification] The Gem specification to output
# spec_path:: [String] The location (path) to write the gemspec to
- #
+
def write_spec
rubycode = @spec.to_ruby
@@ -208,7 +215,7 @@ class Gem::Installer
##
# Creates windows .bat files for easy running of commands
- #
+
def generate_windows_script(bindir, filename)
if Gem.win_platform? then
script_name = filename + ".bat"
@@ -227,7 +234,7 @@ class Gem::Installer
# 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 ? @bin_dir : (Gem.bindir @gem_home)
+ bindir = @bin_dir ? @bin_dir : Gem.bindir(@gem_home)
Dir.mkdir bindir unless File.exist? bindir
raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir
@@ -252,7 +259,7 @@ class Gem::Installer
# The Windows script is generated in addition to the regular one due to a
# bug or misfeature in the Windows shell's pipe. See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/193379
- #
+
def generate_bin_script(filename, bindir)
bin_script_path = File.join bindir, formatted_program_filename(filename)
@@ -260,6 +267,8 @@ class Gem::Installer
# HACK some gems don't have #! in their executables, restore 2008/06
#if File.read(exec_path, 2) == '#!' then
+ FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers
+
File.open bin_script_path, 'w', 0755 do |file|
file.print app_script_text(filename)
end
@@ -277,7 +286,7 @@ class Gem::Installer
##
# Creates the symlinks to run the applications in the gem. Moves
# the symlink if the gem being installed has a newer version.
- #
+
def generate_bin_symlink(filename, bindir)
if Gem.win_platform? then
alert_warning "Unable to use symlinks on Windows, installing wrapper"
@@ -303,6 +312,7 @@ class Gem::Installer
##
# Generates a #! line for +bin_file_name+'s wrapper copying arguments if
# necessary.
+
def shebang(bin_file_name)
if @env_shebang then
"#!/usr/bin/env " + Gem::ConfigMap[:ruby_install_name]
@@ -324,7 +334,9 @@ class Gem::Installer
end
end
+ ##
# Return the text for an application file.
+
def app_script_text(bin_file_name)
<<-TEXT
#{shebang bin_file_name}
@@ -349,7 +361,9 @@ load '#{bin_file_name}'
TEXT
end
+ ##
# return the stub script text used to launch the true ruby script
+
def windows_stub_script(bindir, bin_file_name)
<<-TEXT
@ECHO OFF
@@ -361,8 +375,10 @@ GOTO :EOF
TEXT
end
+ ##
# Builds extensions. Valid types of extensions are extconf.rb files,
# configure scripts and rakefiles or mkrf_conf files.
+
def build_extensions
return if @spec.extensions.empty?
say "Building native extensions. This could take a while..."
@@ -418,6 +434,7 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')}
# Reads the file index and extracts each file into the gem directory.
#
# Ensures that files can't be installed outside the gem directory.
+
def extract_files
expand_and_validate_gem_dir
@@ -445,11 +462,15 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')}
out.write file_data
end
+ FileUtils.chmod entry['mode'], path
+
say path if Gem.configuration.really_verbose
end
end
+ ##
# Prefix and suffix the program filename the same as ruby.
+
def formatted_program_filename(filename)
if @format_executable then
self.class.exec_format % File.basename(filename)
@@ -460,7 +481,9 @@ Results logged to #{File.join(Dir.pwd, 'gem_make.out')}
private
+ ##
# HACK Pathname is broken on windows.
+
def absolute_path? pathname
pathname.absolute? or (Gem.win_platform? and pathname.to_s =~ /\A[a-z]:/i)
end
diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb
index 1a5410bef7..799b9d5893 100644
--- a/lib/rubygems/local_remote_options.rb
+++ b/lib/rubygems/local_remote_options.rb
@@ -4,27 +4,34 @@
# See LICENSE.txt for permissions.
#++
+require 'uri'
require 'rubygems'
+##
# Mixin methods for local and remote Gem::Command options.
+
module Gem::LocalRemoteOptions
+ ##
# Allows OptionParser to handle HTTP URIs.
+
def accept_uri_http
OptionParser.accept URI::HTTP do |value|
begin
- value = URI.parse value
+ uri = URI.parse value
rescue URI::InvalidURIError
raise OptionParser::InvalidArgument, value
end
- raise OptionParser::InvalidArgument, value unless value.scheme == 'http'
+ raise OptionParser::InvalidArgument, value unless uri.scheme == 'http'
value
end
end
+ ##
# Add local/remote options to the command line parser.
+
def add_local_remote_options
add_option(:"Local/Remote", '-l', '--local',
'Restrict operations to the LOCAL domain') do |value, options|
@@ -47,7 +54,9 @@ module Gem::LocalRemoteOptions
add_update_sources_option
end
+ ##
# Add the --bulk-threshold option
+
def add_bulk_threshold_option
add_option(:"Local/Remote", '-B', '--bulk-threshold COUNT',
"Threshold for switching to bulk",
@@ -57,7 +66,9 @@ module Gem::LocalRemoteOptions
end
end
+ ##
# Add the --http-proxy option
+
def add_proxy_option
accept_uri_http
@@ -68,22 +79,28 @@ module Gem::LocalRemoteOptions
end
end
+ ##
# Add the --source option
+
def add_source_option
accept_uri_http
add_option(:"Local/Remote", '--source URL', URI::HTTP,
- 'Use URL as the remote source for gems') do |value, options|
+ 'Use URL as the remote source for gems') do |source, options|
+ source << '/' if source !~ /\/\z/
+
if options[:added_source] then
- Gem.sources << value
+ Gem.sources << source
else
options[:added_source] = true
- Gem.sources.replace [value]
+ Gem.sources.replace [source]
end
end
end
+ ##
# Add the --source option
+
def add_update_sources_option
add_option(:"Local/Remote", '-u', '--[no-]update-sources',
@@ -92,12 +109,16 @@ module Gem::LocalRemoteOptions
end
end
+ ##
# Is local fetching enabled?
+
def local?
options[:domain] == :local || options[:domain] == :both
end
+ ##
# Is remote fetching enabled?
+
def remote?
options[:domain] == :remote || options[:domain] == :both
end
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 7abc15ef94..5e932cd592 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -1,7 +1,8 @@
require 'rubygems'
+##
# Available list of platforms for targeting Gem installations.
-#
+
class Gem::Platform
@local = nil
@@ -122,11 +123,20 @@ class Gem::Platform
to_a.compact.join '-'
end
+ ##
+ # Is +other+ equal to this platform? Two platforms are equal if they have
+ # the same CPU, OS and version.
+
def ==(other)
self.class === other and
@cpu == other.cpu and @os == other.os and @version == other.version
end
+ ##
+ # Does +other+ match this platform? Two platforms match if they have the
+ # same CPU, or either has a CPU of 'universal', they have the same OS, and
+ # they have the same version, or either has no version.
+
def ===(other)
return nil unless Gem::Platform === other
@@ -140,6 +150,10 @@ class Gem::Platform
(@version.nil? or other.version.nil? or @version == other.version)
end
+ ##
+ # Does +other+ match this platform? If +other+ is a String it will be
+ # converted to a Gem::Platform first. See #=== for matching rules.
+
def =~(other)
case other
when Gem::Platform then # nop
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 96775c4d00..93252fe83a 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -1,4 +1,5 @@
require 'net/http'
+require 'stringio'
require 'uri'
require 'rubygems'
@@ -11,15 +12,38 @@ class Gem::RemoteFetcher
include Gem::UserInteraction
- class FetchError < Gem::Exception; end
+ ##
+ # A FetchError exception wraps up the various possible IO and HTTP failures
+ # that could happen while downloading from the internet.
+
+ class FetchError < Gem::Exception
+
+ ##
+ # The URI which was being accessed when the exception happened.
+
+ attr_accessor :uri
+
+ def initialize(message, uri)
+ super message
+ @uri = uri
+ end
+
+ def to_s # :nodoc:
+ "#{super} (#{uri})"
+ end
+
+ end
@fetcher = nil
+ ##
# Cached RemoteFetcher instance.
+
def self.fetcher
@fetcher ||= self.new Gem.configuration[:http_proxy]
end
+ ##
# Initialize a remote fetcher using the source URI and possible proxy
# information.
#
@@ -29,6 +53,7 @@ class Gem::RemoteFetcher
# * 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
+
def initialize(proxy)
Socket.do_not_reverse_lookup = true
@@ -47,11 +72,13 @@ class Gem::RemoteFetcher
# Moves the gem +spec+ from +source_uri+ to the cache dir unless it is
# already there. If the source_uri is local the gem cache dir copy is
# always replaced.
+
def download(spec, source_uri, install_dir = Gem.dir)
+ cache_dir = File.join install_dir, 'cache'
gem_file_name = "#{spec.full_name}.gem"
- local_gem_path = File.join install_dir, 'cache', gem_file_name
+ local_gem_path = File.join cache_dir, gem_file_name
- Gem.ensure_gem_subdirectories install_dir
+ FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir
source_uri = URI.parse source_uri unless URI::Generic === source_uri
scheme = source_uri.scheme
@@ -102,21 +129,26 @@ class Gem::RemoteFetcher
local_gem_path
end
- # Downloads +uri+.
+ ##
+ # Downloads +uri+ and returns it as a String.
+
def fetch_path(uri)
open_uri_or_path(uri) do |input|
input.read
end
+ rescue FetchError
+ raise
rescue Timeout::Error
- raise FetchError, "timed out fetching #{uri}"
+ raise FetchError.new('timed out', uri)
rescue IOError, SocketError, SystemCallError => e
- raise FetchError, "#{e.class}: #{e} reading #{uri}"
+ raise FetchError.new("#{e.class}: #{e}", uri)
rescue => e
- message = "#{e.class}: #{e} reading #{uri}"
- raise FetchError, message
+ raise FetchError.new("#{e.class}: #{e}", uri)
end
+ ##
# Returns the size of +uri+ in bytes.
+
def fetch_size(uri)
return File.size(get_file_uri_path(uri)) if file_uri? uri
@@ -124,30 +156,21 @@ class Gem::RemoteFetcher
raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === uri
- http = connect_to uri.host, uri.port
-
- request = Net::HTTP::Head.new uri.request_uri
-
- request.basic_auth unescape(uri.user), unescape(uri.password) unless
- uri.user.nil? or uri.user.empty?
+ response = request uri, Net::HTTP::Head
- resp = http.request request
-
- if resp.code !~ /^2/ then
- raise Gem::RemoteSourceException,
- "HTTP Response #{resp.code} fetching #{uri}"
+ if response.code !~ /^2/ then
+ raise FetchError.new("bad response #{response.message} #{response.code}", uri)
end
- if resp['content-length'] then
- return resp['content-length'].to_i
+ if response['content-length'] then
+ return response['content-length'].to_i
else
- resp = http.get uri.request_uri
- return resp.body.size
+ response = http.get uri.request_uri
+ return response.body.size
end
rescue SocketError, SystemCallError, Timeout::Error => e
- raise Gem::RemoteFetcher::FetchError,
- "#{e.message} (#{e.class})\n\tgetting size of #{uri}"
+ raise FetchError.new("#{e.message} (#{e.class})\n\tfetching size", uri)
end
private
@@ -162,7 +185,9 @@ class Gem::RemoteFetcher
URI.unescape(str)
end
+ ##
# Returns an HTTP proxy URI if one is set in the environment variables.
+
def get_proxy_from_env
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
@@ -179,104 +204,129 @@ class Gem::RemoteFetcher
uri
end
+ ##
# Normalize the URI by adding "http://" if it is missing.
+
def normalize_uri(uri)
(uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
end
- # Connect to the source host/port, using a proxy if needed.
- def connect_to(host, port)
- if @proxy_uri
- Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port)
- else
- Net::HTTP.new(host, port)
+ ##
+ # Creates or an HTTP connection based on +uri+, or retrieves an existing
+ # connection, using a proxy if needed.
+
+ def connection_for(uri)
+ net_http_args = [uri.host, uri.port]
+
+ if @proxy_uri then
+ net_http_args += [
+ @proxy_uri.host,
+ @proxy_uri.port,
+ @proxy_uri.user,
+ @proxy_uri.password
+ ]
+ end
+
+ connection_id = net_http_args.join ':'
+ @connections[connection_id] ||= Net::HTTP.new(*net_http_args)
+ connection = @connections[connection_id]
+
+ if uri.scheme == 'https' and not connection.started? then
+ http_obj.use_ssl = true
+ http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
+
+ connection.start unless connection.started?
+
+ connection
end
+ ##
# Read the data from the (source based) URI, but if it is a file:// URI,
# read from the filesystem instead.
+
def open_uri_or_path(uri, depth = 0, &block)
if file_uri?(uri)
open(get_file_uri_path(uri), &block)
else
uri = URI.parse uri unless URI::Generic === uri
- net_http_args = [uri.host, uri.port]
-
- if @proxy_uri then
- net_http_args += [ @proxy_uri.host,
- @proxy_uri.port,
- @proxy_uri.user,
- @proxy_uri.password
- ]
- end
-
- connection_id = net_http_args.join ':'
- @connections[connection_id] ||= Net::HTTP.new(*net_http_args)
- connection = @connections[connection_id]
- if uri.scheme == 'https' && ! connection.started?
- http_obj.use_ssl = true
- http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE
- end
-
- connection.start unless connection.started?
-
- request = Net::HTTP::Get.new(uri.request_uri)
- unless uri.nil? || uri.user.nil? || uri.user.empty? then
- request.basic_auth(uri.user, uri.password)
- end
-
- ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
- ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
- ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
- ua << ")"
-
- request.add_field 'User-Agent', ua
- request.add_field 'Connection', 'keep-alive'
- request.add_field 'Keep-Alive', '30'
-
- # HACK work around EOFError bug in Net::HTTP
- # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
- # to install gems.
- retried = false
- begin
- @requests[connection_id] += 1
- response = connection.request(request)
- rescue EOFError, Errno::ECONNABORTED
- requests = @requests[connection_id]
- say "connection reset after #{requests} requests, retrying" if
- Gem.configuration.really_verbose
-
- raise Gem::RemoteFetcher::FetchError, 'too many connection resets' if
- retried
-
- @requests[connection_id] = 0
-
- connection.finish
- connection.start
- retried = true
- retry
- end
+ response = request uri
case response
when Net::HTTPOK then
block.call(StringIO.new(response.body)) if block
when Net::HTTPRedirection then
- raise Gem::RemoteFetcher::FetchError, "too many redirects" if depth > 10
+ raise FetchError.new('too many redirects', uri) if depth > 10
+
open_uri_or_path(response['Location'], depth + 1, &block)
else
- raise Gem::RemoteFetcher::FetchError,
- "bad response #{response.message} #{response.code}"
+ raise FetchError.new("bad response #{response.message} #{response.code}", uri)
end
end
end
+ ##
+ # Performs a Net::HTTP request of type +request_class+ on +uri+ returning
+ # a Net::HTTP response object. request maintains a table of persistent
+ # connections to reduce connect overhead.
+
+ def request(uri, request_class = Net::HTTP::Get)
+ request = request_class.new uri.request_uri
+
+ unless uri.nil? || uri.user.nil? || uri.user.empty? then
+ request.basic_auth uri.user, uri.password
+ end
+
+ ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
+ ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
+ ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ ua << ")"
+
+ request.add_field 'User-Agent', ua
+ request.add_field 'Connection', 'keep-alive'
+ request.add_field 'Keep-Alive', '30'
+
+ connection = connection_for uri
+
+ retried = false
+
+ # HACK work around EOFError bug in Net::HTTP
+ # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
+ # to install gems.
+ begin
+ @requests[connection.object_id] += 1
+ response = connection.request request
+ say "#{request.method} #{response.code} #{response.message}: #{uri}" if
+ Gem.configuration.really_verbose
+ rescue EOFError, Errno::ECONNABORTED, Errno::ECONNRESET
+ requests = @requests[connection.object_id]
+ say "connection reset after #{requests} requests, retrying" if
+ Gem.configuration.really_verbose
+
+ raise FetchError.new('too many connection resets', uri) if retried
+
+ @requests.delete connection.object_id
+
+ connection.finish
+ connection.start
+ retried = true
+ retry
+ end
+
+ response
+ end
+
+ ##
# Checks if the provided string is a file:// URI.
+
def file_uri?(uri)
uri =~ %r{\Afile://}
end
+ ##
# Given a file:// URI, returns its local path.
+
def get_file_uri_path(uri)
uri.sub(%r{\Afile://}, '')
end
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index f1213152f2..c9128b5ebc 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -12,6 +12,7 @@ require 'rubygems/version'
#
# A Requirement object can actually contain multiple, er,
# requirements, as in (> 1.2, < 2.0).
+
class Gem::Requirement
include Comparable
@@ -35,7 +36,7 @@ class Gem::Requirement
# Version, a String, or nil. Intended to simplify client code.
#
# If the input is "weird", the default version requirement is returned.
- #
+
def self.create(input)
case input
when Gem::Requirement then
@@ -57,6 +58,7 @@ class Gem::Requirement
# This comment once said:
#
# "A default "version requirement" can surely _only_ be '> 0'."
+
def self.default
self.new ['>= 0']
end
@@ -65,6 +67,7 @@ class Gem::Requirement
# Constructs a Requirement from +requirements+ which can be a String, a
# Gem::Version, or an Array of those. See parse for details on the
# formatting of requirement strings.
+
def initialize(requirements)
@requirements = case requirements
when Array then
@@ -77,13 +80,17 @@ class Gem::Requirement
@version = nil # Avoid warnings.
end
+ ##
# Marshal raw requirements, rather than the full object
- def marshal_dump
+
+ def marshal_dump # :nodoc:
[@requirements]
end
+ ##
# Load custom marshal format
- def marshal_load(array)
+
+ def marshal_load(array) # :nodoc:
@requirements = array[0]
@version = nil
end
@@ -108,20 +115,16 @@ class Gem::Requirement
end
##
- # Is the requirement satisfied by +version+.
- #
- # version:: [Gem::Version] the version to compare against
- # return:: [Boolean] true if this requirement is satisfied by
- # the version, otherwise false
- #
+ # True if this requirement satisfied by the Gem::Version +version+.
+
def satisfied_by?(version)
normalize
@requirements.all? { |op, rv| satisfy?(op, version, rv) }
end
##
- # Is "version op required_version" satisfied?
- #
+ # Is "+version+ +op+ +required_version+" satisfied?
+
def satisfy?(op, version, required_version)
OPS[op].call(version, required_version)
end
@@ -132,6 +135,7 @@ class Gem::Requirement
# The requirement can be a String or a Gem::Version. A String can be an
# operator (<, <=, =, =>, >, !=, ~>), a version number, or both, operator
# first.
+
def parse(obj)
case obj
when /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/o then
@@ -147,7 +151,7 @@ class Gem::Requirement
end
end
- def <=>(other)
+ def <=>(other) # :nodoc:
to_s <=> other.to_s
end
diff --git a/lib/rubygems/rubygems_version.rb b/lib/rubygems/rubygems_version.rb
index 453f9b57b6..d4d4af0558 100644
--- a/lib/rubygems/rubygems_version.rb
+++ b/lib/rubygems/rubygems_version.rb
@@ -2,5 +2,5 @@
# This file is auto-generated by build scripts.
# See: rake update_version
module Gem
- RubyGemsVersion = '1.1.1'
+ RubyGemsVersion = '1.1.1.1778'
end
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
index b4f58f9706..2c617ff144 100644
--- a/lib/rubygems/server.rb
+++ b/lib/rubygems/server.rb
@@ -4,6 +4,7 @@ require 'zlib'
require 'erb'
require 'rubygems'
+require 'rubygems/doc_manager'
##
# Gem::Server and allows users to serve gems for consumption by
@@ -11,18 +12,24 @@ require 'rubygems'
#
# gem_server starts an HTTP server on the given port and serves the following:
# * "/" - Browsing of gem spec files for installed gems
-# * "/Marshal" - Full SourceIndex dump of metadata for installed gems
-# * "/yaml" - YAML dump of metadata for installed gems - deprecated
+# * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
+# * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
+# name/version/platform index
+# * "/quick/" - Individual gemspecs
# * "/gems" - Direct access to download the installable gems
+# * legacy indexes:
+# * "/Marshal.#{Gem.marshal_version}" - Full SourceIndex dump of metadata
+# for installed gems
+# * "/yaml" - YAML dump of metadata for installed gems - deprecated
#
# == Usage
#
-# gem server [-p portnum] [-d gem_path]
+# gem_server = Gem::Server.new Gem.dir, 8089, false
+# gem_server.run
#
-# port_num:: The TCP port the HTTP server will bind to
-# gem_path::
-# Root gem directory containing both "cache" and "specifications"
-# subdirectories.
+#--
+# TODO Refactor into a real WEBrick servlet to remove code duplication.
+
class Gem::Server
include Gem::UserInteraction
@@ -36,7 +43,6 @@ class Gem::Server
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>RubyGems Documentation Index</title>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
</head>
<body>
@@ -325,32 +331,99 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
new(options[:gemdir], options[:port], options[:daemon]).run
end
- def initialize(gemdir, port, daemon)
+ def initialize(gem_dir, port, daemon)
Socket.do_not_reverse_lookup = true
- @gemdir = gemdir
+ @gem_dir = gem_dir
@port = port
@daemon = daemon
logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
@server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
- @spec_dir = File.join @gemdir, "specifications"
+ @spec_dir = File.join @gem_dir, 'specifications'
+
+ unless File.directory? @spec_dir then
+ raise ArgumentError, "#{@gem_dir} does not appear to be a gem repository"
+ end
+
@source_index = Gem::SourceIndex.from_gems_in @spec_dir
end
+ def Marshal(req, res)
+ @source_index.refresh!
+
+ res['date'] = File.stat(@spec_dir).mtime
+
+ index = Marshal.dump @source_index
+
+ if req.request_method == 'HEAD' then
+ res['content-length'] = index.length
+ return
+ end
+
+ if req.path =~ /Z$/ then
+ res['content-type'] = 'application/x-deflate'
+ index = Gem.deflate index
+ else
+ res['content-type'] = 'application/octet-stream'
+ end
+
+ res.body << index
+ end
+
+ def latest_specs(req, res)
+ @source_index.refresh!
+
+ res['content-type'] = 'application/x-gzip'
+
+ res['date'] = File.stat(@spec_dir).mtime
+
+ specs = @source_index.latest_specs.sort.map do |spec|
+ platform = spec.original_platform
+ platform = Gem::Platform::RUBY if platform.nil?
+ [spec.name, spec.version, platform]
+ end
+
+ specs = Marshal.dump specs
+
+ if req.path =~ /\.gz$/ then
+ specs = Gem.gzip specs
+ res['content-type'] = 'application/x-gzip'
+ else
+ res['content-type'] = 'application/octet-stream'
+ end
+
+ if req.request_method == 'HEAD' then
+ res['content-length'] = specs.length
+ else
+ res.body << specs
+ end
+ end
+
def quick(req, res)
+ @source_index.refresh!
+
res['content-type'] = 'text/plain'
res['date'] = File.stat(@spec_dir).mtime
- case req.request_uri.request_uri
+ case req.request_uri.path
when '/quick/index' then
- res.body << @source_index.map { |name,_| name }.join("\n")
+ res.body << @source_index.map { |name,| name }.sort.join("\n")
when '/quick/index.rz' then
- index = @source_index.map { |name,_| name }.join("\n")
- res.body << Zlib::Deflate.deflate(index)
+ index = @source_index.map { |name,| name }.sort.join("\n")
+ res['content-type'] = 'application/x-deflate'
+ res.body << Gem.deflate(index)
+ when '/quick/latest_index' then
+ index = @source_index.latest_specs.map { |spec| spec.full_name }
+ res.body << index.sort.join("\n")
+ when '/quick/latest_index.rz' then
+ index = @source_index.latest_specs.map { |spec| spec.full_name }
+ res['content-type'] = 'application/x-deflate'
+ res.body << Gem.deflate(index.sort.join("\n"))
when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
dep = Gem::Dependency.new $2, $3
specs = @source_index.search dep
+ marshal_format = $1
selector = [$2, $3, $4].map { |s| s.inspect }.join ' '
@@ -368,17 +441,98 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
elsif specs.length > 1 then
res.status = 500
res.body = "Multiple gems found matching #{selector}"
- elsif $1 then # marshal quickindex instead of YAML
- res.body << Zlib::Deflate.deflate(Marshal.dump(specs.first))
+ elsif marshal_format then
+ res['content-type'] = 'application/x-deflate'
+ res.body << Gem.deflate(Marshal.dump(specs.first))
else # deprecated YAML format
- res.body << Zlib::Deflate.deflate(specs.first.to_yaml)
+ res['content-type'] = 'application/x-deflate'
+ res.body << Gem.deflate(specs.first.to_yaml)
end
else
- res.status = 404
- res.body = "#{req.request_uri} not found"
+ raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
end
end
+ def root(req, res)
+ @source_index.refresh!
+ res['date'] = File.stat(@spec_dir).mtime
+
+ raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
+ req.path == '/'
+
+ specs = []
+ total_file_count = 0
+
+ @source_index.each do |path, spec|
+ total_file_count += spec.files.size
+ deps = spec.dependencies.map do |dep|
+ { "name" => dep.name,
+ "type" => dep.type,
+ "version" => dep.version_requirements.to_s, }
+ end
+
+ deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
+ deps.last["is_last"] = true unless deps.empty?
+
+ # executables
+ executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
+ executables = nil if executables.empty?
+ executables.last["is_last"] = true if executables
+
+ specs << {
+ "authors" => spec.authors.sort.join(", "),
+ "date" => spec.date.to_s,
+ "dependencies" => deps,
+ "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html",
+ "executables" => executables,
+ "only_one_executable" => (executables && executables.size == 1),
+ "full_name" => spec.full_name,
+ "has_deps" => !deps.empty?,
+ "homepage" => spec.homepage,
+ "name" => spec.name,
+ "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
+ "summary" => spec.summary,
+ "version" => spec.version.to_s,
+ }
+ end
+
+ specs << {
+ "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
+ "dependencies" => [],
+ "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
+ "executables" => [{"executable" => 'gem', "is_last" => true}],
+ "only_one_executable" => true,
+ "full_name" => "rubygems-#{Gem::RubyGemsVersion}",
+ "has_deps" => false,
+ "homepage" => "http://rubygems.org/",
+ "name" => 'rubygems',
+ "rdoc_installed" => true,
+ "summary" => "RubyGems itself",
+ "version" => Gem::RubyGemsVersion,
+ }
+
+ specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
+ specs.last["is_last"] = true
+
+ # tag all specs with first_name_entry
+ last_spec = nil
+ specs.each do |spec|
+ is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
+ spec["first_name_entry"] = is_first
+ last_spec = spec
+ end
+
+ # create page from template
+ template = ERB.new(DOC_TEMPLATE)
+ res['content-type'] = 'text/html'
+
+ values = { "gem_count" => specs.size.to_s, "specs" => specs,
+ "total_file_count" => total_file_count.to_s }
+
+ result = template.result binding
+ res.body = result
+ end
+
def run
@server.listen nil, @port
@@ -386,27 +540,21 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
WEBrick::Daemon.start if @daemon
- @server.mount_proc("/yaml") do |req, res|
- res['content-type'] = 'text/plain'
- res['date'] = File.stat(@spec_dir).mtime
- if req.request_method == 'HEAD' then
- res['content-length'] = @source_index.to_yaml.length
- else
- res.body << @source_index.to_yaml
- end
- end
+ @server.mount_proc "/yaml", method(:yaml)
+ @server.mount_proc "/yaml.Z", method(:yaml)
- @server.mount_proc("/Marshal") do |req, res|
- res['content-type'] = 'text/plain'
- res['date'] = File.stat(@spec_dir).mtime
- if req.request_method == 'HEAD' then
- res['content-length'] = Marshal.dump(@source_index).length
- else
- res.body << Marshal.dump(@source_index)
- end
- end
+ @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
+ @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
+
+ @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
+ @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
+
+ @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
+ method(:latest_specs)
+ @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
+ method(:latest_specs)
- @server.mount_proc("/quick/", &method(:quick))
+ @server.mount_proc "/quick/", method(:quick)
@server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
res['content-type'] = 'text/css'
@@ -414,80 +562,12 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
res.body << RDOC_CSS
end
- @server.mount_proc("/") do |req, res|
- specs = []
- total_file_count = 0
-
- @source_index.each do |path, spec|
- total_file_count += spec.files.size
- deps = spec.dependencies.collect { |dep|
- { "name" => dep.name,
- "version" => dep.version_requirements.to_s, }
- }
- deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
- deps.last["is_last"] = true unless deps.empty?
-
- # executables
- executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
- executables = nil if executables.empty?
- executables.last["is_last"] = true if executables
-
- specs << {
- "authors" => spec.authors.sort.join(", "),
- "date" => spec.date.to_s,
- "dependencies" => deps,
- "doc_path" => ('/doc_root/' + spec.full_name + '/rdoc/index.html'),
- "executables" => executables,
- "only_one_executable" => (executables && executables.size==1),
- "full_name" => spec.full_name,
- "has_deps" => !deps.empty?,
- "homepage" => spec.homepage,
- "name" => spec.name,
- "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
- "summary" => spec.summary,
- "version" => spec.version.to_s,
- }
- end
-
- specs << {
- "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
- "dependencies" => [],
- "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
- "executables" => [{"executable" => 'gem', "is_last" => true}],
- "only_one_executable" => true,
- "full_name" => "rubygems-#{Gem::RubyGemsVersion}",
- "has_deps" => false,
- "homepage" => "http://rubygems.org/",
- "name" => 'rubygems',
- "rdoc_installed" => true,
- "summary" => "RubyGems itself",
- "version" => Gem::RubyGemsVersion,
- }
-
- specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
- specs.last["is_last"] = true
-
- # tag all specs with first_name_entry
- last_spec = nil
- specs.each do |spec|
- is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
- spec["first_name_entry"] = is_first
- last_spec = spec
- end
-
- # create page from template
- template = ERB.new(DOC_TEMPLATE)
- res['content-type'] = 'text/html'
- values = { "gem_count" => specs.size.to_s, "specs" => specs,
- "total_file_count" => total_file_count.to_s }
- result = template.result binding
- res.body = result
- end
+ @server.mount_proc "/", method(:root)
paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
paths.each do |mount_point, mount_dir|
@server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
- File.join(@gemdir, mount_dir), true)
+ File.join(@gem_dir, mount_dir), true)
end
trap("INT") { @server.shutdown; exit! }
@@ -496,5 +576,54 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
@server.start
end
+ def specs(req, res)
+ @source_index.refresh!
+
+ res['date'] = File.stat(@spec_dir).mtime
+
+ specs = @source_index.sort.map do |_, spec|
+ platform = spec.original_platform
+ platform = Gem::Platform::RUBY if platform.nil?
+ [spec.name, spec.version, platform]
+ end
+
+ specs = Marshal.dump specs
+
+ if req.path =~ /\.gz$/ then
+ specs = Gem.gzip specs
+ res['content-type'] = 'application/x-gzip'
+ else
+ res['content-type'] = 'application/octet-stream'
+ end
+
+ if req.request_method == 'HEAD' then
+ res['content-length'] = specs.length
+ else
+ res.body << specs
+ end
+ end
+
+ def yaml(req, res)
+ @source_index.refresh!
+
+ res['date'] = File.stat(@spec_dir).mtime
+
+ index = @source_index.to_yaml
+
+ if req.path =~ /Z$/ then
+ res['content-type'] = 'application/x-deflate'
+ index = Gem.deflate index
+ else
+ res['content-type'] = 'text/plain'
+ end
+
+ if req.request_method == 'HEAD' then
+ res['content-length'] = index.length
+ return
+ end
+
+ res.body << index
+ end
+
end
diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb
index 8057fd1d0f..b940b83cf8 100644
--- a/lib/rubygems/source_index.rb
+++ b/lib/rubygems/source_index.rb
@@ -7,6 +7,7 @@
require 'rubygems'
require 'rubygems/user_interaction'
require 'rubygems/specification'
+require 'rubygems/spec_fetcher'
##
# The SourceIndex object indexes all the gems available from a
@@ -27,6 +28,11 @@ class Gem::SourceIndex
attr_reader :gems # :nodoc:
+ ##
+ # Directories to use to refresh this SourceIndex when calling refresh!
+
+ attr_accessor :spec_dirs
+
class << self
include Gem::UserInteraction
@@ -39,7 +45,7 @@ class Gem::SourceIndex
# +from_gems_in+. This argument is deprecated and is provided
# just for backwards compatibility, and should not generally
# be used.
- #
+ #
# return::
# SourceIndex instance
@@ -63,7 +69,9 @@ class Gem::SourceIndex
# +spec_dirs+.
def from_gems_in(*spec_dirs)
- self.new.load_gems_in(*spec_dirs)
+ source_index = new
+ source_index.spec_dirs = spec_dirs
+ source_index.refresh!
end
##
@@ -79,6 +87,8 @@ class Gem::SourceIndex
return gemspec
end
alert_warning "File '#{file_name}' does not evaluate to a gem specification"
+ rescue SignalException, SystemExit
+ raise
rescue SyntaxError => e
alert_warning e
alert_warning spec_code
@@ -100,6 +110,7 @@ class Gem::SourceIndex
def initialize(specifications={})
@gems = specifications
+ @spec_dirs = nil
end
##
@@ -121,8 +132,8 @@ class Gem::SourceIndex
end
##
- # Returns a Hash of name => Specification of the latest versions of each
- # gem in this index.
+ # Returns an Array specifications for the latest versions of each gem in
+ # this index.
def latest_specs
result = Hash.new { |h,k| h[k] = [] }
@@ -241,7 +252,9 @@ class Gem::SourceIndex
when Gem::Dependency then
only_platform = platform_only
version_requirement = gem_pattern.version_requirements
- gem_pattern = if gem_pattern.name.empty? then
+ gem_pattern = if Regexp === gem_pattern.name then
+ gem_pattern.name
+ elsif gem_pattern.name.empty? then
//
else
/^#{Regexp.escape gem_pattern.name}$/
@@ -271,29 +284,43 @@ class Gem::SourceIndex
##
# Replaces the gems in the source index from specifications in the
- # installed_spec_directories,
+ # directories this source index was created from. Raises an exception if
+ # this source index wasn't created from a directory (via from_gems_in or
+ # from_installed_gems, or having spec_dirs set).
def refresh!
- load_gems_in(*self.class.installed_spec_directories)
+ raise 'source index not created from disk' if @spec_dirs.nil?
+ load_gems_in(*@spec_dirs)
end
##
# Returns an Array of Gem::Specifications that are not up to date.
def outdated
- dep = Gem::Dependency.new '', Gem::Requirement.default
-
- remotes = Gem::SourceInfoCache.search dep, true
-
outdateds = []
latest_specs.each do |local|
name = local.name
- remote = remotes.select { |spec| spec.name == name }.
- sort_by { |spec| spec.version.to_ints }.
- last
- outdateds << name if remote and local.version < remote.version
+ dependency = Gem::Dependency.new name, ">= #{local.version}"
+
+ begin
+ fetcher = Gem::SpecFetcher.fetcher
+ remotes = fetcher.find_matching dependency
+ remotes = remotes.map { |(name, version,),| version }
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise unless fetcher.warn_legacy e do
+ require 'rubygems/source_info_cache'
+
+ specs = Gem::SourceInfoCache.search_with_source dependency, true
+
+ remotes = specs.map { |spec,| spec.version }
+ end
+ end
+
+ latest = remotes.sort.last
+
+ outdateds << name if latest and local.version < latest
end
outdateds
@@ -387,7 +414,8 @@ class Gem::SourceIndex
end
def fetch_bulk_index(source_uri)
- say "Bulk updating Gem source index for: #{source_uri}"
+ say "Bulk updating Gem source index for: #{source_uri}" if
+ Gem.configuration.verbose
index = fetch_index_from(source_uri)
if index.nil? then
@@ -447,7 +475,7 @@ class Gem::SourceIndex
def unzip(string)
require 'zlib'
- Zlib::Inflate.inflate(string)
+ Gem.inflate string
end
##
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
new file mode 100644
index 0000000000..29db889af5
--- /dev/null
+++ b/lib/rubygems/spec_fetcher.rb
@@ -0,0 +1,251 @@
+require 'zlib'
+
+require 'rubygems'
+require 'rubygems/remote_fetcher'
+require 'rubygems/user_interaction'
+
+##
+# SpecFetcher handles metadata updates from remote gem repositories.
+
+class Gem::SpecFetcher
+
+ include Gem::UserInteraction
+
+ ##
+ # The SpecFetcher cache dir.
+
+ attr_reader :dir # :nodoc:
+
+ ##
+ # Cache of latest specs
+
+ attr_reader :latest_specs # :nodoc:
+
+ ##
+ # Cache of all spces
+
+ attr_reader :specs # :nodoc:
+
+ @fetcher = nil
+
+ def self.fetcher
+ @fetcher ||= new
+ end
+
+ def self.fetcher=(fetcher) # :nodoc:
+ @fetcher = fetcher
+ end
+
+ def initialize
+ @dir = File.join Gem.user_home, '.gem', 'specs'
+ @update_cache = File.stat(Gem.user_home).uid == Process.uid
+
+ @specs = {}
+ @latest_specs = {}
+
+ @fetcher = Gem::RemoteFetcher.fetcher
+ end
+
+ ##
+ # Retuns the local directory to write +uri+ to.
+
+ def cache_dir(uri)
+ File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(uri.path)
+ end
+
+ ##
+ # Fetch specs matching +dependency+. If +all+ is true, all matching
+ # versions are returned. If +matching_platform+ is false, all platforms are
+ # returned.
+
+ def fetch(dependency, all = false, matching_platform = true)
+ specs_and_sources = find_matching dependency, all, matching_platform
+
+ specs_and_sources.map do |spec_tuple, source_uri|
+ [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
+ end
+
+ rescue Gem::RemoteFetcher::FetchError => e
+ raise unless warn_legacy e do
+ require 'rubygems/source_info_cache'
+
+ return Gem::SourceInfoCache.search_with_source(dependency,
+ matching_platform, all)
+ end
+ end
+
+ def fetch_spec(spec, source_uri)
+ spec = spec - [nil, 'ruby', '']
+ spec_file_name = "#{spec.join '-'}.gemspec"
+
+ uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
+
+ cache_dir = cache_dir uri
+
+ local_spec = File.join cache_dir, spec_file_name
+
+ if File.exist? local_spec then
+ spec = Gem.read_binary local_spec
+ else
+ uri.path << '.rz'
+
+ spec = @fetcher.fetch_path uri
+ spec = Gem.inflate spec
+
+ if @update_cache then
+ FileUtils.mkdir_p cache_dir
+
+ open local_spec, 'wb' do |io|
+ io.write spec
+ end
+ end
+ end
+
+ # TODO: Investigate setting Gem::Specification#loaded_from to a URI
+ Marshal.load spec
+ end
+
+ ##
+ # Find spec names that match +dependency+. If +all+ is true, all matching
+ # versions are returned. If +matching_platform+ is false, gems for all
+ # platforms are returned.
+
+ def find_matching(dependency, all = false, matching_platform = true)
+ found = {}
+
+ list(all).each do |source_uri, specs|
+ found[source_uri] = specs.select do |spec_name, version, spec_platform|
+ dependency =~ Gem::Dependency.new(spec_name, version) and
+ (not matching_platform or Gem::Platform.match(spec_platform))
+ end
+ end
+
+ specs_and_sources = []
+
+ found.each do |source_uri, specs|
+ uri_str = source_uri.to_s
+ specs_and_sources.push(*specs.map { |spec| [spec, uri_str] })
+ end
+
+ specs_and_sources
+ end
+
+ ##
+ # Returns Array of gem repositories that were generated with RubyGems less
+ # than 1.2.
+
+ def legacy_repos
+ Gem.sources.reject do |source_uri|
+ source_uri = URI.parse source_uri
+ spec_path = source_uri + "specs.#{Gem.marshal_version}.gz"
+
+ begin
+ @fetcher.fetch_size spec_path
+ rescue Gem::RemoteFetcher::FetchError
+ begin
+ @fetcher.fetch_size(source_uri + 'yaml') # re-raise if non-repo
+ rescue Gem::RemoteFetcher::FetchError
+ alert_error "#{source_uri} does not appear to be a repository"
+ raise
+ end
+ false
+ end
+ end
+ end
+
+ ##
+ # Returns a list of gems available for each source in Gem::sources. If
+ # +all+ is true, all versions are returned instead of only latest versions.
+
+ def list(all = false)
+ list = {}
+
+ file = all ? 'specs' : 'latest_specs'
+
+ Gem.sources.each do |source_uri|
+ source_uri = URI.parse source_uri
+
+ if all and @specs.include? source_uri then
+ list[source_uri] = @specs[source_uri]
+ elsif @latest_specs.include? source_uri then
+ list[source_uri] = @latest_specs[source_uri]
+ else
+ specs = load_specs source_uri, file
+
+ cache = all ? @specs : @latest_specs
+
+ cache[source_uri] = specs
+ list[source_uri] = specs
+ end
+ end
+
+ list
+ end
+
+ def load_specs(source_uri, file)
+ file_name = "#{file}.#{Gem.marshal_version}.gz"
+
+ spec_path = source_uri + file_name
+
+ cache_dir = cache_dir spec_path
+
+ local_file = File.join(cache_dir, file_name).chomp '.gz'
+
+ if File.exist? local_file then
+ local_size = File.stat(local_file).size
+
+ remote_file = spec_path.dup
+ remote_file.path = remote_file.path.chomp '.gz'
+ remote_size = @fetcher.fetch_size remote_file
+
+ spec_dump = Gem.read_binary local_file if remote_size == local_size
+ end
+
+ unless spec_dump then
+ loaded = true
+
+ spec_dump_gz = @fetcher.fetch_path spec_path
+ spec_dump = Gem.gunzip spec_dump_gz
+ end
+
+ specs = Marshal.load spec_dump
+
+ if loaded and @update_cache then
+ begin
+ FileUtils.mkdir_p cache_dir
+
+ open local_file, 'wb' do |io|
+ Marshal.dump specs, io
+ end
+ rescue
+ end
+ end
+
+ specs
+ end
+
+ ##
+ # Warn about legacy repositories if +exception+ indicates only legacy
+ # repositories are available, and yield to the block. Returns false if the
+ # exception indicates some other FetchError.
+
+ def warn_legacy(exception)
+ uri = exception.uri.to_s
+ if uri =~ /specs\.#{Regexp.escape Gem.marshal_version}\.gz$/ then
+ alert_warning <<-EOF
+RubyGems 1.2+ index not found for:
+\t#{legacy_repos.join "\n\t"}
+
+RubyGems will revert to legacy indexes degrading performance.
+ EOF
+
+ yield
+
+ return true
+ end
+
+ false
+ end
+
+end
+
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index c50910aeb4..0642a4f3e0 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -6,6 +6,7 @@
require 'rubygems'
require 'rubygems/version'
+require 'rubygems/requirement'
require 'rubygems/platform'
# :stopdoc:
@@ -16,6 +17,9 @@ if RUBY_VERSION < '1.9' then
t - ((t.to_f + t.gmt_offset) % 86400)
end unless defined? Time.today
end
+
+class Date; end # for ruby_code if date.rb wasn't required
+
# :startdoc:
module Gem
@@ -37,22 +41,32 @@ module Gem
#
class Specification
+ ##
# Allows deinstallation of gems with legacy platforms.
+
attr_accessor :original_platform # :nodoc:
# ------------------------- Specification version constants.
+ ##
# The the version number of a specification that does not specify one
# (i.e. RubyGems 0.7 or earlier).
+
NONEXISTENT_SPECIFICATION_VERSION = -1
+ ##
# The specification version applied to any new Specification instances
# created. This should be bumped whenever something in the spec format
# changes.
- CURRENT_SPECIFICATION_VERSION = 2
+ #--
+ # When updating this number, be sure to also update #to_ruby.
+ CURRENT_SPECIFICATION_VERSION = 3
+
+ ##
# An informal list of changes to the specification. The highest-valued
# key should be equal to the CURRENT_SPECIFICATION_VERSION.
+
SPECIFICATION_VERSION_HISTORY = {
-1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'],
1 => [
@@ -63,10 +77,13 @@ module Gem
'Added "required_rubygems_version"',
'Now forward-compatible with future versions',
],
+ 3 => [
+ 'Added dependency types',
+ ],
}
# :stopdoc:
- MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16 }
+ MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 16 }
now = Time.at(Time.now.to_i)
TODAY = now - ((now.to_i + now.gmt_offset) % 86400)
@@ -335,6 +352,14 @@ module Gem
read_only :dependencies
+ def runtime_dependencies
+ dependencies.select { |d| d.type == :runtime || d.type == nil }
+ end
+
+ def development_dependencies
+ dependencies.select { |d| d.type == :development }
+ end
+
# ALIASED gemspec attributes -------------------------------------
attribute_alias_singular :executable, :executables
@@ -629,27 +654,31 @@ module Gem
end
end
- # Adds a dependency to this Gem. For example,
+ # Adds a development dependency to this Gem. For example,
+ #
+ # spec.add_development_dependency('jabber4r', '> 0.1', '<= 0.5')
#
- # spec.add_dependency('jabber4r', '> 0.1', '<= 0.5')
+ # Development dependencies aren't installed by default, and
+ # aren't activated when a gem is required.
#
# gem:: [String or Gem::Dependency] The Gem name/dependency.
# requirements:: [default=">= 0"] The version requirements.
+ def add_development_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :development, *requirements)
+ end
+
+ # Adds a runtime dependency to this Gem. For example,
#
- def add_dependency(gem, *requirements)
- requirements = if requirements.empty? then
- Gem::Requirement.default
- else
- requirements.flatten
- end
+ # spec.add_runtime_dependency('jabber4r', '> 0.1', '<= 0.5')
+ #
+ # gem:: [String or Gem::Dependency] The Gem name/dependency.
+ # requirements:: [default=">= 0"] The version requirements.
+ def add_runtime_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :runtime, *requirements)
+ end
- unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
- gem = Dependency.new(gem, requirements)
- end
+ alias add_dependency add_runtime_dependency
- dependencies << gem
- end
-
# Returns the full name (name-version) of this Gem. Platform information
# is included (name-version-platform) if it is specified (and not the
# default Ruby platform).
@@ -673,30 +702,31 @@ module Gem
end
end
+ ##
# The full path to the gem (install path + full name).
- #
- # return:: [String] the full gem path
- #
+
def full_gem_path
path = File.join installation_path, 'gems', full_name
return path if File.directory? path
File.join installation_path, 'gems', original_name
end
-
+
+ ##
# The default (generated) file name of the gem.
+
def file_name
full_name + ".gem"
end
-
- # The root directory that the gem was installed into.
- #
- # return:: [String] the installation path
- #
+
+ ##
+ # The directory that this gem was installed into.
+
def installation_path
- (File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2]).
- join(File::SEPARATOR)
+ path = File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2]
+ path = path.join File::SEPARATOR
+ File.expand_path path
end
-
+
# Checks if this Specification meets the requirement of the supplied
# dependency.
#
@@ -778,9 +808,11 @@ module Gem
self.platform = Gem::Platform.new @platform
end
+ ##
# Returns a Ruby code representation of this specification, such that it
# can be eval'ed and reconstruct the same specification later. Attributes
# that still have their default values are omitted.
+
def to_ruby
mark_version
result = []
@@ -792,8 +824,6 @@ module Gem
result << " s.platform = #{ruby_code original_platform}"
end
result << ""
- result << " s.specification_version = #{specification_version} if s.respond_to? :specification_version="
- result << ""
result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version="
handled = [
@@ -816,15 +846,42 @@ module Gem
end
end
- result << "" unless dependencies.empty?
+ result << nil
+ result << " if s.respond_to? :specification_version then"
+ result << " current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION"
+ result << " s.specification_version = #{specification_version}"
+ result << nil
+
+ result << " if current_version >= 3 then"
+
+ unless dependencies.empty? then
+ dependencies.each do |dep|
+ version_reqs_param = dep.requirements_list.inspect
+ dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
+ result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{version_reqs_param})"
+ end
+ end
+
+ result << " else"
- dependencies.each do |dep|
- version_reqs_param = dep.requirements_list.inspect
- result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
+ unless dependencies.empty? then
+ dependencies.each do |dep|
+ version_reqs_param = dep.requirements_list.inspect
+ result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
+ end
end
+ result << ' end'
+
+ result << " else"
+ dependencies.each do |dep|
+ version_reqs_param = dep.requirements_list.inspect
+ result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
+ end
+ result << " end"
+
result << "end"
- result << ""
+ result << nil
result.join "\n"
end
@@ -940,6 +997,22 @@ module Gem
private
+ def add_dependency_with_type(dependency, type, *requirements)
+ requirements = if requirements.empty? then
+ Gem::Requirement.default
+ else
+ requirements.flatten
+ end
+
+ unless dependency.respond_to?(:name) &&
+ dependency.respond_to?(:version_requirements)
+
+ dependency = Dependency.new(dependency, requirements, type)
+ end
+
+ dependencies << dependency
+ end
+
def find_all_satisfiers(dep)
Gem.source_index.each do |name,gem|
if(gem.satisfies_requirement?(dep)) then
diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb
new file mode 100644
index 0000000000..0486db2b32
--- /dev/null
+++ b/lib/rubygems/test_utilities.rb
@@ -0,0 +1,120 @@
+require 'tempfile'
+require 'rubygems'
+require 'rubygems/remote_fetcher'
+
+##
+# A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP
+# requests when testing code that uses RubyGems.
+#
+# Example:
+#
+# @fetcher = Gem::FakeFetcher.new
+# @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml
+# Gem::RemoteFetcher.fetcher = @fetcher
+#
+# # invoke RubyGems code
+#
+# paths = @fetcher.paths
+# assert_equal 'http://gems.example.com/yaml', paths.shift
+# assert paths.empty?, paths.join(', ')
+#
+# See RubyGems' tests for more examples of FakeFetcher.
+
+class Gem::FakeFetcher
+
+ attr_reader :data
+ attr_accessor :paths
+
+ def initialize
+ @data = {}
+ @paths = []
+ end
+
+ def fetch_path(path)
+ path = path.to_s
+ @paths << path
+ raise ArgumentError, 'need full URI' unless path =~ %r'^http://'
+ data = @data[path]
+
+ if data.nil? then
+ raise Gem::RemoteFetcher::FetchError.new('no data', path)
+ end
+
+ data.respond_to?(:call) ? data.call : data
+ end
+
+ def fetch_size(path)
+ path = path.to_s
+ @paths << path
+ raise ArgumentError, 'need full URI' unless path =~ %r'^http://'
+ data = @data[path]
+
+ if data.nil? then
+ raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", nil)
+ end
+
+ data.respond_to?(:call) ? data.call : data.length
+ end
+
+ def download spec, source_uri, install_dir = Gem.dir
+ name = "#{spec.full_name}.gem"
+ path = File.join(install_dir, 'cache', name)
+
+ Gem.ensure_gem_subdirectories install_dir
+
+ if source_uri =~ /^http/ then
+ File.open(path, "wb") do |f|
+ f.write fetch_path(File.join(source_uri, "gems", name))
+ end
+ else
+ FileUtils.cp source_uri, path
+ end
+
+ path
+ end
+
+end
+
+# :stopdoc:
+class Gem::RemoteFetcher
+
+ def self.fetcher=(fetcher)
+ @fetcher = fetcher
+ end
+
+end
+# :startdoc:
+
+##
+# A StringIO duck-typed class that uses Tempfile instead of String as the
+# backing store.
+#--
+# This class was added to flush out problems in Rubinius' IO implementation.
+
+class TempIO
+
+ @@count = 0
+
+ def initialize(string = '')
+ @tempfile = Tempfile.new "TempIO-#{@@count += 1}"
+ @tempfile.binmode
+ @tempfile.write string
+ @tempfile.rewind
+ end
+
+ def method_missing(meth, *args, &block)
+ @tempfile.send(meth, *args, &block)
+ end
+
+ def respond_to?(meth)
+ @tempfile.respond_to? meth
+ end
+
+ def string
+ @tempfile.flush
+
+ Gem.read_binary @tempfile.path
+ end
+
+end
+
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index e2b5e5372b..c5ae47b7eb 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -176,9 +176,10 @@ class Gem::Uninstaller
end
def path_ok?(spec)
- match_path = File.join @gem_home, 'gems', spec.full_name
+ full_path = File.join @gem_home, 'gems', spec.full_name
+ original_path = File.join @gem_home, 'gems', spec.original_name
- match_path == spec.full_gem_path
+ full_path == spec.full_gem_path || original_path == spec.full_gem_path
end
def dependencies_ok?(spec)
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 8d27df8768..30a728c597 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -6,54 +6,71 @@
module Gem
- ####################################################################
- # Module that defines the default UserInteraction. Any class
- # including this module will have access to the +ui+ method that
- # returns the default UI.
+ ##
+ # Module that defines the default UserInteraction. Any class including this
+ # module will have access to the +ui+ method that returns the default UI.
+
module DefaultUserInteraction
+ ##
+ # The default UI is a class variable of the singleton class for this
+ # module.
+
+ @ui = nil
+
+ ##
# Return the default UI.
+
+ def self.ui
+ @ui ||= Gem::ConsoleUI.new
+ end
+
+ ##
+ # Set the default UI. If the default UI is never explicitly set, a simple
+ # console based UserInteraction will be used automatically.
+
+ def self.ui=(new_ui)
+ @ui = new_ui
+ end
+
+ ##
+ # Use +new_ui+ for the duration of +block+.
+
+ def self.use_ui(new_ui)
+ old_ui = @ui
+ @ui = new_ui
+ yield
+ ensure
+ @ui = old_ui
+ end
+
+ ##
+ # See DefaultUserInteraction::ui
+
def ui
DefaultUserInteraction.ui
end
- # Set the default UI. If the default UI is never explicitly set, a
- # simple console based UserInteraction will be used automatically.
+ ##
+ # See DefaultUserInteraction::ui=
+
def ui=(new_ui)
DefaultUserInteraction.ui = new_ui
end
+ ##
+ # See DefaultUserInteraction::use_ui
+
def use_ui(new_ui, &block)
DefaultUserInteraction.use_ui(new_ui, &block)
end
- # The default UI is a class variable of the singleton class for
- # this module.
-
- @ui = nil
-
- class << self
- def ui
- @ui ||= Gem::ConsoleUI.new
- end
- def ui=(new_ui)
- @ui = new_ui
- end
- def use_ui(new_ui)
- old_ui = @ui
- @ui = new_ui
- yield
- ensure
- @ui = old_ui
- end
- end
end
- ####################################################################
+ ##
# Make the default UI accessable without the "ui." prefix. Classes
- # including this module may use the interaction methods on the
- # default UI directly. Classes may also reference the +ui+ and
- # <tt>ui=</tt> methods.
+ # including this module may use the interaction methods on the default UI
+ # directly. Classes may also reference the ui and ui= methods.
#
# Example:
#
@@ -64,22 +81,30 @@ module Gem
# n = ask("What is the meaning of life?")
# end
# end
+
module UserInteraction
+
include DefaultUserInteraction
- [
- :choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning,
- :alert_error, :terminate_interaction
- ].each do |methname|
+
+ [:alert,
+ :alert_error,
+ :alert_warning,
+ :ask,
+ :ask_yes_no,
+ :choose_from_list,
+ :say,
+ :terminate_interaction ].each do |methname|
class_eval %{
def #{methname}(*args)
ui.#{methname}(*args)
end
- }
+ }, __FILE__, __LINE__
end
end
- ####################################################################
+ ##
# StreamUI implements a simple stream based user interface.
+
class StreamUI
attr_reader :ins, :outs, :errs
@@ -89,15 +114,19 @@ module Gem
@outs = out_stream
@errs = err_stream
end
-
- # Choose from a list of options. +question+ is a prompt displayed
- # above the list. +list+ is a list of option strings. Returns
- # the pair [option_name, option_index].
+
+ ##
+ # Choose from a list of options. +question+ is a prompt displayed above
+ # the list. +list+ is a list of option strings. Returns the pair
+ # [option_name, option_index].
+
def choose_from_list(question, list)
@outs.puts question
+
list.each_with_index do |item, index|
@outs.puts " #{index+1}. #{item}"
end
+
@outs.print "> "
@outs.flush
@@ -109,28 +138,32 @@ module Gem
return list[result], result
end
- # Ask a question. Returns a true for yes, false for no. If not
- # connected to a tty, raises an exception if default is nil,
- # otherwise returns default.
+ ##
+ # Ask a question. Returns a true for yes, false for no. If not connected
+ # to a tty, raises an exception if default is nil, otherwise returns
+ # default.
+
def ask_yes_no(question, default=nil)
- if not @ins.tty? then
+ unless @ins.tty? then
if default.nil? then
- raise(
- Gem::OperationNotSupportedError,
- "Not connected to a tty and no default specified")
+ raise Gem::OperationNotSupportedError,
+ "Not connected to a tty and no default specified"
else
return default
end
end
+
qstr = case default
- when nil
- 'yn'
- when true
- 'Yn'
- else
- 'yN'
- end
+ when nil
+ 'yn'
+ when true
+ 'Yn'
+ else
+ 'yN'
+ end
+
result = nil
+
while result.nil?
result = ask("#{question} [#{qstr}]")
result = case result
@@ -144,51 +177,68 @@ module Gem
nil
end
end
+
return result
end
-
- # Ask a question. Returns an answer if connected to a tty, nil
- # otherwise.
+
+ ##
+ # Ask a question. Returns an answer if connected to a tty, nil otherwise.
+
def ask(question)
return nil if not @ins.tty?
+
@outs.print(question + " ")
@outs.flush
+
result = @ins.gets
result.chomp! if result
result
end
-
+
+ ##
# Display a statement.
+
def say(statement="")
@outs.puts statement
end
-
- # Display an informational alert.
+
+ ##
+ # Display an informational alert. Will ask +question+ if it is not nil.
+
def alert(statement, question=nil)
@outs.puts "INFO: #{statement}"
- return ask(question) if question
+ ask(question) if question
end
-
- # Display a warning in a location expected to get error messages.
+
+ ##
+ # Display a warning in a location expected to get error messages. Will
+ # ask +question+ if it is not nil.
+
def alert_warning(statement, question=nil)
@errs.puts "WARNING: #{statement}"
- ask(question) if question
+ ask(question) if question
end
-
- # Display an error message in a location expected to get error
- # messages.
+
+ ##
+ # Display an error message in a location expected to get error messages.
+ # Will ask +question+ if it is not nil.
+
def alert_error(statement, question=nil)
@errs.puts "ERROR: #{statement}"
ask(question) if question
end
- # Terminate the application normally, running any exit handlers
- # that might have been defined.
+ ##
+ # Terminate the application with exit code +status+, running any exit
+ # handlers that might have been defined.
+
def terminate_interaction(status = 0)
raise Gem::SystemExitException, status
end
- # Return a progress reporter object
+ ##
+ # Return a progress reporter object chosen from the current verbosity.
+
def progress_reporter(*args)
case Gem.configuration.verbose
when nil, false
@@ -200,6 +250,9 @@ module Gem
end
end
+ ##
+ # An absolutely silent progress reporter.
+
class SilentProgressReporter
attr_reader :count
@@ -213,6 +266,9 @@ module Gem
end
end
+ ##
+ # A basic dotted progress reporter.
+
class SimpleProgressReporter
include DefaultUserInteraction
@@ -228,17 +284,27 @@ module Gem
@out.puts initial_message
end
+ ##
+ # Prints out a dot and ignores +message+.
+
def updated(message)
@count += 1
@out.print "."
@out.flush
end
+ ##
+ # Prints out the terminal message.
+
def done
@out.puts "\n#{@terminal_message}"
end
+
end
+ ##
+ # A progress reporter that prints out messages about the current progress.
+
class VerboseProgressReporter
include DefaultUserInteraction
@@ -254,32 +320,41 @@ module Gem
@out.puts initial_message
end
+ ##
+ # Prints out the position relative to the total and the +message+.
+
def updated(message)
@count += 1
@out.puts "#{@count}/#{@total}: #{message}"
end
+ ##
+ # Prints out the terminal message.
+
def done
@out.puts @terminal_message
end
end
end
- ####################################################################
- # Subclass of StreamUI that instantiates the user interaction using
- # standard in, out and error.
+ ##
+ # Subclass of StreamUI that instantiates the user interaction using STDIN,
+ # STDOUT, and STDERR.
+
class ConsoleUI < StreamUI
def initialize
super(STDIN, STDOUT, STDERR)
end
end
- ####################################################################
+ ##
# SilentUI is a UI choice that is absolutely silent.
+
class SilentUI
def method_missing(sym, *args, &block)
self
end
end
+
end
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 87a1bc72ed..ff4a7bf079 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -8,6 +8,7 @@ require 'rubygems'
##
# The Version class processes string versions into comparable values
+
class Gem::Version
include Comparable
@@ -17,11 +18,8 @@ class Gem::Version
attr_reader :version
##
- # Checks if version string is valid format
- #
- # str:: [String] the version string
- # return:: [Boolean] true if the string format is correct, otherwise false
- #
+ # Returns true if +version+ is a valid version string.
+
def self.correct?(version)
case version
when Integer, /\A\s*(\d+(\.\d+)*)*\s*\z/ then true
@@ -36,7 +34,7 @@ class Gem::Version
# ver1 = Version.create('1.3.17') # -> (Version object)
# ver2 = Version.create(ver1) # -> (ver1)
# ver3 = Version.create(nil) # -> nil
- #
+
def self.create(input)
if input.respond_to? :version then
input
@@ -48,10 +46,9 @@ class Gem::Version
end
##
- # Constructs a version from the supplied string
- #
- # version:: [String] The version string. Format is digit.digit...
- #
+ # Constructs a Version from the +version+ string. A version string is a
+ # series of digits separated by dots.
+
def initialize(version)
raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version)
@@ -73,7 +70,9 @@ class Gem::Version
self.version = array[0]
end
+ ##
# Strip ignored trailing zeros.
+
def normalize
@ints = build_array_from_version_string
@@ -94,10 +93,8 @@ class Gem::Version
end
##
- # Convert version to integer array
- #
- # return:: [Array] list of integers
- #
+ # Returns an integer array representation of this Version.
+
def to_ints
normalize unless @ints
@ints
@@ -117,20 +114,25 @@ class Gem::Version
end
##
- # Compares two versions
- #
- # other:: [Version or .ints] other version to compare to
- # return:: [Fixnum] -1, 0, 1
- #
+ # Compares this version with +other+ returning -1, 0, or 1 if the other
+ # version is larger, the same, or smaller than this one.
+
def <=>(other)
+ return nil unless self.class === other
return 1 unless other
@ints <=> other.ints
end
- alias eql? == # :nodoc:
+ ##
+ # A Version is only eql? to another version if it has the same version
+ # string. "1.0" is not the same version as "1".
+
+ def eql?(other)
+ self.class === other and @version == other.version
+ end
def hash # :nodoc:
- to_ints.inject { |hash_code, n| hash_code + n }
+ @version.hash
end
# Return a new version object where the next to the last revision