summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2012-11-29 06:52:18 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2012-11-29 06:52:18 +0000
commit9694bb8cac12969300692dac5a1cf7aa4e3a46cd (patch)
treec3cb423d701f7049ba9382de052e2a937cd1302d /lib
parent3f606b7063fc7a8b191556365ad343a314719a8d (diff)
* lib/rubygems*: Updated to RubyGems 2.0
* test/rubygems*: ditto. * common.mk (prelude): Updated for RubyGems 2.0 source rearrangement. * tool/change_maker.rb: Allow invalid UTF-8 characters in source files. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37976 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r--lib/rubygems.rb563
-rw-r--r--lib/rubygems/available_set.rb95
-rw-r--r--lib/rubygems/builder.rb99
-rw-r--r--lib/rubygems/command.rb126
-rw-r--r--lib/rubygems/command_manager.rb107
-rw-r--r--lib/rubygems/commands/build_command.rb28
-rw-r--r--lib/rubygems/commands/cert_command.rb256
-rw-r--r--lib/rubygems/commands/check_command.rb53
-rw-r--r--lib/rubygems/commands/cleanup_command.rb7
-rw-r--r--lib/rubygems/commands/contents_command.rb40
-rw-r--r--lib/rubygems/commands/dependency_command.rb11
-rw-r--r--lib/rubygems/commands/environment_command.rb21
-rw-r--r--lib/rubygems/commands/fetch_command.rb19
-rw-r--r--lib/rubygems/commands/generate_index_command.rb54
-rw-r--r--lib/rubygems/commands/help_command.rb2
-rw-r--r--lib/rubygems/commands/install_command.rb105
-rw-r--r--lib/rubygems/commands/list_command.rb10
-rw-r--r--lib/rubygems/commands/lock_command.rb2
-rw-r--r--lib/rubygems/commands/mirror_command.rb17
-rw-r--r--lib/rubygems/commands/outdated_command.rb9
-rw-r--r--lib/rubygems/commands/owner_command.rb18
-rw-r--r--lib/rubygems/commands/pristine_command.rb21
-rw-r--r--lib/rubygems/commands/push_command.rb13
-rw-r--r--lib/rubygems/commands/query_command.rb70
-rw-r--r--lib/rubygems/commands/rdoc_command.rb51
-rw-r--r--lib/rubygems/commands/search_command.rb22
-rw-r--r--lib/rubygems/commands/server_command.rb2
-rw-r--r--lib/rubygems/commands/setup_command.rb145
-rw-r--r--lib/rubygems/commands/sources_command.rb32
-rw-r--r--lib/rubygems/commands/specification_command.rb24
-rw-r--r--lib/rubygems/commands/uninstall_command.rb31
-rw-r--r--lib/rubygems/commands/unpack_command.rb10
-rw-r--r--lib/rubygems/commands/update_command.rb58
-rw-r--r--lib/rubygems/commands/yank_command.rb98
-rw-r--r--lib/rubygems/compatibility.rb51
-rw-r--r--lib/rubygems/config_file.rb136
-rw-r--r--lib/rubygems/core_ext/kernel_gem.rb53
-rwxr-xr-xlib/rubygems/core_ext/kernel_require.rb119
-rw-r--r--lib/rubygems/custom_require.rb69
-rw-r--r--lib/rubygems/defaults.rb24
-rw-r--r--lib/rubygems/dependency.rb71
-rw-r--r--lib/rubygems/dependency_installer.rb226
-rw-r--r--lib/rubygems/dependency_list.rb30
-rw-r--r--lib/rubygems/dependency_resolver.rb562
-rw-r--r--lib/rubygems/deprecate.rb80
-rw-r--r--lib/rubygems/doc_manager.rb243
-rw-r--r--lib/rubygems/errors.rb101
-rw-r--r--lib/rubygems/exceptions.rb32
-rw-r--r--lib/rubygems/ext/builder.rb26
-rw-r--r--lib/rubygems/ext/configure_builder.rb4
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb4
-rw-r--r--lib/rubygems/ext/rake_builder.rb4
-rw-r--r--lib/rubygems/format.rb82
-rw-r--r--lib/rubygems/gem_openssl.rb90
-rw-r--r--lib/rubygems/gem_path_searcher.rb172
-rw-r--r--lib/rubygems/gem_runner.rb19
-rw-r--r--lib/rubygems/gemcutter_utilities.rb29
-rw-r--r--lib/rubygems/indexer.rb168
-rw-r--r--lib/rubygems/install_message.rb12
-rw-r--r--lib/rubygems/install_update_options.rb74
-rw-r--r--lib/rubygems/installer.rb372
-rw-r--r--lib/rubygems/installer_test_case.rb90
-rw-r--r--lib/rubygems/mock_gem_ui.rb17
-rw-r--r--lib/rubygems/name_tuple.rb110
-rw-r--r--lib/rubygems/old_format.rb153
-rw-r--r--lib/rubygems/package.rb556
-rw-r--r--lib/rubygems/package/digest_io.rb64
-rw-r--r--lib/rubygems/package/f_sync_dir.rb23
-rw-r--r--lib/rubygems/package/old.rb147
-rw-r--r--lib/rubygems/package/tar_header.rb73
-rw-r--r--lib/rubygems/package/tar_input.rb235
-rw-r--r--lib/rubygems/package/tar_output.rb146
-rw-r--r--lib/rubygems/package/tar_reader.rb23
-rw-r--r--lib/rubygems/package/tar_writer.rb70
-rw-r--r--lib/rubygems/package_task.rb7
-rw-r--r--lib/rubygems/path_support.rb21
-rw-r--r--lib/rubygems/platform.rb45
-rw-r--r--lib/rubygems/rdoc.rb316
-rw-r--r--lib/rubygems/remote_fetcher.rb171
-rw-r--r--lib/rubygems/request_set.rb182
-rw-r--r--lib/rubygems/require_paths_builder.rb18
-rw-r--r--lib/rubygems/requirement.rb87
-rw-r--r--lib/rubygems/security.rb850
-rw-r--r--lib/rubygems/security/policies.rb115
-rw-r--r--lib/rubygems/security/policy.rb227
-rw-r--r--lib/rubygems/security/signer.rb136
-rw-r--r--lib/rubygems/security/trust_dir.rb104
-rw-r--r--lib/rubygems/server.rb100
-rw-r--r--lib/rubygems/source.rb144
-rw-r--r--lib/rubygems/source_index.rb406
-rw-r--r--lib/rubygems/source_list.rb87
-rw-r--r--lib/rubygems/source_local.rb92
-rw-r--r--lib/rubygems/source_specific_file.rb28
-rw-r--r--lib/rubygems/spec_fetcher.rb298
-rw-r--r--lib/rubygems/specification.rb1046
-rw-r--r--lib/rubygems/ssl_certs/AddTrustExternalCARoot.pem90
-rw-r--r--lib/rubygems/ssl_certs/Entrust_net-Secure-Server-Certification-Authority.pem90
-rw-r--r--lib/rubygems/ssl_certs/VerisignClass3PublicPrimaryCertificationAuthority-G2.pem57
-rw-r--r--lib/rubygems/syck_hack.rb2
-rw-r--r--lib/rubygems/test_case.rb245
-rw-r--r--lib/rubygems/test_utilities.rb30
-rw-r--r--lib/rubygems/uninstaller.rb82
-rw-r--r--lib/rubygems/user_interaction.rb10
-rw-r--r--lib/rubygems/validator.rb73
-rw-r--r--lib/rubygems/version.rb23
105 files changed, 7208 insertions, 4753 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index d3a1f7c7b5..1494e915f7 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -5,32 +5,6 @@
# See LICENSE.txt for permissions.
#++
-module Gem
- QUICKLOADER_SUCKAGE = RUBY_VERSION =~ /^1\.9\.1/
-
- # Only MRI 1.9.2 has the custom prelude.
- GEM_PRELUDE_SUCKAGE = RUBY_VERSION =~ /^1\.9\.2/ && RUBY_ENGINE == "ruby"
-end
-
-if Gem::GEM_PRELUDE_SUCKAGE and defined?(Gem::QuickLoader) then
- Gem::QuickLoader.remove
-
- $LOADED_FEATURES.delete Gem::QuickLoader.path_to_full_rubygems_library
-
- if $LOADED_FEATURES.any? do |path| path.end_with? '/rubygems.rb' end then
- # TODO path does not exist here
- raise LoadError, "another rubygems is already loaded from #{path}"
- end
-
- class << Gem
- remove_method :try_activate if Gem.respond_to?(:try_activate, true)
- end
-end
-
-require 'rubygems/defaults'
-require 'rbconfig'
-require "rubygems/deprecate"
-
##
# RubyGems is the Ruby standard for publishing and managing third party
# libraries.
@@ -72,8 +46,8 @@ require "rubygems/deprecate"
# For RubyGems packagers, provide lib/rubygems/operating_system.rb and
# override any defaults from lib/rubygems/defaults.rb.
#
-# For Ruby implementers, provide lib/rubygems/#{RUBY_ENGINE}.rb and override
-# any defaults from lib/rubygems/defaults.rb.
+# For Ruby implementers, provide lib/rubygems/defaults/#{RUBY_ENGINE}.rb and
+# override any defaults from lib/rubygems/defaults.rb.
#
# If you need RubyGems to perform extra work on install or uninstall, your
# defaults override file can set pre and post install and uninstall hooks.
@@ -83,8 +57,8 @@ require "rubygems/deprecate"
# == Bugs
#
# You can submit bugs to the
-# {RubyGems bug tracker}[http://rubyforge.org/tracker/?atid=575&group_id=126]
-# on RubyForge
+# {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues]
+# on GitHub
#
# == Credits
#
@@ -112,7 +86,8 @@ require "rubygems/deprecate"
# * Daniel Berger -- djberg96(at)gmail.com
# * Phil Hagelberg -- technomancy(at)gmail.com
# * Ryan Davis -- ryand-ruby(at)zenspider.com
-# * Evan Phoenix -- evan@phx.io
+# * Evan Phoenix -- evan(at)fallingsnow.net
+# * Steve Klabnik -- steve(at)steveklabnik.com
#
# (If your name is missing, PLEASE let us know!)
#
@@ -120,49 +95,22 @@ require "rubygems/deprecate"
#
# -The RubyGems Team
-module Gem
- VERSION = '1.8.24'
-
- ##
- # Raised when RubyGems is unable to load or activate a gem. Contains the
- # name and version requirements of the gem that either conflicts with
- # already activated gems or that RubyGems is otherwise unable to activate.
-
- class LoadError < ::LoadError
- # Name of gem
- attr_accessor :name
-
- # Version requirement of gem
- attr_accessor :requirement
- end
-
- # :stopdoc:
-
- RubyGemsVersion = VERSION
+require 'rbconfig'
- RbConfigPriorities = %w[
- EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name
- ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir
- ]
+module Gem
+ VERSION = '2.0.a'
+end
- unless defined?(ConfigMap)
- ##
- # Configuration settings from ::RbConfig
- ConfigMap = Hash.new do |cm, key|
- cm[key] = RbConfig::CONFIG[key.to_s]
- end
- else
- RbConfigPriorities.each do |key|
- ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
- end
- end
+# Must be first since it unloads the prelude from 1.9.2
+require 'rubygems/compatibility'
- RubyGemsPackageVersion = VERSION
+require 'rubygems/defaults'
+require 'rubygems/deprecate'
+require 'rubygems/errors'
+module Gem
RUBYGEMS_DIR = File.dirname File.expand_path(__FILE__)
- # :startdoc:
-
##
# An Array of Regexps that match windows ruby platforms.
@@ -175,11 +123,13 @@ module Gem
/wince/i,
]
- @@source_index = nil
+ GEM_DEP_FILES = %w!gem.deps.rb Gemfile Isolate!
+
@@win_platform = nil
@configuration = nil
@loaded_specs = {}
+ @path_to_default_spec_map = {}
@platforms = []
@ruby = nil
@sources = nil
@@ -198,12 +148,13 @@ module Gem
# activated. Returns false if it can't find the path in a gem.
def self.try_activate path
- # TODO: deprecate when 1.9.3 comes out.
# finds the _latest_ version... regardless of loaded specs and their deps
+ # if another gem had a requirement that would mean we shouldn't
+ # activate the latest version, then either it would alreaby be activated
+ # or if it was ambigious (and thus unresolved) the code in our custom
+ # require will try to activate the more specific version.
- # TODO: use find_all and bork if ambiguous
-
- spec = Gem::Specification.find_by_path path
+ spec = Gem::Specification.find_inactive_by_path path
return false unless spec
begin
@@ -215,77 +166,43 @@ module Gem
return true
end
- ##
- # Activates an installed gem matching +dep+. The gem must satisfy
- # +requirements+.
- #
- # Returns true if the gem is activated, false if it is already
- # loaded, or an exception otherwise.
- #
- # Gem#activate adds the library paths in +dep+ to $LOAD_PATH. Before a Gem
- # is activated its required Gems are activated. If the version information
- # is omitted, the highest version Gem of the supplied name is loaded. If a
- # Gem is not found that meets the version requirements or a required Gem is
- # not found, a Gem::LoadError is raised.
- #
- # More information on version requirements can be found in the
- # Gem::Requirement and Gem::Version documentation.
-
- def self.activate(dep, *requirements)
- raise ArgumentError, "Deprecated use of Gem.activate(dep)" if
- Gem::Dependency === dep
-
- Gem::Specification.find_by_name(dep, *requirements).activate
- end
-
- def self.activate_dep dep, *requirements # :nodoc:
- dep.to_spec.activate
- end
+ def self.needs
+ rs = Gem::RequestSet.new
- def self.activate_spec spec # :nodoc:
- spec.activate
- end
+ yield rs
- def self.unresolved_deps
- @unresolved_deps ||= Hash.new { |h, n| h[n] = Gem::Dependency.new n }
+ finish_resolve rs
end
- ##
- # An Array of all possible load paths for all versions of all gems in the
- # Gem installation.
-
- def self.all_load_paths
- result = []
+ def self.finish_resolve(request_set=Gem::RequestSet.new)
+ request_set.import Gem::Specification.unresolved_deps.values
- Gem.path.each do |gemdir|
- each_load_path all_partials(gemdir) do |load_path|
- result << load_path
- end
+ request_set.resolve_current.each do |s|
+ s.full_spec.activate
end
-
- result
end
- ##
- # Return all the partial paths in +gemdir+.
+ def self.detect_gemdeps
+ if path = ENV['RUBYGEMS_GEMDEPS']
+ path = path.dup.untaint
- def self.all_partials(gemdir)
- Dir[File.join(gemdir, "gems/*")]
- end
+ if path == "-"
+ path = GEM_DEP_FILES.find { |f| File.exists?(f) }
- private_class_method :all_partials
+ return unless path
+ end
- ##
- # See if a given gem is available.
+ return unless File.exists? path
- def self.available?(dep, *requirements)
- requirements = Gem::Requirement.default if requirements.empty?
+ rs = Gem::RequestSet.new
+ rs.load_gemdeps path
- unless dep.respond_to?(:name) and dep.respond_to?(:requirement) then
- dep = Gem::Dependency.new dep, requirements
+ rs.resolve_current.map do |s|
+ sp = s.full_spec
+ sp.activate
+ sp
+ end
end
-
- not dep.matching_specs(true).empty?
end
##
@@ -343,11 +260,10 @@ module Gem
# mainly used by the unit tests to provide test isolation.
def self.clear_paths
- @@source_index = nil
@paths = nil
@user_home = nil
- @searcher = nil
Gem::Specification.reset
+ Gem::Security.reset if const_defined? :Security
end
##
@@ -377,7 +293,8 @@ module Gem
# package is not available as a gem, return nil.
def self.datadir(gem_name)
-# TODO: deprecate
+# TODO: deprecate and move to Gem::Specification
+# and drop the extra ", gem_name" which is uselessly redundant
spec = @loaded_specs[gem_name]
return nil if spec.nil?
File.join spec.full_gem_path, "data", gem_name
@@ -391,10 +308,12 @@ module Gem
Zlib::Deflate.deflate data
end
+ # DOC: needs doc'd or :nodoc'd
def self.paths
@paths ||= Gem::PathSupport.new
end
+ # DOC: needs doc'd or :nodoc'd
def self.paths=(env)
clear_paths
@paths = Gem::PathSupport.new env
@@ -450,7 +369,7 @@ module Gem
require 'fileutils'
- %w[cache doc gems specifications].each do |name|
+ %w[cache build_info doc gems specifications].each do |name|
subdir = File.join dir, name
next if File.exist? subdir
FileUtils.mkdir_p subdir rescue nil # in case of perms issues -- lame
@@ -567,12 +486,27 @@ module Gem
end
##
+ # Top level install helper method. Allows you to install gems interactively:
+ #
+ # % irb
+ # >> Gem.install "minitest"
+ # Fetching: minitest-3.0.1.gem (100%)
+ # => [#<Gem::Specification:0x1013b4528 @name="minitest", ...>]
+
+ def self.install name, version = Gem::Requirement.default
+ require "rubygems/dependency_installer"
+ inst = Gem::DependencyInstaller.new
+ inst.install name, version
+ inst.installed_gems
+ end
+
+ ##
# Get the default RubyGems API host. This is normally
# <tt>https://rubygems.org</tt>.
def self.host
# TODO: move to utils
- @host ||= "https://rubygems.org"
+ @host ||= Gem::DEFAULT_HOST
end
## Set the default RubyGems API host.
@@ -583,43 +517,6 @@ module Gem
end
##
- # Return a list of all possible load paths for the latest version for all
- # gems in the Gem installation.
-
- def self.latest_load_paths
- result = []
-
- Gem.path.each do |gemdir|
- each_load_path(latest_partials(gemdir)) do |load_path|
- result << load_path
- end
- end
-
- result
- end
-
- ##
- # Return only the latest partial paths in the given +gemdir+.
-
- def self.latest_partials(gemdir)
- latest = {}
- all_partials(gemdir).each do |gp|
- base = File.basename gp
-
- if base.to_s =~ /(.*)-((\d+\.)*\d+)/ then
- name, version = $1, $2
- ver = Gem::Version.new(version)
- if latest[name].nil? || ver > latest[name][0]
- latest[name] = [ver, gp]
- end
- end
- end
- latest.collect { |k,v| v[1] }
- end
-
- private_class_method :latest_partials
-
- ##
# The index to insert activated gem paths into the $LOAD_PATH.
#
# Defaults to the site lib directory unless gem_prelude.rb has loaded paths,
@@ -629,16 +526,6 @@ module Gem
def self.load_path_insert_index
index = $LOAD_PATH.index ConfigMap[:sitelibdir]
- if QUICKLOADER_SUCKAGE then
- $LOAD_PATH.each_with_index do |path, i|
- if path.instance_variables.include?(:@gem_prelude_index) or
- path.instance_variables.include?('@gem_prelude_index') then
- index = i
- break
- end
- end
- end
-
index
end
@@ -712,27 +599,6 @@ module Gem
end
##
- # Get the appropriate cache path.
- #
- # Pass a string to use a different base path, or nil/false (default) for
- # Gem.dir.
- #
-
- def self.cache_dir(custom_dir=false)
- File.join(custom_dir || Gem.dir, "cache")
- end
-
- ##
- # Given a gem path, find the gem in cache.
- #
- # Pass a string as the second argument to use a different base path, or
- # nil/false (default) for Gem.dir.
-
- def self.cache_gem(filename, user_dir=false)
- cache_dir(user_dir).add(filename)
- end
-
- ##
# Set array of platforms this RubyGems supports (primarily for testing).
def self.platforms=(platforms)
@@ -770,6 +636,15 @@ module Gem
end
##
+ # Adds a post-installs hook that will be passed a Gem::DependencyInstaller
+ # and a list of installed specifications when
+ # Gem::DependencyInstaller#install is complete
+
+ def self.done_installing(&hook)
+ @done_installing_hooks << hook
+ end
+
+ ##
# Adds a hook that will get run after Gem::Specification.reset is
# run.
@@ -828,39 +703,10 @@ module Gem
end
##
- # Promotes the load paths of the +gem_name+ over the load paths of
- # +over_name+. Useful for allowing one gem to override features in another
- # using #find_files.
-
- def self.promote_load_path(gem_name, over_name)
- gem = Gem.loaded_specs[gem_name]
- over = Gem.loaded_specs[over_name]
-
- raise ArgumentError, "gem #{gem_name} is not activated" if gem.nil?
- raise ArgumentError, "gem #{over_name} is not activated" if over.nil?
-
- last_gem_path = Gem::Path.path(gem.full_gem_path).add(gem.require_paths.last)
-
- over_paths = over.require_paths.map do |path|
- Gem::Path.path(over.full_gem_path).add(path).to_s
- end
-
- over_paths.each do |path|
- $LOAD_PATH.delete path
- end
-
- gem = $LOAD_PATH.index(last_gem_path) + 1
-
- $LOAD_PATH.insert(gem, *over_paths)
- end
-
- ##
- # Refresh source_index from disk and clear searcher.
+ # Refresh available gems from disk.
def self.refresh
Gem::Specification.reset
- @source_index = nil
- @searcher = nil
end
##
@@ -871,50 +717,6 @@ module Gem
end
##
- # Report a load error during activation. The message of load error
- # depends on whether it was a version mismatch or if there are not gems of
- # any version by the requested name.
-
- def self.report_activate_error(gem)
- matches = Gem::Specification.find_by_name(gem.name)
-
- if matches.empty? then
- error = Gem::LoadError.new(
- "Could not find RubyGem #{gem.name} (#{gem.requirement})\n")
- else
- error = Gem::LoadError.new(
- "RubyGem version error: " +
- "#{gem.name}(#{matches.first.version} not #{gem.requirement})\n")
- end
-
- error.name = gem.name
- error.requirement = gem.requirement
- raise error
- end
-
- private_class_method :report_activate_error
-
- ##
- # Full path to +libfile+ in +gemname+. Searches for the latest gem unless
- # +requirements+ is given.
-
- def self.required_location(gemname, libfile, *requirements)
- requirements = Gem::Requirement.default if requirements.empty?
-
- matches = Gem::Specification.find_all_by_name gemname, *requirements
-
- return nil if matches.empty?
-
- spec = matches.last
- spec.require_paths.each do |path|
- result = Gem::Path.path(spec.full_gem_path).add(path, libfile)
- return result if result.exist?
- end
-
- nil
- end
-
- ##
# The path to the running Ruby interpreter.
def self.ruby
@@ -928,6 +730,7 @@ module Gem
@ruby
end
+ # DOC: needs doc'd or :nodoc'd
def self.latest_spec_for name
dependency = Gem::Dependency.new name
fetcher = Gem::SpecFetcher.fetcher
@@ -942,13 +745,16 @@ module Gem
match and fetcher.fetch_spec(*match)
end
+ # DOC: needs doc'd or :nodoc'd
def self.latest_version_for name
spec = latest_spec_for name
spec and spec.version
end
+ # DOC: needs doc'd or :nodoc'd
def self.latest_rubygems_version
- latest_version_for "rubygems-update"
+ latest_version_for("rubygems-update") or
+ raise "Can't find 'rubygems-update' in any repo. Check `gem source list`."
end
##
@@ -968,36 +774,34 @@ module Gem
end
##
- # The GemPathSearcher object used to search for matching installed gems.
-
- def self.searcher
- @searcher ||= Gem::GemPathSearcher.new
- end
-
- ##
- # Returns the Gem::SourceIndex of specifications that are in the Gem.path
+ # A Gem::Version for the currently running RubyGems
- def self.source_index
- @@source_index ||= Gem::Deprecate.skip_during do
- SourceIndex.new Gem::Specification.dirs
- end
+ def self.rubygems_version
+ return @rubygems_version if defined? @rubygems_version
+ @rubygems_version = Gem::Version.new Gem::VERSION
end
##
- # Returns an Array of sources to fetch remote gems from. If the sources
- # list is empty, attempts to load the "sources" gem, then uses
- # default_sources if it is not installed.
+ # Returns an Array of sources to fetch remote gems from. Uses
+ # default_sources if the sources list is empty.
def self.sources
- @sources ||= default_sources
+ @sources ||= Gem::SourceList.from(default_sources)
end
##
# Need to be able to set the sources without calling
# Gem.sources.replace since that would cause an infinite loop.
+ #
+ # DOC: This comment is not documentation about the method itself, it's
+ # more of a code comment about the implementation.
def self.sources= new_sources
- @sources = new_sources
+ if !new_sources
+ @sources = nil
+ else
+ @sources = Gem::SourceList.from(new_sources)
+ end
end
##
@@ -1007,12 +811,6 @@ module Gem
@suffix_pattern ||= "{#{suffixes.join(',')}}"
end
- def self.loaded_path? path
- # TODO: ruby needs a feature to let us query what's loaded in 1.8 and 1.9
- re = /(^|\/)#{Regexp.escape path}#{Regexp.union(*Gem.suffixes)}$/
- $LOADED_FEATURES.any? { |s| s =~ re }
- end
-
##
# Suffixes for require-able paths.
@@ -1067,7 +865,7 @@ module Gem
# The home directory for the user.
def self.user_home
- @user_home ||= find_home
+ @user_home ||= find_home.untaint
end
##
@@ -1126,6 +924,9 @@ module Gem
load_plugin_files files
end
+ # FIX: Almost everywhere else we use the `def self.` way of defining class
+ # methods, and then we switch over to `class << self` here. Pick one or the
+ # other.
class << self
##
@@ -1134,29 +935,73 @@ module Gem
attr_reader :loaded_specs
##
- # The list of hooks to be run before Gem::Install#install finishes
- # installation
+ # Register a Gem::Specification for default gem
+
+ def register_default_spec(spec)
+ spec.files.each do |file|
+ @path_to_default_spec_map[file] = spec
+ end
+ end
+
+ ##
+ # Find a Gem::Specification of default gem from +path+
+
+ def find_unresolved_default_spec(path)
+ Gem.suffixes.each do |suffix|
+ spec = @path_to_default_spec_map["#{path}#{suffix}"]
+ return spec if spec
+ end
+ nil
+ end
+
+ ##
+ # Remove needless Gem::Specification of default gem from
+ # unresolved default gem list
+
+ def remove_unresolved_default_spec(spec)
+ spec.files.each do |file|
+ @path_to_default_spec_map.delete(file)
+ end
+ end
+
+ ##
+ # Clear default gem related varibles. It is for test
+
+ def clear_default_specs
+ @path_to_default_spec_map.clear
+ end
+
+ ##
+ # The list of hooks to be run after Gem::Installer#install extracts files
+ # and builds extensions
attr_reader :post_build_hooks
##
- # The list of hooks to be run before Gem::Install#install does any work
+ # The list of hooks to be run after Gem::Installer#install completes
+ # installation
attr_reader :post_install_hooks
##
+ # The list of hooks to be run after Gem::DependencyInstaller installs a
+ # set of gems
+
+ attr_reader :done_installing_hooks
+
+ ##
# The list of hooks to be run after Gem::Specification.reset is run.
attr_reader :post_reset_hooks
##
- # The list of hooks to be run before Gem::Uninstall#uninstall does any
- # work
+ # The list of hooks to be run after Gem::Uninstaller#uninstall completes
+ # installation
attr_reader :post_uninstall_hooks
##
- # The list of hooks to be run after Gem::Install#install is finished
+ # The list of hooks to be run before Gem::Installer#install does any work
attr_reader :pre_install_hooks
@@ -1166,15 +1011,12 @@ module Gem
attr_reader :pre_reset_hooks
##
- # The list of hooks to be run after Gem::Uninstall#uninstall is finished
+ # The list of hooks to be run before Gem::Uninstaller#uninstall does any
+ # work
attr_reader :pre_uninstall_hooks
end
- def self.cache # :nodoc:
- source_index
- end
-
##
# Location of Marshal quick gemspecs on remote repositories
@@ -1184,77 +1026,21 @@ module Gem
autoload :Requirement, 'rubygems/requirement'
autoload :Dependency, 'rubygems/dependency'
autoload :DependencyList, 'rubygems/dependency_list'
- autoload :GemPathSearcher, 'rubygems/gem_path_searcher'
+ autoload :SourceList, 'rubygems/source_list'
autoload :SpecFetcher, 'rubygems/spec_fetcher'
autoload :Specification, 'rubygems/specification'
- autoload :Cache, 'rubygems/source_index'
- autoload :SourceIndex, 'rubygems/source_index'
autoload :PathSupport, 'rubygems/path_support'
autoload :Platform, 'rubygems/platform'
- autoload :Builder, 'rubygems/builder'
autoload :ConfigFile, 'rubygems/config_file'
-end
+ autoload :DependencyResolver, 'rubygems/dependency_resolver'
+ autoload :RequestSet, 'rubygems/request_set'
-module Kernel
-
- remove_method :gem if 'method' == defined? gem # from gem_prelude.rb on 1.9
-
- ##
- # Use Kernel#gem to activate a specific version of +gem_name+.
- #
- # +requirements+ is a list of version requirements that the
- # specified gem must match, most commonly "= example.version.number". See
- # Gem::Requirement for how to specify a version requirement.
- #
- # If you will be activating the latest version of a gem, there is no need to
- # call Kernel#gem, Kernel#require will do the right thing for you.
- #
- # Kernel#gem returns true if the gem was activated, otherwise false. If the
- # gem could not be found, didn't match the version requirements, or a
- # different version was already activated, an exception will be raised.
- #
- # Kernel#gem should be called *before* any require statements (otherwise
- # RubyGems may load a conflicting library version).
- #
- # In older RubyGems versions, the environment variable GEM_SKIP could be
- # used to skip activation of specified gems, for example to test out changes
- # that haven't been installed yet. Now RubyGems defers to -I and the
- # RUBYLIB environment variable to skip activation of a gem.
- #
- # Example:
- #
- # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
-
- def gem(gem_name, *requirements) # :doc:
- skip_list = (ENV['GEM_SKIP'] || "").split(/:/)
- raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name
- spec = Gem::Dependency.new(gem_name, *requirements).to_spec
- spec.activate if spec
- end
-
- private :gem
-
-end
-
-##
-# 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 RbConfig.datadir(package_name) # :nodoc:
- warn "#{Gem.location_of_caller.join ':'}:Warning: " \
- "RbConfig.datadir is deprecated and will be removed on or after " \
- "August 2011. " \
- "Use Gem::datadir."
-
- require 'rbconfig/datadir'
-
- Gem.datadir(package_name) || File.join(Gem::ConfigMap[:datadir], package_name)
+ require "rubygems/specification"
end
require 'rubygems/exceptions'
+# REFACTOR: This should be pulled out into some kind of hacks file.
gem_preluded = Gem::GEM_PRELUDE_SUCKAGE and defined? Gem
unless gem_preluded then # TODO: remove guard after 1.9.2 dropped
begin
@@ -1277,29 +1063,10 @@ unless gem_preluded then # TODO: remove guard after 1.9.2 dropped
end
##
-# Enables the require hook for RubyGems.
+# Loads the default specs.
+Gem::Specification.load_defaults
-require 'rubygems/custom_require'
+require 'rubygems/core_ext/kernel_gem'
+require 'rubygems/core_ext/kernel_require'
-module Gem
- class << self
- extend Gem::Deprecate
- deprecate :activate_dep, "Specification#activate", 2011, 6
- deprecate :activate_spec, "Specification#activate", 2011, 6
- deprecate :cache, "Gem::source_index", 2011, 8
- deprecate :activate, "Specification#activate", 2011, 10
- deprecate :all_load_paths, :none, 2011, 10
- deprecate :all_partials, :none, 2011, 10
- deprecate :latest_load_paths, :none, 2011, 10
- deprecate :promote_load_path, :none, 2011, 10
- deprecate :available?, "Specification::find_by_name", 2011, 11
- deprecate :cache_dir, "Specification#cache_dir", 2011, 11
- deprecate :cache_gem, "Specification#cache_file", 2011, 11
- deprecate :default_system_source_cache_dir, :none, 2011, 11
- deprecate :default_user_source_cache_dir, :none, 2011, 11
- deprecate :report_activate_error, :none, 2011, 11
- deprecate :required_location, :none, 2011, 11
- deprecate :searcher, "Specification", 2011, 11
- deprecate :source_index, "Specification", 2011, 11
- end
-end
+Gem.detect_gemdeps
diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb
new file mode 100644
index 0000000000..cd6df1ddc7
--- /dev/null
+++ b/lib/rubygems/available_set.rb
@@ -0,0 +1,95 @@
+module Gem
+ class AvailableSet
+ Tuple = Struct.new(:spec, :source)
+
+ def initialize
+ @set = []
+ @sorted = nil
+ end
+
+ attr_reader :set
+
+ def add(spec, source)
+ @set << Tuple.new(spec, source)
+ @sorted = nil
+ self
+ end
+
+ def <<(o)
+ case o
+ when AvailableSet
+ s = o.set
+ when Array
+ s = o.map do |sp,so|
+ if !sp.kind_of?(Specification) or !so.kind_of?(Source)
+ raise TypeError, "Array must be in [[spec, source], ...] form"
+ end
+
+ Tuple.new(sp,so)
+ end
+ else
+ raise TypeError, "Must be an AvailableSet"
+ end
+
+ @set += s
+ @sorted = nil
+
+ self
+ end
+
+ def empty?
+ @set.empty?
+ end
+
+ def all_specs
+ @set.map { |t| t.spec }
+ end
+
+ def match_platform!
+ @set.reject! { |t| !Gem::Platform.match(t.spec.platform) }
+ @sorted = nil
+ self
+ end
+
+ def sorted
+ @sorted ||= @set.sort do |a,b|
+ i = b.spec <=> a.spec
+ i != 0 ? i : (a.source <=> b.source)
+ end
+ end
+
+ def size
+ @set.size
+ end
+
+ def source_for(spec)
+ f = @set.find { |t| t.spec == spec }
+ f.source
+ end
+
+ def pick_best!
+ return self if empty?
+
+ @set = [sorted.first]
+ @sorted = nil
+ self
+ end
+
+ def remove_installed!(dep)
+ @set.reject! do |t|
+ # already locally installed
+ Gem::Specification.any? do |installed_spec|
+ dep.name == installed_spec.name and
+ dep.requirement.satisfied_by? installed_spec.version
+ end
+ end
+
+ @sorted = nil
+ self
+ end
+
+ def inject_into_list(dep_list)
+ @set.each { |t| dep_list.add t.spec }
+ end
+ end
+end
diff --git a/lib/rubygems/builder.rb b/lib/rubygems/builder.rb
deleted file mode 100644
index 25e8fc8321..0000000000
--- a/lib/rubygems/builder.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-require 'rubygems'
-require 'rubygems/user_interaction'
-
-Gem.load_yaml
-
-require 'rubygems/package'
-
-##
-# The Builder class processes RubyGem specification files
-# to produce a .gem file.
-
-class Gem::Builder
-
- include Gem::UserInteraction
-
- ##
- # Constructs a builder instance for the provided specification
- #
- # spec:: [Gem::Specification] The specification instance
-
- def initialize(spec)
- @spec = spec
- end
-
- ##
- # Builds the gem from the specification. Returns the name of the file
- # written.
-
- def build(skip_validation=false)
- @spec.mark_version
- @spec.validate unless skip_validation
- @signer = sign
- write_package
- say success if Gem.configuration.verbose
- File.basename @spec.cache_file
- end
-
- def success
- <<-EOM
- Successfully built RubyGem
- Name: #{@spec.name}
- Version: #{@spec.version}
- File: #{File.basename @spec.cache_file}
-EOM
- end
-
- private
-
- ##
- # If the signing key was specified, then load the file, and swap to the
- # public key (TODO: we should probably just omit the signing key in favor of
- # the signing certificate, but that's for the future, also the signature
- # algorithm should be configurable)
-
- def sign
- signer = nil
-
- if @spec.respond_to?(:signing_key) and @spec.signing_key then
- require 'rubygems/security'
-
- signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain
- @spec.signing_key = nil
- @spec.cert_chain = signer.cert_chain.map { |cert| cert.to_s }
- end
-
- signer
- end
-
- def write_package
- file_name = File.basename @spec.cache_file
- open file_name, 'wb' do |gem_io|
- Gem::Package.open gem_io, 'w', @signer do |pkg|
- yaml = @spec.to_yaml
- pkg.metadata = yaml
-
- @spec.files.each do |file|
- next if File.directory?(file)
- next if file == file_name # Don't add gem onto itself
-
- stat = File.stat(file)
- mode = stat.mode & 0777
- size = stat.size
-
- pkg.add_file_simple file, mode, size do |tar_io|
- tar_io.write open(file, "rb") { |f| f.read }
- end
- end
- end
- end
- end
-
-end
-
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
index 49253ef56b..69cbf3c269 100644
--- a/lib/rubygems/command.rb
+++ b/lib/rubygems/command.rb
@@ -9,7 +9,7 @@ require 'rubygems/user_interaction'
##
# Base class for all Gem commands. When creating a new gem command, define
-# #new, #execute, #arguments, #defaults_str, #description and #usage
+# #initialize, #execute, #arguments, #defaults_str, #description and #usage
# (as appropriate). See the above mentioned methods for details.
#
# A very good example to look at is Gem::Commands::ContentsCommand
@@ -150,8 +150,9 @@ class Gem::Command
def show_lookup_failure(gem_name, version, errors, domain)
if errors and !errors.empty?
- alert_error "Could not find a valid gem '#{gem_name}' (#{version}), here is why:"
- errors.each { |x| say " #{x.wordy}" }
+ msg = "Could not find a valid gem '#{gem_name}' (#{version}), here is why:\n"
+ errors.each { |x| msg << " #{x.wordy}\n" }
+ alert_error msg
else
alert_error "Could not find a valid gem '#{gem_name}' (#{version}) in any repository"
end
@@ -180,6 +181,15 @@ class Gem::Command
end
##
+ # Get all [gem, version] from the command line.
+ #
+ # An argument in the form gem:ver is pull apart into the gen name and version,
+ # respectively.
+ def get_all_gem_names_and_versions
+ get_all_gem_names.map { |name| name.split(":", 2) }
+ end
+
+ ##
# Get a single gem name from the command line. Fail if there is no gem name
# or if there is more than one gem name given.
@@ -268,8 +278,18 @@ class Gem::Command
# Invoke the command with the given list of arguments.
def invoke(*args)
+ invoke_with_build_args args, nil
+ end
+
+ ##
+ # Invoke the command with the given list of normal arguments
+ # and additional build arguments.
+
+ def invoke_with_build_args(args, build_args)
handle_options args
+ options[:build_args] = build_args
+
if options[:help] then
show_help
elsif @when_invoked then
@@ -344,7 +364,7 @@ class Gem::Command
def handle_options(args)
args = add_extra_args(args)
- @options = @defaults.clone
+ @options = Marshal.load Marshal.dump @defaults # deep copy
parser.parse!(args)
@options[:args] = args
end
@@ -372,18 +392,23 @@ class Gem::Command
private
- ##
- # Create on demand parser.
+ def add_parser_description # :nodoc:
+ return unless description
- def parser
- create_option_parser if @parser.nil?
- @parser
- end
+ formatted = description.split("\n\n").map do |chunk|
+ wrap chunk, 80 - 4
+ end.join "\n"
- def create_option_parser
- @parser = OptionParser.new
+ @parser.separator nil
+ @parser.separator " Description:"
+ formatted.split("\n").each do |line|
+ @parser.separator " #{line.rstrip}"
+ end
+ end
+ def add_parser_options # :nodoc:
@parser.separator nil
+
regular_options = @option_groups.delete :options
configure_options "", regular_options
@@ -392,45 +417,56 @@ class Gem::Command
@parser.separator nil
configure_options group_name, option_list
end
+ end
- @parser.separator nil
- configure_options "Common", Gem::Command.common_options
+ ##
+ # Adds a section with +title+ and +content+ to the parser help view. Used
+ # for adding command arguments and default arguments.
- unless arguments.empty?
- @parser.separator nil
- @parser.separator " Arguments:"
- arguments.split(/\n/).each do |arg_desc|
- @parser.separator " #{arg_desc}"
- end
- end
+ def add_parser_run_info title, content
+ return if content.empty?
- if @summary then
- @parser.separator nil
- @parser.separator " Summary:"
- wrap(@summary, 80 - 4).split("\n").each do |line|
- @parser.separator " #{line.strip}"
- end
+ @parser.separator nil
+ @parser.separator " #{title}:"
+ content.split(/\n/).each do |line|
+ @parser.separator " #{line}"
end
+ end
- if description then
- formatted = description.split("\n\n").map do |chunk|
- wrap chunk, 80 - 4
- end.join "\n"
+ def add_parser_summary # :nodoc:
+ return unless @summary
- @parser.separator nil
- @parser.separator " Description:"
- formatted.split("\n").each do |line|
- @parser.separator " #{line.rstrip}"
- end
+ @parser.separator nil
+ @parser.separator " Summary:"
+ wrap(@summary, 80 - 4).split("\n").each do |line|
+ @parser.separator " #{line.strip}"
end
+ end
- unless defaults_str.empty?
- @parser.separator nil
- @parser.separator " Defaults:"
- defaults_str.split(/\n/).each do |line|
- @parser.separator " #{line}"
- end
- end
+ ##
+ # Create on demand parser.
+
+ def parser
+ create_option_parser if @parser.nil?
+ @parser
+ end
+
+ ##
+ # Creates an option parser and fills it in with the help info for the
+ # command.
+
+ def create_option_parser
+ @parser = OptionParser.new
+
+ add_parser_options
+
+ @parser.separator nil
+ configure_options "Common", Gem::Command.common_options
+
+ add_parser_run_info "Arguments", arguments
+ add_parser_summary
+ add_parser_description
+ add_parser_run_info "Defaults", defaults_str
end
def configure_options(header, option_list)
@@ -521,7 +557,7 @@ basic help message containing pointers to more information.
http://localhost:8808/
with info about installed gems
Further information:
- http://rubygems.rubyforge.org
+ http://guides.rubygems.org
HELP
# :startdoc:
@@ -529,7 +565,7 @@ basic help message containing pointers to more information.
end
##
-# This is where Commands will be placed in the namespace
+# \Commands will be placed in this namespace
module Gem::Commands
end
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index 9edd550136..e39c509ef9 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -18,12 +18,15 @@ require 'rubygems/user_interaction'
# # file rubygems_plugin.rb
# require 'rubygems/command_manager'
#
+# Gem::CommandManager.instance.register_command :edit
+#
+# You should put the implementation of your command in rubygems/commands.
+#
+# # file rubygems/commands/edit_command.rb
# class Gem::Commands::EditCommand < Gem::Command
# # ...
# end
#
-# Gem::CommandManager.instance.register_command :edit
-#
# See Gem::Command for instructions on writing gem commands.
class Gem::CommandManager
@@ -38,6 +41,14 @@ class Gem::CommandManager
end
##
+ # Returns self. Allows a CommandManager instance to stand
+ # in for the class itself.
+
+ def instance
+ self
+ end
+
+ ##
# Reset the authoritative instance of the command manager.
def self.reset
@@ -63,6 +74,7 @@ class Gem::CommandManager
register_command :install
register_command :list
register_command :lock
+ register_command :mirror
register_command :outdated
register_command :owner
register_command :pristine
@@ -78,13 +90,14 @@ class Gem::CommandManager
register_command :unpack
register_command :update
register_command :which
+ register_command :yank
end
##
# Register the Symbol +command+ as a gem command.
- def register_command(command)
- @commands[command] = false
+ def register_command(command, obj=false)
+ @commands[command] = obj
end
##
@@ -95,7 +108,7 @@ class Gem::CommandManager
end
##
- # Return the registered command from the command name.
+ # Returns a Command instance for +command_name+
def [](command_name)
command_name = command_name.intern
@@ -104,56 +117,69 @@ class Gem::CommandManager
end
##
- # Return a sorted list of all command names (as strings).
+ # Return a sorted list of all command names as strings.
def command_names
@commands.keys.collect {|key| key.to_s}.sort
end
##
- # Run the config specified by +args+.
+ # Run the command specified by +args+.
- def run(args)
- process_args(args)
+ def run(args, build_args=nil)
+ process_args(args, build_args)
rescue StandardError, Timeout::Error => ex
alert_error "While executing gem ... (#{ex.class})\n #{ex.to_s}"
- ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if
- Gem.configuration.backtrace
+ ui.backtrace ex
+
+ if Gem.configuration.really_verbose and \
+ ex.kind_of?(Gem::Exception) and ex.source_exception
+ e = ex.source_exception
+
+ ui.errs.puts "Because of: (#{e.class})\n #{e.to_s}"
+ ui.backtrace e
+ end
+
terminate_interaction(1)
rescue Interrupt
alert_error "Interrupted"
terminate_interaction(1)
end
- def process_args(args)
+ def process_args(args, build_args=nil)
args = args.to_str.split(/\s+/) if args.respond_to?(:to_str)
- if args.size == 0
+
+ if args.empty? then
say Gem::Command::HELP
- terminate_interaction(1)
+ terminate_interaction 1
end
- case args[0]
- when '-h', '--help'
+
+ case args.first
+ when '-h', '--help' then
say Gem::Command::HELP
- terminate_interaction(0)
- when '-v', '--version'
+ terminate_interaction 0
+ when '-v', '--version' then
say Gem::VERSION
- terminate_interaction(0)
- when /^-/
- alert_error "Invalid option: #{args[0]}. See 'gem --help'."
- terminate_interaction(1)
+ terminate_interaction 0
+ when /^-/ then
+ alert_error "Invalid option: #{args.first}. See 'gem --help'."
+ terminate_interaction 1
else
cmd_name = args.shift.downcase
- cmd = find_command(cmd_name)
- cmd.invoke(*args)
+ cmd = find_command cmd_name
+ cmd.invoke_with_build_args args, build_args
end
end
def find_command(cmd_name)
possibilities = find_command_possibilities cmd_name
+
if possibilities.size > 1 then
- raise "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]"
- elsif possibilities.size < 1 then
- raise "Unknown command #{cmd_name}"
+ raise Gem::CommandLineError,
+ "Ambiguous command #{cmd_name} " \
+ "matches [#{possibilities.join(', ')}]"
+ elsif possibilities.empty? then
+ raise Gem::CommandLineError, "Unknown command #{cmd_name}"
end
self[possibilities.first]
@@ -162,7 +188,11 @@ class Gem::CommandManager
def find_command_possibilities(cmd_name)
len = cmd_name.length
- command_names.select { |n| cmd_name == n[0, len] }
+ found = command_names.select { |name| cmd_name == name[0, len] }
+
+ exact = found.find { |name| name == cmd_name }
+
+ exact ? [exact] : found
end
private
@@ -170,23 +200,20 @@ class Gem::CommandManager
def load_and_instantiate(command_name)
command_name = command_name.to_s
const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase } << "Command"
- commands = Gem::Commands
- retried = false
+ load_error = nil
begin
- commands.const_get(const_name).new
- rescue NameError
- raise if retried
-
- retried = true
begin
require "rubygems/commands/#{command_name}_command"
- rescue Exception => e
- alert_error "Loading command: #{command_name} (#{e.class})\n #{e}"
- ui.errs.puts "\t#{e.backtrace.join "\n\t"}" if
- Gem.configuration.backtrace
+ rescue LoadError => e
+ load_error = e
end
- retry
+ Gem::Commands.const_get(const_name).new
+ rescue Exception => e
+ e = load_error if load_error
+
+ alert_error "Loading command: #{command_name} (#{e.class})\n\t#{e}"
+ ui.backtrace e
end
end
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb
index 36a6fe48f2..64563ed3db 100644
--- a/lib/rubygems/commands/build_command.rb
+++ b/lib/rubygems/commands/build_command.rb
@@ -1,5 +1,5 @@
require 'rubygems/command'
-require 'rubygems/builder'
+require 'rubygems/package'
class Gem::Commands::BuildCommand < Gem::Command
@@ -22,11 +22,11 @@ class Gem::Commands::BuildCommand < Gem::Command
def execute
gemspec = get_one_gem_name
- if File.exist? gemspec
- spec = load_gemspec gemspec
+ if File.exist? gemspec then
+ spec = Gem::Specification.load gemspec
if spec then
- Gem::Builder.new(spec).build options[:force]
+ Gem::Package.build spec, options[:force]
else
alert_error "Error loading gemspec. Aborting."
terminate_interaction 1
@@ -37,23 +37,5 @@ class Gem::Commands::BuildCommand < Gem::Command
end
end
- def load_gemspec filename
- if yaml?(filename)
- open(filename) do |f|
- begin
- Gem::Specification.from_yaml(f)
- rescue Gem::EndOfYAMLException
- nil
- end
- end
- else
- Gem::Specification.load(filename) # can return nil
- end
- end
-
- def yaml?(filename)
- line = open(filename) { |f| line = f.gets }
- result = line =~ %r{!ruby/object:Gem::Specification}
- result
- end
end
+
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index b416b3863d..371ab403c6 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -4,82 +4,224 @@ require 'rubygems/security'
class Gem::Commands::CertCommand < Gem::Command
def initialize
- super 'cert', 'Manage RubyGems certificates and signing settings'
-
- add_option('-a', '--add CERT',
- 'Add a trusted certificate.') do |value, options|
- cert = OpenSSL::X509::Certificate.new(File.read(value))
- Gem::Security.add_trusted_cert(cert)
- say "Added '#{cert.subject.to_s}'"
- end
-
- add_option('-l', '--list',
- 'List trusted certificates.') do |value, options|
- glob_str = File::join(Gem::Security::OPT[:trust_dir], '*.pem')
- Dir::glob(glob_str) do |path|
- begin
- cert = OpenSSL::X509::Certificate.new(File.read(path))
- # this could probably be formatted more gracefully
- say cert.subject.to_s
- rescue OpenSSL::X509::CertificateError
- next
- end
+ super 'cert', 'Manage RubyGems certificates and signing settings',
+ :add => [], :remove => [], :list => [], :build => [], :sign => []
+
+ OptionParser.accept OpenSSL::X509::Certificate do |certificate|
+ begin
+ OpenSSL::X509::Certificate.new File.read certificate
+ rescue Errno::ENOENT
+ raise OptionParser::InvalidArgument, "#{certificate}: does not exist"
+ rescue OpenSSL::X509::CertificateError
+ raise OptionParser::InvalidArgument,
+ "#{certificate}: invalid X509 certificate"
end
end
- add_option('-r', '--remove STRING',
- 'Remove trusted certificates containing',
- 'STRING.') do |value, options|
- trust_dir = Gem::Security::OPT[:trust_dir]
- glob_str = File::join(trust_dir, '*.pem')
-
- Dir::glob(glob_str) do |path|
- begin
- cert = OpenSSL::X509::Certificate.new(File.read(path))
- if cert.subject.to_s.downcase.index(value)
- say "Removed '#{cert.subject.to_s}'"
- File.unlink(path)
- end
- rescue OpenSSL::X509::CertificateError
- next
- end
+ OptionParser.accept OpenSSL::PKey::RSA do |key_file|
+ begin
+ key = OpenSSL::PKey::RSA.new File.read key_file
+ rescue Errno::ENOENT
+ raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
+ rescue OpenSSL::PKey::RSAError
+ raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
end
+
+ raise OptionParser::InvalidArgument,
+ "#{key_file}: private key not found" unless key.private?
+
+ key
+ end
+
+ add_option('-a', '--add CERT', OpenSSL::X509::Certificate,
+ 'Add a trusted certificate.') do |cert, options|
+ options[:add] << cert
+ end
+
+ add_option('-l', '--list [FILTER]',
+ 'List trusted certificates where the',
+ 'subject contains FILTER') do |filter, options|
+ filter ||= ''
+
+ options[:list] << filter
+ end
+
+ add_option('-r', '--remove FILTER',
+ 'Remove trusted certificates where the',
+ 'subject contains FILTER') do |filter, options|
+ options[:remove] << filter
end
add_option('-b', '--build EMAIL_ADDR',
'Build private key and self-signed',
- 'certificate for EMAIL_ADDR.') do |value, options|
- vals = Gem::Security.build_self_signed_cert(value)
- FileUtils.chmod 0600, vals[:key_path]
- say "Public Cert: #{vals[:cert_path]}"
- say "Private Key: #{vals[:key_path]}"
- say "Don't forget to move the key file to somewhere private..."
+ 'certificate for EMAIL_ADDR') do |email_address, options|
+ options[:build] << email_address
end
- add_option('-C', '--certificate CERT',
- 'Certificate for --sign command.') do |value, options|
- cert = OpenSSL::X509::Certificate.new(File.read(value))
+ add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate,
+ 'Signing certificate for --sign') do |cert, options|
options[:issuer_cert] = cert
end
- add_option('-K', '--private-key KEY',
- 'Private key for --sign command.') do |value, options|
- key = OpenSSL::PKey::RSA.new(File.read(value))
- options[:issuer_key] = key
+ add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA,
+ 'Key for --sign or --build') do |key, options|
+ options[:key] = key
end
- add_option('-s', '--sign NEWCERT',
- 'Sign a certificate with my key and',
- 'certificate.') do |value, options|
- cert = OpenSSL::X509::Certificate.new(File.read(value))
- my_cert = options[:issuer_cert]
- my_key = options[:issuer_key]
- cert = Gem::Security.sign_cert(cert, my_key, my_cert)
- File.open(value, 'wb') { |file| file.write(cert.to_pem) }
+ add_option('-s', '--sign CERT',
+ 'Signs CERT with the key from -K',
+ 'and the certificate from -C') do |cert_file, options|
+ raise OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
+ File.file? cert_file
+
+ options[:sign] << cert_file
end
end
def execute
+ options[:add].each do |certificate|
+ Gem::Security.trust_dir.trust_cert certificate
+
+ say "Added '#{certificate.subject}'"
+ end
+
+ options[:remove].each do |filter|
+ certificates_matching filter do |certificate, path|
+ FileUtils.rm path
+ say "Removed '#{certificate.subject}'"
+ end
+ end
+
+ options[:list].each do |filter|
+ certificates_matching filter do |certificate, _|
+ # this could probably be formatted more gracefully
+ say certificate.subject.to_s
+ end
+ end
+
+ options[:build].each do |name|
+ build name
+ end
+
+ unless options[:sign].empty? then
+ load_default_cert unless options[:issuer_cert]
+ load_default_key unless options[:key]
+ end
+
+ options[:sign].each do |cert_file|
+ sign cert_file
+ end
+ end
+
+ def build name
+ key = options[:key] || Gem::Security.create_key
+
+ cert = Gem::Security.create_cert_email name, key
+
+ key_path = Gem::Security.write key, "gem-private_key.pem"
+ cert_path = Gem::Security.write cert, "gem-public_cert.pem"
+
+ say "Certificate: #{cert_path}"
+ say "Private Key: #{key_path}"
+ say "Don't forget to move the key file to somewhere private!"
+ end
+
+ def certificates_matching filter
+ return enum_for __method__, filter unless block_given?
+
+ Gem::Security.trusted_certificates.select do |certificate, _|
+ subject = certificate.subject.to_s
+ subject.downcase.index filter
+ end.sort_by do |certificate, _|
+ certificate.subject.to_a.map { |name, data,| [name, data] }
+ end.each do |certificate, path|
+ yield certificate, path
+ end
+ end
+
+ def description # :nodoc:
+ <<-EOF
+The cert command manages signing keys and certificates for creating signed
+gems. Your signing certificate and private key are typically stored in
+~/.gem/gem-public_cert.pem and ~/.gem/gem-private_key.pem respectively.
+
+To build a certificate for signing gems:
+
+ gem cert --build you@example
+
+If you already have an RSA key, or are creating a new certificate for an
+existing key:
+
+ gem cert --build you@example --private-key /path/to/key.pem
+
+If you wish to trust a certificate you can add it to the trust list with:
+
+ gem cert --add /path/to/cert.pem
+
+You can list trusted certificates with:
+
+ gem cert --list
+
+or:
+
+ gem cert --list cert_subject_substring
+
+If you wish to remove a previously trusted certificate:
+
+ gem cert --remove cert_subject_substring
+
+To sign another gem author's certificate:
+
+ gem cert --sign /path/to/other_cert.pem
+
+For further reading on signing gems see `ri Gem::Security`.
+ EOF
+ end
+
+ def load_default_cert
+ cert_file = File.join Gem.user_home, 'gem-public_cert.pem'
+ cert = File.read cert_file
+ options[:issuer_cert] = OpenSSL::X509::Certificate.new cert
+ rescue Errno::ENOENT
+ alert_error \
+ "--certificate not specified and ~/.gem/gem-public_cert.pem does not exist"
+
+ terminate_interaction 1
+ rescue OpenSSL::X509::CertificateError
+ alert_error \
+ "--certificate not specified and ~/.gem/gem-public_cert.pem is not valid"
+
+ terminate_interaction 1
+ end
+
+ def load_default_key
+ key_file = File.join Gem.user_home, 'gem-private_key.pem'
+ key = File.read key_file
+ options[:key] = OpenSSL::PKey::RSA.new key
+ rescue Errno::ENOENT
+ alert_error \
+ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
+
+ terminate_interaction 1
+ rescue OpenSSL::PKey::RSAError
+ alert_error \
+ "--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
+
+ terminate_interaction 1
+ end
+
+ def sign cert_file
+ cert = File.read cert_file
+ cert = OpenSSL::X509::Certificate.new cert
+
+ permissions = File.stat(cert_file).mode & 0777
+
+ issuer_cert = options[:issuer_cert]
+ issuer_key = options[:key]
+
+ cert = Gem::Security.sign cert, issuer_key, issuer_cert
+
+ Gem::Security.write cert, cert_file, permissions
end
end
diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb
index 5a1bfd4f12..4bdfcfa645 100644
--- a/lib/rubygems/commands/check_command.rb
+++ b/lib/rubygems/commands/check_command.rb
@@ -8,13 +8,7 @@ class Gem::Commands::CheckCommand < Gem::Command
def initialize
super 'check', 'Check installed gems',
- :verify => false, :alien => false
-
- add_option( '--verify FILE',
- 'Verify gem file against its internal',
- 'checksum') do |value, options|
- options[:verify] = value
- end
+ :alien => true
add_option('-a', '--alien', "Report 'unmanaged' or rogue files in the",
"gem repository") do |value, options|
@@ -25,40 +19,21 @@ class Gem::Commands::CheckCommand < Gem::Command
end
def execute
- if options[:alien]
- say "Performing the 'alien' operation"
- say
- gems = get_all_gem_names rescue []
- Gem::Validator.new.alien(gems).sort.each do |key, val|
- unless val.empty? then
- say "#{key} has #{val.size} problems"
- val.each do |error_entry|
- say " #{error_entry.path}:"
- say " #{error_entry.problem}"
- end
- else
- say "#{key} is error-free" if Gem.configuration.verbose
+ say "Checking gems..."
+ say
+ gems = get_all_gem_names rescue []
+
+ Gem::Validator.new.alien(gems).sort.each do |key, val|
+ unless val.empty? then
+ say "#{key} has #{val.size} problems"
+ val.each do |error_entry|
+ say " #{error_entry.path}:"
+ say " #{error_entry.problem}"
end
- say
- end
- end
-
- if options[:verify]
- gem_name = options[:verify]
- unless gem_name
- alert_error "Must specify a .gem file with --verify NAME"
- return
- end
- unless File.exist?(gem_name)
- alert_error "Unknown file: #{gem_name}."
- return
- end
- say "Verifying gem: '#{gem_name}'"
- begin
- Gem::Validator.new.verify_gem_file(gem_name)
- rescue Exception
- alert_error "#{gem_name} is invalid."
+ else
+ say "#{key} is error-free" if Gem.configuration.verbose
end
+ say
end
end
diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb
index 124c4c203a..dc919e5570 100644
--- a/lib/rubygems/commands/cleanup_command.rb
+++ b/lib/rubygems/commands/cleanup_command.rb
@@ -26,6 +26,9 @@ class Gem::Commands::CleanupCommand < Gem::Command
<<-EOF
The cleanup command removes old gems from GEM_HOME. If an older version is
installed elsewhere in GEM_PATH the cleanup command won't touch it.
+
+Older gems that are required to satisify the dependencies of gems
+are not removed.
EOF
end
@@ -56,6 +59,8 @@ installed elsewhere in GEM_PATH the cleanup command won't touch it.
primary_gems[spec.name].version != spec.version
}
+ full = Gem::DependencyList.from_specs
+
deplist = Gem::DependencyList.new
gems_to_cleanup.uniq.each do |spec| deplist.add spec end
@@ -64,6 +69,8 @@ installed elsewhere in GEM_PATH the cleanup command won't touch it.
original_path = Gem.path
deps.each do |spec|
+ next unless full.ok_to_remove?(spec.full_name)
+
if options[:dryrun] then
say "Dry Run Mode: Would uninstall #{spec.full_name}"
else
diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb
index e483484615..404c6745bd 100644
--- a/lib/rubygems/commands/contents_command.rb
+++ b/lib/rubygems/commands/contents_command.rb
@@ -1,3 +1,4 @@
+require 'English'
require 'rubygems/command'
require 'rubygems/version_option'
@@ -80,19 +81,36 @@ class Gem::Commands::ContentsCommand < Gem::Command
terminate_interaction 1 if gem_names.length == 1
end
- gem_path = spec.full_gem_path
- extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
- glob = "#{gem_path}#{extra}/**/*"
- files = Dir[glob]
-
- gem_path = File.join gem_path, '' # add trailing / if missing
-
- files.sort.each do |file|
- next if File.directory? file
+ if spec.default_gem?
+ files = spec.files.map do |file|
+ case file
+ when /\A#{spec.bindir}\//
+ [Gem::ConfigMap[:bindir], $POSTMATCH]
+ when /\.so\z/
+ [Gem::ConfigMap[:archdir], file]
+ else
+ [Gem::ConfigMap[:rubylibdir], file]
+ end
+ end
+ else
+ gem_path = spec.full_gem_path
+ extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
+ glob = "#{gem_path}#{extra}/**/*"
+ prefix_re = /#{Regexp.escape(gem_path)}\//
+ files = Dir[glob].map do |file|
+ [gem_path, file.sub(prefix_re, "")]
+ end
+ end
- file = file.sub gem_path, '' unless options[:prefix]
+ files.sort.each do |prefix, basename|
+ absolute_path = File.join(prefix, basename)
+ next if File.directory? absolute_path
- say file
+ if options[:prefix]
+ say absolute_path
+ else
+ say basename
+ end
end
end
end
diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb
index 67cbbc1d5e..4690b13a94 100644
--- a/lib/rubygems/commands/dependency_command.rb
+++ b/lib/rubygems/commands/dependency_command.rb
@@ -71,14 +71,9 @@ class Gem::Commands::DependencyCommand < Gem::Command
if remote? and not options[:reverse_dependencies] then
fetcher = Gem::SpecFetcher.fetcher
- # REFACTOR: fetcher.find_specs_matching => specs
- specs_and_sources = fetcher.find_matching(dependency,
- dependency.specific?, true,
- dependency.prerelease?)
-
- specs.concat specs_and_sources.map { |spec_tuple, source_uri|
- fetcher.fetch_spec spec_tuple, URI.parse(source_uri)
- }
+ ss, _ = fetcher.spec_for_dependency dependency
+
+ ss.each { |s,o| specs << s }
end
if specs.empty? then
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index 9585c71250..40e71cf094 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -24,33 +24,38 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
The RubyGems environment can be controlled through command line arguments,
gemrc files, environment variables and built-in defaults.
-Command line argument defaults and some RubyGems defaults can be set in
-~/.gemrc file for individual users and a /etc/gemrc for all users. A gemrc
-is a YAML file with the following YAML keys:
+Command line argument defaults and some RubyGems defaults can be set in a
+~/.gemrc file for individual users and a /etc/gemrc for all users. These
+files are YAML files with the following YAML keys:
:sources: A YAML array of remote gem repositories to install gems from
- :verbose: Verbosity of the gem command. false, true, and :really are the
+ :verbose: Verbosity of the gem command. false, true, and :really are the
levels
:update_sources: Enable/disable automatic updating of repository metadata
:backtrace: Print backtrace when RubyGems encounters an error
:gempath: The paths in which to look for gems
- gem_command: A string containing arguments for the specified gem command
+ :disable_default_gem_server: Force specification of gem server host on push
+ <gem_command>: A string containing arguments for the specified gem command
Example:
:verbose: false
install: --no-wrappers
update: --no-wrappers
+ :disable_default_gem_server: true
RubyGems' default local repository can be overridden with the GEM_PATH and
-GEM_HOME environment variables. GEM_HOME sets the default repository to
-install into. GEM_PATH allows multiple local repositories to be searched for
+GEM_HOME environment variables. GEM_HOME sets the default repository to
+install into. GEM_PATH allows multiple local repositories to be searched for
gems.
If you are behind a proxy server, RubyGems uses the HTTP_PROXY,
HTTP_PROXY_USER and HTTP_PROXY_PASS environment variables to discover the
proxy server.
+If you would like to push gems to a private gem server the RUBYGEMS_HOST
+environment variable can be set to the URI for that server.
+
If you are packaging RubyGems all of RubyGems' defaults are in
lib/rubygems/defaults.rb. You may override these in
lib/rubygems/defaults/operating_system.rb
@@ -74,7 +79,7 @@ lib/rubygems/defaults/operating_system.rb
when /^gempath/, /^path/, /^GEM_PATH/ then
out << Gem.path.join(File::PATH_SEPARATOR)
when /^remotesources/ then
- out << Gem.sources.join("\n")
+ out << Gem.sources.to_a.join("\n")
when /^platform/ then
out << Gem.platforms.join(File::PATH_SEPARATOR)
when nil then
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index e7c9cc9525..ec021359b6 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -34,7 +34,6 @@ class Gem::Commands::FetchCommand < Gem::Command
def execute
version = options[:version] || Gem::Requirement.default
- all = Gem::Requirement.default != version
platform = Gem.platforms.last
gem_names = get_all_gem_names
@@ -43,32 +42,20 @@ class Gem::Commands::FetchCommand < Gem::Command
dep = Gem::Dependency.new gem_name, version
dep.prerelease = options[:prerelease]
- specs_and_sources, errors =
- Gem::SpecFetcher.fetcher.fetch_with_errors(dep, all, true,
- dep.prerelease?)
-
+ specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
if platform then
filtered = specs_and_sources.select { |s,| s.platform == platform }
specs_and_sources = filtered unless filtered.empty?
end
- spec, source_uri = specs_and_sources.sort_by { |s,| s.version }.last
+ spec, source = specs_and_sources.sort_by { |s,| s.version }.first
if spec.nil? then
show_lookup_failure gem_name, version, errors, options[:domain]
next
end
- file = "#{spec.full_name}.gem"
- remote_path = URI.parse(source_uri) + "gems/#{file}"
-
- fetch = Gem::RemoteFetcher.fetcher
-
- gem = fetch.fetch_path remote_path.to_s
-
- File.open file, "wb" do |f|
- f.write gem
- end
+ source.download spec
say "Downloaded #{spec.full_name}"
end
diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb
index d4b4790649..a7db013caf 100644
--- a/lib/rubygems/commands/generate_index_command.rb
+++ b/lib/rubygems/commands/generate_index_command.rb
@@ -11,29 +11,16 @@ class Gem::Commands::GenerateIndexCommand < Gem::Command
def initialize
super 'generate_index',
'Generates the index files for a gem server directory',
- :directory => '.', :build_legacy => true, :build_modern => true
+ :directory => '.', :build_modern => true
add_option '-d', '--directory=DIRNAME',
'repository base dir containing gems subdir' do |dir, options|
options[:directory] = File.expand_path dir
end
- add_option '--[no-]legacy',
- 'Generate Marshal.4.8' do |value, options|
- unless options[:build_modern] or value then
- raise OptionParser::InvalidOption, 'no indicies will be built'
- end
-
- options[:build_legacy] = value
- end
-
add_option '--[no-]modern',
- 'Generate indexes for RubyGems newer',
- 'than 1.2.0' do |value, options|
- unless options[:build_legacy] or value then
- raise OptionParser::InvalidOption, 'no indicies will be built'
- end
-
+ 'Generate indexes for RubyGems',
+ '(always true)' do |value, options|
options[:build_modern] = value
end
@@ -42,27 +29,10 @@ class Gem::Commands::GenerateIndexCommand < Gem::Command
'since the last update' do |value, options|
options[:update] = value
end
-
- add_option :RSS, '--rss-gems-host=GEM_HOST',
- 'Host name where gems are served from,',
- 'used for GUID and enclosure values' do |value, options|
- options[:rss_gems_host] = value
- end
-
- add_option :RSS, '--rss-host=HOST',
- 'Host name for more gems information,',
- 'used for RSS feed link' do |value, options|
- options[:rss_host] = value
- end
-
- add_option :RSS, '--rss-title=TITLE',
- 'Set title for RSS feed' do |value, options|
- options[:rss_title] = value
- end
end
def defaults_str # :nodoc:
- "--directory . --legacy --modern"
+ "--directory . --modern"
end
def description # :nodoc:
@@ -85,25 +55,15 @@ When done, it will generate a set of files like this:
prerelease_specs.<version>.gz # prerelease specs index
quick/Marshal.<version>/<gemname>.gemspec.rz # Marshal quick index file
- # these files support legacy RubyGems
- Marshal.<version>
- Marshal.<version>.Z # Marshal full index
-
-The .Z and .rz extension files are compressed with the inflate algorithm.
+The .rz extension files are compressed with the inflate algorithm.
The Marshal version number comes from ruby's Marshal::MAJOR_VERSION and
Marshal::MINOR_VERSION constants. It is used to ensure compatibility.
-
-If --rss-host and --rss-gem-host are given an RSS feed will be generated at
-index.rss containing gems released in the last two days.
EOF
end
def execute
- if options[:update] and
- (options[:rss_host] or options[:rss_gems_host]) then
- alert_error '--update not compatible with RSS generation'
- terminate_interaction 1
- end
+ # This is always true becasue it's the only way now.
+ options[:build_modern] = true
if not File.exist?(options[:directory]) or
not File.directory?(options[:directory]) then
diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb
index 20b52429b2..8e3d97edd3 100644
--- a/lib/rubygems/commands/help_command.rb
+++ b/lib/rubygems/commands/help_command.rb
@@ -37,7 +37,7 @@ Some examples of 'gem' usage.
* Create a gem:
- See http://rubygems.rubyforge.org/wiki/wiki.pl?CreateAGemInTenMinutes
+ See http://guides.rubygems.org/make-your-own-gem/
* See information about RubyGems:
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 003ba8601c..883526c2a5 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -1,10 +1,11 @@
require 'rubygems/command'
-require 'rubygems/doc_manager'
require 'rubygems/install_update_options'
require 'rubygems/dependency_installer'
require 'rubygems/local_remote_options'
require 'rubygems/validator'
require 'rubygems/version_option'
+require 'rubygems/install_message' # must come before rdoc for messaging
+require 'rubygems/rdoc'
##
# Gem installer command line tool
@@ -13,14 +14,14 @@ require 'rubygems/version_option'
class Gem::Commands::InstallCommand < Gem::Command
+ attr_reader :installed_specs # :nodoc:
+
include Gem::VersionOption
include Gem::LocalRemoteOptions
include Gem::InstallUpdateOptions
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
- :generate_rdoc => true,
- :generate_ri => true,
:format_executable => false,
:version => Gem::Requirement.default,
})
@@ -32,6 +33,14 @@ class Gem::Commands::InstallCommand < Gem::Command
add_platform_option
add_version_option
add_prerelease_option "to be installed. (Only for listed gems)"
+
+ add_option(:"Install/Update", '-g', '--file FILE',
+ 'Read from a gem dependencies API file and',
+ 'install the listed gems') do |v,o|
+ o[:gemdeps] = v
+ end
+
+ @installed_specs = nil
end
def arguments # :nodoc:
@@ -39,7 +48,7 @@ class Gem::Commands::InstallCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--both --version '#{Gem::Requirement.default}' --rdoc --ri --no-force\n" \
+ "--both --version '#{Gem::Requirement.default}' --document --no-force\n" \
"--install-dir #{Gem.dir}"
end
@@ -100,31 +109,73 @@ to write the specification by hand. For example:
"#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags"
end
+ def install_from_gemdeps(gf)
+ require 'rubygems/request_set'
+ rs = Gem::RequestSet.new
+ rs.load_gemdeps gf
+
+ rs.resolve
+
+ specs = rs.install options do |req, inst|
+ s = req.full_spec
+
+ if inst
+ say "Installing #{s.name} (#{s.version})"
+ else
+ say "Using #{s.name} (#{s.version})"
+ end
+ end
+
+ @installed_specs = specs
+
+ raise Gem::SystemExitException, 0
+ end
+
def execute
- if options[:include_dependencies] then
- alert "`gem install -y` is now default and will be removed"
- alert "use --ignore-dependencies to install only the gems you list"
+ if gf = options[:gemdeps] then
+ install_from_gemdeps gf
+ return
end
- installed_gems = []
+ @installed_specs = []
ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
+ if options[:install_dir] and options[:user_install]
+ alert_error "Use --install-dir or --user-install but not both"
+ terminate_interaction 1
+ end
+
exit_code = 0
- get_all_gem_names.each do |gem_name|
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1 then
+ alert_error "Can't use --version w/ multiple gems. Use name:ver instead."
+ terminate_interaction 1
+ end
+
+
+ get_all_gem_names_and_versions.each do |gem_name, gem_version|
+ gem_version ||= options[:version]
+
begin
next if options[:conservative] and
- not Gem::Dependency.new(gem_name, options[:version]).matching_specs.empty?
+ not Gem::Dependency.new(gem_name, gem_version).matching_specs.empty?
inst = Gem::DependencyInstaller.new options
- inst.install gem_name, options[:version]
+ inst.install gem_name, Gem::Requirement.create(gem_version)
- inst.installed_gems.each do |spec|
- say "Successfully installed #{spec.full_name}"
- end
+ @installed_specs.push(*inst.installed_gems)
- installed_gems.push(*inst.installed_gems)
+ next unless errs = inst.errors
+
+ errs.each do |x|
+ next unless Gem::SourceFetchProblem === x
+
+ msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}"
+
+ alert_warning msg
+ end
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
@@ -135,27 +186,9 @@ to write the specification by hand. For example:
end
end
- unless installed_gems.empty? then
- gems = installed_gems.length == 1 ? 'gem' : 'gems'
- say "#{installed_gems.length} #{gems} installed"
-
- # NOTE: *All* of the RI documents must be generated first. For some
- # reason, RI docs cannot be generated after any RDoc documents are
- # generated.
-
- if options[:generate_ri] then
- installed_gems.each do |gem|
- Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri
- end
-
- Gem::DocManager.update_ri_cache
- end
-
- if options[:generate_rdoc] then
- installed_gems.each do |gem|
- Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc
- end
- end
+ unless @installed_specs.empty? then
+ gems = @installed_specs.length == 1 ? 'gem' : 'gems'
+ say "#{@installed_specs.length} #{gems} installed"
end
raise Gem::SystemExitException, exit_code
diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb
index f3e5da9551..d9b7a9535e 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -7,8 +7,9 @@ require 'rubygems/commands/query_command'
class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
- def initialize
- super 'list', 'Display gems whose name starts with STRING'
+ def initialize(name = 'list',
+ summary = 'Display gems whose name starts with STRING')
+ super name, summary
remove_option('--name-matches')
end
@@ -26,8 +27,9 @@ class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
end
def execute
- string = get_one_optional_argument || ''
- options[:name] = /^#{string}/i
+ name = get_one_optional_argument || ''
+ options[:name] = /^#{name}/i
+
super
end
diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb
index a6dca320ef..6b4b25a281 100644
--- a/lib/rubygems/commands/lock_command.rb
+++ b/lib/rubygems/commands/lock_command.rb
@@ -30,7 +30,7 @@ generated.
Example:
- gemlock rails-1.0.0 > lockdown.rb
+ gem lock rails-1.0.0 > lockdown.rb
will produce in lockdown.rb:
diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb
new file mode 100644
index 0000000000..0f98077cbd
--- /dev/null
+++ b/lib/rubygems/commands/mirror_command.rb
@@ -0,0 +1,17 @@
+require 'rubygems/command'
+
+class Gem::Commands::MirrorCommand < Gem::Command
+ def initialize
+ super('mirror', 'Mirror all gem files (requires rubygems-mirror)')
+ begin
+ Gem::Specification.find_by_name('rubygems-mirror').activate
+ rescue Gem::LoadError
+ # no-op
+ end
+ end
+
+ def execute
+ alert_error "Install the rubygems-mirror gem for the mirror command"
+ end
+
+end
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index ea6b9f0abf..887faab0a2 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -19,12 +19,15 @@ class Gem::Commands::OutdatedCommand < Gem::Command
Gem::Specification.outdated.sort.each do |name|
local = Gem::Specification.find_all_by_name(name).max
dep = Gem::Dependency.new local.name, ">= #{local.version}"
- remotes = Gem::SpecFetcher.fetcher.fetch dep
+ remotes, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep
next if remotes.empty?
- remote = remotes.last.first
- say "#{local.name} (#{local.version} < #{remote.version})"
+ remotes.sort! { |a,b| a[0].version <=> b[0].version }
+
+ highest = remotes.last.first
+
+ say "#{local.name} (#{local.version} < #{highest.version})"
end
end
end
diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb
index 6ebf9aa1aa..92674132e8 100644
--- a/lib/rubygems/commands/owner_command.rb
+++ b/lib/rubygems/commands/owner_command.rb
@@ -14,6 +14,10 @@ class Gem::Commands::OwnerCommand < Gem::Command
"GEM gem to manage owners for"
end
+ def usage # :nodoc:
+ "#{program_name} GEM"
+ end
+
def initialize
super 'owner', description
add_proxy_option
@@ -63,12 +67,16 @@ class Gem::Commands::OwnerCommand < Gem::Command
def manage_owners method, name, owners
owners.each do |owner|
- response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request|
- request.set_form_data 'email' => owner
- request.add_field "Authorization", api_key
+ begin
+ response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request|
+ request.set_form_data 'email' => owner
+ request.add_field "Authorization", api_key
+ end
+
+ with_response response
+ rescue
+ # ignore
end
-
- with_response response
end
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index e3771b7212..f7eb9014ea 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -1,5 +1,5 @@
require 'rubygems/command'
-require 'rubygems/format'
+require 'rubygems/package'
require 'rubygems/installer'
require 'rubygems/version_option'
@@ -24,6 +24,11 @@ class Gem::Commands::PristineCommand < Gem::Command
options[:extensions] = value
end
+ add_option('--only-executables',
+ 'Only restore executables') do |value, options|
+ options[:only_executables] = value
+ end
+
add_version_option('restore to', 'pristine condition')
end
@@ -78,6 +83,11 @@ extensions.
say "Restoring gems to pristine condition..."
specs.each do |spec|
+ if spec.default_gem?
+ say "Skipped #{spec.full_name}, it is a default gem"
+ next
+ end
+
unless spec.extensions.empty? or options[:extensions] then
say "Skipped #{spec.full_name}, it needs to compile an extension"
next
@@ -101,8 +111,13 @@ extensions.
:wrappers => true,
:force => true,
:install_dir => spec.base_dir,
- :env_shebang => installer_env_shebang)
- installer.install
+ :env_shebang => installer_env_shebang,
+ :build_args => spec.build_args)
+ if options[:only_executables] then
+ installer.generate_bin
+ else
+ installer.install
+ end
say "Restored #{spec.full_name}"
end
diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb
index a7663edf4a..0667b47dc1 100644
--- a/lib/rubygems/commands/push_command.rb
+++ b/lib/rubygems/commands/push_command.rb
@@ -1,6 +1,7 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/gemcutter_utilities'
+require 'rubygems/package'
class Gem::Commands::PushCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -39,13 +40,23 @@ class Gem::Commands::PushCommand < Gem::Command
def send_gem name
args = [:post, "api/v1/gems"]
- args << options[:host] if options[:host]
if Gem.latest_rubygems_version < Gem::Version.new(Gem::VERSION) then
alert_error "Using beta/unreleased version of rubygems. Not pushing."
terminate_interaction 1
end
+ host = options[:host]
+ unless host
+ if gem_data = Gem::Package.new(name) then
+ host = gem_data.spec.metadata['default_gem_server']
+ end
+ end
+
+ args << host if host
+
+ say "Pushing gem to #{host || Gem.host}..."
+
response = rubygems_api_request(*args) do |request|
request.body = Gem.read_binary name
request.add_field "Content-Length", request.body.size
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index 725da8787b..b6c910d449 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -21,6 +21,10 @@ class Gem::Commands::QueryCommand < Gem::Command
options[:installed] = value
end
+ add_option('-I', 'Equivalent to --no-installed') do |value, options|
+ options[:installed] = false
+ end
+
add_version_option command, "for use with --installed"
add_option('-n', '--name-matches REGEXP',
@@ -80,6 +84,7 @@ class Gem::Commands::QueryCommand < Gem::Command
req = Gem::Requirement.default
# TODO: deprecate for real
dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req }
+ dep.prerelease = prerelease
if local? then
if prerelease and not both? then
@@ -97,7 +102,7 @@ class Gem::Commands::QueryCommand < Gem::Command
}
spec_tuples = specs.map do |spec|
- [[spec.name, spec.version, spec.original_platform, spec], :local]
+ [spec.name_tuple, spec]
end
output_query_results spec_tuples
@@ -110,13 +115,27 @@ class Gem::Commands::QueryCommand < Gem::Command
say
end
- all = options[:all]
-
fetcher = Gem::SpecFetcher.fetcher
- spec_tuples = fetcher.find_matching dep, all, false, prerelease
- spec_tuples += fetcher.find_matching dep, false, false, true if
- prerelease and all
+ type = if options[:all]
+ if options[:prerelease]
+ :complete
+ else
+ :released
+ end
+ elsif options[:prerelease]
+ :prerelease
+ else
+ :latest
+ end
+
+ if options[:name].source.empty?
+ spec_tuples = fetcher.detect(type) { true }
+ else
+ spec_tuples = fetcher.detect(type) do |gem_name, ver, plat|
+ options[:name] === gem_name
+ end
+ end
output_query_results spec_tuples
end
@@ -135,32 +154,30 @@ class Gem::Commands::QueryCommand < Gem::Command
output = []
versions = Hash.new { |h,name| h[name] = [] }
- spec_tuples.each do |spec_tuple, source_uri|
- versions[spec_tuple.first] << [spec_tuple, source_uri]
+ spec_tuples.each do |spec_tuple, source|
+ versions[spec_tuple.name] << [spec_tuple, source]
end
- versions = versions.sort_by do |(name,_),_|
- name.downcase
+ versions = versions.sort_by do |(n,_),_|
+ n.downcase
end
versions.each do |gem_name, matching_tuples|
- matching_tuples = matching_tuples.sort_by do |(_, version,_),_|
- version
- end.reverse
+ matching_tuples = matching_tuples.sort_by { |n,_| n.version }.reverse
platforms = Hash.new { |h,version| h[version] = [] }
- matching_tuples.map do |(_, version, platform,_),_|
- platforms[version] << platform if platform
+ matching_tuples.map do |n,_|
+ platforms[n.version] << n.platform if n.platform
end
seen = {}
- matching_tuples.delete_if do |(_, version,_),_|
- if seen[version] then
+ matching_tuples.delete_if do |n,_|
+ if seen[n.version] then
true
else
- seen[version] = true
+ seen[n.version] = true
false
end
end
@@ -169,7 +186,7 @@ class Gem::Commands::QueryCommand < Gem::Command
if options[:versions] then
list = if platforms.empty? or options[:details] then
- matching_tuples.map { |(_, version,_),_| version }.uniq
+ matching_tuples.map { |n,_| n.version }.uniq
else
platforms.sort.reverse.map do |version, pls|
if pls == [Gem::Platform::RUBY] then
@@ -188,12 +205,11 @@ class Gem::Commands::QueryCommand < Gem::Command
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
+ spec = detail_tuple.last
+
+ unless spec.kind_of? Gem::Specification
+ spec = spec.fetch_spec detail_tuple.first
+ end
entry << "\n"
@@ -243,9 +259,9 @@ class Gem::Commands::QueryCommand < Gem::Command
entry << "\n" << " Installed at: #{loaded_from}"
else
label = 'Installed at'
- matching_tuples.each do |(_,version,_,s),|
+ matching_tuples.each do |n,s|
loaded_from = File.dirname File.dirname(s.loaded_from)
- entry << "\n" << " #{label} (#{version}): #{loaded_from}"
+ entry << "\n" << " #{label} (#{n.version}): #{loaded_from}"
label = ' ' * label.length
end
end
diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb
index ea0f3ad592..9bb07245cd 100644
--- a/lib/rubygems/commands/rdoc_command.rb
+++ b/lib/rubygems/commands/rdoc_command.rb
@@ -1,6 +1,6 @@
require 'rubygems/command'
require 'rubygems/version_option'
-require 'rubygems/doc_manager'
+require 'rubygems/rdoc'
class Gem::Commands::RdocCommand < Gem::Command
include Gem::VersionOption
@@ -8,7 +8,7 @@ class Gem::Commands::RdocCommand < Gem::Command
def initialize
super 'rdoc', 'Generates RDoc for pre-installed gems',
:version => Gem::Requirement.default,
- :include_rdoc => true, :include_ri => true, :overwrite => false
+ :include_rdoc => false, :include_ri => true, :overwrite => false
add_option('--all',
'Generate RDoc/RI documentation for all',
@@ -39,7 +39,7 @@ class Gem::Commands::RdocCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--version '#{Gem::Requirement.default}' --rdoc --ri --no-overwrite"
+ "--version '#{Gem::Requirement.default}' --ri --no-overwrite"
end
def description # :nodoc:
@@ -54,37 +54,32 @@ The rdoc command builds RDoc and RI documentation for installed gems. Use
end
def execute
- if options[:all] then
- specs = Gem::SourceIndex.from_installed_gems.collect { |name, spec|
- spec
- }
- else
- gem_name = get_one_gem_name
- dep = Gem::Dependency.new gem_name, options[:version]
- specs = Gem::SourceIndex.from_installed_gems.search dep
+ specs = if options[:all] then
+ Gem::Specification.to_a
+ else
+ get_all_gem_names.map do |name|
+ Gem::Specification.find_by_name name, options[:version]
+ end.flatten.uniq
+ end
+
+ if specs.empty? then
+ alert_error 'No matching gems found'
+ terminate_interaction 1
end
- if specs.empty?
- raise "Failed to find gem #{gem_name} to generate RDoc for #{options[:version]}"
- end
+ specs.each do |spec|
+ doc = Gem::RDoc.new spec, options[:include_rdoc], options[:include_ri]
- if options[:include_ri]
- specs.sort.each do |spec|
- doc = Gem::DocManager.new(spec)
- doc.generate_ri if options[:overwrite] || !doc.ri_installed?
- end
+ doc.force = options[:overwrite]
- Gem::DocManager.update_ri_cache
- end
-
- if options[:include_rdoc]
- specs.sort.each do |spec|
- doc = Gem::DocManager.new(spec)
- doc.generate_rdoc if options[:overwrite] || !doc.rdoc_installed?
+ begin
+ doc.generate
+ rescue Errno::ENOENT => e
+ e.message =~ / - /
+ alert_error "Unable to document #{spec.full_name}, #{$'} is missing, skipping"
+ terminate_interaction 1 if specs.length == 1
end
end
-
- true
end
end
diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb
index 52e96fd1ef..92d4b3672e 100644
--- a/lib/rubygems/commands/search_command.rb
+++ b/lib/rubygems/commands/search_command.rb
@@ -1,30 +1,16 @@
require 'rubygems/command'
-require 'rubygems/commands/query_command'
+require 'rubygems/commands/list_command'
-class Gem::Commands::SearchCommand < Gem::Commands::QueryCommand
+class Gem::Commands::SearchCommand < Gem::Commands::ListCommand
def initialize
super 'search', 'Display all gems whose name contains STRING'
- remove_option '--name-matches'
- end
-
- def arguments # :nodoc:
- "STRING fragment of gem name to search for"
+ @defaults[:domain] = :remote
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
+ "--remote --no-details"
end
end
diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb
index b65d48c4fc..4796ce2ad6 100644
--- a/lib/rubygems/commands/server_command.rb
+++ b/lib/rubygems/commands/server_command.rb
@@ -78,7 +78,7 @@ You can set up a shortcut to gem server documentation using the URL:
end
def execute
- options[:gemdir] << Gem.dir if options[:gemdir].empty?
+ options[:gemdir] = Gem.path if options[:gemdir].empty?
Gem::Server.run options
end
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 508ff84a0f..2f1cf0091d 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -5,14 +5,22 @@ require 'rubygems/command'
# RubyGems checkout or tarball.
class Gem::Commands::SetupCommand < Gem::Command
+ HISTORY_HEADER = /^===\s*[\d.]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
+ VERSION_MATCHER = /^===\s*([\d.]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
def initialize
require 'tmpdir'
super 'setup', 'Install RubyGems',
- :format_executable => true, :rdoc => true, :ri => true,
+ :format_executable => true, :document => %w[ri],
:site_or_vendor => :sitelibdir,
- :destdir => '', :prefix => ''
+ :destdir => '', :prefix => '', :previous_version => ''
+
+ add_option '--previous-version=VERSION',
+ 'Previous version of rubygems',
+ 'Used for changelog processing' do |version, options|
+ options[:previous_version] = version
+ end
add_option '--prefix=PREFIX',
'Prefix path for installing RubyGems',
@@ -37,14 +45,37 @@ class Gem::Commands::SetupCommand < Gem::Command
options[:format_executable] = value
end
+ add_option '--[no-]document [TYPES]', Array,
+ 'Generate documentation for RubyGems.',
+ 'List the documentation types you wish to',
+ 'generate. For example: rdoc,ri' do |value, options|
+ options[:document] = case value
+ when nil then %w[rdoc ri]
+ when false then []
+ else value
+ end
+ end
+
add_option '--[no-]rdoc',
'Generate RDoc documentation for RubyGems' do |value, options|
- options[:rdoc] = value
+ if value then
+ options[:document] << 'rdoc'
+ else
+ options[:document].delete 'rdoc'
+ end
+
+ options[:document].uniq!
end
add_option '--[no-]ri',
'Generate RI documentation for RubyGems' do |value, options|
- options[:ri] = value
+ if value then
+ options[:document] << 'ri'
+ else
+ options[:document].delete 'ri'
+ end
+
+ options[:document].uniq!
end
end
@@ -58,7 +89,7 @@ class Gem::Commands::SetupCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--format-executable --rdoc --ri"
+ "--format-executable --document ri"
end
def description # :nodoc:
@@ -110,7 +141,7 @@ By default, this RubyGems will install gem as:
uninstall_old_gemcutter
- install_rdoc
+ documentation_success = install_rdoc
say
if @verbose then
@@ -118,14 +149,30 @@ By default, this RubyGems will install gem as:
say
end
+ if options[:previous_version].empty?
+ options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, '0')
+ end
+
+ options[:previous_version] = Gem::Version.new(options[:previous_version])
+
release_notes = File.join Dir.pwd, 'History.txt'
release_notes = if File.exist? release_notes then
- open release_notes do |io|
- text = io.gets '==='
- text << io.gets('===')
- text[0...-3].sub(/^# coding:.*?^=/m, '')
+ history = File.read release_notes
+ history = history.sub(/^# coding:.*?^=/m, '')
+
+ text = history.split(HISTORY_HEADER)
+ text.shift # correct an off-by-one generated by split
+ version_lines = history.scan(HISTORY_HEADER)
+ versions = history.scan(VERSION_MATCHER).flatten.map { |x| Gem::Version.new(x) }
+
+ history_string = ""
+
+ until versions.length == 0 or versions.shift < options[:previous_version]
+ history_string += version_lines.shift + text.shift
end
+
+ history_string
else
"Oh-no! Unable to find release notes!"
end
@@ -145,6 +192,31 @@ By default, this RubyGems will install gem as:
say "to remove it by hand."
say
end
+
+ if documentation_success
+ if options[:document].include? 'rdoc' then
+ say "Rdoc documentation was installed. You may now invoke:"
+ say " gem server"
+ say "and then peruse beautifully formatted documentation for your gems"
+ say "with your web browser."
+ say "If you do not wish to install this documentation in the future, use the"
+ say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
+ say "'gem help env' for details."
+ say
+ end
+
+ if options[:document].include? 'ri' then
+ say "Ruby Interactive (ri) documentation was installed. ri is kind of like man "
+ say "pages for ruby libraries. You may access it like this:"
+ say " ri Classname"
+ say " ri Classname.class_method"
+ say " ri Classname#instance_method"
+ say "If you do not wish to install this documentation in the future, use the"
+ say "--no-document flag, or set it as the default in your ~/.gemrc file. See"
+ say "'gem help env' for details."
+ say
+ end
+ end
end
def install_executables(bin_dir)
@@ -165,7 +237,7 @@ By default, this RubyGems will install gem as:
end
dest_file = File.join bin_dir, bin_file_formatted
- bin_tmp_file = File.join Dir.tmpdir, bin_file
+ bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
begin
bin = File.readlines bin_file
@@ -209,10 +281,7 @@ TEXT
say "Installing RubyGems" if @verbose
Dir.chdir 'lib' do
- lib_files = Dir[File.join('**', '*rb')]
-
- # Be sure to include our SSL ca bundles
- lib_files += Dir[File.join('**', '*pem')]
+ lib_files = Dir[File.join('**', '*rb')]
lib_files.each do |lib_file|
dest_file = File.join lib_dir, lib_file
@@ -229,6 +298,12 @@ TEXT
rubygems_name = "rubygems-#{Gem::VERSION}"
rubygems_doc_dir = File.join gem_doc_dir, rubygems_name
+ begin
+ Gem.ensure_gem_subdirectories Gem.dir
+ rescue SystemCallError
+ # ignore
+ end
+
if File.writable? gem_doc_dir and
(not File.exist? rubygems_doc_dir or
File.writable? rubygems_doc_dir) then
@@ -237,21 +312,26 @@ TEXT
rm_rf dir
end
- if options[:ri] then
- ri_dir = File.join rubygems_doc_dir, 'ri'
- say "Installing #{rubygems_name} ri into #{ri_dir}" if @verbose
- run_rdoc '--ri', '--op', ri_dir
- end
+ require 'rubygems/rdoc'
- if options[:rdoc] then
- rdoc_dir = File.join rubygems_doc_dir, 'rdoc'
- say "Installing #{rubygems_name} rdoc into #{rdoc_dir}" if @verbose
- run_rdoc '--op', rdoc_dir
+ fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION
+ def fake_spec.full_gem_path
+ File.expand_path '../../../..', __FILE__
end
+
+ generate_ri = options[:document].include? 'ri'
+ generate_rdoc = options[:document].include? 'rdoc'
+
+ rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri
+ rdoc.generate
+
+ return true
elsif @verbose then
say "Skipping RDoc generation, #{gem_doc_dir} not writable"
say "Set the GEM_HOME environment variable if you want RDoc generated"
end
+
+ return false
end
def make_destination_dirs(install_destdir)
@@ -331,23 +411,6 @@ abort "#{deprecation_message}"
end
end
- def run_rdoc(*args)
- begin
- gem 'rdoc'
- rescue Gem::LoadError
- end
-
- require 'rdoc/rdoc'
-
- args << '--main' << 'README.rdoc' << '--quiet'
- args << '.'
- args << 'README.rdoc' << 'UPGRADING.rdoc'
- args << 'LICENSE.txt' << 'MIT.txt' << 'History.txt'
-
- r = RDoc::RDoc.new
- r.document args
- end
-
def uninstall_old_gemcutter
require 'rubygems/uninstaller'
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index ac14313e9d..97ed7329ea 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -48,7 +48,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:update])
if options[:clear_all] then
- path = Gem::SpecFetcher.fetcher.dir
+ path = File.join Gem.user_home, '.gem', 'specs'
FileUtils.rm_rf path
unless File.exist? path then
@@ -64,16 +64,19 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
end
- if options[:add] then
- source_uri = options[:add]
- uri = URI.parse source_uri
+ if source_uri = options[:add] then
+ source = Gem::Source.new source_uri
begin
- Gem::SpecFetcher.fetcher.load_specs uri, 'specs'
- Gem.sources << source_uri
- Gem.configuration.write
+ if Gem.sources.include? source_uri then
+ say "source #{source_uri} already present in the cache"
+ else
+ source.load_specs :released
+ Gem.sources << source
+ Gem.configuration.write
- say "#{source_uri} added to sources"
+ say "#{source_uri} added to sources"
+ end
rescue URI::Error, ArgumentError
say "#{source_uri} is not a URI"
terminate_interaction 1
@@ -97,12 +100,9 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
if options[:update] then
- fetcher = Gem::SpecFetcher.fetcher
-
- Gem.sources.each do |update_uri|
- update_uri = URI.parse update_uri
- fetcher.load_specs update_uri, 'specs'
- fetcher.load_specs update_uri, 'latest_specs'
+ Gem.sources.each_source do |src|
+ src.load_specs :released
+ src.load_specs :latest
end
say "source cache successfully updated"
@@ -112,8 +112,8 @@ class Gem::Commands::SourcesCommand < Gem::Command
say "*** CURRENT SOURCES ***"
say
- Gem.sources.each do |source|
- say source
+ Gem.sources.each do |src|
+ say src
end
end
end
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index 566a9cc66e..63da5fef0f 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -1,7 +1,7 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
-require 'rubygems/format'
+require 'rubygems/package'
class Gem::Commands::SpecificationCommand < Gem::Command
@@ -17,6 +17,7 @@ class Gem::Commands::SpecificationCommand < Gem::Command
add_version_option('examine')
add_platform_option
+ add_prerelease_option
add_option('--all', 'Output specifications for all versions of',
'the gem') do |value, options|
@@ -62,13 +63,13 @@ FIELD name of gemspec field to show
"Please specify a gem name or file on the command line"
end
- case options[:version]
+ case v = options[:version]
when String
- req = Gem::Requirement.parse options[:version]
+ req = Gem::Requirement.create v
when Gem::Requirement
- req = options[:version]
+ req = v
else
- raise Gem::CommandLineError, "Unsupported version type: #{options[:version]}"
+ raise Gem::CommandLineError, "Unsupported version type: '#{v}'"
end
if !req.none? and options[:all]
@@ -79,7 +80,7 @@ FIELD name of gemspec field to show
if options[:all]
dep = Gem::Dependency.new gem
else
- dep = Gem::Dependency.new gem, options[:version]
+ dep = Gem::Dependency.new gem, req
end
field = get_one_optional_argument
@@ -89,7 +90,7 @@ FIELD name of gemspec field to show
if local? then
if File.exist? gem then
- specs << Gem::Format.from_file_by_path(gem).spec rescue nil
+ specs << Gem::Package.new(gem).spec rescue nil
end
if specs.empty? then
@@ -98,17 +99,14 @@ FIELD name of gemspec field to show
end
if remote? then
- found = Gem::SpecFetcher.fetcher.fetch dep, true
-
- if dep.prerelease? or options[:prerelease]
- found += Gem::SpecFetcher.fetcher.fetch dep, false, true, true
- end
+ dep.prerelease = options[:prerelease]
+ found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep
specs.push(*found.map { |spec,| spec })
end
if specs.empty? then
- alert_error "Unknown gem '#{gem}'"
+ alert_error "No gem matching '#{dep}' found"
terminate_interaction 1
end
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index aaadb762b5..736574ddff 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -13,7 +13,8 @@ class Gem::Commands::UninstallCommand < Gem::Command
def initialize
super 'uninstall', 'Uninstall gems from the local repository',
- :version => Gem::Requirement.default, :user_install => true
+ :version => Gem::Requirement.default, :user_install => true,
+ :check_dev => false
add_option('-a', '--[no-]all',
'Uninstall all matching versions'
@@ -27,6 +28,12 @@ class Gem::Commands::UninstallCommand < Gem::Command
options[:ignore] = value
end
+ add_option('-D', '--[no-]-check-development',
+ 'Check development dependencies while uninstalling',
+ '(default: false)') do |value, options|
+ options[:check_dev] = value
+ end
+
add_option('-x', '--[no-]executables',
'Uninstall applicable executables without',
'confirmation') do |value, options|
@@ -54,6 +61,12 @@ class Gem::Commands::UninstallCommand < Gem::Command
options[:format_executable] = value
end
+ add_option('--[no-]force',
+ 'Uninstall all versions of the named gems',
+ 'ignoring dependencies') do |value, options|
+ options[:force] = value
+ end
+
add_version_option
add_platform_option
end
@@ -73,19 +86,23 @@ class Gem::Commands::UninstallCommand < Gem::Command
end
def execute
- original_path = Gem.path
+ # REFACTOR: stolen from cleanup_command
+ deplist = Gem::DependencyList.new
+ get_all_gem_names.uniq.each do |name|
+ Gem::Specification.find_all_by_name(name).each do |spec|
+ deplist.add spec
+ end
+ end
+
+ deps = deplist.strongly_connected_components.flatten.reverse
- get_all_gem_names.each do |gem_name|
+ deps.map(&:name).uniq.each do |gem_name|
begin
Gem::Uninstaller.new(gem_name, options).uninstall
- rescue Gem::InstallError => e
- alert e.message
rescue Gem::GemNotInHomeException => e
spec = e.spec
alert("In order to remove #{spec.name}, please execute:\n" \
"\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
- ensure
- Gem.use_paths(*original_path)
end
end
end
diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb
index 64b8ad64f8..7eefd32a6e 100644
--- a/lib/rubygems/commands/unpack_command.rb
+++ b/lib/rubygems/commands/unpack_command.rb
@@ -69,8 +69,10 @@ class Gem::Commands::UnpackCommand < Gem::Command
else
basename = File.basename path, '.gem'
target_dir = File.expand_path basename, options[:target]
- FileUtils.mkdir_p target_dir
- Gem::Installer.new(path, :unpack => true).unpack target_dir
+
+ package = Gem::Package.new path
+ package.extract_files target_dir
+
say "Unpacked gem: '#{target_dir}'"
end
end
@@ -134,9 +136,11 @@ class Gem::Commands::UnpackCommand < Gem::Command
##
# Extracts the Gem::Specification and raw metadata from the .gem file at
# +path+.
+ #--
+ # TODO move to Gem::Package as #raw_spec or something
def get_metadata path
- format = Gem::Format.from_file_by_path path
+ format = Gem::Package.new path
spec = format.spec
metadata = nil
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index d63b943c56..02f9657435 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -1,10 +1,12 @@
require 'rubygems/command'
require 'rubygems/command_manager'
+require 'rubygems/dependency_installer'
require 'rubygems/install_update_options'
require 'rubygems/local_remote_options'
require 'rubygems/spec_fetcher'
require 'rubygems/version_option'
-require 'rubygems/commands/install_command'
+require 'rubygems/install_message' # must come before rdoc for messaging
+require 'rubygems/rdoc'
class Gem::Commands::UpdateCommand < Gem::Command
@@ -13,11 +15,9 @@ class Gem::Commands::UpdateCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'update',
- 'Update the named gems (or all installed gems) in the local repository',
- :generate_rdoc => true,
- :generate_ri => true,
- :force => false
+ super 'update', 'Update installed gems to the latest version',
+ :document => %w[rdoc ri],
+ :force => false
add_install_update_options
@@ -37,6 +37,9 @@ class Gem::Commands::UpdateCommand < Gem::Command
add_local_remote_options
add_platform_option
add_prerelease_option "as update targets"
+
+ @updated = []
+ @installer = Gem::DependencyInstaller.new options
end
def arguments # :nodoc:
@@ -44,7 +47,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--rdoc --ri --no-force --install-dir #{Gem.dir}"
+ "--document --no-force --install-dir #{Gem.dir}"
end
def usage # :nodoc:
@@ -52,9 +55,6 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
def execute
- @installer = Gem::DependencyInstaller.new options
- @updated = []
-
hig = {}
if options[:system] then
@@ -79,21 +79,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
if updated.empty? then
say "Nothing to update"
else
- say "Gems updated: #{updated.map { |spec| spec.name }.join ', '}"
-
- if options[:generate_ri] then
- updated.each do |gem|
- Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri
- end
-
- Gem::DocManager.update_ri_cache
- end
-
- if options[:generate_rdoc] then
- updated.each do |gem|
- Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc
- end
- end
+ say "Gems updated: #{updated.map { |spec| spec.name }.join ' '}"
end
end
@@ -112,7 +98,6 @@ class Gem::Commands::UpdateCommand < Gem::Command
@installer.installed_gems.each do |spec|
@updated << spec
- say "Successfully installed #{spec.full_name}" if success
end
end
@@ -178,8 +163,9 @@ class Gem::Commands::UpdateCommand < Gem::Command
args = []
args << '--prefix' << Gem.prefix if Gem.prefix
- args << '--no-rdoc' unless options[:generate_rdoc]
- args << '--no-ri' unless options[:generate_ri]
+ # TODO use --document for >= 1.9 , --no-rdoc --no-ri < 1.9
+ args << '--no-rdoc' unless options[:document].include? 'rdoc'
+ args << '--no-ri' unless options[:document].include? 'ri'
args << '--no-format-executable' if options[:no_format_executable]
update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
@@ -205,20 +191,20 @@ class Gem::Commands::UpdateCommand < Gem::Command
gem_names.all? { |name| /#{name}/ !~ l_spec.name }
dependency = Gem::Dependency.new l_spec.name, "> #{l_spec.version}"
+ dependency.prerelease = options[:prerelease]
fetcher = Gem::SpecFetcher.fetcher
- spec_tuples = fetcher.find_matching dependency
- matching_gems = spec_tuples.select do |(name, _, platform),|
- name == l_name and Gem::Platform.match platform
+ spec_tuples, _ = fetcher.search_for_dependency dependency
+
+ matching_gems = spec_tuples.select do |g,_|
+ g.name == l_name and g.match_platform?
end
- highest_remote_gem = matching_gems.sort_by do |(_, version),|
- version
- end.last
+ highest_remote_gem = matching_gems.sort_by { |g,_| g.version }.last
- highest_remote_gem ||= [[nil, Gem::Version.new(0), nil]] # "null" object
- highest_remote_ver = highest_remote_gem.first[1]
+ highest_remote_gem ||= [Gem::NameTuple.null]
+ highest_remote_ver = highest_remote_gem.first.version
if system or (l_spec.version < highest_remote_ver) then
result << [l_spec.name, [l_spec.version, highest_remote_ver].max]
diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb
new file mode 100644
index 0000000000..da0cf7ad3b
--- /dev/null
+++ b/lib/rubygems/commands/yank_command.rb
@@ -0,0 +1,98 @@
+require 'rubygems/command'
+require 'rubygems/local_remote_options'
+require 'rubygems/version_option'
+require 'rubygems/gemcutter_utilities'
+
+class Gem::Commands::YankCommand < Gem::Command
+ include Gem::LocalRemoteOptions
+ include Gem::VersionOption
+ include Gem::GemcutterUtilities
+
+ def description # :nodoc:
+ 'Remove a specific gem version release from RubyGems.org'
+ end
+
+ def arguments # :nodoc:
+ "GEM name of gem"
+ end
+
+ def usage # :nodoc:
+ "#{program_name} GEM -v VERSION [-p PLATFORM] [--undo] [--key KEY_NAME]"
+ end
+
+ def initialize
+ super 'yank', description
+
+ add_version_option("remove")
+ add_platform_option("remove")
+
+ add_option('--undo') do |value, options|
+ options[:undo] = true
+ end
+
+ add_option('-k', '--key KEY_NAME',
+ 'Use API key from your gem credentials file') do |value, options|
+ options[:key] = value
+ end
+ end
+
+ def execute
+ sign_in
+
+ version = get_version_from_requirements(options[:version])
+ platform = get_platform_from_requirements(options)
+ api_key = Gem.configuration.rubygems_api_key
+ api_key = Gem.configuration.api_keys[options[:key].to_sym] if options[:key]
+
+ if version then
+ if options[:undo] then
+ unyank_gem(version, platform, api_key)
+ else
+ yank_gem(version, platform, api_key)
+ end
+ else
+ say "A version argument is required: #{usage}"
+ terminate_interaction
+ end
+ end
+
+ def yank_gem(version, platform, api_key)
+ say "Yanking gem from #{self.host}..."
+ yank_api_request(:delete, version, platform, "api/v1/gems/yank", api_key)
+ end
+
+ def unyank_gem(version, platform, api_key)
+ say "Unyanking gem from #{host}..."
+ yank_api_request(:put, version, platform, "api/v1/gems/unyank", api_key)
+ end
+
+ private
+
+ def yank_api_request(method, version, platform, api, api_key)
+ name = get_one_gem_name
+ response = rubygems_api_request(method, api) do |request|
+ request.add_field("Authorization", api_key)
+
+ data = {
+ 'gem_name' => name,
+ 'version' => version,
+ }
+ data['platform'] = platform if platform
+
+ request.set_form_data data
+ end
+ say response.body
+ end
+
+ def get_version_from_requirements(requirements)
+ requirements.requirements.first[1].version
+ rescue
+ nil
+ end
+
+ def get_platform_from_requirements(requirements)
+ Gem.platforms[1].to_s if requirements.key? :added_platform
+ end
+
+end
+
diff --git a/lib/rubygems/compatibility.rb b/lib/rubygems/compatibility.rb
new file mode 100644
index 0000000000..1137407bc5
--- /dev/null
+++ b/lib/rubygems/compatibility.rb
@@ -0,0 +1,51 @@
+# This file contains all sorts of little compatibility hacks that we've
+# had to introduce over the years. Quarantining them into one file helps
+# us know when we can get rid of them.
+
+# Ruby 1.9.x has introduced some things that are awkward, and we need to
+# support them, so we define some constants to use later.
+module Gem
+ # Only MRI 1.9.2 has the custom prelude.
+ GEM_PRELUDE_SUCKAGE = RUBY_VERSION =~ /^1\.9\.2/ and RUBY_ENGINE == "ruby"
+end
+
+# Gem::QuickLoader exists in the gem prelude code in ruby 1.9.2 itself.
+# We gotta get rid of it if it's there, before we do anything else.
+if Gem::GEM_PRELUDE_SUCKAGE and defined?(Gem::QuickLoader) then
+ Gem::QuickLoader.remove
+
+ $LOADED_FEATURES.delete Gem::QuickLoader.path_to_full_rubygems_library
+
+ if $LOADED_FEATURES.any? do |path| path.end_with? '/rubygems.rb' end then
+ # TODO path does not exist here
+ raise LoadError, "another rubygems is already loaded from #{path}"
+ end
+
+ class << Gem
+ remove_method :try_activate if Gem.respond_to?(:try_activate, true)
+ end
+end
+
+module Gem
+ RubyGemsVersion = VERSION
+
+ RbConfigPriorities = %w[
+ EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name
+ ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir
+ rubylibdir
+ ]
+
+ unless defined?(ConfigMap)
+ ##
+ # Configuration settings from ::RbConfig
+ ConfigMap = Hash.new do |cm, key|
+ cm[key] = RbConfig::CONFIG[key.to_s]
+ end
+ else
+ RbConfigPriorities.each do |key|
+ ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
+ end
+ end
+
+ RubyGemsPackageVersion = VERSION
+end
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index 136e8b4610..61ee3b73eb 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -5,9 +5,9 @@
#++
##
-# Gem::ConfigFile RubyGems options and gem command options from ~/.gemrc.
+# Gem::ConfigFile RubyGems options and gem command options from gemrc.
#
-# ~/.gemrc is a YAML file that uses strings to match gem command arguments and
+# gemrc is a YAML file that uses strings to match gem command arguments and
# symbols to match RubyGems options.
#
# Gem command arguments use a String key that matches the command name and
@@ -21,16 +21,19 @@
# RubyGems options use symbol keys. Valid options are:
#
# +:backtrace+:: See #backtrace
-# +:benchmark+:: See #benchmark
# +:sources+:: Sets Gem::sources
# +:verbose+:: See #verbose
-
-require 'rbconfig'
+#
+# gemrc files may exist in various locations and are read and merged in
+# the following order:
+#
+# - system wide (/etc/gemrc)
+# - per user (~/.gemrc)
+# - per environment (gemrc files listed in the GEMRC environment variable)
class Gem::ConfigFile
DEFAULT_BACKTRACE = false
- DEFAULT_BENCHMARK = false
DEFAULT_BULK_THRESHOLD = 1000
DEFAULT_VERBOSITY = true
DEFAULT_UPDATE_SOURCES = true
@@ -97,11 +100,6 @@ class Gem::ConfigFile
attr_writer :backtrace
##
- # True if we are benchmarking this run.
-
- attr_accessor :benchmark
-
- ##
# Bulk threshold value. If the number of missing gems are above this
# threshold value, then a bulk download technique is used. (deprecated)
@@ -131,6 +129,10 @@ class Gem::ConfigFile
attr_reader :api_keys
##
+ # True if we want to force specification of gem server when pushing a gem
+
+ attr_accessor :disable_default_gem_server
+
# openssl verify mode value, used for remote https connection
attr_reader :ssl_verify_mode
@@ -158,29 +160,29 @@ class Gem::ConfigFile
# <tt>--debug</tt>::
# Enable Ruby level debug messages. Handled early for the same reason as
# --backtrace.
+ #--
+ # TODO: parse options upstream, pass in options directly
- def initialize(arg_list)
+ def initialize(args)
@config_file_name = nil
need_config_file_name = false
- arg_list = arg_list.map do |arg|
+ arg_list = []
+
+ args.each do |arg|
if need_config_file_name then
@config_file_name = arg
need_config_file_name = false
- nil
elsif arg =~ /^--config-file=(.*)/ then
@config_file_name = $1
- nil
elsif arg =~ /^--config-file$/ then
need_config_file_name = true
- nil
else
- arg
+ arg_list << arg
end
- end.compact
+ end
@backtrace = DEFAULT_BACKTRACE
- @benchmark = DEFAULT_BENCHMARK
@bulk_threshold = DEFAULT_BULK_THRESHOLD
@verbose = DEFAULT_VERBOSITY
@update_sources = DEFAULT_UPDATE_SOURCES
@@ -189,19 +191,25 @@ class Gem::ConfigFile
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
system_config = load_file SYSTEM_WIDE_CONFIG_FILE
user_config = load_file config_file_name.dup.untaint
+ environment_config = (ENV['GEMRC'] || '').split(/[:;]/).inject({}) do |result, file|
+ result.merge load_file file
+ end
+
@hash = operating_system_config.merge platform_config
@hash = @hash.merge system_config
@hash = @hash.merge user_config
+ @hash = @hash.merge environment_config
# HACK these override command-line args, which is bad
- @backtrace = @hash[:backtrace] if @hash.key? :backtrace
- @benchmark = @hash[:benchmark] if @hash.key? :benchmark
- @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold
- @home = @hash[:gemhome] if @hash.key? :gemhome
- @path = @hash[:gempath] if @hash.key? :gempath
- @update_sources = @hash[:update_sources] if @hash.key? :update_sources
- @verbose = @hash[:verbose] if @hash.key? :verbose
+ @backtrace = @hash[:backtrace] if @hash.key? :backtrace
+ @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold
+ @home = @hash[:gemhome] if @hash.key? :gemhome
+ @path = @hash[:gempath] if @hash.key? :gempath
+ @update_sources = @hash[:update_sources] if @hash.key? :update_sources
+ @verbose = @hash[:verbose] if @hash.key? :verbose
+ @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
+
@ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
@ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
@@ -224,6 +232,7 @@ class Gem::ConfigFile
else
@hash
end
+
if @api_keys.key? :rubygems_api_key then
@rubygems_api_key = @api_keys[:rubygems_api_key]
@api_keys[:rubygems] = @api_keys.delete :rubygems_api_key unless @api_keys.key? :rubygems
@@ -238,7 +247,8 @@ class Gem::ConfigFile
Gem.load_yaml
- File.open(credentials_path, 'w') do |f|
+ permissions = 0600 & (~File.umask)
+ File.open(credentials_path, 'w', permissions) do |f|
f.write config.to_yaml
end
@@ -249,13 +259,21 @@ class Gem::ConfigFile
Gem.load_yaml
return {} unless filename and File.exist? filename
+
begin
- YAML.load(File.read(filename))
+ content = YAML.load(File.read(filename))
+ unless content.kind_of? Hash
+ warn "Failed to load #{config_file_name} because it doesn't contain valid YAML hash"
+ return {}
+ end
+ return content
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
+
+ {}
end
# True if the backtrace option has been specified, or debug is on.
@@ -273,13 +291,11 @@ class Gem::ConfigFile
hash = @hash.dup
hash.delete :update_sources
hash.delete :verbose
- hash.delete :benchmark
hash.delete :backtrace
hash.delete :bulk_threshold
yield :update_sources, @update_sources
yield :verbose, @verbose
- yield :benchmark, @benchmark
yield :backtrace, @backtrace
yield :bulk_threshold, @bulk_threshold
@@ -296,8 +312,6 @@ class Gem::ConfigFile
case arg
when /^--(backtrace|traceback)$/ then
@backtrace = true
- when /^--bench(mark)?$/ then
- @benchmark = true
when /^--debug$/ then
$DEBUG = true
else
@@ -309,25 +323,41 @@ class Gem::ConfigFile
# Really verbose mode gives you extra output.
def really_verbose
case verbose
- when true, false, nil then false
- else true
+ when true, false, nil then
+ false
+ else
+ true
end
end
# to_yaml only overwrites things you can't override on the command line.
def to_yaml # :nodoc:
yaml_hash = {}
- yaml_hash[:backtrace] = @hash.key?(:backtrace) ? @hash[:backtrace] :
- DEFAULT_BACKTRACE
- yaml_hash[:benchmark] = @hash.key?(:benchmark) ? @hash[:benchmark] :
- DEFAULT_BENCHMARK
- yaml_hash[:bulk_threshold] = @hash.key?(:bulk_threshold) ?
- @hash[:bulk_threshold] : DEFAULT_BULK_THRESHOLD
- yaml_hash[:sources] = Gem.sources
- yaml_hash[:update_sources] = @hash.key?(:update_sources) ?
- @hash[:update_sources] : DEFAULT_UPDATE_SOURCES
- yaml_hash[:verbose] = @hash.key?(:verbose) ? @hash[:verbose] :
- DEFAULT_VERBOSITY
+ yaml_hash[:backtrace] = if @hash.key?(:backtrace)
+ @hash[:backtrace]
+ else
+ DEFAULT_BACKTRACE
+ end
+
+ yaml_hash[:bulk_threshold] = if @hash.key?(:bulk_threshold)
+ @hash[:bulk_threshold]
+ else
+ DEFAULT_BULK_THRESHOLD
+ end
+
+ yaml_hash[:sources] = Gem.sources.to_a
+
+ yaml_hash[:update_sources] = if @hash.key?(:update_sources)
+ @hash[:update_sources]
+ else
+ DEFAULT_UPDATE_SOURCES
+ end
+
+ yaml_hash[:verbose] = if @hash.key?(:verbose)
+ @hash[:verbose]
+ else
+ DEFAULT_VERBOSITY
+ end
keys = yaml_hash.keys.map { |key| key.to_s }
keys << 'debug'
@@ -361,15 +391,13 @@ class Gem::ConfigFile
def ==(other) # :nodoc:
self.class === other and
- @backtrace == other.backtrace and
- @benchmark == other.benchmark and
- @bulk_threshold == other.bulk_threshold and
- @verbose == other.verbose and
- @update_sources == other.update_sources and
- @hash == other.hash
+ @backtrace == other.backtrace and
+ @bulk_threshold == other.bulk_threshold and
+ @verbose == other.verbose and
+ @update_sources == other.update_sources and
+ @hash == other.hash
end
- protected
-
attr_reader :hash
+ protected :hash
end
diff --git a/lib/rubygems/core_ext/kernel_gem.rb b/lib/rubygems/core_ext/kernel_gem.rb
new file mode 100644
index 0000000000..f946d0d5d7
--- /dev/null
+++ b/lib/rubygems/core_ext/kernel_gem.rb
@@ -0,0 +1,53 @@
+module Kernel
+
+ # REFACTOR: This should be pulled out into some kind of hacks file.
+ remove_method :gem if 'method' == defined? gem # from gem_prelude.rb on 1.9
+
+ ##
+ # Use Kernel#gem to activate a specific version of +gem_name+.
+ #
+ # +requirements+ is a list of version requirements that the
+ # specified gem must match, most commonly "= example.version.number". See
+ # Gem::Requirement for how to specify a version requirement.
+ #
+ # If you will be activating the latest version of a gem, there is no need to
+ # call Kernel#gem, Kernel#require will do the right thing for you.
+ #
+ # Kernel#gem returns true if the gem was activated, otherwise false. If the
+ # gem could not be found, didn't match the version requirements, or a
+ # different version was already activated, an exception will be raised.
+ #
+ # Kernel#gem should be called *before* any require statements (otherwise
+ # RubyGems may load a conflicting library version).
+ #
+ # In older RubyGems versions, the environment variable GEM_SKIP could be
+ # used to skip activation of specified gems, for example to test out changes
+ # that haven't been installed yet. Now RubyGems defers to -I and the
+ # RUBYLIB environment variable to skip activation of a gem.
+ #
+ # Example:
+ #
+ # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
+
+ def gem(gem_name, *requirements) # :doc:
+ skip_list = (ENV['GEM_SKIP'] || "").split(/:/)
+ raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name
+
+ if gem_name.kind_of? Gem::Dependency
+ unless Gem::Deprecate.skip
+ warn "#{Gem.location_of_caller.join ':'}:Warning: Kernel.gem no longer "\
+ "accepts a Gem::Dependency object, please pass the name "\
+ "and requirements directly"
+ end
+
+ requirements = gem_name.requirement
+ gem_name = gem_name.name
+ end
+
+ spec = Gem::Dependency.new(gem_name, *requirements).to_spec
+ spec.activate if spec
+ end
+
+ private :gem
+
+end
diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb
new file mode 100755
index 0000000000..e6dfce644f
--- /dev/null
+++ b/lib/rubygems/core_ext/kernel_require.rb
@@ -0,0 +1,119 @@
+#--
+# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
+# All rights reserved.
+# See LICENSE.txt for permissions.
+#++
+
+module Kernel
+
+ if defined?(gem_original_require) then
+ # Ruby ships with a custom_require, override its require
+ remove_method :require
+ else
+ ##
+ # The Kernel#require from before RubyGems was loaded.
+
+ alias gem_original_require require
+ private :gem_original_require
+ end
+
+ ##
+ # When RubyGems is required, Kernel#require is replaced with our own which
+ # is capable of loading gems on demand.
+ #
+ # When you call <tt>require 'x'</tt>, this is what happens:
+ # * If the file can be loaded from the existing Ruby loadpath, it
+ # is.
+ # * Otherwise, installed gems are searched for a file that matches.
+ # If it's found in gem 'y', that gem is activated (added to the
+ # loadpath).
+ #
+ # The normal <tt>require</tt> functionality of returning false if
+ # that file has already been loaded is preserved.
+
+ def require path
+ spec = Gem.find_unresolved_default_spec(path)
+ if spec
+ Gem.remove_unresolved_default_spec(spec)
+ gem(spec.name)
+ end
+
+ # If there are no unresolved deps, then we can use just try
+ # normal require handle loading a gem from the rescue below.
+
+ if Gem::Specification.unresolved_deps.empty? then
+ return gem_original_require(path)
+ end
+
+ # If +path+ is for a gem that has already been loaded, don't
+ # bother trying to find it in an unresolved gem, just go straight
+ # to normal require.
+ #--
+ # TODO request access to the C implementation of this to speed up RubyGems
+
+ spec = Gem::Specification.find { |s|
+ s.activated? and s.contains_requirable_file? path
+ }
+
+ return gem_original_require(path) if spec
+
+ # Attempt to find +path+ in any unresolved gems...
+
+ found_specs = Gem::Specification.find_in_unresolved path
+
+ # If there are no directly unresolved gems, then try and find +path+
+ # in any gems that are available via the currently unresolved gems.
+ # For example, given:
+ #
+ # a => b => c => d
+ #
+ # If a and b are currently active with c being unresolved and d.rb is
+ # requested, then find_in_unresolved_tree will find d.rb in d because
+ # it's a dependency of c.
+ #
+ if found_specs.empty? then
+ found_specs = Gem::Specification.find_in_unresolved_tree path
+
+ found_specs.each do |found_spec|
+ found_spec.activate
+ end
+
+ # We found +path+ directly in an unresolved gem. Now we figure out, of
+ # the possible found specs, which one we should activate.
+ else
+
+ # Check that all the found specs are just different
+ # versions of the same gem
+ names = found_specs.map(&:name).uniq
+
+ if names.size > 1 then
+ raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
+ end
+
+ # Ok, now find a gem that has no conflicts, starting
+ # at the highest version.
+ valid = found_specs.select { |s| s.conflicts.empty? }.last
+
+ unless valid then
+ le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
+ le.name = names.first
+ raise le
+ end
+
+ valid.activate
+ end
+
+ gem_original_require path
+ rescue LoadError => load_error
+ if load_error.message.start_with?("Could not find") or
+ (load_error.message.end_with?(path) and Gem.try_activate(path)) then
+ return gem_original_require(path)
+ end
+
+ raise load_error
+ end
+
+ private :require
+
+end
+
diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb
deleted file mode 100644
index c813e3aaa2..0000000000
--- a/lib/rubygems/custom_require.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-module Kernel
-
- if defined?(gem_original_require) then
- # Ruby ships with a custom_require, override its require
- remove_method :require
- else
- ##
- # The Kernel#require from before RubyGems was loaded.
-
- alias gem_original_require require
- private :gem_original_require
- end
-
- ##
- # When RubyGems is required, Kernel#require is replaced with our own which
- # is capable of loading gems on demand.
- #
- # When you call <tt>require 'x'</tt>, this is what happens:
- # * If the file can be loaded from the existing Ruby loadpath, it
- # is.
- # * Otherwise, installed gems are searched for a file that matches.
- # If it's found in gem 'y', that gem is activated (added to the
- # loadpath).
- #
- # The normal <tt>require</tt> functionality of returning false if
- # that file has already been loaded is preserved.
-
- def require path
- if Gem.unresolved_deps.empty? then
- gem_original_require path
- else
- spec = Gem::Specification.find { |s|
- s.activated? and s.contains_requirable_file? path
- }
-
- unless spec then
- found_specs = Gem::Specification.find_in_unresolved path
- unless found_specs.empty? then
- found_specs = [found_specs.last]
- else
- found_specs = Gem::Specification.find_in_unresolved_tree path
- end
-
- found_specs.each do |found_spec|
- found_spec.activate
- end
- end
-
- return gem_original_require path
- end
- rescue LoadError => load_error
- if load_error.message.start_with?("Could not find") or
- (load_error.message.end_with?(path) and Gem.try_activate(path)) then
- return gem_original_require(path)
- end
-
- raise load_error
- end
-
- private :require
-
-end
-
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index d6732adbfa..e32131b9d4 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -1,8 +1,8 @@
module Gem
-
- # TODO: move this whole file back into rubygems.rb
+ DEFAULT_HOST = "https://rubygems.org"
@post_install_hooks ||= []
+ @done_installing_hooks ||= []
@post_uninstall_hooks ||= []
@pre_uninstall_hooks ||= []
@pre_install_hooks ||= []
@@ -61,7 +61,7 @@ module Gem
# Default gem load path
def self.default_path
- if File.exist? Gem.user_home then
+ if Gem.user_home && File.exist?(Gem.user_home) then
[user_dir, default_dir]
else
[default_dir]
@@ -94,24 +94,6 @@ module Gem
end
##
- # The default system-wide source info cache directory
-
- def self.default_system_source_cache_dir
- File.join(Gem.dir, 'source_cache')
- end
-
- ##
- # The default user-specific source info cache directory
-
- def self.default_user_source_cache_dir
- #
- # NOTE Probably an argument for moving this to per-ruby supported dirs like
- # user_dir
- #
- File.join(Gem.user_home, '.gem', 'source_cache')
- end
-
- ##
# A wrapper around RUBY_ENGINE const that may not be defined
def self.ruby_engine
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index 0caf65c6c4..217189bf8e 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -1,8 +1,8 @@
-require "rubygems/requirement"
-
##
# The Dependency class holds a Gem name and a Gem::Requirement.
+require "rubygems/requirement"
+
class Gem::Dependency
##
@@ -11,6 +11,9 @@ class Gem::Dependency
# When this list is updated, be sure to change
# Gem::Specification::CURRENT_SPECIFICATION_VERSION as well.
+ # REFACTOR: This type of constant, TYPES, indicates we might want
+ # two classes, used via inheretance or duck typing.
+
TYPES = [
:development,
:runtime,
@@ -32,18 +35,23 @@ class Gem::Dependency
# <tt>:runtime</tt>.
def initialize name, *requirements
- if Regexp === name then
+ case name
+ when String then # ok
+ when Regexp then
msg = ["NOTE: Dependency.new w/ a regexp is deprecated.",
"Dependency.new called from #{Gem.location_of_caller.join(":")}"]
warn msg.join("\n") unless Gem::Deprecate.skip
+ else
+ raise ArgumentError,
+ "dependency name must be a String, was #{name.inspect}"
end
type = Symbol === requirements.last ? requirements.pop : :runtime
requirements = requirements.first if 1 == requirements.length # unpack
unless TYPES.include? type
- raise ArgumentError, "Valid types are #{TYPES.inspect}, "
- + "not #{type.inspect}"
+ raise ArgumentError, "Valid types are #{TYPES.inspect}, " +
+ "not #{type.inspect}"
end
@name = name
@@ -66,8 +74,13 @@ class Gem::Dependency
end
def inspect # :nodoc:
- "<%s type=%p name=%p requirements=%p>" %
- [self.class, self.type, self.name, requirement.to_s]
+ if @prerelease
+ "<%s type=%p name=%p requirements=%p prerelease=ok>" %
+ [self.class, self.type, self.name, requirement.to_s]
+ else
+ "<%s type=%p name=%p requirements=%p>" %
+ [self.class, self.type, self.name, requirement.to_s]
+ end
end
##
@@ -77,6 +90,14 @@ class Gem::Dependency
@prerelease || requirement.prerelease?
end
+ ##
+ # Is this dependency simply asking for the latest version
+ # of a gem?
+
+ def latest_version?
+ @requirement.none?
+ end
+
def pretty_print q # :nodoc:
q.group 1, 'Gem::Dependency.new(', ')' do
q.pp name
@@ -113,6 +134,8 @@ class Gem::Dependency
# Children, define explicit marshal and unmarshal behavior for
# public classes. Marshal formats are part of your public API.
+ # REFACTOR: See above
+
if defined?(@version_requirement) && @version_requirement
version = @version_requirement.instance_variable_get :@version
@version_requirement = nil
@@ -122,6 +145,7 @@ class Gem::Dependency
@requirement = @version_requirements if defined?(@version_requirements)
end
+ # DOC: this method needs documentation or :nodoc''d
def requirements_list
requirement.as_list
end
@@ -179,13 +203,24 @@ class Gem::Dependency
requirement.satisfied_by? version
end
- def match? name, version
+ # DOC: this method needs either documented or :nodoc'd
+
+ def match? obj, version=nil
+ if !version
+ name = obj.name
+ version = obj.version
+ else
+ name = obj
+ end
+
return false unless self.name === name
return true if requirement.none?
requirement.satisfied_by? Gem::Version.new(version)
end
+ # DOC: this method needs either documented or :nodoc'd
+
def matches_spec? spec
return false unless name === spec.name
return true if requirement.none?
@@ -212,6 +247,8 @@ class Gem::Dependency
self.class.new name, self_req.as_list.concat(other_req.as_list)
end
+ # DOC: this method needs either documented or :nodoc'd
+
def matching_specs platform_only = false
matches = Gem::Specification.find_all { |spec|
self.name === spec.name and # TODO: == instead of ===
@@ -234,14 +271,26 @@ class Gem::Dependency
@requirement.specific?
end
+ # DOC: this method needs either documented or :nodoc'd
+
def to_specs
matches = matching_specs true
# TODO: check Gem.activated_spec[self.name] in case matches falls outside
if matches.empty? then
- specs = Gem::Specification.all_names.join ", "
- error = Gem::LoadError.new "Could not find #{name} (#{requirement}) amongst [#{specs}]"
+ specs = Gem::Specification.find_all { |s|
+ s.name == name
+ }.map { |x| x.full_name }
+
+ if specs.empty?
+ total = Gem::Specification.to_a.size
+ error = Gem::LoadError.new \
+ "Could not find '#{name}' (#{requirement}) among #{total} total gem(s)"
+ else
+ error = Gem::LoadError.new \
+ "Could not find '#{name}' (#{requirement}) - did find: [#{specs.join ','}]"
+ end
error.name = self.name
error.requirement = self.requirement
raise error
@@ -252,6 +301,8 @@ class Gem::Dependency
matches
end
+ # DOC: this method needs either documented or :nodoc'd
+
def to_spec
matches = self.to_specs
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index 6303e8e9ac..a2665633fc 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,8 +1,12 @@
require 'rubygems'
require 'rubygems/dependency_list'
+require 'rubygems/package'
require 'rubygems/installer'
require 'rubygems/spec_fetcher'
require 'rubygems/user_interaction'
+require 'rubygems/source_local'
+require 'rubygems/source_specific_file'
+require 'rubygems/available_set'
##
# Installs a gem along with all its dependencies from local and remote gems.
@@ -14,8 +18,14 @@ class Gem::DependencyInstaller
attr_reader :gems_to_install
attr_reader :installed_gems
+ ##
+ # Documentation types. For use by the Gem.done_installing hook
+
+ attr_reader :document
+
DEFAULT_OPTIONS = {
:env_shebang => false,
+ :document => %w[ri],
:domain => :both, # HACK dup
:force => false,
:format_executable => false, # HACK dup
@@ -23,7 +33,8 @@ class Gem::DependencyInstaller
:prerelease => false,
:security_policy => nil, # HACK NoSecurity requires OpenSSL. AlmostNo? Low?
:wrappers => true,
- }
+ :build_docs_in_background => false,
+ }.freeze
##
# Creates a new installer instance.
@@ -47,6 +58,7 @@ class Gem::DependencyInstaller
if options[:install_dir] then
@gem_home = options[:install_dir]
+ # HACK shouldn't change the global settings
Gem::Specification.dirs = @gem_home
Gem.ensure_gem_subdirectories @gem_home
options[:install_dir] = @gem_home # FIX: because we suck and reuse below
@@ -55,7 +67,9 @@ class Gem::DependencyInstaller
options = DEFAULT_OPTIONS.merge options
@bin_dir = options[:bin_dir]
+ @dev_shallow = options[:dev_shallow]
@development = options[:development]
+ @document = options[:document]
@domain = options[:domain]
@env_shebang = options[:env_shebang]
@force = options[:force]
@@ -65,8 +79,14 @@ class Gem::DependencyInstaller
@security_policy = options[:security_policy]
@user_install = options[:user_install]
@wrappers = options[:wrappers]
+ @build_docs_in_background = options[:build_docs_in_background]
+
+ # Indicates that we should not try to update any deps unless
+ # we absolutely must.
+ @minimal_deps = options[:minimal_deps]
@installed_gems = []
+ @toplevel_specs = nil
@install_dir = options[:install_dir] || Gem.dir
@cache_dir = options[:cache_dir] || @install_dir
@@ -76,6 +96,24 @@ class Gem::DependencyInstaller
@errors = nil
end
+ attr_reader :errors
+
+ ##
+ # Indicated, based on the requested domain, if local
+ # gems should be considered.
+
+ def consider_local?
+ @domain == :both or @domain == :local
+ end
+
+ ##
+ # Indicated, based on the requested domain, if remote
+ # gems should be considered.
+
+ def consider_remote?
+ @domain == :both or @domain == :remote
+ 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)
@@ -83,35 +121,34 @@ class Gem::DependencyInstaller
# local gems preferred over remote gems.
def find_gems_with_sources(dep)
- # Reset the errors
- @errors = nil
- gems_and_sources = []
+ set = Gem::AvailableSet.new
+
+ if consider_local?
+ sl = Gem::Source::Local.new
- if @domain == :both or @domain == :local then
- Dir[File.join(Dir.pwd, "#{dep.name}-[0-9]*.gem")].each do |gem_file|
- spec = Gem::Format.from_file_by_path(gem_file).spec
- gems_and_sources << [spec, gem_file] if spec.name == dep.name
+ if spec = sl.find_gem(dep.name)
+ if dep.matches_spec? spec
+ set.add spec, sl
+ end
end
end
- if @domain == :both or @domain == :remote then
+ if consider_remote?
begin
- # REFACTOR: all = dep.requirement.needs_all?
- requirements = dep.requirement.requirements.map do |req, ver|
- req
- end
-
- all = !dep.prerelease? &&
- # we only need latest if there's one requirement and it is
- # guaranteed to match the newest specs
- (requirements.length > 1 or
- (requirements.first != ">=" and requirements.first != ">"))
+ found, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
- found, @errors = Gem::SpecFetcher.fetcher.fetch_with_errors dep, all, true, dep.prerelease?
+ if @errors
+ @errors += errors
+ else
+ @errors = errors
+ end
- gems_and_sources.push(*found)
+ set << found
rescue Gem::RemoteFetcher::FetchError => e
+ # FIX if there is a problem talking to the network, we either need to always tell
+ # the user (no really_verbose) or fail hard, not silently tell them that we just
+ # couldn't find their requested gem.
if Gem.configuration.really_verbose then
say "Error fetching remote data:\t\t#{e.message}"
say "Falling back to local-only install"
@@ -120,9 +157,7 @@ class Gem::DependencyInstaller
end
end
- gems_and_sources.sort_by do |gem, source|
- [gem, source =~ /^http:\/\// ? 0 : 1] # local gems win
- end
+ set
end
##
@@ -130,17 +165,22 @@ class Gem::DependencyInstaller
# remote sources unless the ignore_dependencies was given.
def gather_dependencies
- specs = @specs_and_sources.map { |spec,_| spec }
+ specs = @available.all_specs
# these gems were listed by the user, always install them
keep_names = specs.map { |spec| spec.full_name }
+ if @dev_shallow
+ @toplevel_specs = keep_names
+ end
+
dependency_list = Gem::DependencyList.new @development
dependency_list.add(*specs)
to_do = specs.dup
-
add_found_dependencies to_do, dependency_list unless @ignore_dependencies
+ # REFACTOR maybe abstract away using Gem::Specification.include? so
+ # that this isn't dependent only on the currently installed gems
dependency_list.specs.reject! { |spec|
not keep_names.include?(spec.full_name) and
Gem::Specification.include?(spec)
@@ -162,32 +202,43 @@ class Gem::DependencyInstaller
until to_do.empty? do
spec = to_do.shift
+
+ # HACK why is spec nil?
next if spec.nil? or seen[spec.name]
seen[spec.name] = true
deps = spec.runtime_dependencies
- deps |= spec.development_dependencies if @development
+
+ if @development
+ if @dev_shallow
+ if @toplevel_specs.include? spec.full_name
+ deps |= spec.development_dependencies
+ end
+ else
+ deps |= spec.development_dependencies
+ end
+ end
deps.each do |dep|
dependencies[dep.name] = dependencies[dep.name].merge dep
- results = find_gems_with_sources(dep).reverse
+ if @minimal_deps
+ next if Gem::Specification.any? do |installed_spec|
+ dep.name == installed_spec.name and
+ dep.requirement.satisfied_by? installed_spec.version
+ end
+ end
- results.reject! do |dep_spec,|
- to_do.push dep_spec
+ results = find_gems_with_sources(dep)
- # already locally installed
- Gem::Specification.any? do |installed_spec|
- dep.name == installed_spec.name and
- dep.requirement.satisfied_by? installed_spec.version
- end
+ results.sorted.each do |t|
+ to_do.push t.spec
end
- results.each do |dep_spec, source_uri|
- @specs_and_sources << [dep_spec, source_uri]
+ results.remove_installed! dep
- dependency_list.add dep_spec
- end
+ @available << results
+ results.inject_into_list dependency_list
end
end
@@ -202,42 +253,36 @@ class Gem::DependencyInstaller
def find_spec_by_name_and_version(gem_name,
version = Gem::Requirement.default,
prerelease = false)
- spec_and_source = nil
- glob = if File::ALT_SEPARATOR then
- gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
- else
- gem_name
- end
+ set = Gem::AvailableSet.new
- local_gems = Dir["#{glob}*"].sort.reverse
+ if consider_local?
+ if File.exists? gem_name
+ src = Gem::Source::SpecificFile.new(gem_name)
+ set.add src.spec, src
+ else
+ local = Gem::Source::Local.new
- local_gems.each do |gem_file|
- next unless gem_file =~ /gem$/
- begin
- spec = Gem::Format.from_file_by_path(gem_file).spec
- spec_and_source = [spec, gem_file]
- break
- rescue SystemCallError, Gem::Package::FormatError
+ if s = local.find_gem(gem_name, version)
+ set.add s, local
+ end
end
end
- unless spec_and_source then
+ if set.empty?
dep = Gem::Dependency.new gem_name, version
+ # HACK Dependency objects should be immutable
dep.prerelease = true if prerelease
- spec_and_sources = find_gems_with_sources(dep).reverse
- spec_and_source = spec_and_sources.find { |spec, source|
- Gem::Platform.match spec.platform
- }
+
+ set = find_gems_with_sources(dep)
+ set.match_platform!
end
- if spec_and_source.nil? then
- raise Gem::GemNotFoundException.new(
- "Could not find a valid gem '#{gem_name}' (#{version}) locally or in a repository",
- gem_name, version, @errors)
+ if set.empty?
+ raise Gem::SpecificGemNotFoundException.new(gem_name, version, @errors)
end
- @specs_and_sources = [spec_and_source]
+ @available = set
end
##
@@ -258,33 +303,49 @@ class Gem::DependencyInstaller
if String === dep_or_name then
find_spec_by_name_and_version dep_or_name, version, @prerelease
else
- dep_or_name.prerelease = @prerelease
- @specs_and_sources = [find_gems_with_sources(dep_or_name).last]
+ dep = dep_or_name.dup
+ dep.prerelease = @prerelease
+ @available = find_gems_with_sources(dep).pick_best!
end
@installed_gems = []
gather_dependencies
+ # REFACTOR is the last gem always the one that the user requested?
+ # This code assumes that but is that actually validated by the code?
+
last = @gems_to_install.size - 1
@gems_to_install.each_with_index do |spec, index|
+ # REFACTOR more current spec set hardcoding, should be abstracted?
next if Gem::Specification.include?(spec) and index != last
# TODO: make this sorta_verbose so other users can benefit from it
say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
- _, source_uri = @specs_and_sources.assoc spec
+ source = @available.source_for spec
+
begin
- local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri,
- @cache_dir
+ # REFACTOR make the fetcher to use configurable
+ local_gem_path = source.download spec, @cache_dir
rescue Gem::RemoteFetcher::FetchError
+ # TODO I doubt all fetch errors are recoverable, we should at least
+ # report the errors probably.
next if @force
raise
end
+ if @development
+ if @dev_shallow
+ is_dev = @toplevel_specs.include? spec.full_name
+ else
+ is_dev = true
+ end
+ end
+
inst = Gem::Installer.new local_gem_path,
:bin_dir => @bin_dir,
- :development => @development,
+ :development => is_dev,
:env_shebang => @env_shebang,
:force => @force,
:format_executable => @format_executable,
@@ -299,6 +360,33 @@ class Gem::DependencyInstaller
@installed_gems << spec
end
+ # Since this is currently only called for docs, we can be lazy and just say
+ # it's documentation. Ideally the hook adder could decide whether to be in
+ # the background or not, and what to call it.
+ in_background "Installing documentation" do
+ start = Time.now
+ Gem.done_installing_hooks.each do |hook|
+ hook.call self, @installed_gems
+ end
+ finish = Time.now
+ say "Done installing documentation for #{@installed_gems.map(&:name).join(', ')} (#{(finish-start).to_i} sec)."
+ end unless Gem.done_installing_hooks.empty?
+
@installed_gems
end
+
+ def in_background what
+ fork_happened = false
+ if @build_docs_in_background and Process.respond_to?(:fork)
+ begin
+ Process.fork do
+ yield
+ end
+ fork_happened = true
+ say "#{what} in a background process."
+ rescue NotImplementedError
+ end
+ end
+ yield unless fork_happened
+ end
end
diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb
index 9f1da9166c..c147efc1ca 100644
--- a/lib/rubygems/dependency_list.rb
+++ b/lib/rubygems/dependency_list.rb
@@ -10,6 +10,10 @@ require 'rubygems/deprecate'
##
# Gem::DependencyList is used for installing and uninstalling gems in the
# correct order to avoid conflicts.
+#--
+# TODO: It appears that all but topo-sort functionality is being duplicated
+# (or is planned to be duplicated) elsewhere in rubygems. Is the majority of
+# this class necessary anymore? Especially #ok?, #why_not_ok?
class Gem::DependencyList
attr_reader :specs
@@ -27,20 +31,11 @@ class Gem::DependencyList
def self.from_specs
list = new
- list.add(*Gem::Specification.map)
+ list.add(*Gem::Specification.to_a)
list
end
##
- # Creates a DependencyList from a Gem::SourceIndex +source_index+
-
- def self.from_source_index(ignored=nil)
- warn "NOTE: DependencyList.from_source_index ignores it's arg" if ignored
-
- from_specs
- end
-
- ##
# Creates a new DependencyList. If +development+ is true, development
# dependencies will be included.
@@ -143,7 +138,7 @@ class Gem::DependencyList
# If removing the gemspec creates breaks a currently ok dependency, then it
# is NOT ok to remove the gemspec.
- def ok_to_remove?(full_name)
+ def ok_to_remove?(full_name, check_dev=true)
gem_to_remove = find_name full_name
siblings = @specs.find_all { |s|
@@ -154,7 +149,9 @@ class Gem::DependencyList
deps = []
@specs.each do |spec|
- spec.dependencies.each do |dep|
+ check = check_dev ? spec.dependencies : spec.runtime_dependencies
+
+ check.each do |dep|
deps << dep if gem_to_remove.satisfies_requirement?(dep)
end
end
@@ -213,7 +210,7 @@ class Gem::DependencyList
@specs.each(&block)
end
- def tsort_each_child(node, &block)
+ def tsort_each_child(node)
specs = @specs.sort.reverse
dependencies = node.runtime_dependencies
@@ -242,11 +239,6 @@ class Gem::DependencyList
def active_count(specs, ignored)
specs.count { |spec| ignored[spec.full_name].nil? }
end
-end
-class Gem::DependencyList
- class << self
- extend Gem::Deprecate
- deprecate :from_source_index, "from_specs", 2011, 11
- end
end
+
diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/dependency_resolver.rb
new file mode 100644
index 0000000000..b841674d43
--- /dev/null
+++ b/lib/rubygems/dependency_resolver.rb
@@ -0,0 +1,562 @@
+require 'rubygems'
+require 'rubygems/dependency'
+require 'rubygems/exceptions'
+
+require 'uri'
+require 'net/http'
+
+module Gem
+
+ # Raised when a DependencyConflict reaches the toplevel.
+ # Indicates which dependencies were incompatible.
+ #
+ class DependencyResolutionError < Gem::Exception
+ def initialize(conflict)
+ @conflict = conflict
+ a, b = conflicting_dependencies
+
+ super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
+ end
+
+ attr_reader :conflict
+
+ def conflicting_dependencies
+ @conflict.conflicting_dependencies
+ end
+ end
+
+ # Raised when a dependency requests a gem for which there is
+ # no spec.
+ #
+ class UnsatisfiableDepedencyError < Gem::Exception
+ def initialize(dep)
+ super "unable to find any gem matching dependency '#{dep}'"
+
+ @dependency = dep
+ end
+
+ attr_reader :dependency
+ end
+
+ # Raised when dependencies conflict and create the inability to
+ # find a valid possible spec for a request.
+ #
+ class ImpossibleDependenciesError < Gem::Exception
+ def initialize(request, conflicts)
+ s = conflicts.size == 1 ? "" : "s"
+ super "detected #{conflicts.size} conflict#{s} with dependency '#{request.dependency}'"
+ @request = request
+ @conflicts = conflicts
+ end
+
+ def dependency
+ @request.dependency
+ end
+
+ attr_reader :conflicts
+ end
+
+ # Given a set of Gem::Dependency objects as +needed+ and a way
+ # to query the set of available specs via +set+, calculates
+ # a set of ActivationRequest objects which indicate all the specs
+ # that should be activated to meet the all the requirements.
+ #
+ class DependencyResolver
+
+ # Represents a specification retrieved via the rubygems.org
+ # API. This is used to avoid having to load the full
+ # Specification object when all we need is the name, version,
+ # and dependencies.
+ #
+ class APISpecification
+ def initialize(set, api_data)
+ @set = set
+ @name = api_data[:name]
+ @version = Gem::Version.new api_data[:number]
+ @dependencies = api_data[:dependencies].map do |name, ver|
+ Gem::Dependency.new name, ver.split(/\s*,\s*/)
+ end
+ end
+
+ attr_reader :name, :version, :dependencies
+
+ def full_name
+ "#{@name}-#{@version}"
+ end
+ end
+
+ # The global rubygems pool, available via the rubygems.org API.
+ # Returns instances of APISpecification.
+ #
+ class APISet
+ def initialize
+ @data = Hash.new { |h,k| h[k] = [] }
+ end
+
+ # Return data for all versions of the gem +name+.
+ #
+ def versions(name)
+ if @data.key?(name)
+ return @data[name]
+ end
+
+ u = URI.parse "http://rubygems.org/api/v1/dependencies?gems=#{name}"
+ str = Net::HTTP.get(u)
+
+ Marshal.load(str).each do |ver|
+ @data[ver[:name]] << ver
+ end
+
+ @data[name]
+ end
+
+ # Return an array of APISpecification objects matching
+ # DependencyRequest +req+.
+ #
+ def find_all(req)
+ res = []
+
+ versions(req.name).each do |ver|
+ if req.dependency.match? req.name, ver[:number]
+ res << APISpecification.new(self, ver)
+ end
+ end
+
+ res
+ end
+
+ # A hint run by the resolver to allow the Set to fetch
+ # data for DependencyRequests +reqs+.
+ #
+ def prefetch(reqs)
+ names = reqs.map { |r| r.dependency.name }
+ needed = names.find_all { |d| !@data.key?(d) }
+
+ return if needed.empty?
+
+ u = URI.parse "http://rubygems.org/api/v1/dependencies?gems=#{needed.join ','}"
+ str = Net::HTTP.get(u)
+
+ Marshal.load(str).each do |ver|
+ @data[ver[:name]] << ver
+ end
+ end
+ end
+
+ # Represents a possible Specification object returned
+ # from IndexSet. Used to delay needed to download full
+ # Specification objects when only the +name+ and +version+
+ # are needed.
+ #
+ class IndexSpecification
+ def initialize(set, name, version, source, plat)
+ @set = set
+ @name = name
+ @version = version
+ @source = source
+ @platform = plat
+
+ @spec = nil
+ end
+
+ attr_reader :name, :version, :source
+
+ def full_name
+ "#{@name}-#{@version}"
+ end
+
+ def spec
+ @spec ||= @set.load_spec(@name, @version, @source)
+ end
+
+ def dependencies
+ spec.dependencies
+ end
+ end
+
+ # The global rubygems pool represented via the traditional
+ # source index.
+ #
+ class IndexSet
+ def initialize
+ @f = Gem::SpecFetcher.fetcher
+
+ @all = Hash.new { |h,k| h[k] = [] }
+
+ list, _ = @f.available_specs(:released)
+ list.each do |uri, specs|
+ specs.each do |n|
+ @all[n.name] << [uri, n]
+ end
+ end
+
+ @specs = {}
+ end
+
+ # Return an array of IndexSpecification objects matching
+ # DependencyRequest +req+.
+ #
+ def find_all(req)
+ res = []
+
+ name = req.dependency.name
+
+ @all[name].each do |uri, n|
+ if req.dependency.match? n
+ res << IndexSpecification.new(self, n.name, n.version,
+ uri, n.platform)
+ end
+ end
+
+ res
+ end
+
+ # No prefetching needed since we load the whole index in
+ # initially.
+ #
+ def prefetch(gems)
+ end
+
+ # Called from IndexSpecification to get a true Specification
+ # object.
+ #
+ def load_spec(name, ver, source)
+ key = "#{name}-#{ver}"
+ @specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver))
+ end
+ end
+
+ # A set which represents the installed gems. Respects
+ # all the normal settings that control where to look
+ # for installed gems.
+ #
+ class CurrentSet
+ def find_all(req)
+ req.dependency.matching_specs
+ end
+
+ def prefetch(gems)
+ end
+ end
+
+ # Create DependencyResolver object which will resolve
+ # the tree starting with +needed+ Depedency objects.
+ #
+ # +set+ is an object that provides where to look for
+ # specifications to satisify the Dependencies. This
+ # defaults to IndexSet, which will query rubygems.org.
+ #
+ def initialize(needed, set=IndexSet.new)
+ @set = set || IndexSet.new # Allow nil to mean IndexSet
+ @needed = needed
+
+ @conflicts = nil
+ end
+
+ # Provide a DependencyResolver that queries only against
+ # the already installed gems.
+ #
+ def self.for_current_gems(needed)
+ new needed, CurrentSet.new
+ end
+
+ # Contains all the conflicts encountered while doing resolution
+ #
+ attr_reader :conflicts
+
+ # Proceed with resolution! Returns an array of ActivationRequest
+ # objects.
+ #
+ def resolve
+ @conflicts = []
+
+ needed = @needed.map { |n| DependencyRequest.new(n, nil) }
+
+ res = resolve_for needed, []
+
+ if res.kind_of? DependencyConflict
+ raise DependencyResolutionError.new(res)
+ end
+
+ res
+ end
+
+ # Used internally to indicate that a dependency conflicted
+ # with a spec that would be activated.
+ #
+ class DependencyConflict
+ def initialize(dependency, activated, failed_dep=dependency)
+ @dependency = dependency
+ @activated = activated
+ @failed_dep = failed_dep
+ end
+
+ attr_reader :dependency, :activated
+
+ # Return the Specification that listed the dependency
+ #
+ def requester
+ @failed_dep.requester
+ end
+
+ def for_spec?(spec)
+ @dependency.name == spec.name
+ end
+
+ # Return the 2 dependency objects that conflicted
+ #
+ def conflicting_dependencies
+ [@failed_dep.dependency, @activated.request.dependency]
+ end
+ end
+
+ # Used Internally. Wraps a Depedency object to also track
+ # which spec contained the Dependency.
+ #
+ class DependencyRequest
+ def initialize(dep, act)
+ @dependency = dep
+ @requester = act
+ end
+
+ attr_reader :dependency, :requester
+
+ def name
+ @dependency.name
+ end
+
+ def matches_spec?(spec)
+ @dependency.matches_spec? spec
+ end
+
+ def to_s
+ @dependency.to_s
+ end
+
+ def ==(other)
+ case other
+ when Dependency
+ @dependency == other
+ when DependencyRequest
+ @dependency == other.dep && @requester == other.requester
+ else
+ false
+ end
+ end
+ end
+
+ # Specifies a Specification object that should be activated.
+ # Also contains a dependency that was used to introduce this
+ # activation.
+ #
+ class ActivationRequest
+ def initialize(spec, req, others_possible=true)
+ @spec = spec
+ @request = req
+ @others_possible = others_possible
+ end
+
+ attr_reader :spec, :request
+
+ # Indicate if this activation is one of a set of possible
+ # requests for the same Dependency request.
+ #
+ def others_possible?
+ @others_possible
+ end
+
+ # Return the ActivationRequest that contained the dependency
+ # that we were activated for.
+ #
+ def parent
+ @request.requester
+ end
+
+ def name
+ @spec.name
+ end
+
+ def full_name
+ @spec.full_name
+ end
+
+ def version
+ @spec.version
+ end
+
+ def full_spec
+ Gem::Specification === @spec ? @spec : @spec.spec
+ end
+
+ def download(path)
+ if @spec.respond_to? :source
+ source = @spec.source
+ else
+ source = Gem.sources.first
+ end
+
+ source.download full_spec, path
+ end
+
+ def ==(other)
+ case other
+ when Gem::Specification
+ @spec == other
+ when ActivationRequest
+ @spec == other.spec && @request == other.request
+ else
+ false
+ end
+ end
+
+ ##
+ # Indicates if the requested gem has already been installed.
+
+ def installed?
+ this_spec = full_spec
+
+ Gem::Specification.any? do |s|
+ s == this_spec
+ end
+ end
+ end
+
+ def requests(s, act)
+ reqs = []
+ s.dependencies.each do |d|
+ next unless d.type == :runtime
+ reqs << DependencyRequest.new(d, act)
+ end
+
+ @set.prefetch(reqs)
+
+ reqs
+ end
+
+ # The meat of the algorithm. Given +needed+ DependencyRequest objects
+ # and +specs+ being a list to ActivationRequest, calculate a new list
+ # of ActivationRequest objects.
+ #
+ def resolve_for(needed, specs)
+ until needed.empty?
+ dep = needed.shift
+
+ # If there is already a spec activated for the requested name...
+ if existing = specs.find { |s| dep.name == s.name }
+
+ # then we're done since this new dep matches the
+ # existing spec.
+ next if dep.matches_spec? existing
+
+ # There is a conflict! We return the conflict
+ # object which will be seen by the caller and be
+ # handled at the right level.
+
+ # If the existing activation indicates that there
+ # are other possibles for it, then issue the conflict
+ # on the dep for the activation itself. Otherwise, issue
+ # it on the requester's request itself.
+ #
+ if existing.others_possible?
+ conflict = DependencyConflict.new(dep, existing)
+ else
+ depreq = existing.request.requester.request
+ conflict = DependencyConflict.new(depreq, existing, dep)
+ end
+ @conflicts << conflict
+
+ return conflict
+ end
+
+ # Get a list of all specs that satisfy dep
+ possible = @set.find_all(dep)
+
+ case possible.size
+ when 0
+ # If there are none, then our work here is done.
+ raise UnsatisfiableDepedencyError.new(dep)
+ when 1
+ # If there is one, then we just add it to specs
+ # and process the specs dependencies by adding
+ # them to needed.
+
+ spec = possible.first
+ act = ActivationRequest.new(spec, dep, false)
+
+ specs << act
+
+ # Put the deps for at the beginning of needed
+ # rather than the end to match the depth first
+ # searching done by the multiple case code below.
+ #
+ # This keeps the error messages consistent.
+ needed = requests(spec, act) + needed
+ else
+ # There are multiple specs for this dep. This is
+ # the case that this class is built to handle.
+
+ # Sort them so that we try the highest versions
+ # first.
+ possible = possible.sort_by { |s| s.version }
+
+ # We track the conflicts seen so that we can report them
+ # to help the user figure out how to fix the situation.
+ conflicts = []
+
+ # To figure out which to pick, we keep resolving
+ # given each one being activated and if there isn't
+ # a conflict, we know we've found a full set.
+ #
+ # We use an until loop rather than #reverse_each
+ # to keep the stack short since we're using a recursive
+ # algorithm.
+ #
+ until possible.empty?
+ s = possible.pop
+
+ # Recursively call #resolve_for with this spec
+ # and add it's dependencies into the picture...
+
+ act = ActivationRequest.new(s, dep)
+
+ try = requests(s, act) + needed
+
+ res = resolve_for(try, specs + [act])
+
+ # While trying to resolve these dependencies, there may
+ # be a conflict!
+
+ if res.kind_of? DependencyConflict
+ # The conflict might be created not by this invocation
+ # but rather one up the stack, so if we can't attempt
+ # to resolve this conflict (conflict isn't with the spec +s+)
+ # then just return it so the caller can try to sort it out.
+ return res unless res.for_spec? s
+
+ # Otherwise, this is a conflict that we can attempt to fix
+ conflicts << [s, res]
+
+ # Optimization:
+ #
+ # Because the conflict indicates the dependency that trigger
+ # it, we can prune possible based on this new information.
+ #
+ # This cuts down on the number of iterations needed.
+ possible.delete_if { |x| !res.dependency.matches_spec? x }
+ else
+ # No conflict, return the specs
+ return res
+ end
+ end
+
+ # We tried all possibles and nothing worked, so we let the user
+ # know and include as much information about the problem since
+ # the user is going to have to take action to fix this.
+ raise ImpossibleDependenciesError.new(dep, conflicts)
+ end
+ end
+
+ specs
+ end
+ end
+end
diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb
index a78208ec24..274d6a5c12 100644
--- a/lib/rubygems/deprecate.rb
+++ b/lib/rubygems/deprecate.rb
@@ -20,51 +20,51 @@
# end
# end
-module Gem
- module Deprecate
+module Gem::Deprecate
- def self.skip # :nodoc:
- @skip ||= false
- end
-
- def self.skip= v # :nodoc:
- @skip = v
- end
+ def self.skip # :nodoc:
+ @skip ||= false
+ end
- ##
- # Temporarily turn off warnings. Intended for tests only.
+ def self.skip= v # :nodoc:
+ @skip = v
+ end
- def skip_during
- Gem::Deprecate.skip, original = true, Gem::Deprecate.skip
- yield
- ensure
- Gem::Deprecate.skip = original
- end
+ ##
+ # Temporarily turn off warnings. Intended for tests only.
- ##
- # Simple deprecation method that deprecates +name+ by wrapping it up
- # in a dummy method. It warns on each call to the dummy method
- # telling the user of +repl+ (unless +repl+ is :none) and the
- # year/month that it is planned to go away.
+ def skip_during
+ Gem::Deprecate.skip, original = true, Gem::Deprecate.skip
+ yield
+ ensure
+ Gem::Deprecate.skip = original
+ end
- def deprecate name, repl, year, month
- class_eval {
- old = "_deprecated_#{name}"
- alias_method old, name
- define_method name do |*args, &block| # TODO: really works on 1.8.7?
- klass = self.kind_of? Module
- target = klass ? "#{self}." : "#{self.class}#"
- msg = [ "NOTE: #{target}#{name} is deprecated",
- repl == :none ? " with no replacement" : ", use #{repl}",
- ". It will be removed on or after %4d-%02d-01." % [year, month],
- "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
- ]
- warn "#{msg.join}." unless Gem::Deprecate.skip
- send old, *args, &block
- end
- }
- end
+ ##
+ # Simple deprecation method that deprecates +name+ by wrapping it up
+ # in a dummy method. It warns on each call to the dummy method
+ # telling the user of +repl+ (unless +repl+ is :none) and the
+ # year/month that it is planned to go away.
- module_function :deprecate, :skip_during
+ def deprecate name, repl, year, month
+ class_eval {
+ old = "_deprecated_#{name}"
+ alias_method old, name
+ define_method name do |*args, &block| # TODO: really works on 1.8.7?
+ klass = self.kind_of? Module
+ target = klass ? "#{self}." : "#{self.class}#"
+ msg = [ "NOTE: #{target}#{name} is deprecated",
+ repl == :none ? " with no replacement" : "; use #{repl} instead",
+ ". It will be removed on or after %4d-%02d-01." % [year, month],
+ "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
+ ]
+ warn "#{msg.join}." unless Gem::Deprecate.skip
+ send old, *args, &block
+ end
+ }
end
+
+ module_function :deprecate, :skip_during
+
end
+
diff --git a/lib/rubygems/doc_manager.rb b/lib/rubygems/doc_manager.rb
deleted file mode 100644
index 826f57d9dd..0000000000
--- a/lib/rubygems/doc_manager.rb
+++ /dev/null
@@ -1,243 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-require 'rubygems'
-
-##
-# The documentation manager generates RDoc and RI for RubyGems.
-
-class Gem::DocManager
-
- include Gem::UserInteraction
-
- @configured_args = []
-
- def self.configured_args
- @configured_args ||= []
- end
-
- def self.configured_args=(args)
- case args
- when Array
- @configured_args = args
- when String
- @configured_args = args.split
- end
- end
-
- ##
- # Load RDoc from a gem if it is available, otherwise from Ruby's stdlib
-
- def self.load_rdoc
- begin
- gem 'rdoc'
- rescue Gem::LoadError
- # use built-in RDoc
- end
-
- begin
- require 'rdoc/rdoc'
-
- @rdoc_version = if defined? RDoc::VERSION then
- Gem::Version.new RDoc::VERSION
- else
- Gem::Version.new '1.0.1' # HACK parsing is hard
- end
-
- rescue LoadError => e
- raise Gem::DocumentError,
- "ERROR: RDoc documentation generator not installed: #{e}"
- end
- end
-
- def self.rdoc_version
- @rdoc_version
- end
-
- ##
- # Updates the RI cache for RDoc 2 if it is installed
-
- def self.update_ri_cache
- load_rdoc rescue return
-
- return unless defined? RDoc::VERSION # RDoc 1 does not have VERSION
-
- require 'rdoc/ri/driver'
-
- options = {
- :use_cache => true,
- :use_system => true,
- :use_site => true,
- :use_home => true,
- :use_gems => true,
- :formatter => RDoc::RI::Formatter,
- }
-
- RDoc::RI::Driver.new(options).class_cache
- end
-
- ##
- # Create a document manager for +spec+. +rdoc_args+ contains arguments for
- # RDoc (template etc.) as a String.
-
- def initialize(spec, rdoc_args="")
- require 'fileutils'
- @spec = spec
- @doc_dir = spec.doc_dir
- @rdoc_args = rdoc_args.nil? ? [] : rdoc_args.split
- end
-
- ##
- # Is the RDoc documentation installed?
-
- def rdoc_installed?
- File.exist?(File.join(@doc_dir, "rdoc"))
- end
-
- ##
- # Is the RI documentation installed?
-
- def ri_installed?
- File.exist?(File.join(@doc_dir, "ri"))
- end
-
- ##
- # Generate the RI documents for this gem spec.
- #
- # Note that if both RI and RDoc documents are generated from the same
- # process, the RI docs should be done first (a likely bug in RDoc will cause
- # RI docs generation to fail if run after RDoc).
-
- def generate_ri
- setup_rdoc
- install_ri # RDoc bug, ri goes first
-
- FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
- end
-
- ##
- # Generate the RDoc documents for this gem spec.
- #
- # Note that if both RI and RDoc documents are generated from the same
- # process, the RI docs should be done first (a likely bug in RDoc will cause
- # RI docs generation to fail if run after RDoc).
-
- def generate_rdoc
- setup_rdoc
- install_rdoc
-
- FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
- end
-
- ##
- # Generate and install RDoc into the documentation directory
-
- def install_rdoc
- rdoc_dir = File.join @doc_dir, 'rdoc'
-
- FileUtils.rm_rf rdoc_dir
-
- say "Installing RDoc documentation for #{@spec.full_name}..."
- run_rdoc '--op', rdoc_dir
- end
-
- ##
- # Generate and install RI into the documentation directory
-
- def install_ri
- ri_dir = File.join @doc_dir, 'ri'
-
- FileUtils.rm_rf ri_dir
-
- say "Installing ri documentation for #{@spec.full_name}..."
- run_rdoc '--ri', '--op', ri_dir
- end
-
- ##
- # Run RDoc with +args+, which is an ARGV style argument list
-
- def run_rdoc(*args)
- args << @spec.rdoc_options
- args << self.class.configured_args
- args << @spec.require_paths.clone
- args << @spec.extra_rdoc_files
- args << '--title' << "#{@spec.full_name} Documentation"
- args << '--quiet'
- args = args.flatten.map do |arg| arg.to_s end
-
- if self.class.rdoc_version >= Gem::Version.new('2.4.0') then
- args.delete '--inline-source'
- args.delete '--promiscuous'
- args.delete '-p'
- args.delete '--one-file'
- # HACK more
- end
-
- debug_args = args.dup
-
- r = RDoc::RDoc.new
-
- old_pwd = Dir.pwd
- Dir.chdir @spec.full_gem_path
-
- say "rdoc #{args.join ' '}" if Gem.configuration.really_verbose
-
- begin
- r.document args
- rescue Errno::EACCES => e
- dirname = File.dirname e.message.split("-")[1].strip
- raise Gem::FilePermissionError.new(dirname)
- rescue Interrupt => e
- raise e
- rescue Exception => ex
- alert_error "While generating documentation for #{@spec.full_name}"
- ui.errs.puts "... MESSAGE: #{ex}"
- ui.errs.puts "... RDOC args: #{debug_args.join(' ')}"
- ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if
- Gem.configuration.backtrace
- terminate_interaction 1
- ensure
- Dir.chdir old_pwd
- end
- end
-
- def setup_rdoc
- if File.exist?(@doc_dir) && !File.writable?(@doc_dir) then
- raise Gem::FilePermissionError.new(@doc_dir)
- end
-
- FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
-
- self.class.load_rdoc
- end
-
- ##
- # Remove RDoc and RI documentation
-
- def uninstall_doc
- base_dir = @spec.base_dir
- raise Gem::FilePermissionError.new base_dir unless File.writable? base_dir
-
- # TODO: ok... that's twice... ugh
- old_name = [
- @spec.name, @spec.version, @spec.original_platform].join '-'
-
- doc_dir = @spec.doc_dir
- unless File.directory? doc_dir then
- doc_dir = File.join File.dirname(doc_dir), old_name
- end
-
- ri_dir = @spec.ri_dir
- unless File.directory? ri_dir then
- ri_dir = File.join File.dirname(ri_dir), old_name
- end
-
- FileUtils.rm_rf doc_dir
- FileUtils.rm_rf ri_dir
- end
-
-end
-
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index 950b34d744..29e732cb1d 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -1,35 +1,88 @@
-class Gem::ErrorReason; end
-
-# Generated when trying to lookup a gem to indicate that the gem
-# was found, but that it isn't usable on the current platform.
-#
-# fetch and install read these and report them to the user to aid
-# in figuring out why a gem couldn't be installed.
+##
+# This file contains all the various exceptions and other errors that are used
+# inside of RubyGems.
#
-class Gem::PlatformMismatch < Gem::ErrorReason
+# DOC: Confirm _all_
- attr_reader :name
- attr_reader :version
- attr_reader :platforms
+module Gem
+ ##
+ # Raised when RubyGems is unable to load or activate a gem. Contains the
+ # name and version requirements of the gem that either conflicts with
+ # already activated gems or that RubyGems is otherwise unable to activate.
- def initialize(name, version)
- @name = name
- @version = version
- @platforms = []
- end
+ class LoadError < ::LoadError
+ # Name of gem
+ attr_accessor :name
- def add_platform(platform)
- @platforms << platform
+ # Version requirement of gem
+ attr_accessor :requirement
end
- def wordy
- prefix = "Found #{@name} (#{@version})"
+ # FIX: does this need to exist? The subclass is the only other reference
+ # I can find.
+ class ErrorReason; end
+
+ # Generated when trying to lookup a gem to indicate that the gem
+ # was found, but that it isn't usable on the current platform.
+ #
+ # fetch and install read these and report them to the user to aid
+ # in figuring out why a gem couldn't be installed.
+ #
+ class PlatformMismatch < ErrorReason
+
+ ##
+ # the name of the gem
+ attr_reader :name
+
+ ##
+ # the version
+ attr_reader :version
+
+ ##
+ # The platforms that are mismatched
+ attr_reader :platforms
+
+ def initialize(name, version)
+ @name = name
+ @version = version
+ @platforms = []
+ end
- if @platforms.size == 1
- "#{prefix}, but was for platform #{@platforms[0]}"
- else
- "#{prefix}, but was for platforms #{@platforms.join(' ,')}"
+ ##
+ # append a platform to the list of mismatched platforms.
+ #
+ # Platforms are added via this instead of injected via the constructor
+ # so that we can loop over a list of mismatches and just add them rather
+ # than perform some kind of calculation mismatch summary before creation.
+ def add_platform(platform)
+ @platforms << platform
+ end
+
+ ##
+ # A wordy description of the error.
+ def wordy
+ "Found %s (%), but was for platform%s %s" %
+ [@name,
+ @version,
+ @platforms.size == 1 ? 's' : '',
+ @platforms.join(' ,')]
end
end
+ ##
+ # An error that indicates we weren't able to fetch some
+ # data from a source
+
+ class SourceFetchProblem < ErrorReason
+ def initialize(source, error)
+ @source = source
+ @error = error
+ end
+
+ attr_reader :source, :error
+
+ def wordy
+ "Unable to download data from #{@source.uri} - #{@error.message}"
+ end
+ end
end
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index 55d67f9125..ff389b320b 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -1,7 +1,14 @@
+# TODO: the documentation in here is terrible.
+#
+# Each exception needs a brief description and the scenarios where it is
+# likely to be raised
+
##
# Base exception class for RubyGems. All exception raised by RubyGems are a
# subclass of this one.
-class Gem::Exception < RuntimeError; end
+class Gem::Exception < RuntimeError
+ attr_accessor :source_exception
+end
class Gem::CommandLineError < Gem::Exception; end
@@ -24,11 +31,18 @@ class Gem::EndOfYAMLException < Gem::Exception; end
##
# Signals that a file permission error is preventing the user from
-# installing in the requested directories.
+# operating on the given directory.
+
class Gem::FilePermissionError < Gem::Exception
- def initialize(path)
- super("You don't have write permissions into the #{path} directory.")
+
+ attr_reader :directory
+
+ def initialize directory
+ @directory = directory
+
+ super "You don't have write permissions for the #{directory} directory."
end
+
end
##
@@ -37,9 +51,12 @@ class Gem::FormatException < Gem::Exception
attr_accessor :file_path
end
-class Gem::GemNotFoundException < Gem::Exception
- def initialize(msg, name=nil, version=nil, errors=nil)
- super msg
+class Gem::GemNotFoundException < Gem::Exception; end
+
+class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException
+ def initialize(name, version, errors=nil)
+ super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository"
+
@name = name
@version = version
@errors = errors
@@ -89,3 +106,4 @@ class Gem::SystemExitException < SystemExit
end
end
+
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 5e518962ce..e0e7387d9c 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -16,7 +16,7 @@ class Gem::Ext::Builder
raise Gem::InstallError, "Makefile not found:\n\n#{results.join "\n"}"
end
- mf = File.read('Makefile')
+ mf = Gem.read_binary 'Makefile'
mf = mf.gsub(/^RUBYARCHDIR\s*=\s*\$[^$]*/, "RUBYARCHDIR = #{dest_path}")
mf = mf.gsub(/^RUBYLIBDIR\s*=\s*\$[^$]*/, "RUBYLIBDIR = #{dest_path}")
@@ -24,18 +24,14 @@ class Gem::Ext::Builder
# try to find make program from Ruby configure arguments first
RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
- make_program = $1 || ENV['make']
+ make_program = $1 || ENV['MAKE'] || ENV['make']
unless make_program then
make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end
['', ' install'].each do |target|
cmd = "#{make_program}#{target}"
- results << cmd
- results << `#{cmd} #{redirector}`
-
- raise Gem::InstallError, "make#{target} failed:\n\n#{results}" unless
- $?.success?
+ run(cmd, results, "make#{target}")
end
end
@@ -43,12 +39,20 @@ class Gem::Ext::Builder
'2>&1'
end
- def self.run(command, results)
- results << command
- results << `#{command} #{redirector}`
+ def self.run(command, results, command_name = nil)
+ verbose = Gem.configuration.really_verbose
+
+ if verbose
+ puts(command)
+ system(command)
+ else
+ results << command
+ results << `#{command} #{redirector}`
+ end
unless $?.success? then
- raise Gem::InstallError, "#{class_name} failed:\n\n#{results.join "\n"}"
+ results << "Building has failed. See above output for more information on the failure." if verbose
+ raise Gem::InstallError, "#{command_name || class_name} failed:\n\n#{results.join "\n"}"
end
end
diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb
index c2087eb5ad..ef8b29e427 100644
--- a/lib/rubygems/ext/configure_builder.rb
+++ b/lib/rubygems/ext/configure_builder.rb
@@ -8,10 +8,10 @@ require 'rubygems/ext/builder'
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results)
+ def self.build(extension, directory, dest_path, results, args=[])
unless File.exist?('Makefile') then
cmd = "sh ./configure --prefix=#{dest_path}"
- cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty?
+ cmd << " #{args.join ' '}" unless args.empty?
run cmd, results
end
diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb
index b3d588dc9c..7ca322d3e5 100644
--- a/lib/rubygems/ext/ext_conf_builder.rb
+++ b/lib/rubygems/ext/ext_conf_builder.rb
@@ -9,9 +9,9 @@ require 'rubygems/command'
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results)
+ def self.build(extension, directory, dest_path, results, args=[])
cmd = "#{Gem.ruby} #{File.basename extension}"
- cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty?
+ cmd << " #{args.join ' '}" unless args.empty?
run cmd, results
diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb
index a1df694366..2ac6edd5c8 100644
--- a/lib/rubygems/ext/rake_builder.rb
+++ b/lib/rubygems/ext/rake_builder.rb
@@ -9,10 +9,10 @@ require 'rubygems/command'
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
- def self.build(extension, directory, dest_path, results)
+ def self.build(extension, directory, dest_path, results, args=[])
if File.basename(extension) =~ /mkrf_conf/i then
cmd = "#{Gem.ruby} #{File.basename extension}"
- cmd << " #{Gem::Command.build_args.join " "}" unless Gem::Command.build_args.empty?
+ cmd << " #{args.join " "}" unless args.empty?
run cmd, results
end
diff --git a/lib/rubygems/format.rb b/lib/rubygems/format.rb
deleted file mode 100644
index 9644f6ab8e..0000000000
--- a/lib/rubygems/format.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-require 'rubygems/package'
-
-##
-# Gem::Format knows the guts of the RubyGem .gem file format and provides the
-# capability to read gem files
-
-class Gem::Format
-
- attr_accessor :spec
- attr_accessor :file_entries
- attr_accessor :gem_path
-
- ##
- # Constructs a Format representing the gem's data which came from +gem_path+
-
- def initialize(gem_path)
- @gem_path = gem_path
- end
-
- ##
- # Reads the gem +file_path+ using +security_policy+ and returns a Format
- # representing the data in the gem
-
- def self.from_file_by_path(file_path, security_policy = nil)
- unless File.file?(file_path)
- raise Gem::Exception, "Cannot load gem at [#{file_path}] in #{Dir.pwd}"
- end
-
- start = File.read file_path, 20
-
- if start.nil? or start.length < 20 then
- nil
- elsif start.include?("MD5SUM =") # old version gems
- require 'rubygems/old_format'
-
- Gem::OldFormat.from_file_by_path file_path
- else
- begin
- open file_path, Gem.binary_mode do |io|
- from_io io, file_path, security_policy
- end
- rescue Gem::Package::TarInvalidError => e
- message = "corrupt gem (#{e.class}: #{e.message})"
- raise Gem::Package::FormatError.new(message, file_path)
- end
- end
- end
-
- ##
- # Reads a gem from +io+ at +gem_path+ using +security_policy+ and returns a
- # Format representing the data from the gem
-
- def self.from_io(io, gem_path="(io)", security_policy = nil)
- format = new gem_path
-
- Gem::Package.open io, 'r', security_policy do |pkg|
- format.spec = pkg.metadata
- format.file_entries = []
-
- pkg.each do |entry|
- size = entry.header.size
- mode = entry.header.mode
-
- format.file_entries << [{
- "size" => size, "mode" => mode, "path" => entry.full_name,
- },
- entry.read
- ]
- end
- end
-
- format
- end
-
-end
-
diff --git a/lib/rubygems/gem_openssl.rb b/lib/rubygems/gem_openssl.rb
deleted file mode 100644
index 682058f2c1..0000000000
--- a/lib/rubygems/gem_openssl.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-#--
-# Some system might not have OpenSSL installed, therefore the core
-# library file openssl might not be available. We localize testing
-# for the presence of OpenSSL in this file.
-#++
-
-module Gem
- class << self
- ##
- # Is SSL (used by the signing commands) available on this
- # platform?
-
- def ssl_available?
- @ssl_available
- end
-
- ##
- # Is SSL available?
-
- attr_writer :ssl_available
-
- ##
- # Ensure that SSL is available. Throw an exception if it is not.
-
- def ensure_ssl_available
- unless ssl_available?
- raise Gem::Exception, "SSL is not installed on this system"
- end
- end
- end
-end
-
-# :stopdoc:
-
-begin
- require 'openssl'
-
- # Reference a constant defined in the .rb portion of ssl (just to
- # make sure that part is loaded too).
-
- Gem.ssl_available = !!OpenSSL::Digest::SHA1
-
- class OpenSSL::X509::Certificate
- # Check the validity of this certificate.
- def check_validity(issuer_cert = nil, time = Time.now)
- ret = if @not_before && @not_before > time
- [false, :expired, "not valid before '#@not_before'"]
- elsif @not_after && @not_after < time
- [false, :expired, "not valid after '#@not_after'"]
- elsif issuer_cert && !verify(issuer_cert.public_key)
- [false, :issuer, "#{issuer_cert.subject} is not issuer"]
- else
- [true, :ok, 'Valid certificate']
- end
-
- # return hash
- { :is_valid => ret[0], :error => ret[1], :desc => ret[2] }
- end
- end
-
-rescue LoadError, StandardError
- Gem.ssl_available = false
-end
-
-module Gem::SSL
-
- # We make our own versions of the constants here. This allows us
- # to reference the constants, even though some systems might not
- # have SSL installed in the Ruby core package.
- #
- # These constants are only used during load time. At runtime, any
- # method that makes a direct reference to SSL software must be
- # protected with a Gem.ensure_ssl_available call.
-
- if Gem.ssl_available? then
- PKEY_RSA = OpenSSL::PKey::RSA
- DIGEST_SHA1 = OpenSSL::Digest::SHA1
- else
- PKEY_RSA = :rsa
- DIGEST_SHA1 = :sha1
- end
-
-end
-
diff --git a/lib/rubygems/gem_path_searcher.rb b/lib/rubygems/gem_path_searcher.rb
deleted file mode 100644
index 814b5fb0e5..0000000000
--- a/lib/rubygems/gem_path_searcher.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-require "rubygems"
-require "rubygems/deprecate"
-
-##
-# GemPathSearcher has the capability to find loadable files inside
-# gems. It generates data up front to speed up searches later.
-
-class Gem::GemPathSearcher
-
- ##
- # Initialise the data we need to make searches later.
-
- def initialize
- # We want a record of all the installed gemspecs, in the order we wish to
- # examine them.
- # TODO: remove this stupid method
- @gemspecs = init_gemspecs
-
- # Map gem spec to glob of full require_path directories. Preparing this
- # information may speed up searches later.
- @lib_dirs = {}
-
- @gemspecs.each do |spec|
- @lib_dirs[spec.object_id] = lib_dirs_for spec
- end
- end
-
- ##
- # Look in all the installed gems until a matching +glob+ is found.
- # Return the _gemspec_ of the gem where it was found. If no match
- # is found, return nil.
- #
- # The gems are searched in alphabetical order, and in reverse
- # version order.
- #
- # For example:
- #
- # find('log4r') # -> (log4r-1.1 spec)
- # find('log4r.rb') # -> (log4r-1.1 spec)
- # find('rake/rdoctask') # -> (rake-0.4.12 spec)
- # find('foobarbaz') # -> nil
- #
- # Matching paths can have various suffixes ('.rb', '.so', and
- # others), which may or may not already be attached to _file_.
- # This method doesn't care about the full filename that matches;
- # only that there is a match.
-
- def find(glob)
- # HACK violation of encapsulation
- @gemspecs.find do |spec|
- # TODO: inverted responsibility
- matching_file? spec, glob
- end
- end
-
- # Looks through the available gemspecs and finds the first
- # one that contains +file+ as a requirable file.
-
- def find_spec_for_file(file)
- @gemspecs.find do |spec|
- return spec if spec.contains_requirable_file?(file)
- end
- end
-
- def find_active(glob)
- # HACK violation of encapsulation
- @gemspecs.find do |spec|
- # TODO: inverted responsibility
- spec.loaded? and matching_file? spec, glob
- end
- end
-
- ##
- # Works like #find, but finds all gemspecs matching +glob+.
-
- def find_all(glob)
- # HACK violation of encapsulation
- @gemspecs.select do |spec|
- # TODO: inverted responsibility
- matching_file? spec, glob
- end || []
- end
-
- def find_in_unresolved(glob)
- # HACK violation
- specs = Gem.unresolved_deps.values.map { |dep|
- Gem.source_index.search dep, true
- }.flatten
-
- specs.select do |spec|
- # TODO: inverted responsibility
- matching_file? spec, glob
- end || []
- end
-
- def find_in_unresolved_tree glob
- # HACK violation
- # TODO: inverted responsibility
- specs = Gem.unresolved_deps.values.map { |dep|
- Gem.source_index.search dep, true
- }.flatten
-
- specs.reverse_each do |spec|
- trails = matching_paths(spec, glob)
- next if trails.empty?
- return trails.map(&:reverse).sort.first.reverse
- end
-
- []
- end
-
- ##
- # Attempts to find a matching path using the require_paths of the given
- # +spec+.
-
- def matching_file?(spec, path)
- not matching_files(spec, path).empty?
- end
-
- def matching_paths(spec, path)
- trails = []
-
- spec.traverse do |from_spec, dep, to_spec, trail|
- next unless to_spec.conflicts.empty?
- trails << trail unless matching_files(to_spec, path).empty?
- end
-
- trails
- end
-
- ##
- # Returns files matching +path+ in +spec+.
- #--
- # Some of the intermediate results are cached in @lib_dirs for speed.
-
- def matching_files(spec, path)
- return [] unless @lib_dirs[spec.object_id] # case no paths
- glob = File.join @lib_dirs[spec.object_id], "#{path}#{Gem.suffix_pattern}"
- Dir[glob].select { |f| File.file? f.untaint }
- end
-
- ##
- # Return a list of all installed gemspecs, sorted by alphabetical order and
- # in reverse version order. (bar-2, bar-1, foo-2)
-
- def init_gemspecs
- Gem::Specification.sort { |a, b|
- names = a.name <=> b.name
- next names if names.nonzero?
- b.version <=> a.version
- }
- end
-
- ##
- # Returns library directories glob for a gemspec. For example,
- # '/usr/local/lib/ruby/gems/1.8/gems/foobar-1.0/{lib,ext}'
-
- def lib_dirs_for(spec)
- "#{spec.full_gem_path}/{#{spec.require_paths.join(',')}}" if
- spec.require_paths
- end
-
- extend Gem::Deprecate
-
- deprecate :initialize, :none, 2011, 10
- deprecate :find, :none, 2011, 10
- deprecate :find_active, :none, 2011, 10
- deprecate :find_all, :none, 2011, 10
- deprecate :find_in_unresolved, :none, 2011, 10
- deprecate :find_in_unresolved_tree, :none, 2011, 10
- deprecate :find_spec_for_file, :none, 2011, 10
-end
diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb
index 6197036f81..8060e15312 100644
--- a/lib/rubygems/gem_runner.rb
+++ b/lib/rubygems/gem_runner.rb
@@ -4,10 +4,9 @@
# See LICENSE.txt for permissions.
#++
-require "rubygems"
+require 'rubygems'
require 'rubygems/command_manager'
require 'rubygems/config_file'
-require 'rubygems/doc_manager'
##
# Load additional plugins from $LOAD_PATH
@@ -29,25 +28,21 @@ class Gem::GemRunner
# TODO: nuke these options
@command_manager_class = options[:command_manager] || Gem::CommandManager
@config_file_class = options[:config_file] || Gem::ConfigFile
- @doc_manager_class = options[:doc_manager] || Gem::DocManager
end
##
# Run the gem command with the following arguments.
def run(args)
- start_time = Time.now
-
if args.include?('--')
# We need to preserve the original ARGV to use for passing gem options
# to source gems. If there is a -- in the line, strip all options after
# it...its for the source building process.
+ # TODO use slice!
build_args = args[args.index("--") + 1...args.length]
args = args[0...args.index("--")]
end
- Gem::Command.build_args = build_args if build_args
-
do_configuration args
cmd = @command_manager_class.instance
@@ -62,14 +57,7 @@ class Gem::GemRunner
Gem::Command.add_specific_extra_args command_name, config_args
end
- cmd.run Gem.configuration.args
- end_time = Time.now
-
- if Gem.configuration.benchmark then
- printf "\nExecution time: %0.2f seconds.\n", end_time - start_time
- puts "Press Enter to finish"
- STDIN.gets
- end
+ cmd.run Gem.configuration.args, build_args
end
private
@@ -78,7 +66,6 @@ class Gem::GemRunner
Gem.configuration = @config_file_class.new(args)
Gem.use_paths Gem.configuration[:gemhome], Gem.configuration[:gempath]
Gem::Command.extra_args = Gem.configuration[:gem]
- @doc_manager_class.configured_args = Gem.configuration[:rdoc]
end
end
diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb
index c0e7ee99e9..3042092125 100644
--- a/lib/rubygems/gemcutter_utilities.rb
+++ b/lib/rubygems/gemcutter_utilities.rb
@@ -1,6 +1,7 @@
require 'rubygems/remote_fetcher'
module Gem::GemcutterUtilities
+ # TODO: move to Gem::Command
OptionParser.accept Symbol do |value|
value.to_sym
end
@@ -19,6 +20,8 @@ module Gem::GemcutterUtilities
def api_key
if options[:key] then
verify_api_key options[:key]
+ elsif Gem.configuration.api_keys.key?(host)
+ Gem.configuration.api_keys[host]
else
Gem.configuration.rubygems_api_key
end
@@ -44,12 +47,24 @@ module Gem::GemcutterUtilities
end
end
- def rubygems_api_request(method, path, host = Gem.host, &block)
+ attr_writer :host
+ def host
+ configured_host = Gem.host unless
+ Gem.configuration.disable_default_gem_server
+
+ @host ||= ENV['RUBYGEMS_HOST'] || configured_host
+ end
+
+ def rubygems_api_request(method, path, host = nil, &block)
require 'net/http'
- host = ENV['RUBYGEMS_HOST'] if ENV['RUBYGEMS_HOST']
- uri = URI.parse "#{host}/#{path}"
- say "Pushing gem to #{host}..."
+ self.host = host if host
+ unless self.host
+ alert_error "You must specify a gem server"
+ terminate_interaction 1 # TODO: question this
+ end
+
+ uri = URI.parse "#{self.host}/#{path}"
request_method = Net::HTTP.const_get method.to_s.capitalize
@@ -66,7 +81,7 @@ module Gem::GemcutterUtilities
end
else
say resp.body
- terminate_interaction 1
+ terminate_interaction 1 # TODO: question this
end
end
@@ -74,8 +89,8 @@ module Gem::GemcutterUtilities
if Gem.configuration.api_keys.key? key then
Gem.configuration.api_keys[key]
else
- alert_error "No such API key. You can add it with gem keys --add #{key}"
- terminate_interaction 1
+ alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)."
+ terminate_interaction 1 # TODO: question this
end
end
diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb
index e87e5a3632..77282f27ef 100644
--- a/lib/rubygems/indexer.rb
+++ b/lib/rubygems/indexer.rb
@@ -1,5 +1,5 @@
require 'rubygems'
-require 'rubygems/format'
+require 'rubygems/package'
require 'time'
begin
@@ -16,11 +16,6 @@ class Gem::Indexer
include Gem::UserInteraction
##
- # Build indexes for RubyGems older than 1.2.0 when true
-
- attr_accessor :build_legacy
-
- ##
# Build indexes for RubyGems 1.2.0 and newer when true
attr_accessor :build_modern
@@ -63,15 +58,10 @@ class Gem::Indexer
"\n\tgem install builder"
end
- options = { :build_legacy => true, :build_modern => true }.merge options
+ options = { :build_modern => true }.merge options
- @build_legacy = options[:build_legacy]
@build_modern = options[:build_modern]
- @rss_title = options[:rss_title]
- @rss_host = options[:rss_host]
- @rss_gems_host = options[:rss_gems_host]
-
@dest_directory = directory
@directory = File.join(Dir.tmpdir, "gem_generate_index_#{$$}")
@@ -99,8 +89,6 @@ class Gem::Indexer
@dest_prerelease_specs_index =
File.join(@dest_directory, "prerelease_specs.#{Gem.marshal_version}")
- @rss_index = File.join @directory, 'index.rss'
-
@files = []
end
@@ -109,6 +97,8 @@ class Gem::Indexer
# 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.
+ #--
+ # TODO move to Gem::Specification
def abbreviate(spec)
spec.files = []
@@ -123,38 +113,16 @@ class Gem::Indexer
# Build various indicies
def build_indicies
- # Marshal gemspecs are used by both modern and legacy RubyGems
-
Gem::Specification.dirs = []
Gem::Specification.add_specs(*map_gems_to_specs(gem_file_list))
build_marshal_gemspecs
- build_legacy_indicies if @build_legacy
build_modern_indicies if @build_modern
- build_rss
compress_indicies
end
##
- # Builds indicies for RubyGems older than 1.2.x
-
- def build_legacy_indicies
- index = collect_specs
-
- say "Generating Marshal master index"
-
- Gem.time 'Generated Marshal master index' do
- open @marshal_index, 'wb' do |io|
- io.write index.dump
- end
- end
-
- @files << @marshal_index
- @files << "#{@marshal_index}.Z"
- end
-
- ##
# Builds Marshal quick index gemspecs.
def build_marshal_gemspecs
@@ -238,104 +206,6 @@ class Gem::Indexer
"#{@prerelease_specs_index}.gz"]
end
- ##
- # Builds an RSS feed for past two days gem releases according to the gem's
- # date.
-
- def build_rss
- if @rss_host.nil? or @rss_gems_host.nil? then
- if Gem.configuration.really_verbose then
- alert_warning "no --rss-host or --rss-gems-host, RSS generation disabled"
- end
- return
- end
-
- require 'cgi'
- require 'rubygems/text'
-
- extend Gem::Text
-
- Gem.time 'Generated rss' do
- open @rss_index, 'wb' do |io|
- rss_host = CGI.escapeHTML @rss_host
- rss_title = CGI.escapeHTML(@rss_title || 'gems')
-
- io.puts <<-HEADER
-<?xml version="1.0"?>
-<rss version="2.0">
- <channel>
- <title>#{rss_title}</title>
- <link>http://#{rss_host}</link>
- <description>Recently released gems from http://#{rss_host}</description>
- <generator>RubyGems v#{Gem::VERSION}</generator>
- <docs>http://cyber.law.harvard.edu/rss/rss.html</docs>
- HEADER
-
- today = Gem::Specification::TODAY
- yesterday = today - 86400
-
- index = Gem::Specification.select do |spec|
- spec_date = spec.date
- # TODO: remove this and make YAML based specs properly normalized
- spec_date = Time.parse(spec_date.to_s) if Date === spec_date
-
- spec_date >= yesterday && spec_date <= today
- end
-
- index.sort_by { |spec| [-spec.date.to_i, spec] }.each do |spec|
- file_name = File.basename spec.cache_file
- gem_path = CGI.escapeHTML "http://#{@rss_gems_host}/gems/#{file_name}"
- size = File.stat(spec.loaded_from).size # rescue next
-
- description = spec.description || spec.summary || ''
- authors = Array spec.authors
- emails = Array spec.email
- authors = emails.zip(authors).map do |email, author|
- email += " (#{author})" if author and not author.empty?
- end.join ', '
-
- description = description.split(/\n\n+/).map do |chunk|
- format_text chunk, 78
- end
-
- description = description.join "\n\n"
-
- item = ''
-
- item << <<-ITEM
- <item>
- <title>#{CGI.escapeHTML spec.full_name}</title>
- <description>
-&lt;pre&gt;#{CGI.escapeHTML description.chomp}&lt;/pre&gt;
- </description>
- <author>#{CGI.escapeHTML authors}</author>
- <guid>#{CGI.escapeHTML spec.full_name}</guid>
- <enclosure url=\"#{gem_path}\"
- length=\"#{size}\" type=\"application/octet-stream\" />
- <pubDate>#{spec.date.rfc2822}</pubDate>
- ITEM
-
- item << <<-ITEM if spec.homepage
- <link>#{CGI.escapeHTML spec.homepage}</link>
- ITEM
-
- item << <<-ITEM
- </item>
- ITEM
-
- io.puts item
- end
-
- io.puts <<-FOOTER
- </channel>
-</rss>
- FOOTER
- end
- end
-
- @files << @rss_index
- end
-
def map_gems_to_specs gems
gems.map { |gemfile|
if File.size(gemfile) == 0 then
@@ -344,7 +214,7 @@ class Gem::Indexer
end
begin
- spec = Gem::Format.from_file_by_path(gemfile).spec
+ spec = Gem::Package.new(gemfile).spec
spec.loaded_from = gemfile
# HACK: fuck this shit - borks all tests that use pl1
@@ -374,21 +244,6 @@ class Gem::Indexer
end
##
- # Collect specifications from .gem files from the gem directory.
-
- def collect_specs(gems = gem_file_list)
- Gem::Deprecate.skip_during do
- index = Gem::SourceIndex.new
-
- map_gems_to_specs(gems).each do |spec|
- index.add_spec spec, spec.original_name
- end
-
- index
- end
- end
-
- ##
# Compresses indicies on disk
#--
# All future files should be compressed using gzip, not deflate
@@ -397,11 +252,6 @@ class Gem::Indexer
say "Compressing indicies"
Gem.time 'Compressed indicies' do
- if @build_legacy then
- compress @marshal_index, 'Z'
- paranoid @marshal_index, 'Z'
- end
-
if @build_modern then
gzip @specs_index
gzip @latest_specs_index
@@ -559,12 +409,9 @@ class Gem::Indexer
end
##
- # Perform an in-place update of the repository from newly added gems. Only
- # works for modern indicies, and sets #build_legacy to false when run.
+ # Perform an in-place update of the repository from newly added gems.
def update_index
- @build_legacy = false
-
make_temp_directories
specs_mtime = File.stat(@dest_specs_index).mtime
@@ -584,6 +431,9 @@ class Gem::Indexer
specs = map_gems_to_specs updated_gems
prerelease, released = specs.partition { |s| s.version.prerelease? }
+ Gem::Specification.dirs = []
+ Gem::Specification.add_specs(*specs)
+
files = build_marshal_gemspecs
Gem.time 'Updated indexes' do
diff --git a/lib/rubygems/install_message.rb b/lib/rubygems/install_message.rb
new file mode 100644
index 0000000000..c1979c1549
--- /dev/null
+++ b/lib/rubygems/install_message.rb
@@ -0,0 +1,12 @@
+require 'rubygems'
+require 'rubygems/user_interaction'
+
+##
+# A default post-install hook that displays "Successfully installed
+# some_gem-1.0"
+
+Gem.post_install do |installer|
+ ui = Gem::DefaultUserInteraction.ui
+ ui.say "Successfully installed #{installer.spec.full_name}"
+end
+
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index 3ee6432b4c..ffa8f910df 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -22,6 +22,7 @@ module Gem::InstallUpdateOptions
# Add the install/update options to the option parser.
def add_install_update_options
+ # TODO: use @parser.accept
OptionParser.accept Gem::Security::Policy do |value|
require 'rubygems/security'
@@ -39,21 +40,49 @@ module Gem::InstallUpdateOptions
end
add_option(:"Install/Update", '-n', '--bindir DIR',
- 'Directory where binary files are',
- 'located') do |value, options|
+ 'Directory where binary files are',
+ 'located') do |value, options|
options[:bin_dir] = File.expand_path(value)
end
- add_option(:"Install/Update", '-d', '--[no-]rdoc',
- 'Generate RDoc documentation for the gem on',
- 'install') do |value, options|
- options[:generate_rdoc] = value
+ add_option(:"Install/Update", '--[no-]document [TYPES]', Array,
+ 'Generate documentation for installed gems',
+ 'List the documentation types you wish to',
+ 'generate. For example: rdoc,ri') do |value, options|
+ options[:document] = case value
+ when nil then %w[ri]
+ when false then []
+ else value
+ end
end
- add_option(:"Install/Update", '--[no-]ri',
- 'Generate RI documentation for the gem on',
- 'install') do |value, options|
- options[:generate_ri] = value
+ add_option(:"Install/Update", '-N', '--no-document',
+ 'Disable documentation generation') do |value, options|
+ options[:document] = []
+ end
+
+ add_option(:Deprecated, '--[no-]rdoc',
+ 'Generate RDoc for installed gems',
+ 'Use --document instead') do |value, options|
+ if value then
+ options[:document] << 'rdoc'
+ else
+ options[:document].delete 'rdoc'
+ end
+
+ options[:document].uniq!
+ end
+
+ add_option(:Deprecated, '--[no-]ri',
+ 'Generate ri data for installed gems.',
+ 'Use --document instead') do |value, options|
+ if value then
+ options[:document] << 'ri'
+ else
+ options[:document].delete 'ri'
+ end
+
+ options[:document].uniq!
end
add_option(:"Install/Update", '-E', '--[no-]env-shebang',
@@ -85,12 +114,6 @@ module Gem::InstallUpdateOptions
options[:ignore_dependencies] = value
end
- add_option(:"Install/Update", '-y', '--include-dependencies',
- 'Unconditionally install the required',
- 'dependent gems') do |value, options|
- options[:include_dependencies] = value
- end
-
add_option(:"Install/Update", '--[no-]format-executable',
'Make installed executable names match ruby.',
'If ruby is ruby18, foo_exec will be',
@@ -105,15 +128,30 @@ module Gem::InstallUpdateOptions
end
add_option(:"Install/Update", "--development",
- "Install any additional development",
+ "Install additional development",
"dependencies") do |value, options|
options[:development] = true
+ options[:dev_shallow] = true
+ end
+
+ add_option(:"Install/Update", "--development-all",
+ "Install development dependencies for all",
+ "gems (including dev deps themselves)") do |value, options|
+ options[:development] = true
+ options[:dev_shallow] = false
end
add_option(:"Install/Update", "--conservative",
"Don't attempt to upgrade gems already",
"meeting version requirement") do |value, options|
options[:conservative] = true
+ options[:minimal_deps] = true
+ end
+
+ add_option(:"Install/Update", "--minimal-deps",
+ "Don't upgrade any dependencies that already",
+ "meet version requirements") do |value, options|
+ options[:minimal_deps] = true
end
end
@@ -121,7 +159,7 @@ module Gem::InstallUpdateOptions
# Default options for the gem install command.
def install_update_defaults_str
- '--rdoc --no-force --wrappers'
+ '--document=rdoc,ri --wrappers'
end
end
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 514316f099..2b7c821727 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -4,15 +4,13 @@
# See LICENSE.txt for permissions.
#++
-require 'rubygems/format'
require 'rubygems/exceptions'
+require 'rubygems/package'
require 'rubygems/ext'
-require 'rubygems/require_paths_builder'
require 'rubygems/user_interaction'
##
-# The installer class processes RubyGem .gem files and installs the files
-# contained in the .gem into the Gem.path.
+# The installer installs the files contained in the .gem into the Gem.home.
#
# Gem::Installer does the work of putting files in all the right places on the
# filesystem including unpacking the gem into its gem dir, installing the
@@ -39,8 +37,7 @@ class Gem::Installer
include Gem::UserInteraction
- include Gem::RequirePathsBuilder if Gem::QUICKLOADER_SUCKAGE
-
+ # DOC: Missing docs or :nodoc:.
attr_reader :gem
##
@@ -67,6 +64,7 @@ class Gem::Installer
attr_accessor :path_warning
+ # DOC: Missing docs or :nodoc:.
attr_writer :exec_format
# Defaults to use Ruby's program prefix and suffix.
@@ -80,24 +78,36 @@ class Gem::Installer
# Constructs an Installer instance that will install the gem located at
# +gem+. +options+ is a Hash with the following keys:
#
+ # :bin_dir:: Where to put a bin wrapper if needed.
+ # :development:: Whether or not development dependencies should be installed.
# :env_shebang:: Use /usr/bin/env in bin wrappers.
# :force:: Overrides all version checks and security policy checks, except
# for a signed-gems-only policy.
- # :ignore_dependencies:: Don't raise if a dependency is missing.
- # :install_dir:: The directory to install the gem into.
# :format_executable:: Format the executable the same as the ruby executable.
# If your ruby is ruby18, foo_exec will be installed as
# foo_exec18.
+ # :ignore_dependencies:: Don't raise if a dependency is missing.
+ # :install_dir:: The directory to install the gem into.
# :security_policy:: Use the specified security policy. See Gem::Security
+ # :user_install:: Indicate that the gem should be unpacked into the users
+ # personal gem directory.
+ # :only_install_dir:: Only validate dependencies against what is in the
+ # install_dir
# :wrappers:: Install wrappers if true, symlinks if false.
+ # :build_args:: An Array of arguments to pass to the extension builder
+ # process. If not set, then Gem::Command.build_args is used
def initialize(gem, options={})
require 'fileutils'
@gem = gem
@options = options
+ @package = Gem::Package.new @gem
+
process_options
+ @package.security_policy = @security_policy
+
if options[:user_install] and not options[:unpack] then
@gem_home = Gem.user_dir
check_that_user_bin_dir_is_in_path
@@ -105,28 +115,79 @@ class Gem::Installer
end
##
- # Lazy accessor for the spec's gem directory.
+ # Checks if +filename+ exists in +@bin_dir+.
+ #
+ # If +@force+ is set +filename+ is overwritten.
+ #
+ # If +filename+ exists and is a RubyGems wrapper for different gem the user
+ # is consulted.
+ #
+ # If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the
+ # user is consulted.
+ #
+ # Otherwise +filename+ is overwritten.
- def gem_dir
- @gem_dir ||= spec.gem_dir.dup.untaint
+ def check_executable_overwrite filename # :nodoc:
+ return if @force
+
+ generated_bin = File.join @bin_dir, filename
+
+ return unless File.exist? generated_bin
+
+ ruby_executable = false
+ existing = nil
+
+ open generated_bin, 'rb' do |io|
+ next unless io.gets =~ /^#!/ # shebang
+ io.gets # blankline
+
+ # TODO detect a specially formatted comment instead of trying
+ # to run a regexp against ruby code.
+ next unless io.gets =~ /This file was generated by RubyGems/
+
+ ruby_executable = true
+ existing = io.read.slice(/^gem (['"])(.*?)(\1),/, 2)
+ end
+
+ return if spec.name == existing
+
+ # somebody has written to RubyGems' directory, overwrite, too bad
+ return if Gem.default_bindir != @bin_dir and not ruby_executable
+
+ question = "#{spec.name}'s executable \"#{filename}\" conflicts with "
+
+ if ruby_executable then
+ question << existing
+
+ return if ask_yes_no "#{question}\nOverwrite the executable?", false
+
+ conflict = "installed executable from #{existing}"
+ else
+ question << generated_bin
+
+ return if ask_yes_no "#{question}\nOverwrite the executable?", false
+
+ conflict = generated_bin
+ end
+
+ raise Gem::InstallError,
+ "\"#{filename}\" from #{spec.name} conflicts with #{conflict}"
end
##
- # Lazy accessor for the installer's Gem::Format instance.
+ # Lazy accessor for the spec's gem directory.
- def format
- begin
- @format ||= Gem::Format.from_file_by_path gem, @security_policy
- rescue Gem::Package::FormatError
- raise Gem::InstallError, "invalid gem format for #{gem}"
- end
+ def gem_dir
+ @gem_dir ||= File.join(gem_home, "gems", spec.full_name)
end
##
# Lazy accessor for the installer's spec.
def spec
- @spec ||= format.spec
+ @spec ||= @package.spec
+ rescue Gem::Package::Error => e
+ raise Gem::InstallError, "invalid gem: #{e.message}"
end
##
@@ -141,11 +202,7 @@ class Gem::Installer
# specifications/<gem-version>.gemspec #=> the Gem::Specification
def install
- current_home = Gem.dir
- current_path = Gem.paths.path
-
verify_gem_home(options[:unpack])
- Gem.use_paths gem_home, current_path # HACK: shouldn't need Gem.paths.path
# If we're forcing the install then disable security unless the security
# policy says that we only install signed gems.
@@ -158,65 +215,96 @@ class Gem::Installer
ensure_dependencies_met unless @ignore_dependencies
end
- Gem.pre_install_hooks.each do |hook|
- result = hook.call self
-
- if result == false then
- location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
-
- message = "pre-install hook#{location} failed for #{spec.full_name}"
- raise Gem::InstallError, message
- end
- end
+ run_pre_install_hooks
Gem.ensure_gem_subdirectories gem_home
# Completely remove any previous gem files
- FileUtils.rm_rf(gem_dir) if File.exist? gem_dir
+ FileUtils.rm_rf(gem_dir)
FileUtils.mkdir_p gem_dir
extract_files
build_extensions
- Gem.post_build_hooks.each do |hook|
- result = hook.call self
-
- if result == false then
- FileUtils.rm_rf gem_dir
+ run_post_build_hooks
- location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
+ generate_bin
+ write_spec
- message = "post-build hook#{location} failed for #{spec.full_name}"
- raise Gem::InstallError, message
+ unless @build_args.empty?
+ File.open spec.build_info_file, "w" do |f|
+ @build_args.each { |a| f.puts a }
end
end
- generate_bin
- write_spec
-
- write_require_paths_file_if_needed if Gem::QUICKLOADER_SUCKAGE
+ # TODO should be always cache the file? Other classes have options
+ # to controls if caching is done.
+ cache_file = File.join(gem_home, "cache", "#{spec.full_name}.gem")
- cache_file = spec.cache_file
FileUtils.cp gem, cache_file unless File.exist? cache_file
say spec.post_install_message unless spec.post_install_message.nil?
- spec.loaded_from = spec.spec_file
+ spec.loaded_from = spec_file
Gem::Specification.add_spec spec unless Gem::Specification.include? spec
+ run_post_install_hooks
+
+ spec
+
+ # TODO This rescue is in the wrong place. What is raising this exception?
+ # move this rescue to arround the code that actually might raise it.
+ rescue Zlib::GzipFile::Error
+ raise Gem::InstallError, "gzip error installing #{gem}"
+ end
+
+ def run_pre_install_hooks # :nodoc:
+ Gem.pre_install_hooks.each do |hook|
+ if hook.call(self) == false then
+ location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
+
+ message = "pre-install hook#{location} failed for #{spec.full_name}"
+ raise Gem::InstallError, message
+ end
+ end
+ end
+
+ def run_post_build_hooks # :nodoc:
+ Gem.post_build_hooks.each do |hook|
+ if hook.call(self) == false then
+ FileUtils.rm_rf gem_dir
+
+ location = " at #{$1}" if hook.inspect =~ /@(.*:\d+)/
+
+ message = "post-build hook#{location} failed for #{spec.full_name}"
+ raise Gem::InstallError, message
+ end
+ end
+ end
+
+ def run_post_install_hooks # :nodoc:
Gem.post_install_hooks.each do |hook|
hook.call self
end
+ end
- return spec
- rescue Zlib::GzipFile::Error
- raise Gem::InstallError, "gzip error installing #{gem}"
- ensure
- # conditional since we might be here because we're erroring out early.
- if current_path
- Gem.use_paths current_home, current_path
+ ##
+ #
+ # Return an Array of Specifications contained within the gem_home
+ # we'll be installing into.
+
+ def installed_specs
+ @specs ||= begin
+ specs = []
+
+ Dir[File.join(gem_home, "specifications", "*.gemspec")].each do |path|
+ spec = Gem::Specification.load path.untaint
+ specs << spec if spec
+ end
+
+ specs
end
end
@@ -235,9 +323,11 @@ class Gem::Installer
end
##
- # True if the gems in the source_index satisfy +dependency+.
+ # True if the gems in the system satisfy +dependency+.
def installation_satisfies_dependency?(dependency)
+ return true if installed_specs.detect { |s| dependency.matches_spec? s }
+ return false if @only_install_dir
not dependency.matching_specs.empty?
end
@@ -246,18 +336,23 @@ class Gem::Installer
def unpack(directory)
@gem_dir = directory
- @format = Gem::Format.from_file_by_path gem, @security_policy
extract_files
end
##
+ # The location of of the spec file that is installed.
+ #
+
+ def spec_file
+ File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
+ end
+
+ ##
# Writes the .gemspec specification (in Ruby) to the gem home's
# specifications directory.
def write_spec
- file_name = spec.spec_file.untaint
-
- File.open(file_name, "w") do |file|
+ File.open(spec_file, "w") do |file|
file.puts spec.to_ruby_for_cache
end
end
@@ -277,34 +372,34 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def generate_bin
return if spec.executables.nil? or spec.executables.empty?
- # If the user has asked for the gem to be installed in a directory that is
- # the system gem directory, then use the system bin directory, else create
- # (or use) a new bin dir under the gem_home.
- bindir = @bin_dir || Gem.bindir(gem_home)
-
- Dir.mkdir bindir unless File.exist? bindir
- raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir
+ Dir.mkdir @bin_dir unless File.exist? @bin_dir
+ raise Gem::FilePermissionError.new(@bin_dir) unless File.writable? @bin_dir
spec.executables.each do |filename|
filename.untaint
- bin_path = File.expand_path File.join(gem_dir, spec.bindir, filename)
+ bin_path = File.join gem_dir, spec.bindir, filename
- unless File.exist? bin_path
- warn "Hey?!?! Where did #{bin_path} go??"
+ unless File.exist? bin_path then
+ # TODO change this to a more useful warning
+ warn "#{bin_path} maybe `gem pristine #{spec.name}` will fix it?"
next
end
mode = File.stat(bin_path).mode | 0111
FileUtils.chmod mode, bin_path
+ check_executable_overwrite filename
+
if @wrappers then
- generate_bin_script filename, bindir
+ generate_bin_script filename, @bin_dir
else
- generate_bin_symlink filename, bindir
+ generate_bin_symlink filename, @bin_dir
end
+
end
end
@@ -358,10 +453,21 @@ class Gem::Installer
##
# Generates a #! line for +bin_file_name+'s wrapper copying arguments if
# necessary.
+ #
+ # If the :custom_shebang config is set, then it is used as a template
+ # for how to create the shebang used for to run a gem's executables.
+ #
+ # The template supports 4 expansions:
+ #
+ # $env the path to the unix env utility
+ # $ruby the path to the currently running ruby interpreter
+ # $exec the path to the gem's executable
+ # $name the name of the gem the executable is for
+ #
def shebang(bin_file_name)
ruby_name = Gem::ConfigMap[:ruby_install_name] if @env_shebang
- path = spec.bin_file bin_file_name
+ path = File.join gem_dir, spec.bindir, bin_file_name
first_line = File.open(path, "rb") {|file| file.gets}
if /\A#!/ =~ first_line then
@@ -371,7 +477,25 @@ class Gem::Installer
shebang.strip! # Avoid nasty ^M issues.
end
- if not ruby_name then
+ if which = Gem.configuration[:custom_shebang]
+ # replace bin_file_name with "ruby" to avoid endless loops
+ which = which.gsub(/ #{bin_file_name}$/," #{Gem::ConfigMap[:ruby_install_name]}")
+
+ which = which.gsub(/\$(\w+)/) do
+ case $1
+ when "env"
+ @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
+ when "ruby"
+ "#{Gem.ruby}#{opts}"
+ when "exec"
+ bin_file_name
+ when "name"
+ spec.name
+ end
+ end
+
+ "#!#{which}"
+ elsif not ruby_name then
"#!#{Gem.ruby}#{opts}"
elsif opts then
"#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}"
@@ -382,6 +506,7 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def ensure_required_ruby_version_met
if rrv = spec.required_ruby_version then
unless rrv.satisfied_by? Gem.ruby_version then
@@ -390,9 +515,10 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def ensure_required_rubygems_version_met
if rrgv = spec.required_rubygems_version then
- unless rrgv.satisfied_by? Gem::Version.new(Gem::VERSION) then
+ unless rrgv.satisfied_by? Gem.rubygems_version then
raise Gem::InstallError,
"#{spec.name} requires RubyGems version #{rrgv}. " +
"Try 'gem update --system' to update RubyGems itself."
@@ -400,6 +526,7 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def ensure_dependencies_met
deps = spec.runtime_dependencies
deps |= spec.development_dependencies if @development
@@ -409,13 +536,14 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def process_options
@options = {
:bin_dir => nil,
:env_shebang => false,
- :exec_format => false,
:force => false,
:install_dir => Gem.dir,
+ :only_install_dir => false
}.merge options
@env_shebang = options[:env_shebang]
@@ -425,13 +553,18 @@ class Gem::Installer
@format_executable = options[:format_executable]
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
- @bin_dir = options[:bin_dir]
+ @only_install_dir = options[:only_install_dir]
+
+ # If the user has asked for the gem to be installed in a directory that is
+ # the system gem directory, then use the system bin directory, else create
+ # (or use) a new bin dir under the gem_home.
+ @bin_dir = options[:bin_dir] || Gem.bindir(gem_home)
@development = options[:development]
- raise "NOTE: Installer option :source_index is dead" if
- options[:source_index]
+ @build_args = options[:build_args] || Gem::Command.build_args
end
+ # DOC: Missing docs or :nodoc:.
def check_that_user_bin_dir_is_in_path
user_bin_dir = @bin_dir || Gem.bindir(gem_home)
user_bin_dir.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
@@ -449,6 +582,7 @@ class Gem::Installer
end
end
+ # DOC: Missing docs or :nodoc:.
def verify_gem_home(unpack = false)
FileUtils.mkdir_p gem_home
raise Gem::FilePermissionError, gem_home unless
@@ -499,7 +633,6 @@ GOTO :EOF
:WinNT
@"#{ruby}" "%~dpn0" %*
TEXT
-
end
##
@@ -508,7 +641,14 @@ TEXT
def build_extensions
return if spec.extensions.empty?
- say "Building native extensions. This could take a while..."
+
+ if @build_args.empty?
+ say "Building native extensions. This could take a while..."
+ else
+ say "Building native extensions with: '#{@build_args.join(' ')}'"
+ say "This could take a while..."
+ end
+
dest_path = File.join gem_dir, spec.require_paths.first
ran_rake = false # only run rake once
@@ -516,6 +656,9 @@ TEXT
break if ran_rake
results = []
+ extension ||= ""
+ extension_dir = File.join gem_dir, File.dirname(extension)
+
builder = case extension
when /extconf/ then
Gem::Ext::ExtConfBuilder
@@ -525,43 +668,41 @@ TEXT
ran_rake = true
Gem::Ext::RakeBuilder
else
- results = ["No builder for extension '#{extension}'"]
- nil
+ message = "No builder for extension '#{extension}'"
+ extension_build_error extension_dir, message
end
-
- extension_dir = begin
- File.join gem_dir, File.dirname(extension)
- rescue TypeError # extension == nil
- gem_dir
- end
-
-
begin
Dir.chdir extension_dir do
- results = builder.build(extension, gem_dir, dest_path, results)
+ results = builder.build(extension, gem_dir, dest_path,
+ results, @build_args)
say results.join("\n") if Gem.configuration.really_verbose
end
rescue
- results = results.join "\n"
+ extension_build_error(extension_dir, results.join("\n"))
+ end
+ end
+ end
- gem_make_out = File.join extension_dir, 'gem_make.out'
+ ##
+ # Logs the build +output+ in +build_dir+, then raises ExtensionBuildError.
+
+ def extension_build_error(build_dir, output)
+ gem_make_out = File.join build_dir, 'gem_make.out'
- open gem_make_out, 'wb' do |io| io.puts results end
+ open gem_make_out, 'wb' do |io| io.puts output end
- message = <<-EOF
+ message = <<-EOF
ERROR: Failed to build gem native extension.
- #{results}
+ #{output}
Gem files will remain installed in #{gem_dir} for inspection.
Results logged to #{gem_make_out}
EOF
- raise ExtensionBuildError, message
- end
- end
+ raise ExtensionBuildError, message
end
##
@@ -570,36 +711,7 @@ EOF
# Ensures that files can't be installed outside the gem directory.
def extract_files
- raise ArgumentError, "format required to extract from" if @format.nil?
-
- @format.file_entries.each do |entry, file_data|
- path = entry['path'].untaint
-
- if path.start_with? "/" then # for extra sanity
- raise Gem::InstallError, "attempt to install file into #{entry['path']}"
- end
-
- path = File.expand_path File.join(gem_dir, path)
-
- unless path.start_with? gem_dir then
- msg = "attempt to install file into %p under %s" %
- [entry['path'], gem_dir]
- raise Gem::InstallError, msg
- end
-
- FileUtils.rm_rf(path) if File.exist? path
-
- dir = File.dirname path
- FileUtils.mkdir_p dir unless File.exist? dir
-
- File.open(path, "wb") do |out|
- out.write file_data
- end
-
- FileUtils.chmod entry['mode'], path
-
- say path if Gem.configuration.really_verbose
- end
+ @package.extract_files gem_dir
end
##
diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb
index 96a5156995..62a468a8a2 100644
--- a/lib/rubygems/installer_test_case.rb
+++ b/lib/rubygems/installer_test_case.rb
@@ -6,11 +6,26 @@ class Gem::Installer
##
# Available through requiring rubygems/installer_test_case
+ attr_writer :bin_dir
+
+ ##
+ # Available through requiring rubygems/installer_test_case
+
+ attr_writer :build_args
+
+ ##
+ # Available through requiring rubygems/installer_test_case
+
attr_writer :gem_dir
##
# Available through requiring rubygems/installer_test_case
+ attr_writer :force
+
+ ##
+ # Available through requiring rubygems/installer_test_case
+
attr_writer :format
##
@@ -54,47 +69,68 @@ end
class Gem::InstallerTestCase < Gem::TestCase
+ ##
+ # Creates the following instance variables:
+ #
+ # @spec::
+ # a spec named 'a', intended for regular installs
+ # @user_spec::
+ # a spec named 'b', intended for user installs
+
+ # @gem::
+ # the path to a built gem from @spec
+ # @user_spec::
+ # the path to a built gem from @user_spec
+ #
+ # @installer::
+ # a Gem::Installer for the @spec that installs into @gemhome
+ # @user_installer::
+ # a Gem::Installer for the @user_spec that installs into Gem.user_dir
+
def setup
super
- @installer_tmp = File.join @tempdir, 'installer'
- FileUtils.mkdir_p @installer_tmp
+ @spec = quick_gem 'a' do |spec|
+ util_make_exec spec
+ end
- Gem.use_paths @installer_tmp
- Gem.ensure_gem_subdirectories @installer_tmp
+ @user_spec = quick_gem 'b' do |spec|
+ util_make_exec spec
+ end
- @spec = quick_gem 'a'
- util_make_exec @spec
util_build_gem @spec
- @gem = @spec.cache_file
-
- @user_spec = quick_gem 'b'
- util_make_exec @user_spec
util_build_gem @user_spec
- @user_gem = @user_spec.cache_file
- Gem.use_paths @gemhome
+ @gem = @spec.cache_file
+ @user_gem = @user_spec.cache_file
@installer = util_installer @spec, @gemhome
@user_installer = util_installer @user_spec, Gem.user_dir, :user
-
- Gem.use_paths @gemhome
end
- def util_gem_bindir spec = @spec
+ def util_gem_bindir spec = @spec # :nodoc:
# TODO: deprecate
spec.bin_dir
end
- def util_gem_dir spec = @spec
+ def util_gem_dir spec = @spec # :nodoc:
# TODO: deprecate
spec.gem_dir
end
+ ##
+ # The path where installed executables live
+
def util_inst_bindir
File.join @gemhome, "bin"
end
+ ##
+ # Adds an executable named "executable" to +spec+ with the given +shebang+.
+ #
+ # The executable is also written to the bin dir in @tmpdir and the installed
+ # gem directory for +spec+.
+
def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby")
spec.executables = %w[executable]
spec.files << 'bin/executable'
@@ -110,6 +146,14 @@ class Gem::InstallerTestCase < Gem::TestCase
end
end
+ ##
+ # Builds the @spec gem and returns an installer for it. The built gem
+ # includes:
+ #
+ # bin/executable
+ # lib/code.rb
+ # ext/a/mkrf_conf.rb
+
def util_setup_gem(ui = @ui) # HACK fix use_ui to make this automatic
@spec.files << File.join('lib', 'code.rb')
@spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb')
@@ -129,16 +173,24 @@ class Gem::InstallerTestCase < Gem::TestCase
end
use_ui ui do
- FileUtils.rm @gem
+ FileUtils.rm_f @gem
- @gem = Gem::Builder.new(@spec).build
+ @gem = Gem::Package.build @spec
end
end
@installer = Gem::Installer.new @gem
end
+ ##
+ # Creates an installer for +spec+ that will install into +gem_home+. If
+ # +user+ is true a user-install will be performed.
+
def util_installer(spec, gem_home, user=false)
- Gem::Installer.new spec.cache_file, :user_install => user
+ Gem::Installer.new(spec.cache_file,
+ :install_dir => gem_home,
+ :user_install => user)
end
+
end
+
diff --git a/lib/rubygems/mock_gem_ui.rb b/lib/rubygems/mock_gem_ui.rb
index 13f0bf564b..76a9389676 100644
--- a/lib/rubygems/mock_gem_ui.rb
+++ b/lib/rubygems/mock_gem_ui.rb
@@ -6,6 +6,17 @@ require 'rubygems/user_interaction'
# retrieval during tests.
class Gem::MockGemUi < Gem::StreamUI
+ ##
+ # Raised when you haven't provided enough input to your MockGemUi
+
+ class InputEOFError < RuntimeError
+
+ def initialize question
+ super "Out of input for MockGemUi on #{question.inspect}"
+ end
+
+ end
+
class TermError < RuntimeError
attr_reader :exit_code
@@ -44,6 +55,12 @@ class Gem::MockGemUi < Gem::StreamUI
@terminated = false
end
+ def ask question
+ raise InputEOFError, question if @ins.eof?
+
+ super
+ end
+
def input
@ins.string
end
diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb
new file mode 100644
index 0000000000..d16fad26f1
--- /dev/null
+++ b/lib/rubygems/name_tuple.rb
@@ -0,0 +1,110 @@
+##
+#
+# Represents a gem of name +name+ at +version+ of +platform+. These
+# wrap the data returned from the indexes.
+
+require 'rubygems/platform'
+
+class Gem::NameTuple
+ def initialize(name, version, platform="ruby")
+ @name = name
+ @version = version
+
+ unless platform.kind_of? Gem::Platform
+ platform = "ruby" if !platform or platform.empty?
+ end
+
+ @platform = platform
+ end
+
+ attr_reader :name, :version, :platform
+
+ ##
+ # Turn an array of [name, version, platform] into an array of
+ # NameTuple objects.
+
+ def self.from_list list
+ list.map { |t| new(*t) }
+ end
+
+ ##
+ # Turn an array of NameTuple objects back into an array of
+ # [name, version, platform] tuples.
+
+ def self.to_basic list
+ list.map { |t| t.to_a }
+ end
+
+ ##
+ # A null NameTuple, ie name=nil, version=0
+
+ def self.null
+ new nil, Gem::Version.new(0), nil
+ end
+
+ ##
+ # Indicate if this NameTuple matches the current platform.
+
+ def match_platform?
+ Gem::Platform.match @platform
+ end
+
+ ##
+ # Indicate if this NameTuple is for a prerelease version.
+ def prerelease?
+ @version.prerelease?
+ end
+
+ ##
+ # Return the name that the gemspec file would be
+
+ def spec_name
+ case @platform
+ when nil, 'ruby', ''
+ "#{@name}-#{@version}.gemspec"
+ else
+ "#{@name}-#{@version}-#{@platform}.gemspec"
+ end
+ end
+
+ ##
+ # Convert back to the [name, version, platform] tuple
+
+ def to_a
+ [@name, @version, @platform]
+ end
+
+ def to_s
+ "#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>"
+ end
+
+ def <=> other
+ to_a <=> other.to_a
+ end
+
+ include Comparable
+
+ ##
+ # Compare with +other+. Supports another NameTuple or an Array
+ # in the [name, version, platform] format.
+
+ def == other
+ case other
+ when self.class
+ @name == other.name and
+ @version == other.version and
+ @platform == other.platform
+ when Array
+ to_a == other
+ else
+ false
+ end
+ end
+
+ alias_method :eql?, :==
+
+ def hash
+ to_a.hash
+ end
+
+end
diff --git a/lib/rubygems/old_format.rb b/lib/rubygems/old_format.rb
deleted file mode 100644
index a44fd533a5..0000000000
--- a/lib/rubygems/old_format.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-require 'rubygems'
-
-##
-# The format class knows the guts of the RubyGem .gem file format and provides
-# the capability to read gem files
-
-class Gem::OldFormat
-
- attr_accessor :spec, :file_entries, :gem_path
-
- ##
- # Constructs an instance of a Format object, representing the gem's data
- # structure.
- #
- # gem:: [String] The file name of the gem
-
- def initialize(gem_path)
- require 'fileutils'
- require 'zlib'
- Gem.load_yaml
-
- @gem_path = gem_path
- end
-
- ##
- # Reads the named gem file and returns a Format object, representing the
- # data from the gem file
- #
- # file_path:: [String] Path to the gem file
-
- def self.from_file_by_path(file_path)
- unless File.exist?(file_path)
- raise Gem::Exception, "Cannot load gem file [#{file_path}]"
- end
-
- File.open(file_path, 'rb') do |file|
- from_io(file, file_path)
- end
- end
-
- ##
- # Reads a gem from an io stream and returns a Format object, representing
- # the data from the gem file
- #
- # io:: [IO] Stream from which to read the gem
-
- def self.from_io(io, gem_path="(io)")
- format = self.new(gem_path)
- skip_ruby(io)
- format.spec = read_spec(io)
- format.file_entries = []
- read_files_from_gem(io) do |entry, file_data|
- format.file_entries << [entry, file_data]
- end
- format
- end
-
- private
-
- ##
- # Skips the Ruby self-install header. After calling this method, the
- # IO index will be set after the Ruby code.
- #
- # file:: [IO] The IO to process (skip the Ruby code)
-
- def self.skip_ruby(file)
- end_seen = false
- loop {
- line = file.gets
- if(line == nil || line.chomp == "__END__") then
- end_seen = true
- break
- end
- }
-
- if end_seen == false then
- raise Gem::Exception.new("Failed to find end of ruby script while reading gem")
- end
- end
-
- ##
- # Reads the specification YAML from the supplied IO and constructs
- # a Gem::Specification from it. After calling this method, the
- # IO index will be set after the specification header.
- #
- # file:: [IO] The IO to process
-
- def self.read_spec(file)
- yaml = ''
-
- read_until_dashes file do |line|
- yaml << line
- end
-
- Gem::Specification.from_yaml yaml
- rescue YAML::Error => e
- raise Gem::Exception, "Failed to parse gem specification out of gem file"
- rescue ArgumentError => e
- raise Gem::Exception, "Failed to parse gem specification out of gem file"
- end
-
- ##
- # Reads lines from the supplied IO until a end-of-yaml (---) is
- # reached
- #
- # file:: [IO] The IO to process
- # block:: [String] The read line
-
- def self.read_until_dashes(file)
- while((line = file.gets) && line.chomp.strip != "---") do
- yield line
- end
- end
-
- ##
- # Reads the embedded file data from a gem file, yielding an entry
- # containing metadata about the file and the file contents themselves
- # for each file that's archived in the gem.
- # NOTE: Many of these methods should be extracted into some kind of
- # Gem file read/writer
- #
- # gem_file:: [IO] The IO to process
-
- def self.read_files_from_gem(gem_file)
- errstr = "Error reading files from gem"
- header_yaml = ''
- begin
- self.read_until_dashes(gem_file) do |line|
- header_yaml << line
- end
- header = YAML.load(header_yaml)
- raise Gem::Exception, errstr unless header
-
- header.each do |entry|
- file_data = ''
- self.read_until_dashes(gem_file) do |line|
- file_data << line
- end
- yield [entry, Zlib::Inflate.inflate(file_data.strip.unpack("m")[0])]
- end
- rescue Zlib::DataError
- raise Gem::Exception, errstr
- end
- end
-
-end
-
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 2b50c588ee..51df43be93 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -3,16 +3,54 @@
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#++
+#
+# Example using a Gem::Package
+#
+# Builds a .gem file given a Gem::Specification. A .gem file is a tarball
+# which contains a data.tar.gz and metadata.gz, and possibly signatures.
+#
+# require 'rubygems'
+# require 'rubygems/package'
+#
+# spec = Gem::Specification.new do |s|
+# s.summary = "Ruby based make-like utility."
+# s.name = 'rake'
+# s.version = PKG_VERSION
+# s.requirements << 'none'
+# s.files = PKG_FILES
+# s.description = <<-EOF
+# Rake is a Make-like program implemented in Ruby. Tasks
+# and dependencies are specified in standard Ruby syntax.
+# EOF
+# end
+#
+# Gem::Package.build spec
+#
+# Reads a .gem file.
+#
+# require 'rubygems'
+# require 'rubygems/package'
+#
+# the_gem = Gem::Package.new(path_to_dot_gem)
+# the_gem.contents # get the files in the gem
+# the_gem.extract_files destination_directory # extract the gem into a directory
+# the_gem.spec # get the spec out of the gem
+# the_gem.verify # check the gem is OK (contains valid gem specification, contains a not corrupt contents archive)
+#
+# #files are the files in the .gem tar file, not the ruby files in the gem
+# #extract_files and #contents automatically call #verify
+require 'rubygems/security'
require 'rubygems/specification'
+require 'rubygems/user_interaction'
+require 'zlib'
-module Gem::Package
+class Gem::Package
+
+ include Gem::UserInteraction
+
+ class Error < Gem::Exception; end
- class Error < StandardError; end
- class NonSeekableIO < Error; end
- class ClosedIO < Error; end
- class BadCheckSum < Error; end
- class TooLongFileName < Error; end
class FormatError < Error
attr_reader :path
@@ -26,57 +64,489 @@ module Gem::Package
end
+ class PathError < Error
+ def initialize destination, destination_dir
+ super "installing into parent path %s of %s is not allowed" %
+ [destination, destination_dir]
+ end
+ end
+
+ class NonSeekableIO < Error; end
+
+ class TooLongFileName < Error; end
+
##
# Raised when a tar file is corrupt
class TarInvalidError < Error; end
- # FIX: zenspider said: does it really take an IO?
- # passed to a method called open?!? that seems stupid.
- def self.open(io, mode = "r", signer = nil, &block)
- tar_type = case mode
- when 'r' then TarInput
- when 'w' then TarOutput
- else
- raise "Unknown Package open mode"
- end
-
- tar_type.open(io, signer, &block)
- end
-
- def self.pack(src, destname, signer = nil)
- TarOutput.open(destname, signer) do |outp|
- dir_class.chdir(src) do
- outp.metadata = (file_class.read("RPA/metadata") rescue nil)
- find_class.find('.') do |entry|
- case
- when file_class.file?(entry)
- entry.sub!(%r{\./}, "")
- next if entry =~ /\ARPA\//
- stat = File.stat(entry)
- outp.add_file_simple(entry, stat.mode, stat.size) do |os|
- file_class.open(entry, "rb") do |f|
- os.write(f.read(4096)) until f.eof?
- end
- end
- when file_class.dir?(entry)
- entry.sub!(%r{\./}, "")
- next if entry == "RPA"
- outp.mkdir(entry, file_class.stat(entry).mode)
- else
- raise "Don't know how to pack this yet!"
+ attr_accessor :build_time # :nodoc:
+
+ ##
+ # Checksums for the contents of the package
+
+ attr_reader :checksums
+
+ ##
+ # The files in this package. This is not the contents of the gem, just the
+ # files in the top-level container.
+
+ attr_reader :files
+
+ ##
+ # The security policy used for verifying the contents of this package.
+
+ attr_accessor :security_policy
+
+ ##
+ # Sets the Gem::Specification to use to build this package.
+
+ attr_writer :spec
+
+ def self.build spec, skip_validation=false
+ gem_file = spec.file_name
+
+ package = new gem_file
+ package.spec = spec
+ package.build skip_validation
+
+ gem_file
+ end
+
+ ##
+ # Creates a new Gem::Package for the file at +gem+.
+ #
+ # If +gem+ is an existing file in the old format a Gem::Package::Old will be
+ # returned.
+
+ def self.new gem
+ return super unless Gem::Package == self
+ return super unless File.exist? gem
+
+ start = File.read gem, 20
+
+ return super unless start
+ return super unless start.include? 'MD5SUM ='
+
+ Gem::Package::Old.new gem
+ end
+
+ ##
+ # Creates a new package that will read or write to the file +gem+.
+
+ def initialize gem # :notnew:
+ @gem = gem
+
+ @build_time = Time.now
+ @checksums = {}
+ @contents = nil
+ @digests = Hash.new { |h, algorithm| h[algorithm] = {} }
+ @files = nil
+ @security_policy = nil
+ @signatures = {}
+ @signer = nil
+ @spec = nil
+ end
+
+ ##
+ # Adds a checksum for each entry in the gem to checksums.yaml.gz.
+
+ def add_checksums tar
+ Gem.load_yaml
+
+ checksums_by_algorithm = Hash.new { |h, algorithm| h[algorithm] = {} }
+
+ @checksums.each do |name, digests|
+ digests.each do |algorithm, digest|
+ checksums_by_algorithm[algorithm][name] = digest.hexdigest
+ end
+ end
+
+ tar.add_file_signed 'checksums.yaml.gz', 0444, @signer do |io|
+ gzip_to io do |gz_io|
+ YAML.dump checksums_by_algorithm, gz_io
+ end
+ end
+ end
+
+ ##
+ # Adds the files listed in the packages's Gem::Specification to data.tar.gz
+ # and adds this file to the +tar+.
+
+ def add_contents tar # :nodoc:
+ digests = tar.add_file_signed 'data.tar.gz', 0444, @signer do |io|
+ gzip_to io do |gz_io|
+ Gem::Package::TarWriter.new gz_io do |data_tar|
+ add_files data_tar
+ end
+ end
+ end
+
+ @checksums['data.tar.gz'] = digests
+ end
+
+ ##
+ # Adds files included the package's Gem::Specification to the +tar+ file
+
+ def add_files tar # :nodoc:
+ @spec.files.each do |file|
+ stat = File.stat file
+
+ tar.add_file_simple file, stat.mode, stat.size do |dst_io|
+ open file, 'rb' do |src_io|
+ dst_io.write src_io.read 16384 until src_io.eof?
+ end
+ end
+ end
+ end
+
+ ##
+ # Adds the package's Gem::Specification to the +tar+ file
+
+ def add_metadata tar # :nodoc:
+ digests = tar.add_file_signed 'metadata.gz', 0444, @signer do |io|
+ gzip_to io do |gz_io|
+ gz_io.write @spec.to_yaml
+ end
+ end
+
+ @checksums['metadata.gz'] = digests
+ end
+
+ ##
+ # Builds this package based on the specification set by #spec=
+
+ def build skip_validation = false
+ require 'rubygems/security'
+
+ @spec.validate unless skip_validation
+ @spec.mark_version
+
+ setup_signer
+
+ open @gem, 'wb' do |gem_io|
+ Gem::Package::TarWriter.new gem_io do |gem|
+ add_metadata gem
+ add_contents gem
+ add_checksums gem
+ end
+ end
+
+ say <<-EOM
+ Successfully built RubyGem
+ Name: #{@spec.name}
+ Version: #{@spec.version}
+ File: #{File.basename @spec.cache_file}
+EOM
+ ensure
+ @signer = nil
+ end
+
+ ##
+ # A list of file names contained in this gem
+
+ def contents
+ return @contents if @contents
+
+ verify unless @spec
+
+ @contents = []
+
+ open @gem, 'rb' do |io|
+ gem_tar = Gem::Package::TarReader.new io
+
+ gem_tar.each do |entry|
+ next unless entry.full_name == 'data.tar.gz'
+
+ open_tar_gz entry do |pkg_tar|
+ pkg_tar.each do |contents_entry|
+ @contents << contents_entry.full_name
end
end
+
+ return @contents
+ end
+ end
+ end
+
+ ##
+ # Creates a digest of the TarEntry +entry+ from the digest algorithm set by
+ # the security policy.
+
+ def digest entry # :nodoc:
+ return unless @checksums
+
+ @checksums.each_key do |algorithm|
+ digester = OpenSSL::Digest.new algorithm
+
+ digester << entry.read(16384) until entry.eof?
+
+ entry.rewind
+
+ @digests[algorithm][entry.full_name] = digester
+ end
+
+ @digests
+ end
+
+ ##
+ # Extracts the files in this package into +destination_dir+
+
+ def extract_files destination_dir
+ verify unless @spec
+
+ FileUtils.mkdir_p destination_dir
+
+ open @gem, 'rb' do |io|
+ reader = Gem::Package::TarReader.new io
+
+ reader.each do |entry|
+ next unless entry.full_name == 'data.tar.gz'
+
+ extract_tar_gz entry, destination_dir
+
+ return # ignore further entries
+ end
+ end
+ end
+
+ ##
+ # Extracts all the files in the gzipped tar archive +io+ into
+ # +destination_dir+.
+ #
+ # If an entry in the archive contains a relative path above
+ # +destination_dir+ or an absolute path is encountered an exception is
+ # raised.
+
+ def extract_tar_gz io, destination_dir # :nodoc:
+ open_tar_gz io do |tar|
+ tar.each do |entry|
+ destination = install_location entry.full_name, destination_dir
+
+ FileUtils.rm_rf destination
+
+ FileUtils.mkdir_p File.dirname destination
+
+ open destination, 'wb', entry.header.mode do |out|
+ out.write entry.read
+ out.fsync rescue nil # for filesystems without fsync(2)
+ end
+
+ say destination if Gem.configuration.really_verbose
+ end
+ end
+ end
+
+ ##
+ # Gzips content written to +gz_io+ to +io+.
+ #--
+ # Also sets the gzip modification time to the package build time to ease
+ # testing.
+
+ def gzip_to io # :yields: gz_io
+ gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION
+ gz_io.mtime = @build_time
+
+ yield gz_io
+ ensure
+ gz_io.close
+ end
+
+ ##
+ # Returns the full path for installing +filename+.
+ #
+ # If +filename+ is not inside +destination_dir+ an exception is raised.
+
+ def install_location filename, destination_dir # :nodoc:
+ raise Gem::Package::PathError.new(filename, destination_dir) if
+ filename.start_with? '/'
+
+ destination = File.join destination_dir, filename
+ destination = File.expand_path destination
+
+ raise Gem::Package::PathError.new(destination, destination_dir) unless
+ destination.start_with? destination_dir
+
+ destination.untaint
+ destination
+ end
+
+ ##
+ # Loads a Gem::Specification from the TarEntry +entry+
+
+ def load_spec entry # :nodoc:
+ case entry.full_name
+ when 'metadata' then
+ @spec = Gem::Specification.from_yaml entry.read
+ when 'metadata.gz' then
+ args = [entry]
+ args << { :external_encoding => Encoding::UTF_8 } if
+ Object.const_defined? :Encoding
+
+ Zlib::GzipReader.wrap(*args) do |gzio|
+ @spec = Gem::Specification.from_yaml gzio.read
+ end
+ end
+ end
+
+ ##
+ # Opens +io+ as a gzipped tar archive
+
+ def open_tar_gz io # :nodoc:
+ Zlib::GzipReader.wrap io do |gzio|
+ tar = Gem::Package::TarReader.new gzio
+
+ yield tar
+ end
+ end
+
+ ##
+ # Reads and loads checksums.yaml.gz from the tar file +gem+
+
+ def read_checksums gem
+ Gem.load_yaml
+
+ @checksums = gem.seek 'checksums.yaml.gz' do |entry|
+ Zlib::GzipReader.wrap entry do |gz_io|
+ YAML.load gz_io.read
+ end
+ end
+ end
+
+ ##
+ # Prepares the gem for signing and checksum generation. If a signing
+ # certificate and key are not present only checksum generation is set up.
+
+ def setup_signer
+ if @spec.signing_key then
+ @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain
+ @spec.signing_key = nil
+ @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s }
+ else
+ @signer = Gem::Security::Signer.new nil, nil
+ @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if
+ @signer.cert_chain
+ end
+ end
+
+ ##
+ # The spec for this gem.
+ #
+ # If this is a package for a built gem the spec is loaded from the
+ # gem and returned. If this is a package for a gem being built the provided
+ # spec is returned.
+
+ def spec
+ verify unless @spec
+
+ @spec
+ end
+
+ ##
+ # Verifies that this gem:
+ #
+ # * Contains a valid gem specification
+ # * Contains a contents archive
+ # * The contents archive is not corrupt
+ #
+ # After verification the gem specification from the gem is available from
+ # #spec
+
+ def verify
+ @files = []
+ @spec = nil
+
+ open @gem, 'rb' do |io|
+ Gem::Package::TarReader.new io do |reader|
+ read_checksums reader
+
+ verify_files reader
+ end
+ end
+
+ verify_checksums @digests, @checksums
+
+ @security_policy.verify_signatures @spec, @digests, @signatures if
+ @security_policy
+
+ true
+ rescue Errno::ENOENT => e
+ raise Gem::Package::FormatError.new e.message
+ rescue Gem::Package::TarInvalidError => e
+ raise Gem::Package::FormatError.new e.message, @gem
+ end
+
+ ##
+ # Verifies the +checksums+ against the +digests+. This check is not
+ # cryptographically secure. Missing checksums are ignored.
+
+ def verify_checksums digests, checksums # :nodoc:
+ return unless checksums
+
+ checksums.sort.each do |algorithm, gem_digests|
+ gem_digests.sort.each do |file_name, gem_hexdigest|
+ computed_digest = digests[algorithm][file_name]
+
+ unless computed_digest.hexdigest == gem_hexdigest then
+ raise Gem::Package::FormatError.new \
+ "#{algorithm} checksum mismatch for #{file_name}", @gem
+ end
end
end
end
+ ##
+ # Verifies the files of the +gem+
+
+ def verify_files gem
+ gem.each do |entry|
+ file_name = entry.full_name
+ @files << file_name
+
+ case file_name
+ when /\.sig$/ then
+ @signatures[$`] = entry.read if @security_policy
+ next
+ when 'checksums.yaml.gz' then
+ next # already handled
+ else
+ digest entry
+ end
+
+ case file_name
+ when /^metadata(.gz)?$/ then
+ load_spec entry
+ when 'data.tar.gz' then
+ verify_gz entry
+ end
+ end
+
+ unless @spec then
+ raise Gem::Package::FormatError.new 'package metadata is missing', @gem
+ end
+
+ unless @files.include? 'data.tar.gz' then
+ raise Gem::Package::FormatError.new \
+ 'package content (data.tar.gz) is missing', @gem
+ end
+ end
+
+ ##
+ # Verifies that +entry+ is a valid gzipped file.
+
+ def verify_gz entry # :nodoc:
+ Zlib::GzipReader.wrap entry do |gzio|
+ gzio.read 16384 until gzio.eof? # gzip checksum verification
+ end
+ rescue Zlib::GzipFile::Error => e
+ raise Gem::Package::FormatError.new(e.message, entry.full_name)
+ end
+
end
-require 'rubygems/package/f_sync_dir'
+require 'rubygems/package/digest_io'
+require 'rubygems/package/old'
require 'rubygems/package/tar_header'
-require 'rubygems/package/tar_input'
-require 'rubygems/package/tar_output'
require 'rubygems/package/tar_reader'
require 'rubygems/package/tar_reader/entry'
require 'rubygems/package/tar_writer'
diff --git a/lib/rubygems/package/digest_io.rb b/lib/rubygems/package/digest_io.rb
new file mode 100644
index 0000000000..f8bde0f557
--- /dev/null
+++ b/lib/rubygems/package/digest_io.rb
@@ -0,0 +1,64 @@
+##
+# IO wrapper that creates digests of contents written to the IO it wraps.
+
+class Gem::Package::DigestIO
+
+ ##
+ # Collected digests for wrapped writes.
+ #
+ # {
+ # 'SHA1' => #<OpenSSL::Digest: [...]>,
+ # 'SHA512' => #<OpenSSL::Digest: [...]>,
+ # }
+
+ attr_reader :digests
+
+ ##
+ # Wraps +io+ and updates digest for each of the digest algorithms in
+ # the +digests+ Hash. Returns the digests hash. Example:
+ #
+ # io = StringIO.new
+ # digests = {
+ # 'SHA1' => OpenSSL::Digest.new('SHA1'),
+ # 'SHA512' => OpenSSL::Digest.new('SHA512'),
+ # }
+ #
+ # Gem::Package::DigestIO.wrap io, digests do |digest_io|
+ # digest_io.write "hello"
+ # end
+ #
+ # digests['SHA1'].hexdigest #=> "aaf4c61d[...]"
+ # digests['SHA512'].hexdigest #=> "9b71d224[...]"
+
+ def self.wrap io, digests
+ digest_io = new io, digests
+
+ yield digest_io
+
+ return digests
+ end
+
+ ##
+ # Creates a new DigestIO instance. Using ::wrap is recommended, see the
+ # ::wrap documentation for documentation of +io+ and +digests+.
+
+ def initialize io, digests
+ @io = io
+ @digests = digests
+ end
+
+ ##
+ # Writes +data+ to the underlying IO and updates the digests
+
+ def write data
+ result = @io.write data
+
+ @digests.each do |_, digest|
+ digest << data
+ end
+
+ result
+ end
+
+end
+
diff --git a/lib/rubygems/package/f_sync_dir.rb b/lib/rubygems/package/f_sync_dir.rb
deleted file mode 100644
index f7eb7f3ce3..0000000000
--- a/lib/rubygems/package/f_sync_dir.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-#--
-# Copyright (C) 2004 Mauricio Julio Fernández Pradier
-# See LICENSE.txt for additional licensing information.
-#++
-
-module Gem::Package::FSyncDir
-
- private
-
- ##
- # make sure this hits the disc
-
- def fsync_dir(dirname)
- dir = open dirname, 'r'
- dir.fsync
- rescue # ignore IOError if it's an unpatched (old) Ruby
- ensure
- dir.close if dir rescue nil
- end
-
-end
-
diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb
new file mode 100644
index 0000000000..552a5f3591
--- /dev/null
+++ b/lib/rubygems/package/old.rb
@@ -0,0 +1,147 @@
+#--
+# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
+# All rights reserved.
+# See LICENSE.txt for permissions.
+#++
+
+##
+# The format class knows the guts of the ancient .gem file format and provides
+# the capability to read such ancient gems.
+#
+# Please pretend this doesn't exist.
+
+class Gem::Package::Old < Gem::Package
+
+ undef_method :spec=
+
+ ##
+ # Creates a new old-format package reader for +gem+. Old-format packages
+ # cannot be written.
+
+ def initialize gem
+ require 'fileutils'
+ require 'zlib'
+ Gem.load_yaml
+
+ @gem = gem
+ @contents = nil
+ @spec = nil
+ end
+
+ ##
+ # A list of file names contained in this gem
+
+ def contents
+ return @contents if @contents
+
+ open @gem, 'rb' do |io|
+ read_until_dashes io # spec
+ header = file_list io
+
+ @contents = header.map { |file| file['path'] }
+ end
+ end
+
+ ##
+ # Extracts the files in this package into +destination_dir+
+
+ def extract_files destination_dir
+ errstr = "Error reading files from gem"
+
+ open @gem, 'rb' do |io|
+ read_until_dashes io # spec
+ header = file_list io
+ raise Gem::Exception, errstr unless header
+
+ header.each do |entry|
+ full_name = entry['path']
+
+ destination = install_location full_name, destination_dir
+
+ file_data = ''
+
+ read_until_dashes io do |line|
+ file_data << line
+ end
+
+ file_data = file_data.strip.unpack("m")[0]
+ file_data = Zlib::Inflate.inflate file_data
+
+ raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if
+ file_data.length != entry['size'].to_i
+
+ FileUtils.rm_rf destination
+
+ FileUtils.mkdir_p File.dirname destination
+
+ open destination, 'wb', entry['mode'] do |out|
+ out.write file_data
+ end
+
+ say destination if Gem.configuration.really_verbose
+ end
+ end
+ rescue Zlib::DataError
+ raise Gem::Exception, errstr
+ end
+
+ ##
+ # Reads the file list section from the old-format gem +io+
+
+ def file_list io # :nodoc:
+ header = ''
+
+ read_until_dashes io do |line|
+ header << line
+ end
+
+ YAML.load header
+ end
+
+ ##
+ # Reads lines until a "---" separator is found
+
+ def read_until_dashes io # :nodoc:
+ while (line = io.gets) && line.chomp.strip != "---" do
+ yield line if block_given?
+ end
+ end
+
+ ##
+ # Skips the Ruby self-install header in +io+.
+
+ def skip_ruby io # :nodoc:
+ loop do
+ line = io.gets
+
+ return if line.chomp == '__END__'
+ break unless line
+ end
+
+ raise Gem::Exception, "Failed to find end of ruby script while reading gem"
+ end
+
+ ##
+ # The specification for this gem
+
+ def spec
+ return @spec if @spec
+
+ yaml = ''
+
+ open @gem, 'rb' do |io|
+ skip_ruby io
+ read_until_dashes io do |line|
+ yaml << line
+ end
+ end
+
+ @spec = Gem::Specification.from_yaml yaml
+ rescue YAML::SyntaxError => e
+ raise Gem::Exception, "Failed to parse gem specification out of gem file"
+ rescue ArgumentError => e
+ raise Gem::Exception, "Failed to parse gem specification out of gem file"
+ end
+
+end
+
diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index 4f923b9b5e..28da1db0b5 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -102,61 +102,24 @@ class Gem::Package::TarHeader
fields = header.unpack UNPACK_FORMAT
- name = fields.shift
- mode = fields.shift.oct
- uid = fields.shift.oct
- gid = fields.shift.oct
- size = fields.shift.oct
- mtime = fields.shift.oct
- checksum = fields.shift.oct
- typeflag = fields.shift
- linkname = fields.shift
- magic = fields.shift
- version = fields.shift.oct
- uname = fields.shift
- gname = fields.shift
- devmajor = fields.shift.oct
- devminor = fields.shift.oct
- prefix = fields.shift
-
- new :name => name,
- :mode => mode,
- :uid => uid,
- :gid => gid,
- :size => size,
- :mtime => mtime,
- :checksum => checksum,
- :typeflag => typeflag,
- :linkname => linkname,
- :magic => magic,
- :version => version,
- :uname => uname,
- :gname => gname,
- :devmajor => devmajor,
- :devminor => devminor,
- :prefix => prefix,
-
- :empty => empty
-
- # HACK unfactor for Rubinius
- #new :name => fields.shift,
- # :mode => fields.shift.oct,
- # :uid => fields.shift.oct,
- # :gid => fields.shift.oct,
- # :size => fields.shift.oct,
- # :mtime => fields.shift.oct,
- # :checksum => fields.shift.oct,
- # :typeflag => fields.shift,
- # :linkname => fields.shift,
- # :magic => fields.shift,
- # :version => fields.shift.oct,
- # :uname => fields.shift,
- # :gname => fields.shift,
- # :devmajor => fields.shift.oct,
- # :devminor => fields.shift.oct,
- # :prefix => fields.shift,
-
- # :empty => empty
+ new :name => fields.shift,
+ :mode => fields.shift.oct,
+ :uid => fields.shift.oct,
+ :gid => fields.shift.oct,
+ :size => fields.shift.oct,
+ :mtime => fields.shift.oct,
+ :checksum => fields.shift.oct,
+ :typeflag => fields.shift,
+ :linkname => fields.shift,
+ :magic => fields.shift,
+ :version => fields.shift.oct,
+ :uname => fields.shift,
+ :gname => fields.shift,
+ :devmajor => fields.shift.oct,
+ :devminor => fields.shift.oct,
+ :prefix => fields.shift,
+
+ :empty => empty
end
##
diff --git a/lib/rubygems/package/tar_input.rb b/lib/rubygems/package/tar_input.rb
deleted file mode 100644
index 77b4d698da..0000000000
--- a/lib/rubygems/package/tar_input.rb
+++ /dev/null
@@ -1,235 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-#++
-# Copyright (C) 2004 Mauricio Julio Fernández Pradier
-# See LICENSE.txt for additional licensing information.
-#--
-
-require 'zlib'
-Gem.load_yaml
-
-class Gem::Package::TarInput
-
- include Gem::Package::FSyncDir
- include Enumerable
-
- attr_reader :metadata
-
- private_class_method :new
-
- def self.open(io, security_policy = nil, &block)
- is = new io, security_policy
-
- yield is
- ensure
- is.close if is
- end
-
- def initialize(io, security_policy = nil)
- @io = io
- @tarreader = Gem::Package::TarReader.new @io
- has_meta = false
-
- data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
- dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil
-
- @tarreader.each do |entry|
- case entry.full_name
- when "metadata"
- @metadata = load_gemspec entry.read
- has_meta = true
- when "metadata.gz"
- begin
- # if we have a security_policy, then pre-read the metadata file
- # and calculate it's digest
- sio = nil
- if security_policy
- Gem.ensure_ssl_available
- sio = StringIO.new(entry.read)
- meta_dgst = dgst_algo.digest(sio.string)
- sio.rewind
- end
-
- # Ruby 1.8 doesn't have encoding and YAML is UTF-8
- args = [sio || entry]
- args << { :external_encoding => Encoding::UTF_8 } if
- Object.const_defined?(:Encoding)
-
- gzis = Zlib::GzipReader.new(*args)
-
- # YAML wants an instance of IO
- @metadata = load_gemspec(gzis)
- has_meta = true
- ensure
- gzis.close unless gzis.nil?
- end
- when 'metadata.gz.sig'
- meta_sig = entry.read
- when 'data.tar.gz.sig'
- data_sig = entry.read
- when 'data.tar.gz'
- if security_policy
- Gem.ensure_ssl_available
- data_dgst = dgst_algo.digest(entry.read)
- end
- end
- end
-
- if security_policy then
- Gem.ensure_ssl_available
-
- # map trust policy from string to actual class (or a serialized YAML
- # file, if that exists)
- if String === security_policy then
- if Gem::Security::Policies.key? security_policy then
- # load one of the pre-defined security policies
- security_policy = Gem::Security::Policies[security_policy]
- elsif File.exist? security_policy then
- # FIXME: this doesn't work yet
- security_policy = YAML.load File.read(security_policy)
- else
- raise Gem::Exception, "Unknown trust policy '#{security_policy}'"
- end
- end
-
- if data_sig && data_dgst && meta_sig && meta_dgst then
- # the user has a trust policy, and we have a signed gem
- # file, so use the trust policy to verify the gem signature
-
- begin
- security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
- rescue Exception => e
- raise "Couldn't verify data signature: #{e}"
- end
-
- begin
- security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
- rescue Exception => e
- raise "Couldn't verify metadata signature: #{e}"
- end
- elsif security_policy.only_signed
- raise Gem::Exception, "Unsigned gem"
- else
- # FIXME: should display warning here (trust policy, but
- # either unsigned or badly signed gem file)
- end
- end
-
- @tarreader.rewind
-
- unless has_meta then
- path = io.path if io.respond_to? :path
- error = Gem::Package::FormatError.new 'no metadata found', path
- raise error
- end
- end
-
- def close
- @io.close
- @tarreader.close
- end
-
- def each(&block)
- @tarreader.each do |entry|
- next unless entry.full_name == "data.tar.gz"
- is = zipped_stream entry
-
- begin
- Gem::Package::TarReader.new is do |inner|
- inner.each(&block)
- end
- ensure
- is.close if is
- end
- end
-
- @tarreader.rewind
- end
-
- def extract_entry(destdir, entry, expected_md5sum = nil)
- if entry.directory? then
- dest = File.join destdir, entry.full_name
-
- if File.directory? dest then
- FileUtils.chmod entry.header.mode, dest, :verbose => false
- else
- FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
- end
-
- fsync_dir dest
- fsync_dir File.join(dest, "..")
-
- return
- end
-
- # it's a file
- md5 = Digest::MD5.new if expected_md5sum
- destdir = File.join destdir, File.dirname(entry.full_name)
- FileUtils.mkdir_p destdir, :mode => 0755, :verbose => false
- destfile = File.join destdir, File.basename(entry.full_name)
- FileUtils.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT
-
- open destfile, "wb", entry.header.mode do |os|
- loop do
- data = entry.read 4096
- break unless data
- # HACK shouldn't we check the MD5 before writing to disk?
- md5 << data if expected_md5sum
- os.write(data)
- end
-
- os.fsync
- end
-
- FileUtils.chmod entry.header.mode, destfile, :verbose => false
- fsync_dir File.dirname(destfile)
- fsync_dir File.join(File.dirname(destfile), "..")
-
- if expected_md5sum && expected_md5sum != md5.hexdigest then
- raise Gem::Package::BadCheckSum
- end
- end
-
- # Attempt to YAML-load a gemspec from the given _io_ parameter. Return
- # nil if it fails.
- def load_gemspec(io)
- Gem::Specification.from_yaml io
- rescue Gem::Exception
- nil
- end
-
- ##
- # Return an IO stream for the zipped entry.
- #
- # NOTE: Originally this method used two approaches, Return a GZipReader
- # directly, or read the GZipReader into a string and return a StringIO on
- # the string. The string IO approach was used for versions of ZLib before
- # 1.2.1 to avoid buffer errors on windows machines. Then we found that
- # errors happened with 1.2.1 as well, so we changed the condition. Then
- # we discovered errors occurred with versions as late as 1.2.3. At this
- # point (after some benchmarking to show we weren't seriously crippling
- # the unpacking speed) we threw our hands in the air and declared that
- # this method would use the String IO approach on all platforms at all
- # times. And that's the way it is.
- #
- # Revisited. Here's the beginning of the long story.
- # http://osdir.com/ml/lang.ruby.gems.devel/2007-06/msg00045.html
- #
- # StringIO wraping has never worked as a workaround by definition. Skipping
- # initial 10 bytes and passing -MAX_WBITS to Zlib::Inflate luckily works as
- # gzip reader, but it only works if the GZip header is 10 bytes long (see
- # below) and it does not check inflated stream consistency (CRC value in the
- # Gzip trailer.)
- #
- # RubyGems generated Gzip Header: 10 bytes
- # magic(2) + method(1) + flag(1) + mtime(4) + exflag(1) + os(1) +
- # orig_name(0) + comment(0)
- #
- # Ideally, it must return a GZipReader without meaningless buffering. We
- # have lots of CRuby committers around so let's fix windows build when we
- # received an error.
- def zipped_stream(entry)
- Zlib::GzipReader.new entry
- end
-
-end
-
diff --git a/lib/rubygems/package/tar_output.rb b/lib/rubygems/package/tar_output.rb
deleted file mode 100644
index fdc8f4fb7c..0000000000
--- a/lib/rubygems/package/tar_output.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-# -*- coding: utf-8 -*-
-#--
-# Copyright (C) 2004 Mauricio Julio Fernández Pradier
-# See LICENSE.txt for additional licensing information.
-#++
-
-##
-# TarOutput is a wrapper to TarWriter that builds gem-format tar file.
-#
-# Gem-format tar files contain the following files:
-# [data.tar.gz] A gzipped tar file containing the files that compose the gem
-# which will be extracted into the gem/ dir on installation.
-# [metadata.gz] A YAML format Gem::Specification.
-# [data.tar.gz.sig] A signature for the gem's data.tar.gz.
-# [metadata.gz.sig] A signature for the gem's metadata.gz.
-#
-# See TarOutput::open for usage details.
-
-class Gem::Package::TarOutput
-
- ##
- # Creates a new TarOutput which will yield a TarWriter object for the
- # data.tar.gz portion of a gem-format tar file.
- #
- # See #initialize for details on +io+ and +signer+.
- #
- # See #add_gem_contents for details on adding metadata to the tar file.
-
- def self.open(io, signer = nil, &block) # :yield: data_tar_writer
- tar_outputter = new io, signer
- tar_outputter.add_gem_contents(&block)
- tar_outputter.add_metadata
- tar_outputter.add_signatures
-
- ensure
- tar_outputter.close
- end
-
- ##
- # Creates a new TarOutput that will write a gem-format tar file to +io+. If
- # +signer+ is given, the data.tar.gz and metadata.gz will be signed and
- # the signatures will be added to the tar file.
-
- def initialize(io, signer)
- @io = io
- @signer = signer
-
- @tar_writer = Gem::Package::TarWriter.new @io
-
- @metadata = nil
-
- @data_signature = nil
- @meta_signature = nil
- end
-
- ##
- # Yields a TarWriter for the data.tar.gz inside a gem-format tar file.
- # The yielded TarWriter has been extended with a #metadata= method for
- # attaching a YAML format Gem::Specification which will be written by
- # add_metadata.
-
- def add_gem_contents
- @tar_writer.add_file "data.tar.gz", 0644 do |inner|
- sio = @signer ? StringIO.new : nil
- Zlib::GzipWriter.wrap(sio || inner) do |os|
-
- Gem::Package::TarWriter.new os do |data_tar_writer|
- # :stopdoc:
- def data_tar_writer.metadata() @metadata end
- def data_tar_writer.metadata=(metadata) @metadata = metadata end
- # :startdoc:
-
- yield data_tar_writer
-
- @metadata = data_tar_writer.metadata
- end
- end
-
- # if we have a signing key, then sign the data
- # digest and return the signature
- if @signer then
- require 'rubygems/security'
- digest = Gem::Security::OPT[:dgst_algo].digest sio.string
- @data_signature = @signer.sign digest
- inner.write sio.string
- end
- end
-
- self
- end
-
- ##
- # Adds metadata.gz to the gem-format tar file which was saved from a
- # previous #add_gem_contents call.
-
- def add_metadata
- return if @metadata.nil?
-
- @tar_writer.add_file "metadata.gz", 0644 do |io|
- begin
- sio = @signer ? StringIO.new : nil
- gzos = Zlib::GzipWriter.new(sio || io)
- gzos.write @metadata
- ensure
- gzos.flush
- gzos.finish
-
- # if we have a signing key, then sign the metadata digest and return
- # the signature
- if @signer then
- require 'rubygems/security'
- digest = Gem::Security::OPT[:dgst_algo].digest sio.string
- @meta_signature = @signer.sign digest
- io.write sio.string
- end
- end
- end
- end
-
- ##
- # Adds data.tar.gz.sig and metadata.gz.sig to the gem-format tar files if
- # a Gem::Security::Signer was sent to initialize.
-
- def add_signatures
- if @data_signature then
- @tar_writer.add_file 'data.tar.gz.sig', 0644 do |io|
- io.write @data_signature
- end
- end
-
- if @meta_signature then
- @tar_writer.add_file 'metadata.gz.sig', 0644 do |io|
- io.write @meta_signature
- end
- end
- end
-
- ##
- # Closes the TarOutput.
-
- def close
- @tar_writer.close
- end
-
-end
-
diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb
index e6a71d386c..e257fdd846 100644
--- a/lib/rubygems/package/tar_reader.rb
+++ b/lib/rubygems/package/tar_reader.rb
@@ -9,7 +9,7 @@
class Gem::Package::TarReader
- include Gem::Package
+ include Enumerable
##
# Raised if the tar IO is not seekable
@@ -52,9 +52,9 @@ class Gem::Package::TarReader
# Iterates over files in the tarball yielding each entry
def each
- loop do
- return if @io.eof?
+ return enum_for __method__ unless block_given?
+ until @io.eof? do
header = Gem::Package::TarHeader.from @io
return if header.empty?
@@ -100,6 +100,23 @@ class Gem::Package::TarReader
end
end
+ ##
+ # Seeks through the tar file until it finds the +entry+ with +name+ and
+ # yields it. Rewinds the tar file to the beginning when the block
+ # terminates.
+
+ def seek name # :yields: entry
+ found = find do |entry|
+ entry.full_name == name
+ end
+
+ return unless found
+
+ return yield found
+ ensure
+ rewind
+ end
+
end
require 'rubygems/package/tar_reader/entry'
diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb
index a73b5e5cab..f2c11e3544 100644
--- a/lib/rubygems/package/tar_writer.rb
+++ b/lib/rubygems/package/tar_writer.rb
@@ -40,12 +40,12 @@ class Gem::Package::TarWriter
# number of bytes will be more than #limit
def write(data)
- if data.size + @written > @limit
+ if data.bytesize + @written > @limit
raise FileOverflow, "You tried to feed more data than fits in the file."
end
@io.write data
- @written += data.size
- data.size
+ @written += data.bytesize
+ data.bytesize
end
end
@@ -130,6 +130,62 @@ class Gem::Package::TarWriter
end
##
+ # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing
+ # the file. The +digest_algorithm+ is written to a read-only +name+.sum
+ # file following the given file contents containing the digest name and
+ # hexdigest separated by a tab.
+ #
+ # The created digest object is returned.
+
+ def add_file_digest name, mode, digest_algorithms # :yields: io
+ digests = digest_algorithms.map do |digest_algorithm|
+ digest = digest_algorithm.new
+ [digest.name, digest]
+ end
+
+ digests = Hash[*digests.flatten]
+
+ add_file name, mode do |io|
+ Gem::Package::DigestIO.wrap io, digests do |digest_io|
+ yield digest_io
+ end
+ end
+
+ digests
+ end
+
+ ##
+ # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing
+ # the file. The +signer+ is used to add a digest file using its
+ # digest_algorithm per add_file_digest and a cryptographic signature in
+ # +name+.sig. If the signer has no key only the checksum file is added.
+ #
+ # Returns the digest.
+
+ def add_file_signed name, mode, signer
+ digest_algorithms = [
+ signer.digest_algorithm,
+ OpenSSL::Digest::SHA512,
+ ].uniq
+
+ digests = add_file_digest name, mode, digest_algorithms do |io|
+ yield io
+ end
+
+ signature_digest = digests.values.find do |digest|
+ digest.name == signer.digest_name
+ end
+
+ signature = signer.sign signature_digest.digest
+
+ add_file_simple "#{name}.sig", 0444, signature.length do |io|
+ io.write signature
+ end if signature
+
+ digests
+ end
+
+ ##
# Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO
# to write the file to.
@@ -211,9 +267,9 @@ class Gem::Package::TarWriter
# Splits +name+ into a name and prefix that can fit in the TarHeader
def split_name(name) # :nodoc:
- raise Gem::Package::TooLongFileName if name.size > 256
+ raise Gem::Package::TooLongFileName if name.bytesize > 256
- if name.size <= 100 then
+ if name.bytesize <= 100 then
prefix = ""
else
parts = name.split(/\//)
@@ -222,14 +278,14 @@ class Gem::Package::TarWriter
loop do
nxt = parts.pop
- break if newname.size + 1 + nxt.size > 100
+ break if newname.bytesize + 1 + nxt.bytesize > 100
newname = nxt + "/" + newname
end
prefix = (parts + [nxt]).join "/"
name = newname
- if name.size > 100 or prefix.size > 155 then
+ if name.bytesize > 100 or prefix.bytesize > 155 then
raise Gem::Package::TooLongFileName
end
end
diff --git a/lib/rubygems/package_task.rb b/lib/rubygems/package_task.rb
index fe32a03b27..463f8d32cc 100644
--- a/lib/rubygems/package_task.rb
+++ b/lib/rubygems/package_task.rb
@@ -20,6 +20,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'rubygems'
+require 'rubygems/package'
begin
gem 'rake'
rescue Gem::LoadError
@@ -43,13 +44,10 @@ require 'rake/packagetask'
# require 'rubygems/package_task'
#
# spec = Gem::Specification.new do |s|
-# s.platform = Gem::Platform::RUBY
# s.summary = "Ruby based make-like utility."
# s.name = 'rake'
# s.version = PKG_VERSION
# s.requirements << 'none'
-# s.require_path = 'lib'
-# s.autorequire = 'rake'
# s.files = PKG_FILES
# s.description = <<-EOF
# Rake is a Make-like program implemented in Ruby. Tasks
@@ -113,7 +111,8 @@ class Gem::PackageTask < Rake::PackageTask
file gem_path => [package_dir, gem_dir] + @gem_spec.files do
chdir(gem_dir) do
when_writing "Creating #{gem_spec.file_name}" do
- Gem::Builder.new(gem_spec).build
+ Gem::Package.build gem_spec
+
verbose trace do
mv gem_file, '..'
end
diff --git a/lib/rubygems/path_support.rb b/lib/rubygems/path_support.rb
index 0aaf2c1bed..059e372112 100644
--- a/lib/rubygems/path_support.rb
+++ b/lib/rubygems/path_support.rb
@@ -1,4 +1,5 @@
##
+#
# Gem::PathSupport facilitates the GEM_HOME and GEM_PATH environment settings
# to the rest of RubyGems.
#
@@ -42,16 +43,18 @@ class Gem::PathSupport
# Set the Gem search path (as reported by Gem.path).
def path=(gpaths)
- gem_path = [@home]
+ # FIX: it should be [home, *path], not [*path, home]
+
+ gem_path = []
# FIX: I can't tell wtf this is doing.
gpaths ||= (ENV['GEM_PATH'] || "").empty? ? nil : ENV["GEM_PATH"]
- if gpaths then
- if gpaths.kind_of?(Array) then
- gem_path.push(*gpaths)
+ if gpaths
+ if gpaths.kind_of?(Array)
+ gem_path = gpaths.dup
else
- gem_path.push(*gpaths.split(File::PATH_SEPARATOR))
+ gem_path = gpaths.split(File::PATH_SEPARATOR)
end
if File::ALT_SEPARATOR then
@@ -59,10 +62,14 @@ class Gem::PathSupport
this_path.gsub File::ALT_SEPARATOR, File::SEPARATOR
end
end
+
+ gem_path << @home
else
- gem_path.push(*Gem.default_path)
+ gem_path = Gem.default_path + [@home]
- gem_path << APPLE_GEM_HOME if defined?(APPLE_GEM_HOME)
+ if defined?(APPLE_GEM_HOME)
+ gem_path << APPLE_GEM_HOME
+ end
end
@path = gem_path.uniq
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index f67e4022b6..7301153c4b 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -21,7 +21,8 @@ class Gem::Platform
def self.match(platform)
Gem.platforms.any? do |local_platform|
- platform.nil? or local_platform == platform or
+ platform.nil? or
+ local_platform == platform or
(local_platform != Gem::Platform::RUBY and local_platform =~ platform)
end
end
@@ -65,28 +66,28 @@ class Gem::Platform
@cpu, os = nil, cpu if os.nil? # legacy jruby
@os, @version = case os
- when /aix(\d+)/ then [ 'aix', $1 ]
- when /cygwin/ then [ 'cygwin', nil ]
- when /darwin(\d+)?/ then [ 'darwin', $1 ]
- when /^macruby$/ then [ 'macruby', nil ]
- when /freebsd(\d+)/ then [ 'freebsd', $1 ]
- when /hpux(\d+)/ then [ 'hpux', $1 ]
- when /^java$/, /^jruby$/ then [ 'java', nil ]
- when /^java([\d.]*)/ then [ 'java', $1 ]
- when /^dotnet$/ then [ 'dotnet', nil ]
- when /^dotnet([\d.]*)/ then [ 'dotnet', $1 ]
- when /linux/ then [ 'linux', $1 ]
- when /mingw32/ then [ 'mingw32', nil ]
+ when /aix(\d+)?/ then [ 'aix', $1 ]
+ when /cygwin/ then [ 'cygwin', nil ]
+ when /darwin(\d+)?/ then [ 'darwin', $1 ]
+ when /^macruby$/ then [ 'macruby', nil ]
+ when /freebsd(\d+)?/ then [ 'freebsd', $1 ]
+ when /hpux(\d+)?/ then [ 'hpux', $1 ]
+ when /^java$/, /^jruby$/ then [ 'java', nil ]
+ when /^java([\d.]*)/ then [ 'java', $1 ]
+ when /^dotnet$/ then [ 'dotnet', nil ]
+ when /^dotnet([\d.]*)/ then [ 'dotnet', $1 ]
+ when /linux/ then [ 'linux', $1 ]
+ when /mingw32/ then [ 'mingw32', nil ]
when /(mswin\d+)(\_(\d+))?/ then
os, version = $1, $3
@cpu = 'x86' if @cpu.nil? and os =~ /32$/
[os, version]
- when /(netbsd[a-z]*)(\d+)/ then [ $1, $2 ]
- when /openbsd(\d+\.\d+)/ then [ 'openbsd', $1 ]
- when /solaris(\d+\.\d+)/ then [ 'solaris', $1 ]
+ when /netbsdelf/ then [ 'netbsdelf', nil ]
+ when /openbsd(\d+\.\d+)?/ then [ 'openbsd', $1 ]
+ when /solaris(\d+\.\d+)?/ then [ 'solaris', $1 ]
# test
- when /^(\w+_platform)(\d+)/ then [ $1, $2 ]
- else [ 'unknown', nil ]
+ when /^(\w+_platform)(\d+)?/ then [ $1, $2 ]
+ else [ 'unknown', nil ]
end
when Gem::Platform then
@cpu = arch.cpu
@@ -109,10 +110,6 @@ class Gem::Platform
to_a.compact.join '-'
end
- def empty?
- to_s.empty?
- end
-
##
# Is +other+ equal to this platform? Two platforms are equal if they have
# the same CPU, OS and version.
@@ -186,9 +183,5 @@ class Gem::Platform
# This will be replaced with Gem::Platform::local.
CURRENT = 'current'
-
- extend Gem::Deprecate
-
- deprecate :empty?, :none, 2011, 11
end
diff --git a/lib/rubygems/rdoc.rb b/lib/rubygems/rdoc.rb
new file mode 100644
index 0000000000..65414462fa
--- /dev/null
+++ b/lib/rubygems/rdoc.rb
@@ -0,0 +1,316 @@
+require 'rubygems'
+require 'rubygems/user_interaction'
+require 'fileutils'
+
+begin
+ gem 'rdoc'
+rescue Gem::LoadError
+ # swallow
+else
+ # This will force any deps that 'rdoc' might have
+ # (such as json) that are ambigious to be activated, which
+ # is important because we end up using Specification.reset
+ # and we don't want the warning it pops out.
+ Gem.finish_resolve
+end
+
+loaded_hook = false
+
+begin
+ require 'rdoc/rubygems_hook'
+ loaded_hook = true
+ module Gem
+ RDoc = RDoc::RubygemsHook
+ end
+rescue LoadError
+end
+
+##
+# Gem::RDoc provides methods to generate RDoc and ri data for installed gems.
+# It works for RDoc 1.0.1 (in Ruby 1.8) up to RDoc 3.6.
+#
+# This implementation is considered obsolete. The RDoc project is the
+# appropriate location to find this functionality. This file provides the
+# hooks to load RDoc generation code from the "rdoc" gem and a fallback in
+# case the installed version of RDoc does not have them.
+
+class Gem::RDoc # :nodoc: all
+
+ include Gem::UserInteraction
+
+ @rdoc_version = nil
+ @specs = []
+
+ ##
+ # Force installation of documentation?
+
+ attr_accessor :force
+
+ ##
+ # Generate rdoc?
+
+ attr_accessor :generate_rdoc
+
+ ##
+ # Generate ri data?
+
+ attr_accessor :generate_ri
+
+ class << self
+
+ ##
+ # Loaded version of RDoc. Set by ::load_rdoc
+
+ attr_reader :rdoc_version
+
+ end
+
+ ##
+ # Post installs hook that generates documentation for each specification in
+ # +specs+
+
+ def self.generation_hook installer, specs
+ types = installer.document
+
+ generate_rdoc = types.include? 'rdoc'
+ generate_ri = types.include? 'ri'
+
+ specs.each do |spec|
+ new(spec, generate_rdoc, generate_ri).generate
+ end
+ end
+
+ ##
+ # Loads the RDoc generator
+
+ def self.load_rdoc
+ return if @rdoc_version
+
+ begin
+ require 'rdoc/rdoc'
+
+ @rdoc_version = if ::RDoc.const_defined? :VERSION then
+ Gem::Version.new ::RDoc::VERSION
+ else
+ Gem::Version.new '1.0.1'
+ end
+
+ rescue LoadError => e
+ raise Gem::DocumentError, "RDoc is not installed: #{e}"
+ end
+ end
+
+ ##
+ # Creates a new documentation generator for +spec+. RDoc and ri data
+ # generation can be enabled or disabled through +generate_rdoc+ and
+ # +generate_ri+ respectively.
+ #
+ # Only +generate_ri+ is enabled by default.
+
+ def initialize spec, generate_rdoc = false, generate_ri = true
+ @doc_dir = spec.doc_dir
+ @file_info = nil
+ @force = false
+ @rdoc = nil
+ @spec = spec
+
+ @generate_rdoc = generate_rdoc
+ @generate_ri = generate_ri
+
+ @rdoc_dir = spec.doc_dir 'rdoc'
+ @ri_dir = spec.doc_dir 'ri'
+ end
+
+ ##
+ # Removes legacy rdoc arguments from +args+
+
+ def delete_legacy_args args
+ args.delete '--inline-source'
+ args.delete '--promiscuous'
+ args.delete '-p'
+ args.delete '--one-file'
+ end
+
+ ##
+ # Generates documentation using the named +generator+ ("darkfish" or "ri")
+ # and following the given +options+.
+ #
+ # Documentation will be generated into +destination+
+
+ def document generator, options, destination
+ options = options.dup
+ options.exclude ||= [] # TODO maybe move to RDoc::Options#finish
+ options.setup_generator generator
+ options.op_dir = destination
+ options.finish
+
+ @rdoc.options = options
+ @rdoc.generator = options.generator.new options
+
+ say "Installing #{generator} documentation for #{@spec.full_name}"
+
+ FileUtils.mkdir_p options.op_dir
+
+ Dir.chdir options.op_dir do
+ begin
+ @rdoc.class.current = @rdoc
+ @rdoc.generator.generate @file_info
+ ensure
+ @rdoc.class.current = nil
+ end
+ end
+ end
+
+ ##
+ # Generates RDoc and ri data
+
+ def generate
+ return unless @generate_ri or @generate_rdoc
+
+ setup
+
+ if Gem::Requirement.new('< 3').satisfied_by? self.class.rdoc_version then
+ generate_legacy
+ else
+ ::RDoc::TopLevel.reset # TODO ::RDoc::RDoc.reset
+ ::RDoc::Parser::C.reset
+
+ options = ::RDoc::Options.new
+ options.default_title = "#{@spec.full_name} Documentation"
+ options.files = []
+ options.files.push(*@spec.require_paths)
+ options.files.push(*@spec.extra_rdoc_files)
+
+ args = @spec.rdoc_options
+
+ case config_args = Gem.configuration[:rdoc]
+ when String then
+ args = args.concat config_args.split
+ when Array then
+ args = args.concat config_args
+ end
+
+ delete_legacy_args args
+ options.parse args
+ options.quiet = !Gem.configuration.really_verbose
+
+ @rdoc = new_rdoc
+ @rdoc.options = options
+
+ Dir.chdir @spec.full_gem_path do
+ @file_info = @rdoc.parse_files options.files
+ end
+
+ document 'ri', options, @ri_dir if
+ @generate_ri and (@force or not File.exist? @ri_dir)
+
+ document 'darkfish', options, @rdoc_dir if
+ @generate_rdoc and (@force or not File.exist? @rdoc_dir)
+ end
+ end
+
+ ##
+ # Generates RDoc and ri data for legacy RDoc versions. This method will not
+ # exist in future versions.
+
+ def generate_legacy
+ if @generate_rdoc then
+ FileUtils.rm_rf @rdoc_dir
+ say "Installing RDoc documentation for #{@spec.full_name}"
+ legacy_rdoc '--op', @rdoc_dir
+ end
+
+ if @generate_ri then
+ FileUtils.rm_rf @ri_dir
+ say "Installing ri documentation for #{@spec.full_name}"
+ legacy_rdoc '--ri', '--op', @ri_dir
+ end
+ end
+
+ ##
+ # Generates RDoc using a legacy version of RDoc from the ARGV-like +args+.
+ # This method will not exist in future versions.
+
+ def legacy_rdoc *args
+ args << @spec.rdoc_options
+ args << '--quiet'
+ args << @spec.require_paths.clone
+ args << @spec.extra_rdoc_files
+ args << '--title' << "#{@spec.full_name} Documentation"
+ args = args.flatten.map do |arg| arg.to_s end
+
+ delete_legacy_args args if
+ Gem::Requirement.new('>= 2.4.0') =~ self.class.rdoc_version
+
+ r = new_rdoc
+ say "rdoc #{args.join ' '}" if Gem.configuration.really_verbose
+
+ Dir.chdir @spec.full_gem_path do
+ begin
+ r.document args
+ rescue Errno::EACCES => e
+ dirname = File.dirname e.message.split("-")[1].strip
+ raise Gem::FilePermissionError, dirname
+ rescue Interrupt => e
+ raise e
+ rescue Exception => ex
+ alert_error "While generating documentation for #{@spec.full_name}"
+ ui.errs.puts "... MESSAGE: #{ex}"
+ ui.errs.puts "... RDOC args: #{args.join(' ')}"
+ ui.backtrace ex
+ ui.errs.puts "(continuing with the rest of the installation)"
+ ensure
+ end
+ end
+ end
+
+ ##
+ # #new_rdoc creates a new RDoc instance. This method is provided only to
+ # make testing easier.
+
+ def new_rdoc
+ ::RDoc::RDoc.new
+ end
+
+ ##
+ # Is rdoc documentation installed?
+
+ def rdoc_installed?
+ File.exist? @rdoc_dir
+ end
+
+ ##
+ # Removes generated RDoc and ri data
+
+ def remove
+ base_dir = @spec.base_dir
+
+ raise Gem::FilePermissionError, base_dir unless File.writable? base_dir
+
+ FileUtils.rm_rf @rdoc_dir
+ FileUtils.rm_rf @ri_dir
+ end
+
+ ##
+ # Is ri data installed?
+
+ def ri_installed?
+ File.exist? @ri_dir
+ end
+
+ ##
+ # Prepares the spec for documentation generation
+
+ def setup
+ self.class.load_rdoc
+
+ raise Gem::FilePermissionError, @doc_dir if
+ File.exist?(@doc_dir) and not File.writable?(@doc_dir)
+
+ FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir
+ end
+
+end unless loaded_hook
+
+Gem.done_installing(&Gem::RDoc.method(:generation_hook))
+
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 37699d17fc..4bb2e3604f 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -1,6 +1,7 @@
require 'rubygems'
require 'rubygems/user_interaction'
require 'uri'
+require 'resolv'
##
# RemoteFetcher handles the details of fetching gems and gem information from
@@ -8,8 +9,6 @@ require 'uri'
class Gem::RemoteFetcher
- BuiltinSSLCerts = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__))
-
include Gem::UserInteraction
##
@@ -34,6 +33,13 @@ class Gem::RemoteFetcher
end
+ ##
+ # A FetchError that indicates that the reason for not being
+ # able to fetch data was that the host could not be contacted
+
+ class UnknownHostError < FetchError
+ end
+
@fetcher = nil
##
@@ -53,8 +59,11 @@ 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
+ #
+ # +dns+: An object to use for DNS resolution of the API endpoint.
+ # By default, use Resolv::DNS.
- def initialize(proxy = nil)
+ def initialize(proxy=nil, dns=Resolv::DNS.new)
require 'net/http'
require 'stringio'
require 'time'
@@ -72,6 +81,27 @@ class Gem::RemoteFetcher
else URI.parse(proxy)
end
@user_agent = user_agent
+ @env_no_proxy = get_no_proxy_from_env
+
+ @dns = dns
+ end
+
+ ##
+ #
+ # Given a source at +uri+, calculate what hostname to actually
+ # connect to query the data for it.
+
+ def api_endpoint(uri)
+ host = uri.host
+
+ begin
+ res = @dns.getresource "_rubygems._tcp.#{host}",
+ Resolv::DNS::Resource::IN::SRV
+ rescue Resolv::ResolvError
+ uri
+ else
+ URI.parse "#{res.target}#{uri.path}"
+ end
end
##
@@ -82,14 +112,13 @@ class Gem::RemoteFetcher
# larger, more emcompassing effort. -erikh
def download_to_cache dependency
- found = Gem::SpecFetcher.fetcher.fetch dependency, true, true,
- dependency.prerelease?
+ found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
return if found.empty?
- spec, source_uri = found.sort_by { |(s,_)| s.version }.last
+ spec, source = found.sort_by { |(s,_)| s.version }.last
- download spec, source_uri
+ download spec, source.uri.to_s
end
##
@@ -100,11 +129,14 @@ class Gem::RemoteFetcher
def download(spec, source_uri, install_dir = Gem.dir)
Gem.ensure_gem_subdirectories(install_dir) rescue nil
- if File.writable?(install_dir)
- cache_dir = File.join install_dir, "cache"
- else
- cache_dir = File.join Gem.user_dir, "cache"
- end
+ cache_dir =
+ if Dir.pwd == install_dir then # see fetch_command
+ install_dir
+ elsif File.writable? install_dir then
+ File.join install_dir, "cache"
+ else
+ File.join Gem.user_dir, "cache"
+ end
gem_file_name = File.basename spec.cache_file
local_gem_path = File.join cache_dir, gem_file_name
@@ -123,6 +155,8 @@ class Gem::RemoteFetcher
# URI.parse gets confused by MS Windows paths with forward slashes.
scheme = nil if scheme =~ /^[a-z]$/i
+ # REFACTOR: split this up and dispatch on scheme (eg download_http)
+ # REFACTOR: be sure to clean up fake fetcher when you do this... cleaner
case scheme
when 'http', 'https' then
unless File.exist? local_gem_path then
@@ -132,7 +166,7 @@ class Gem::RemoteFetcher
remote_gem_path = source_uri + "gems/#{gem_file_name}"
- gem = self.fetch_path remote_gem_path
+ self.cache_update_path remote_gem_path, local_gem_path
rescue Gem::RemoteFetcher::FetchError
raise if spec.original_platform == spec.platform
@@ -143,11 +177,7 @@ class Gem::RemoteFetcher
remote_gem_path = source_uri + "gems/#{alternate_name}"
- gem = self.fetch_path remote_gem_path
- end
-
- File.open local_gem_path, 'wb' do |fp|
- fp.write gem
+ self.cache_update_path remote_gem_path, local_gem_path
end
end
when 'file' then
@@ -184,7 +214,7 @@ class Gem::RemoteFetcher
say "Using local gem #{local_gem_path}" if
Gem.configuration.really_verbose
else
- raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
+ raise ArgumentError, "unsupported URI scheme #{source_uri.scheme}"
end
local_gem_path
@@ -232,18 +262,54 @@ class Gem::RemoteFetcher
uri = URI.parse uri unless URI::Generic === uri
raise ArgumentError, "bad uri: #{uri}" unless uri
- raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" unless
- uri.scheme
+
+ unless uri.scheme
+ raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
+ end
data = send "fetch_#{uri.scheme}", uri, mtime, head
- data = Gem.gunzip data if data and not head and uri.to_s =~ /gz$/
+
+ if data and !head and uri.to_s =~ /gz$/
+ begin
+ data = Gem.gunzip data
+ rescue Zlib::GzipFile::Error
+ raise FetchError.new("server did not return a valid file", uri.to_s)
+ end
+ end
+
data
rescue FetchError
raise
rescue Timeout::Error
- raise FetchError.new('timed out', uri.to_s)
+ raise UnknownHostError.new('timed out', uri.to_s)
rescue IOError, SocketError, SystemCallError => e
- raise FetchError.new("#{e.class}: #{e}", uri.to_s)
+ if e.message =~ /getaddrinfo/
+ raise UnknownHostError.new('no such name', uri.to_s)
+ else
+ raise FetchError.new("#{e.class}: #{e}", uri.to_s)
+ end
+ end
+
+ ##
+ # Downloads +uri+ to +path+ if necessary. If no path is given, it just
+ # passes the data.
+
+ def cache_update_path(uri, path = nil)
+ mtime = path && File.stat(path).mtime rescue nil
+
+ if mtime && Net::HTTPNotModified === fetch_path(uri, mtime, true)
+ Gem.read_binary(path)
+ else
+ data = fetch_path(uri)
+
+ if path
+ open(path, 'wb') do |io|
+ io.write data
+ end
+ end
+
+ data
+ end
end
##
@@ -274,6 +340,17 @@ class Gem::RemoteFetcher
end
##
+ # Returns list of no_proxy entries (if any) from the environment
+
+ def get_no_proxy_from_env
+ env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
+
+ return [] if env_no_proxy.nil? or env_no_proxy.empty?
+
+ env_no_proxy.split(/\s*,\s*/)
+ end
+
+ ##
# Returns an HTTP proxy URI if one is set in the environment variables.
def get_proxy_from_env
@@ -296,7 +373,7 @@ class Gem::RemoteFetcher
# Normalize the URI by adding "http://" if it is missing.
def normalize_uri(uri)
- (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
+ (uri =~ /^(https?|ftp|file):/i) ? uri : "http://#{uri}"
end
##
@@ -306,7 +383,7 @@ class Gem::RemoteFetcher
def connection_for(uri)
net_http_args = [uri.host, uri.port]
- if @proxy_uri then
+ if @proxy_uri and not no_proxy?(uri.host) then
net_http_args += [
@proxy_uri.host,
@proxy_uri.port,
@@ -319,37 +396,23 @@ class Gem::RemoteFetcher
@connections[connection_id] ||= Net::HTTP.new(*net_http_args)
connection = @connections[connection_id]
- if https?(uri) and !connection.started? then
+ if https?(uri) and not connection.started? then
configure_connection_for_https(connection)
-
- # Don't refactor this with the else branch. We don't want the
- # http-only code path to not depend on anything in OpenSSL.
- #
- begin
- connection.start
- rescue OpenSSL::SSL::SSLError, Errno::EHOSTDOWN => e
- raise FetchError.new(e.message, uri)
- end
- else
- begin
- connection.start unless connection.started?
- rescue Errno::EHOSTDOWN => e
- raise FetchError.new(e.message, uri)
- end
end
+ connection.start unless connection.started?
+
connection
+ rescue OpenSSL::SSL::SSLError, Errno::EHOSTDOWN => e
+ raise FetchError.new(e.message, uri)
end
def configure_connection_for_https(connection)
require 'net/https'
-
connection.use_ssl = true
connection.verify_mode =
Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
-
store = OpenSSL::X509::Store.new
-
if Gem.configuration.ssl_ca_cert
if File.directory? Gem.configuration.ssl_ca_cert
store.add_path Gem.configuration.ssl_ca_cert
@@ -360,12 +423,12 @@ class Gem::RemoteFetcher
store.set_default_paths
add_rubygems_trusted_certs(store)
end
-
connection.cert_store = store
end
def add_rubygems_trusted_certs(store)
- Dir.glob(BuiltinSSLCerts).each do |ssl_cert_file|
+ pattern = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__))
+ Dir.glob(pattern).each do |ssl_cert_file|
store.add_file ssl_cert_file
end
end
@@ -378,13 +441,13 @@ class Gem::RemoteFetcher
end
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, last_modified = nil, head = false, depth = 0)
- raise "NO: Use fetch_path instead"
- # TODO: deprecate for fetch_path
+ def no_proxy? host
+ host = host.downcase
+ @env_no_proxy.each do |pattern|
+ pattern = pattern.downcase
+ return true if host[-pattern.length, pattern.length ] == pattern
+ end
+ return false
end
##
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
new file mode 100644
index 0000000000..6c52b90c40
--- /dev/null
+++ b/lib/rubygems/request_set.rb
@@ -0,0 +1,182 @@
+require 'rubygems'
+require 'rubygems/dependency'
+require 'rubygems/dependency_resolver'
+require 'rubygems/dependency_list'
+require 'rubygems/installer'
+require 'tsort'
+
+module Gem
+ class RequestSet
+
+ include TSort
+
+ def initialize(*deps)
+ @dependencies = deps
+
+ yield self if block_given?
+ end
+
+ attr_reader :dependencies
+
+ # Declare that a gem of name +name+ with +reqs+ requirements
+ # is needed.
+ #
+ def gem(name, *reqs)
+ @dependencies << Gem::Dependency.new(name, reqs)
+ end
+
+ # Add +deps+ Gem::Depedency objects to the set.
+ #
+ def import(deps)
+ @dependencies += deps
+ end
+
+ # Resolve the requested dependencies and return an Array of
+ # Specification objects to be activated.
+ #
+ def resolve(set=nil)
+ r = Gem::DependencyResolver.new(@dependencies, set)
+ @requests = r.resolve
+ end
+
+ # Resolve the requested dependencies against the gems
+ # available via Gem.path and return an Array of Specification
+ # objects to be activated.
+ #
+ def resolve_current
+ resolve DependencyResolver::CurrentSet.new
+ end
+
+ # Load a dependency management file.
+ #
+ def load_gemdeps(path)
+ gf = GemDepedencyAPI.new(self, path)
+ gf.load
+ end
+
+ def specs
+ @specs ||= @requests.map { |r| r.full_spec }
+ end
+
+ def tsort_each_node(&block)
+ @requests.each(&block)
+ end
+
+ def tsort_each_child(node)
+ node.spec.dependencies.each do |dep|
+ next if dep.type == :development
+
+ match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
+ if match
+ begin
+ yield match
+ rescue TSort::Cyclic
+ end
+ else
+ raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}"
+ end
+ end
+ end
+
+ def sorted_requests
+ @sorted ||= strongly_connected_components.flatten
+ end
+
+ def specs_in(dir)
+ Dir["#{dir}/specifications/*.gemspec"].map do |g|
+ Gem::Specification.load g
+ end
+ end
+
+ def install_into(dir, force=true, &b)
+ existing = force ? [] : specs_in(dir)
+
+ dir = File.expand_path dir
+
+ installed = []
+
+ sorted_requests.each do |req|
+ if existing.find { |s| s.full_name == req.spec.full_name }
+ b.call req, nil if b
+ next
+ end
+
+ path = req.download(dir)
+
+ inst = Gem::Installer.new path, :install_dir => dir,
+ :only_install_dir => true
+
+ b.call req, inst if b
+
+ inst.install
+
+ installed << req
+ end
+
+ installed
+ end
+
+ def install(options, &b)
+ if dir = options[:install_dir]
+ return install_into(dir, false, &b)
+ end
+
+ cache_dir = options[:cache_dir] || Gem.dir
+
+ specs = []
+
+ sorted_requests.each do |req|
+ if req.installed?
+ b.call req, nil if b
+ next
+ end
+
+ path = req.download cache_dir
+
+ inst = Gem::Installer.new path, options
+
+ b.call req, inst if b
+
+ specs << inst.install
+ end
+
+ specs
+ end
+
+ # A semi-compatible DSL for Bundler's Gemfile format
+ #
+ class GemDepedencyAPI
+ def initialize(set, path)
+ @set = set
+ @path = path
+ end
+
+ def load
+ instance_eval File.read(@path).untaint, @path, 1
+ end
+
+ # DSL
+
+ def source(url)
+ end
+
+ def gem(name, *reqs)
+ # Ignore the opts for now.
+ reqs.pop if reqs.last.kind_of?(Hash)
+
+ @set.gem name, *reqs
+ end
+
+ def platform(what)
+ if what == :ruby
+ yield
+ end
+ end
+
+ alias_method :platforms, :platform
+
+ def group(*what)
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/require_paths_builder.rb b/lib/rubygems/require_paths_builder.rb
deleted file mode 100644
index 23e974639f..0000000000
--- a/lib/rubygems/require_paths_builder.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'rubygems'
-
-# TODO: remove after 1.9.1 dropped
-module Gem::RequirePathsBuilder
- def write_require_paths_file_if_needed(spec = @spec, gem_home = @gem_home)
- return if spec.require_paths == ["lib"] &&
- (spec.bindir.nil? || spec.bindir == "bin")
- file_name = File.join(gem_home, 'gems', "#{@spec.full_name}", ".require_paths")
- file_name.untaint
- File.open(file_name, "w") do |file|
- spec.require_paths.each do |path|
- file.puts path
- end
- file.puts spec.bindir if spec.bindir
- end
- end
-end if Gem::QUICKLOADER_SUCKAGE
-
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index 7abff01c39..b80e8dca19 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -1,5 +1,3 @@
-require "rubygems/version"
-
##
# A Requirement is a set of one or more version restrictions. It supports a
# few (<tt>=, !=, >, <, >=, <=, ~></tt>) different restriction operators.
@@ -13,14 +11,16 @@ require "rubygems/version"
require "rubygems/version"
require "rubygems/deprecate"
-class Gem::Requirement
- include Comparable
+# If we're being loaded after yaml was already required, then
+# load our yaml + workarounds now.
+Gem.load_yaml if defined? ::YAML
+class Gem::Requirement
OPS = { #:nodoc:
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
- ">" => lambda { |v, r| v > r },
- "<" => lambda { |v, r| v < r },
+ ">" => lambda { |v, r| v > r },
+ "<" => lambda { |v, r| v < r },
">=" => lambda { |v, r| v >= r },
"<=" => lambda { |v, r| v <= r },
"~>" => lambda { |v, r| v >= r && v.release < r.bump }
@@ -29,6 +29,10 @@ class Gem::Requirement
quoted = OPS.keys.map { |k| Regexp.quote k }.join "|"
PATTERN = /\A\s*(#{quoted})?\s*(#{Gem::Version::VERSION_PATTERN})\s*\z/
+ DefaultRequirement = [">=", Gem::Version.new(0)]
+
+ class BadRequirementError < ArgumentError; end
+
##
# Factory method to create a Gem::Requirement object. Input may be
# a Version, a String, or nil. Intended to simplify client code.
@@ -36,6 +40,9 @@ class Gem::Requirement
# If the input is "weird", the default version requirement is
# returned.
+ # REFACTOR: There's no reason that this can't be unified with .new.
+ # .new is the standard Ruby factory method.
+
def self.create input
case input
when Gem::Requirement then
@@ -53,10 +60,6 @@ class Gem::Requirement
##
# A default "version requirement" can surely _only_ be '>= 0'.
- #--
- # This comment once said:
- #
- # "A default "version requirement" can surely _only_ be '> 0'."
def self.default
new '>= 0'
@@ -74,14 +77,23 @@ class Gem::Requirement
# parse("1.0") # => ["=", "1.0"]
# parse(Gem::Version.new("1.0")) # => ["=, "1.0"]
+ # REFACTOR: Little two element arrays like this have no real semantic
+ # value. I'd love to see something like this:
+ # Constraint = Struct.new(:operator, :version); (or similar)
+ # and have a Requirement be a list of Constraints.
+
def self.parse obj
return ["=", obj] if Gem::Version === obj
unless PATTERN =~ obj.to_s
- raise ArgumentError, "Illformed requirement [#{obj.inspect}]"
+ raise BadRequirementError, "Illformed requirement [#{obj.inspect}]"
end
- [$1 || "=", Gem::Version.new($2)]
+ if $1 == ">=" && $2 == "0"
+ DefaultRequirement
+ else
+ [$1 || "=", Gem::Version.new($2)]
+ end
end
##
@@ -101,13 +113,23 @@ class Gem::Requirement
requirements.compact!
requirements.uniq!
- requirements << ">= 0" if requirements.empty?
- @none = (requirements == ">= 0")
- @requirements = requirements.map! { |r| self.class.parse r }
+ if requirements.empty?
+ @requirements = [DefaultRequirement]
+ else
+ @requirements = requirements.map! { |r| self.class.parse r }
+ end
end
+ ##
+ # true if this gem has no requirements.
+
+ # FIX: maybe this should be using #default ?
def none?
- @none ||= (to_s == ">= 0")
+ if @requirements.size == 1
+ @requirements[0] == DefaultRequirement
+ else
+ false
+ end
end
def as_list # :nodoc:
@@ -135,6 +157,7 @@ class Gem::Requirement
instance_variable_set "@#{ivar}", val
end
+ Gem.load_yaml
fix_syck_default_key_in_requirements
end
@@ -142,6 +165,18 @@ class Gem::Requirement
yaml_initialize coder.tag, coder.map
end
+ def to_yaml_properties
+ ["@requirements"]
+ end
+
+ def encode_with(coder)
+ coder.add 'requirements', @requirements
+ end
+
+ ##
+ # A requirement is a prerelease if any of the versions inside of it
+ # are prereleases
+
def prerelease?
requirements.any? { |r| r.last.prerelease? }
end
@@ -156,6 +191,8 @@ class Gem::Requirement
# True if +version+ satisfies this Requirement.
def satisfied_by? version
+ raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless
+ Gem::Version === version
# #28965: syck has a bug with unquoted '=' YAML.loading as YAML::DefaultKey
requirements.all? { |op, rv| (OPS[op] || OPS["="]).call version, rv }
end
@@ -176,12 +213,14 @@ class Gem::Requirement
as_list.join ", "
end
- def <=> other # :nodoc:
- to_s <=> other.to_s
+ # DOC: this should probably be :nodoc'd
+ def == other
+ Gem::Requirement === other and to_s == other.to_s
end
private
+ # DOC: this should probably be :nodoc'd
def fix_syck_default_key_in_requirements
Gem.load_yaml
@@ -194,11 +233,9 @@ class Gem::Requirement
end
end
-# :stopdoc:
-# Gem::Version::Requirement is used in a lot of old YAML specs. It's aliased
-# here for backwards compatibility. I'd like to remove this, maybe in RubyGems
-# 2.0.
-
-::Gem::Version::Requirement = ::Gem::Requirement
-# :startdoc:
+# This is needed for compatibility with older yaml
+# gemspecs.
+class Gem::Version
+ Requirement = Gem::Requirement
+end
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index f51da65b4b..bec30e9238 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -5,80 +5,89 @@
#++
require 'rubygems/exceptions'
-require 'rubygems/gem_openssl'
+require 'openssl'
require 'fileutils'
+##
+# = Signing gems
#
-# = Signed Gems README
-#
-# == Table of Contents
-# * Overview
-# * Walkthrough
-# * Command-Line Options
-# * OpenSSL Reference
-# * Bugs/TODO
-# * About the Author
-#
-# == Overview
-#
-# Gem::Security implements cryptographic signatures in RubyGems. The section
+# The Gem::Security implements cryptographic signatures for gems. The section
# below is a step-by-step guide to using signed gems and generating your own.
#
# == Walkthrough
#
+# === Building your certificate
+#
# In order to start signing your gems, you'll need to build a private key and
# a self-signed certificate. Here's how:
#
-# # build a private key and certificate for gemmaster@example.com
-# $ gem cert --build gemmaster@example.com
+# # build a private key and certificate for yourself:
+# $ gem cert --build you@example.com
#
-# This could take anywhere from 5 seconds to 10 minutes, depending on the
-# speed of your computer (public key algorithms aren't exactly the speediest
-# crypto algorithms in the world). When it's finished, you'll see the files
-# "gem-private_key.pem" and "gem-public_cert.pem" in the current directory.
+# This could take anywhere from a few seconds to a minute or two, depending on
+# the speed of your computer (public key algorithms aren't exactly the
+# speediest crypto algorithms in the world). When it's finished, you'll see
+# the files "gem-private_key.pem" and "gem-public_cert.pem" in the current
+# directory.
#
-# First things first: take the "gem-private_key.pem" file and move it
-# somewhere private, preferably a directory only you have access to, a floppy
-# (yuck!), a CD-ROM, or something comparably secure. Keep your private key
-# hidden; if it's compromised, someone can sign packages as you (note: PKI has
-# ways of mitigating the risk of stolen keys; more on that later).
+# First things first: Move both files to ~/.gem if you don't already have a
+# key and certificate in that directory. Ensure the file permissions make the
+# key unreadable by others (by default the file is saved securely).
#
-# Now, let's sign an existing gem. I'll be using my Imlib2-Ruby bindings, but
-# you can use whatever gem you'd like. Open up your existing gemspec file and
-# add the following lines:
+# Keep your private key hidden; if it's compromised, someone can sign packages
+# as you (note: PKI has ways of mitigating the risk of stolen keys; more on
+# that later).
#
-# # signing key and certificate chain
-# s.signing_key = '/mnt/floppy/gem-private_key.pem'
-# s.cert_chain = ['gem-public_cert.pem']
+# === Signing Gems
#
-# (Be sure to replace "/mnt/floppy" with the ultra-secret path to your private
-# key).
+# In RubyGems 2 and newer there is no extra work to sign a gem. RubyGems will
+# automatically find your key and certificate in your home directory and use
+# them to sign newly packaged gems.
#
-# After that, go ahead and build your gem as usual. Congratulations, you've
-# just built your first signed gem! If you peek inside your gem file, you'll
-# see a couple of new files have been added:
+# If your certificate is not self-signed (signed by a third party) RubyGems
+# will attempt to load the certificate chain from the trusted certificates.
+# Use <code>gem cert --add signing_cert.pem</code> to add your signers as
+# trusted certificates. See below for further information on certificate
+# chains.
#
-# $ tar tf tar tf Imlib2-Ruby-0.5.0.gem
-# data.tar.gz
-# data.tar.gz.sig
+# If you build your gem it will automatically be signed. If you peek inside
+# your gem file, you'll see a couple of new files have been added:
+#
+# $ tar tf your-gem-1.0.gem
# metadata.gz
-# metadata.gz.sig
+# metadata.gz.sum
+# metadata.gz.sig # metadata signature
+# data.tar.gz
+# data.tar.gz.sum
+# data.tar.gz.sig # data signature
+#
+# === Manually signing gems
+#
+# If you wish to store your key in a separate secure location you'll need to
+# set your gems up for signing by hand. To do this, set the
+# <code>signing_key</code> and <code>cert_chain</code> in the gemspec before
+# packaging your gem:
+#
+# s.signing_key = '/secure/path/to/gem-private_key.pem'
+# s.cert_chain = %w[/secure/path/to/gem-public_cert.pem]
+#
+# When you package your gem with these options set RubyGems will automatically
+# load your key and certificate from the secure paths.
+#
+# === Signed gems and security policies
#
# Now let's verify the signature. Go ahead and install the gem, but add the
-# following options: "-P HighSecurity", like this:
+# following options: <code>-P HighSecurity</code>, like this:
#
# # install the gem with using the security policy "HighSecurity"
-# $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
+# $ sudo gem install your.gem -P HighSecurity
#
-# The -P option sets your security policy -- we'll talk about that in just a
-# minute. Eh, what's this?
+# The <code>-P</code> option sets your security policy -- we'll talk about
+# that in just a minute. Eh, what's this?
#
-# Attempting local installation of 'Imlib2-Ruby-0.5.0.gem'
-# ERROR: Error installing gem Imlib2-Ruby-0.5.0.gem[.gem]: Couldn't
-# verify data signature: Untrusted Signing Chain Root: cert =
-# '/CN=gemmaster/DC=example/DC=com', error = 'path
-# "/root/.rubygems/trust/cert-15dbb43a6edf6a70a85d4e784e2e45312cff7030.pem"
-# does not exist'
+# $ gem install -P HighSecurity your-gem-1.0.gem
+# ERROR: While executing gem ... (Gem::Security::Exception)
+# root cert /CN=you/DC=example is not trusted
#
# The culprit here is the security policy. RubyGems has several different
# security policies. Let's take a short break and go over the security
@@ -111,46 +120,48 @@ require 'fileutils'
# RubyGems will simply refuse to install the package. Oh well, maybe
# he'll have better luck causing problems for CPAN users instead :).
#
-# So, the reason RubyGems refused to install our shiny new signed gem was
-# because it was from an untrusted source. Well, my code is infallible
-# (hah!), so I'm going to add myself as a trusted source.
-#
-# Here's how:
+# The reason RubyGems refused to install your shiny new signed gem was because
+# it was from an untrusted source. Well, your code is infallible (naturally),
+# so you need to add yourself as a trusted source:
#
-# # add trusted certificate
-# gem cert --add gem-public_cert.pem
+# # add trusted certificate
+# gem cert --add ~/.gem/gem-public_cert.pem
#
-# I've added my public certificate as a trusted source. Now I can install
-# packages signed my private key without any hassle. Let's try the install
-# command above again:
+# You've now added your public certificate as a trusted source. Now you can
+# install packages signed by your private key without any hassle. Let's try
+# the install command above again:
#
# # install the gem with using the HighSecurity policy (and this time
# # without any shenanigans)
-# $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
+# $ gem install -P HighSecurity your-gem-1.0.gem
+# Successfully installed your-gem-1.0
+# 1 gem installed
#
-# This time RubyGems should accept your signed package and begin installing.
-# While you're waiting for RubyGems to work it's magic, have a look at some of
-# the other security commands:
+# This time RubyGems will accept your signed package and begin installing.
#
-# Usage: gem cert [options]
+# While you're waiting for RubyGems to work it's magic, have a look at some of
+# the other security commands by running <code>gem help cert</code>:
#
# Options:
-# -a, --add CERT Add a trusted certificate.
-# -l, --list List trusted certificates.
-# -r, --remove STRING Remove trusted certificates containing STRING.
-# -b, --build EMAIL_ADDR Build private key and self-signed certificate
-# for EMAIL_ADDR.
-# -C, --certificate CERT Certificate for --sign command.
-# -K, --private-key KEY Private key for --sign command.
-# -s, --sign NEWCERT Sign a certificate with my key and certificate.
-#
-# (By the way, you can pull up this list any time you'd like by typing "gem
-# cert --help")
-#
-# Hmm. We've already covered the "--build" option, and the "--add", "--list",
-# and "--remove" commands seem fairly straightforward; they allow you to add,
-# list, and remove the certificates in your trusted certificate list. But
-# what's with this "--sign" option?
+# -a, --add CERT Add a trusted certificate.
+# -l, --list [FILTER] List trusted certificates where the
+# subject contains FILTER
+# -r, --remove FILTER Remove trusted certificates where the
+# subject contains FILTER
+# -b, --build EMAIL_ADDR Build private key and self-signed
+# certificate for EMAIL_ADDR
+# -C, --certificate CERT Signing certificate for --sign
+# -K, --private-key KEY Key for --sign or --build
+# -s, --sign CERT Signs CERT with the key from -K
+# and the certificate from -C
+#
+# We've already covered the <code>--build</code> option, and the
+# <code>--add</code>, <code>--list</code>, and <code>--remove</code> commands
+# seem fairly straightforward; they allow you to add, list, and remove the
+# certificates in your trusted certificate list. But what's with this
+# <code>--sign</code> option?
+#
+# === Certificate chains
#
# To answer that question, let's take a look at "certificate chains", a
# concept I mentioned earlier. There are a couple of problems with
@@ -172,134 +183,102 @@ require 'fileutils'
# trust. Here's a hypothetical example of a trust hierarchy based (roughly)
# on geography:
#
-#
# --------------------------
-# | rubygems@rubyforge.org |
+# | rubygems@rubygems.org |
# --------------------------
# |
# -----------------------------------
# | |
# ---------------------------- -----------------------------
-# | seattle.rb@zenspider.com | | dcrubyists@richkilmer.com |
+# | seattlerb@seattlerb.org | | dcrubyists@richkilmer.com |
# ---------------------------- -----------------------------
# | | | |
# --------------- ---------------- ----------- --------------
-# | alf@seattle | | bob@portland | | pabs@dc | | tomcope@dc |
+# | drbrain | | zenspider | | pabs@dc | | tomcope@dc |
# --------------- ---------------- ----------- --------------
#
#
-# Now, rather than having 4 trusted certificates (one for alf@seattle,
-# bob@portland, pabs@dc, and tomecope@dc), a user could actually get by with 1
-# certificate: the "rubygems@rubyforge.org" certificate. Here's how it works:
+# Now, rather than having 4 trusted certificates (one for drbrain, zenspider,
+# pabs@dc, and tomecope@dc), a user could actually get by with one
+# certificate, the "rubygems@rubygems.org" certificate.
+#
+# Here's how it works:
+#
+# I install "rdoc-3.12.gem", a package signed by "drbrain". I've never heard
+# of "drbrain", but his certificate has a valid signature from the
+# "seattle.rb@seattlerb.org" certificate, which in turn has a valid signature
+# from the "rubygems@rubygems.org" certificate. Voila! At this point, it's
+# much more reasonable for me to trust a package signed by "drbrain", because
+# I can establish a chain to "rubygems@rubygems.org", which I do trust.
#
-# I install "Alf2000-Ruby-0.1.0.gem", a package signed by "alf@seattle". I've
-# never heard of "alf@seattle", but his certificate has a valid signature from
-# the "seattle.rb@zenspider.com" certificate, which in turn has a valid
-# signature from the "rubygems@rubyforge.org" certificate. Voila! At this
-# point, it's much more reasonable for me to trust a package signed by
-# "alf@seattle", because I can establish a chain to "rubygems@rubyforge.org",
-# which I do trust.
+# === Signing certificates
#
-# And the "--sign" option allows all this to happen. A developer creates
-# their build certificate with the "--build" option, then has their
-# certificate signed by taking it with them to their next regional Ruby meetup
-# (in our hypothetical example), and it's signed there by the person holding
-# the regional RubyGems signing certificate, which is signed at the next
-# RubyConf by the holder of the top-level RubyGems certificate. At each point
-# the issuer runs the same command:
+# The <code>--sign</code> option allows all this to happen. A developer
+# creates their build certificate with the <code>--build</code> option, then
+# has their certificate signed by taking it with them to their next regional
+# Ruby meetup (in our hypothetical example), and it's signed there by the
+# person holding the regional RubyGems signing certificate, which is signed at
+# the next RubyConf by the holder of the top-level RubyGems certificate. At
+# each point the issuer runs the same command:
#
# # sign a certificate with the specified key and certificate
# # (note that this modifies client_cert.pem!)
# $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
# --sign client_cert.pem
#
-# Then the holder of issued certificate (in this case, our buddy
-# "alf@seattle"), can start using this signed certificate to sign RubyGems.
-# By the way, in order to let everyone else know about his new fancy signed
-# certificate, "alf@seattle" would change his gemspec file to look like this:
+# Then the holder of issued certificate (in this case, your buddy "drbrain"),
+# can start using this signed certificate to sign RubyGems. By the way, in
+# order to let everyone else know about his new fancy signed certificate,
+# "drbrain" would save his newly signed certificate as
+# <code>~/.gem/gem-public_cert.pem</code>
#
-# # signing key (still kept in an undisclosed location!)
-# s.signing_key = '/mnt/floppy/alf-private_key.pem'
-#
-# # certificate chain (includes the issuer certificate now too)
-# s.cert_chain = ['/home/alf/doc/seattlerb-public_cert.pem',
-# '/home/alf/doc/alf_at_seattle-public_cert.pem']
-#
-# Obviously, this RubyGems trust infrastructure doesn't exist yet. Also, in
-# the "real world" issuers actually generate the child certificate from a
+# Obviously this RubyGems trust infrastructure doesn't exist yet. Also, in
+# the "real world", issuers actually generate the child certificate from a
# certificate request, rather than sign an existing certificate. And our
# hypothetical infrastructure is missing a certificate revocation system.
# These are that can be fixed in the future...
#
-# I'm sure your new signed gem has finished installing by now (unless you're
-# installing rails and all it's dependencies, that is ;D). At this point you
-# should know how to do all of these new and interesting things:
+# At this point you should know how to do all of these new and interesting
+# things:
#
# * build a gem signing key and certificate
-# * modify your existing gems to support signing
# * adjust your security policy
# * modify your trusted certificate list
# * sign a certificate
#
-# If you've got any questions, feel free to contact me at the email address
-# below. The next couple of sections
-#
-#
-# == Command-Line Options
-#
-# Here's a brief summary of the certificate-related command line options:
-#
-# gem install
-# -P, --trust-policy POLICY Specify gem trust policy.
-#
-# gem cert
-# -a, --add CERT Add a trusted certificate.
-# -l, --list List trusted certificates.
-# -r, --remove STRING Remove trusted certificates containing
-# STRING.
-# -b, --build EMAIL_ADDR Build private key and self-signed
-# certificate for EMAIL_ADDR.
-# -C, --certificate CERT Certificate for --sign command.
-# -K, --private-key KEY Private key for --sign command.
-# -s, --sign NEWCERT Sign a certificate with my key and
-# certificate.
-#
-# A more detailed description of each options is available in the walkthrough
-# above.
-#
# == Manually verifying signatures
#
# In case you don't trust RubyGems you can verify gem signatures manually:
#
# 1. Fetch and unpack the gem
#
-# gem fetch some_signed_gem
-# tar -xf some_signed_gem-1.0.gem
+# gem fetch some_signed_gem
+# tar -xf some_signed_gem-1.0.gem
#
# 2. Grab the public key from the gemspec
#
-# gem spec some_signed_gem-1.0.gem cert_chain | \
-# ruby -pe 'sub(/^ +/, "")' > public_key.crt
+# gem spec some_signed_gem-1.0.gem cert_chain | \
+# ruby -ryaml -e 'puts YAML.load_documents($stdin)' > public_key.crt
#
# 3. Generate a SHA1 hash of the data.tar.gz
#
-# openssl dgst -sha1 < data.tar.gz > my.hash
+# openssl dgst -sha1 < data.tar.gz > my.hash
#
# 4. Verify the signature
#
-# openssl rsautl -verify -inkey public_key.crt -certin \
-# -in data.tar.gz.sig > verified.hash
+# openssl rsautl -verify -inkey public_key.crt -certin \
+# -in data.tar.gz.sig > verified.hash
#
# 5. Compare your hash to the verified hash
#
-# diff -s verified.hash my.hash
+# diff -s verified.hash my.hash
#
# 6. Repeat 5 and 6 with metadata.gz
#
# == OpenSSL Reference
#
-# The .pem files generated by --build and --sign are just basic OpenSSL PEM
-# files. Here's a couple of useful commands for manipulating them:
+# The .pem files generated by --build and --sign are PEM files. Here's a
+# couple of useful OpenSSL commands for manipulating them:
#
# # convert a PEM format X509 certificate into DER format:
# # (note: Windows .cer files are X509 certificates in DER format)
@@ -321,8 +300,8 @@ require 'fileutils'
# * There's no way to define a system-wide trust list.
# * custom security policies (from a YAML file, etc)
# * Simple method to generate a signed certificate request
-# * Support for OCSP, SCVP, CRLs, or some other form of cert
-# status check (list is in order of preference)
+# * Support for OCSP, SCVP, CRLs, or some other form of cert status check
+# (list is in order of preference)
# * Support for encrypted private keys
# * Some sort of semi-formal trust hierarchy (see long-winded explanation
# above)
@@ -332,17 +311,13 @@ require 'fileutils'
# MediumSecurity and HighSecurity policies)
# * Better explanation of X509 naming (ie, we don't have to use email
# addresses)
-# * Possible alternate signing mechanisms (eg, via PGP). this could be done
-# pretty easily by adding a :signing_type attribute to the gemspec, then add
-# the necessary support in other places
# * Honor AIA field (see note about OCSP above)
-# * Maybe honor restriction extensions?
+# * Honor extension restrictions
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
-# file, instead of an array embedded in the metadata. ideas?
-# * Possibly embed signature and key algorithms into metadata (right now
-# they're assumed to be the same as what's set in Gem::Security::OPT)
+# file, instead of an array embedded in the metadata.
+# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
#
-# == About the Author
+# == Original author
#
# Paul Duncan <pabs@pablotron.org>
# http://pablotron.org/
@@ -355,472 +330,237 @@ module Gem::Security
class Exception < Gem::Exception; end
##
- # Default options for most of the methods below
-
- OPT = {
- # private key options
- :key_algo => Gem::SSL::PKEY_RSA,
- :key_size => 2048,
-
- # public cert options
- :cert_age => 365 * 24 * 3600, # 1 year
- :dgst_algo => Gem::SSL::DIGEST_SHA1,
-
- # x509 certificate extensions
- :cert_exts => {
- 'basicConstraints' => 'CA:FALSE',
- 'subjectKeyIdentifier' => 'hash',
- 'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature',
- },
-
- # save the key and cert to a file in build_self_signed_cert()?
- :save_key => true,
- :save_cert => true,
-
- # if you define either of these, then they'll be used instead of
- # the output_fmt macro below
- :save_key_path => nil,
- :save_cert_path => nil,
-
- # output name format for self-signed certs
- :output_fmt => 'gem-%s.pem',
- :munge_re => Regexp.new(/[^a-z0-9_.-]+/),
-
- # output directory for trusted certificate checksums
- :trust_dir => File.join(Gem.user_home, '.gem', 'trust'),
-
- # default permissions for trust directory and certs
- :perms => {
- :trust_dir => 0700,
- :trusted_cert => 0600,
- :signing_cert => 0600,
- :signing_key => 0600,
- },
- }
+ # Digest algorithm used to sign gems
+
+ DIGEST_ALGORITHM = OpenSSL::Digest::SHA1
##
- # A Gem::Security::Policy object encapsulates the settings for verifying
- # signed gem files. This is the base class. You can either declare an
- # instance of this or use one of the preset security policies below.
-
- class Policy
- attr_accessor :verify_data, :verify_signer, :verify_chain,
- :verify_root, :only_trusted, :only_signed
-
- #
- # Create a new Gem::Security::Policy object with the given mode and
- # options.
- #
- def initialize(policy = {}, opt = {})
- # set options
- @opt = Gem::Security::OPT.merge(opt)
-
- # build policy
- policy.each_pair do |key, val|
- case key
- when :verify_data then @verify_data = val
- when :verify_signer then @verify_signer = val
- when :verify_chain then @verify_chain = val
- when :verify_root then @verify_root = val
- when :only_trusted then @only_trusted = val
- when :only_signed then @only_signed = val
- end
- end
- end
+ # Used internally to select the signing digest from all computed digests
- #
- # Get the path to the file for this cert.
- #
- def self.trusted_cert_path(cert, opt = {})
- opt = Gem::Security::OPT.merge(opt)
+ DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc:
- # get digest algorithm, calculate checksum of root.subject
- algo = opt[:dgst_algo]
- dgst = algo.hexdigest(cert.subject.to_s)
+ ##
+ # Algorithm for creating the key pair used to sign gems
- # build path to trusted cert file
- name = "cert-#{dgst}.pem"
+ KEY_ALGORITHM = OpenSSL::PKey::RSA
- # join and return path components
- File::join(opt[:trust_dir], name)
- end
+ ##
+ # Length of keys created by KEY_ALGORITHM
- #
- # Verify that the gem data with the given signature and signing chain
- # matched this security policy at the specified time.
- #
- def verify_gem(signature, data, chain, time = Time.now)
- Gem.ensure_ssl_available
- cert_class = OpenSSL::X509::Certificate
- exc = Gem::Security::Exception
- chain ||= []
-
- chain = chain.map{ |str| cert_class.new(str) }
- signer, ch_len = chain[-1], chain.size
-
- # make sure signature is valid
- if @verify_data
- # get digest algorithm (TODO: this should be configurable)
- dgst = @opt[:dgst_algo]
-
- # verify the data signature (this is the most important part, so don't
- # screw it up :D)
- v = signer.public_key.verify(dgst.new, signature, data)
- raise exc, "Invalid Gem Signature" unless v
-
- # make sure the signer is valid
- if @verify_signer
- # make sure the signing cert is valid right now
- v = signer.check_validity(nil, time)
- raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid]
- end
- end
-
- # make sure the certificate chain is valid
- if @verify_chain
- # iterate down over the chain and verify each certificate against it's
- # issuer
- (ch_len - 1).downto(1) do |i|
- issuer, cert = chain[i - 1, 2]
- v = cert.check_validity(issuer, time)
- raise exc, "%s: cert = '%s', error = '%s'" % [
- 'Invalid Signing Chain', cert.subject, v[:desc]
- ] unless v[:is_valid]
- end
-
- # verify root of chain
- if @verify_root
- # make sure root is self-signed
- root = chain[0]
- raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [
- 'Invalid Signing Chain Root',
- 'Subject does not match Issuer for Gem Signing Chain',
- root.subject.to_s,
- root.issuer.to_s,
- ] unless root.issuer.to_s == root.subject.to_s
-
- # make sure root is valid
- v = root.check_validity(root, time)
- raise exc, "%s: cert = '%s', error = '%s'" % [
- 'Invalid Signing Chain Root', root.subject, v[:desc]
- ] unless v[:is_valid]
-
- # verify that the chain root is trusted
- if @only_trusted
- # get digest algorithm, calculate checksum of root.subject
- algo = @opt[:dgst_algo]
- path = Gem::Security::Policy.trusted_cert_path(root, @opt)
-
- # check to make sure trusted path exists
- raise exc, "%s: cert = '%s', error = '%s'" % [
- 'Untrusted Signing Chain Root',
- root.subject.to_s,
- "path \"#{path}\" does not exist",
- ] unless File.exist?(path)
-
- # load calculate digest from saved cert file
- save_cert = OpenSSL::X509::Certificate.new(File.read(path))
- save_dgst = algo.digest(save_cert.public_key.to_s)
-
- # create digest of public key
- pkey_str = root.public_key.to_s
- cert_dgst = algo.digest(pkey_str)
-
- # now compare the two digests, raise exception
- # if they don't match
- raise exc, "%s: %s (saved = '%s', root = '%s')" % [
- 'Invalid Signing Chain Root',
- "Saved checksum doesn't match root checksum",
- save_dgst, cert_dgst,
- ] unless save_dgst == cert_dgst
- end
- end
-
- # return the signing chain
- chain.map { |cert| cert.subject }
- end
- end
- end
+ KEY_LENGTH = 2048
##
- # No security policy: all package signature checks are disabled.
+ # One year in seconds
- NoSecurity = Policy.new(
- :verify_data => false,
- :verify_signer => false,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
- )
+ ONE_YEAR = 86400 * 365
##
- # AlmostNo security policy: only verify that the signing certificate is the
- # one that actually signed the data. Make no attempt to verify the signing
- # certificate chain.
+ # The default set of extensions are:
#
- # This policy is basically useless. better than nothing, but can still be
- # easily spoofed, and is not recommended.
-
- AlmostNoSecurity = Policy.new(
- :verify_data => true,
- :verify_signer => false,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
- )
+ # * The certificate is not a certificate authority
+ # * The key for the certificate may be used for key and data encipherment
+ # and digital signatures
+ # * The certificate contains a subject key identifier
+
+ EXTENSIONS = {
+ 'basicConstraints' => 'CA:FALSE',
+ 'keyUsage' =>
+ 'keyEncipherment,dataEncipherment,digitalSignature',
+ 'subjectKeyIdentifier' => 'hash',
+ }
- ##
- # Low security policy: only verify that the signing certificate is actually
- # the gem signer, and that the signing certificate is valid.
- #
- # This policy is better than nothing, but can still be easily spoofed, and
- # is not recommended.
-
- LowSecurity = Policy.new(
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => false,
- :verify_root => false,
- :only_trusted => false,
- :only_signed => false
- )
+ def self.alt_name_or_x509_entry certificate, x509_entry
+ alt_name = certificate.extensions.find do |extension|
+ extension.oid == "#{x509_entry}AltName"
+ end
- ##
- # Medium security policy: verify the signing certificate, verify the signing
- # certificate chain all the way to the root certificate, and only trust root
- # certificates that we have explicitly allowed trust for.
- #
- # This security policy is reasonable, but it allows unsigned packages, so a
- # malicious person could simply delete the package signature and pass the
- # gem off as unsigned.
-
- MediumSecurity = Policy.new(
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => true,
- :verify_root => true,
- :only_trusted => true,
- :only_signed => false
- )
+ return alt_name.value if alt_name
+
+ certificate.send x509_entry
+ end
##
- # High security policy: only allow signed gems to be installed, verify the
- # signing certificate, verify the signing certificate chain all the way to
- # the root certificate, and only trust root certificates that we have
- # explicitly allowed trust for.
+ # Creates an unsigned certificate for +subject+ and +key+. The lifetime of
+ # the key is from the current time to +age+ which defaults to one year.
#
- # This security policy is significantly more difficult to bypass, and offers
- # a reasonable guarantee that the contents of the gem have not been altered.
-
- HighSecurity = Policy.new(
- :verify_data => true,
- :verify_signer => true,
- :verify_chain => true,
- :verify_root => true,
- :only_trusted => true,
- :only_signed => true
- )
+ # The +extensions+ restrict the key to the indicated uses.
- ##
- # Hash of configured security policies
-
- Policies = {
- 'NoSecurity' => NoSecurity,
- 'AlmostNoSecurity' => AlmostNoSecurity,
- 'LowSecurity' => LowSecurity,
- 'MediumSecurity' => MediumSecurity,
- 'HighSecurity' => HighSecurity,
- }
+ def self.create_cert subject, key, age = ONE_YEAR, extensions = EXTENSIONS,
+ serial = 1
+ cert = OpenSSL::X509::Certificate.new
- ##
- # Sign the cert cert with @signing_key and @signing_cert, using the digest
- # algorithm opt[:dgst_algo]. Returns the newly signed certificate.
+ cert.public_key = key.public_key
+ cert.version = 2
+ cert.serial = serial
- def self.sign_cert(cert, signing_key, signing_cert, opt = {})
- opt = OPT.merge(opt)
+ cert.not_before = Time.now
+ cert.not_after = Time.now + age
- cert.issuer = signing_cert.subject
- cert.sign signing_key, opt[:dgst_algo].new
+ cert.subject = subject
- cert
- end
+ ef = OpenSSL::X509::ExtensionFactory.new nil, cert
- ##
- # Make sure the trust directory exists. If it does exist, make sure it's
- # actually a directory. If not, then create it with the appropriate
- # permissions.
-
- def self.verify_trust_dir(path, perms)
- # if the directory exists, then make sure it is in fact a directory. if
- # it doesn't exist, then create it with the appropriate permissions
- if File.exist?(path)
- # verify that the trust directory is actually a directory
- unless File.directory?(path)
- err = "trust directory #{path} isn't a directory"
- raise Gem::Security::Exception, err
- end
- else
- # trust directory doesn't exist, so create it with permissions
- FileUtils.mkdir_p(path)
- FileUtils.chmod(perms, path)
+ cert.extensions = extensions.map do |ext_name, value|
+ ef.create_extension ext_name, value
end
+
+ cert
end
##
- # Build a certificate from the given DN and private key.
+ # Creates a self-signed certificate with an issuer and subject from +email+,
+ # a subject alternative name of +email+ and the given +extensions+ for the
+ # +key+.
- def self.build_cert(name, key, opt = {})
- Gem.ensure_ssl_available
- opt = OPT.merge opt
+ def self.create_cert_email email, key, age = ONE_YEAR, extensions = EXTENSIONS
+ subject = email_to_name email
- cert = OpenSSL::X509::Certificate.new
+ extensions = extensions.merge "subjectAltName" => "email:#{email}"
- cert.not_after = Time.now + opt[:cert_age]
- cert.not_before = Time.now
- cert.public_key = key.public_key
- cert.serial = 0
- cert.subject = name
- cert.version = 2
+ create_cert_self_signed subject, key, age, extensions
+ end
- ef = OpenSSL::X509::ExtensionFactory.new nil, cert
+ ##
+ # Creates a self-signed certificate with an issuer and subject of +subject+
+ # and the given +extensions+ for the +key+.
- cert.extensions = opt[:cert_exts].map do |ext_name, value|
- ef.create_extension ext_name, value
- end
+ def self.create_cert_self_signed subject, key, age = ONE_YEAR,
+ extensions = EXTENSIONS, serial = 1
+ certificate = create_cert subject, key, age, extensions
- i_key = opt[:issuer_key] || key
- i_cert = opt[:issuer_cert] || cert
+ sign certificate, key, certificate, age, extensions, serial
+ end
- cert = sign_cert cert, i_key, i_cert, opt
+ ##
+ # Creates a new key pair of the specified +length+ and +algorithm+. The
+ # default is a 2048 bit RSA key.
- cert
+ def self.create_key length = KEY_LENGTH, algorithm = KEY_ALGORITHM
+ algorithm.new length
end
##
- # Build a self-signed certificate for the given email address.
+ # Turns +email_address+ into an OpenSSL::X509::Name
- def self.build_self_signed_cert(email_addr, opt = {})
- Gem.ensure_ssl_available
- opt = OPT.merge(opt)
- path = { :key => nil, :cert => nil }
+ def self.email_to_name email_address
+ email_address = email_address.gsub(/[^\w@.-]+/i, '_')
- name = email_to_name email_addr, opt[:munge_re]
+ cn, dcs = email_address.split '@'
- key = opt[:key_algo].new opt[:key_size]
+ dcs = dcs.split '.'
- verify_trust_dir opt[:trust_dir], opt[:perms][:trust_dir]
+ name = "CN=#{cn}/#{dcs.map { |dc| "DC=#{dc}" }.join '/'}"
- if opt[:save_key] then
- path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')
+ OpenSSL::X509::Name.parse name
+ end
- open path[:key], 'wb' do |io|
- io.chmod opt[:perms][:signing_key]
- io.write key.to_pem
- end
+ ##
+ # Signs +expired_certificate+ with +private_key+ if the keys match and the
+ # expired certificate was self-signed.
+ #--
+ # TODO increment serial
+
+ def self.re_sign expired_certificate, private_key, age = ONE_YEAR,
+ extensions = EXTENSIONS
+ raise Gem::Security::Exception,
+ "incorrect signing key for re-signing " \
+ "#{expired_certificate.subject}" unless
+ expired_certificate.public_key.to_pem == private_key.public_key.to_pem
+
+ unless expired_certificate.subject.to_s ==
+ expired_certificate.issuer.to_s then
+ subject = alt_name_or_x509_entry expired_certificate, :subject
+ issuer = alt_name_or_x509_entry expired_certificate, :issuer
+
+ raise Gem::Security::Exception,
+ "#{subject} is not self-signed, contact #{issuer} " \
+ "to obtain a valid certificate"
end
- cert = build_cert name, key, opt
+ serial = expired_certificate.serial + 1
- if opt[:save_cert] then
- path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')
+ create_cert_self_signed(expired_certificate.subject, private_key, age,
+ extensions, serial)
+ end
- open path[:cert], 'wb' do |file|
- file.chmod opt[:perms][:signing_cert]
- file.write cert.to_pem
- end
- end
+ ##
+ # Resets the trust directory for verifying gems.
- { :key => key, :cert => cert,
- :key_path => path[:key], :cert_path => path[:cert] }
+ def self.reset
+ @trust_dir = nil
end
##
- # Turns +email_address+ into an OpenSSL::X509::Name
+ # Sign the public key from +certificate+ with the +signing_key+ and
+ # +signing_cert+, using the Gem::Security::DIGEST_ALGORITHM. Uses the
+ # default certificate validity range and extensions.
+ #
+ # Returns the newly signed certificate.
- def self.email_to_name email_address, munge_re
- cn, dcs = email_address.split '@'
+ def self.sign certificate, signing_key, signing_cert,
+ age = ONE_YEAR, extensions = EXTENSIONS, serial = 1
+ signee_subject = certificate.subject
+ signee_key = certificate.public_key
- dcs = dcs.split '.'
+ alt_name = certificate.extensions.find do |extension|
+ extension.oid == 'subjectAltName'
+ end
- cn = cn.gsub munge_re, '_'
+ extensions = extensions.merge 'subjectAltName' => alt_name.value if
+ alt_name
- dcs = dcs.map do |dc|
- dc.gsub munge_re, '_'
+ issuer_alt_name = signing_cert.extensions.find do |extension|
+ extension.oid == 'subjectAltName'
end
- name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/')
+ extensions = extensions.merge 'issuerAltName' => issuer_alt_name.value if
+ issuer_alt_name
- OpenSSL::X509::Name.parse name
+ signed = create_cert signee_subject, signee_key, age, extensions, serial
+ signed.issuer = signing_cert.subject
+
+ signed.sign signing_key, Gem::Security::DIGEST_ALGORITHM.new
end
##
- # Add certificate to trusted cert list.
- #
- # Note: At the moment these are stored in OPT[:trust_dir], although that
- # directory may change in the future.
+ # Returns a Gem::Security::TrustDir which wraps the directory where trusted
+ # certificates live.
- def self.add_trusted_cert(cert, opt = {})
- opt = OPT.merge(opt)
+ def self.trust_dir
+ return @trust_dir if @trust_dir
- # get destination path
- path = Gem::Security::Policy.trusted_cert_path(cert, opt)
+ dir = File.join Gem.user_home, '.gem', 'trust'
- # verify trust directory (can't write to nowhere, you know)
- verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
+ @trust_dir ||= Gem::Security::TrustDir.new dir
+ end
- # write cert to output file
- File.open(path, 'wb') do |file|
- file.chmod(opt[:perms][:trusted_cert])
- file.write(cert.to_pem)
- end
+ ##
+ # Enumerates the trusted certificates via Gem::Security::TrustDir.
- # return nil
- nil
+ def self.trusted_certificates &block
+ trust_dir.each_certificate(&block)
end
##
- # Basic OpenSSL-based package signing class.
-
- class Signer
-
- attr_accessor :cert_chain
- attr_accessor :key
-
- def initialize(key, cert_chain)
- Gem.ensure_ssl_available
- @algo = Gem::Security::OPT[:dgst_algo]
- @key, @cert_chain = key, cert_chain
-
- # check key, if it's a file, and if it's key, leave it alone
- if @key && !@key.kind_of?(OpenSSL::PKey::PKey)
- @key = OpenSSL::PKey::RSA.new(File.read(@key))
- end
-
- # check cert chain, if it's a file, load it, if it's cert data, convert
- # it into a cert object, and if it's a cert object, leave it alone
- if @cert_chain
- @cert_chain = @cert_chain.map do |cert|
- # check cert, if it's a file, load it, if it's cert data, convert it
- # into a cert object, and if it's a cert object, leave it alone
- if cert && !cert.kind_of?(OpenSSL::X509::Certificate)
- cert = File.read(cert) if File::exist?(cert)
- cert = OpenSSL::X509::Certificate.new(cert)
- end
- cert
- end
- end
- end
+ # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
+ # +permissions+.
- ##
- # Sign data with given digest algorithm
+ def self.write pemmable, path, permissions = 0600
+ path = File.expand_path path
- def sign(data)
- @key.sign(@algo.new, data)
+ open path, 'wb', permissions do |io|
+ io.write pemmable.to_pem
end
+ path
end
+ reset
+
end
+require 'rubygems/security/policy'
+require 'rubygems/security/policies'
+require 'rubygems/security/signer'
+require 'rubygems/security/trust_dir'
+
diff --git a/lib/rubygems/security/policies.rb b/lib/rubygems/security/policies.rb
new file mode 100644
index 0000000000..a976ecaf59
--- /dev/null
+++ b/lib/rubygems/security/policies.rb
@@ -0,0 +1,115 @@
+module Gem::Security
+
+ ##
+ # No security policy: all package signature checks are disabled.
+
+ NoSecurity = Policy.new(
+ 'No Security',
+ :verify_data => false,
+ :verify_signer => false,
+ :verify_chain => false,
+ :verify_root => false,
+ :only_trusted => false,
+ :only_signed => false
+ )
+
+ ##
+ # AlmostNo security policy: only verify that the signing certificate is the
+ # one that actually signed the data. Make no attempt to verify the signing
+ # certificate chain.
+ #
+ # This policy is basically useless. better than nothing, but can still be
+ # easily spoofed, and is not recommended.
+
+ AlmostNoSecurity = Policy.new(
+ 'Almost No Security',
+ :verify_data => true,
+ :verify_signer => false,
+ :verify_chain => false,
+ :verify_root => false,
+ :only_trusted => false,
+ :only_signed => false
+ )
+
+ ##
+ # Low security policy: only verify that the signing certificate is actually
+ # the gem signer, and that the signing certificate is valid.
+ #
+ # This policy is better than nothing, but can still be easily spoofed, and
+ # is not recommended.
+
+ LowSecurity = Policy.new(
+ 'Low Security',
+ :verify_data => true,
+ :verify_signer => true,
+ :verify_chain => false,
+ :verify_root => false,
+ :only_trusted => false,
+ :only_signed => false
+ )
+
+ ##
+ # Medium security policy: verify the signing certificate, verify the signing
+ # certificate chain all the way to the root certificate, and only trust root
+ # certificates that we have explicitly allowed trust for.
+ #
+ # This security policy is reasonable, but it allows unsigned packages, so a
+ # malicious person could simply delete the package signature and pass the
+ # gem off as unsigned.
+
+ MediumSecurity = Policy.new(
+ 'Medium Security',
+ :verify_data => true,
+ :verify_signer => true,
+ :verify_chain => true,
+ :verify_root => true,
+ :only_trusted => true,
+ :only_signed => false
+ )
+
+ ##
+ # High security policy: only allow signed gems to be installed, verify the
+ # signing certificate, verify the signing certificate chain all the way to
+ # the root certificate, and only trust root certificates that we have
+ # explicitly allowed trust for.
+ #
+ # This security policy is significantly more difficult to bypass, and offers
+ # a reasonable guarantee that the contents of the gem have not been altered.
+
+ HighSecurity = Policy.new(
+ 'High Security',
+ :verify_data => true,
+ :verify_signer => true,
+ :verify_chain => true,
+ :verify_root => true,
+ :only_trusted => true,
+ :only_signed => true
+ )
+
+ ##
+ # Policy used to verify a certificate and key when signing a gem
+
+ SigningPolicy = Policy.new(
+ 'Signing Policy',
+ :verify_data => false,
+ :verify_signer => true,
+ :verify_chain => true,
+ :verify_root => true,
+ :only_trusted => false,
+ :only_signed => false
+ )
+
+ ##
+ # Hash of configured security policies
+
+ Policies = {
+ 'NoSecurity' => NoSecurity,
+ 'AlmostNoSecurity' => AlmostNoSecurity,
+ 'LowSecurity' => LowSecurity,
+ 'MediumSecurity' => MediumSecurity,
+ 'HighSecurity' => HighSecurity,
+ # SigningPolicy is not intended for use by `gem -P` so do not list it
+ }
+
+end
+
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
new file mode 100644
index 0000000000..d22b7ce5bd
--- /dev/null
+++ b/lib/rubygems/security/policy.rb
@@ -0,0 +1,227 @@
+##
+# A Gem::Security::Policy object encapsulates the settings for verifying
+# signed gem files. This is the base class. You can either declare an
+# instance of this or use one of the preset security policies in
+# Gem::Security::Policies.
+
+class Gem::Security::Policy
+
+ attr_reader :name
+
+ attr_accessor :only_signed
+ attr_accessor :only_trusted
+ attr_accessor :verify_chain
+ attr_accessor :verify_data
+ attr_accessor :verify_root
+ attr_accessor :verify_signer
+
+ ##
+ # Create a new Gem::Security::Policy object with the given mode and
+ # options.
+
+ def initialize name, policy = {}, opt = {}
+ @name = name
+
+ @opt = opt
+
+ # Default to security
+ @only_signed = true
+ @only_trusted = true
+ @verify_chain = true
+ @verify_data = true
+ @verify_root = true
+ @verify_signer = true
+
+ policy.each_pair do |key, val|
+ case key
+ when :verify_data then @verify_data = val
+ when :verify_signer then @verify_signer = val
+ when :verify_chain then @verify_chain = val
+ when :verify_root then @verify_root = val
+ when :only_trusted then @only_trusted = val
+ when :only_signed then @only_signed = val
+ end
+ end
+ end
+
+ ##
+ # Verifies each certificate in +chain+ has signed the following certificate
+ # and is valid for the given +time+.
+
+ def check_chain chain, time
+ chain.each_cons 2 do |issuer, cert|
+ check_cert cert, issuer, time
+ end
+
+ true
+ rescue Gem::Security::Exception => e
+ raise Gem::Security::Exception, "invalid signing chain: #{e.message}"
+ end
+
+ ##
+ # Verifies that +data+ matches the +signature+ created by +public_key+ and
+ # the +digest+ algorithm.
+
+ def check_data public_key, digest, signature, data
+ raise Gem::Security::Exception, "invalid signature" unless
+ public_key.verify digest.new, signature, data.digest
+
+ true
+ end
+
+ ##
+ # Ensures that +signer+ is valid for +time+ and was signed by the +issuer+.
+ # If the +issuer+ is +nil+ no verification is performed.
+
+ def check_cert signer, issuer, time
+ message = "certificate #{signer.subject}"
+
+ if not_before = signer.not_before and not_before > time then
+ raise Gem::Security::Exception,
+ "#{message} not valid before #{not_before}"
+ end
+
+ if not_after = signer.not_after and not_after < time then
+ raise Gem::Security::Exception, "#{message} not valid after #{not_after}"
+ end
+
+ if issuer and not signer.verify issuer.public_key then
+ raise Gem::Security::Exception,
+ "#{message} was not issued by #{issuer.subject}"
+ end
+
+ true
+ end
+
+ ##
+ # Ensures the public key of +key+ matches the public key in +signer+
+
+ def check_key signer, key
+ raise Gem::Security::Exception,
+ "certificate #{signer.subject} does not match the signing key" unless
+ signer.public_key.to_pem == key.public_key.to_pem
+
+ true
+ end
+
+ ##
+ # Ensures the root certificate in +chain+ is self-signed and valid for
+ # +time+.
+
+ def check_root chain, time
+ root = chain.first
+
+ raise Gem::Security::Exception,
+ "root certificate #{root.subject} is not self-signed " \
+ "(issuer #{root.issuer})" if
+ root.issuer.to_s != root.subject.to_s # HACK to_s is for ruby 1.8
+
+ check_cert root, root, time
+ end
+
+ ##
+ # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and
+ # the digests of the two certificates match according to +digester+
+
+ def check_trust chain, digester, trust_dir
+ root = chain.first
+
+ path = Gem::Security.trust_dir.cert_path root
+
+ unless File.exist? path then
+ message = "root cert #{root.subject} is not trusted"
+
+ message << " (root of signing cert #{chain.last.subject})" if
+ chain.length > 1
+
+ raise Gem::Security::Exception, message
+ end
+
+ save_cert = OpenSSL::X509::Certificate.new File.read path
+ save_dgst = digester.digest save_cert.public_key.to_s
+
+ pkey_str = root.public_key.to_s
+ cert_dgst = digester.digest pkey_str
+
+ raise Gem::Security::Exception,
+ "trusted root certificate #{root.subject} checksum " \
+ "does not match signing root certificate checksum" unless
+ save_dgst == cert_dgst
+
+ true
+ end
+
+ def inspect # :nodoc:
+ "[Policy: %s - data: %p signer: %p chain: %p root: %p " \
+ "signed-only: %p trusted-only: %p]" % [
+ @name, @verify_chain, @verify_data, @verify_root, @verify_signer,
+ @only_signed, @only_trusted,
+ ]
+ end
+
+ ##
+ # Verifies the certificate +chain+ is valid, the +digests+ match the
+ # signatures +signatures+ created by the signer depending on the +policy+
+ # settings.
+ #
+ # If +key+ is given it is used to validate the signing certificate.
+
+ def verify chain, key = nil, digests = {}, signatures = {}
+ if @only_signed and signatures.empty? then
+ raise Gem::Security::Exception,
+ "unsigned gems are not allowed by the #{name} policy"
+ end
+
+ opt = @opt
+ digester = Gem::Security::DIGEST_ALGORITHM
+ trust_dir = opt[:trust_dir]
+ time = Time.now
+
+ signer_digests = digests.find do |algorithm, file_digests|
+ file_digests.values.first.name == Gem::Security::DIGEST_NAME
+ end
+
+ signer_digests = digests.values.first || {}
+
+ signer = chain.last
+
+ check_key signer, key if key
+
+ check_cert signer, nil, time if @verify_signer
+
+ check_chain chain, time if @verify_chain
+
+ check_root chain, time if @verify_root
+
+ check_trust chain, digester, trust_dir if @only_trusted
+
+ signer_digests.each do |file, digest|
+ signature = signatures[file]
+
+ raise Gem::Security::Exception, "missing signature for #{file}" unless
+ signature
+
+ check_data signer.public_key, digester, signature, digest if @verify_data
+ end
+
+ true
+ end
+
+ ##
+ # Extracts the certificate chain from the +spec+ and calls #verify to ensure
+ # the signatures and certificate chain is valid according to the policy..
+
+ def verify_signatures spec, digests, signatures
+ chain = spec.cert_chain.map do |cert_pem|
+ OpenSSL::X509::Certificate.new cert_pem
+ end
+
+ verify chain, nil, digests, signatures
+
+ true
+ end
+
+ alias to_s name # :nodoc:
+
+end
+
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
new file mode 100644
index 0000000000..29b03683b7
--- /dev/null
+++ b/lib/rubygems/security/signer.rb
@@ -0,0 +1,136 @@
+##
+# Basic OpenSSL-based package signing class.
+
+class Gem::Security::Signer
+
+ ##
+ # The chain of certificates for signing including the signing certificate
+
+ attr_accessor :cert_chain
+
+ ##
+ # The private key for the signing certificate
+
+ attr_accessor :key
+
+ ##
+ # The digest algorithm used to create the signature
+
+ attr_reader :digest_algorithm
+
+ ##
+ # The name of the digest algorithm, used to pull digests out of the hash by
+ # name.
+
+ attr_reader :digest_name # :nodoc:
+
+ ##
+ # Creates a new signer with an RSA +key+ or path to a key, and a certificate
+ # +chain+ containing X509 certificates, encoding certificates or paths to
+ # certificates.
+
+ def initialize key, cert_chain
+ @cert_chain = cert_chain
+ @key = key
+
+ unless @key then
+ default_key = File.join Gem.user_home, 'gem-private_key.pem'
+ @key = default_key if File.exist? default_key
+ end
+
+ unless @cert_chain then
+ default_cert = File.join Gem.user_home, 'gem-public_cert.pem'
+ @cert_chain = [default_cert] if File.exist? default_cert
+ end
+
+ @digest_algorithm = Gem::Security::DIGEST_ALGORITHM
+ @digest_name = Gem::Security::DIGEST_NAME
+
+ @key = OpenSSL::PKey::RSA.new File.read @key if
+ @key and not OpenSSL::PKey::RSA === @key
+
+ if @cert_chain then
+ @cert_chain = @cert_chain.compact.map do |cert|
+ next cert if OpenSSL::X509::Certificate === cert
+
+ cert = File.read cert if File.exist? cert
+
+ OpenSSL::X509::Certificate.new cert
+ end
+
+ load_cert_chain
+ end
+ end
+
+ ##
+ # Loads any missing issuers in the cert chain from the trusted certificates.
+ #
+ # If the issuer does not exist it is ignored as it will be checked later.
+
+ def load_cert_chain # :nodoc:
+ return if @cert_chain.empty?
+
+ while @cert_chain.first.issuer.to_s != @cert_chain.first.subject.to_s do
+ issuer = Gem::Security.trust_dir.issuer_of @cert_chain.first
+
+ break unless issuer # cert chain is verified later
+
+ @cert_chain.unshift issuer
+ end
+ end
+
+ ##
+ # Sign data with given digest algorithm
+
+ def sign data
+ return unless @key
+
+ if @cert_chain.length == 1 and @cert_chain.last.not_after < Time.now then
+ re_sign_key
+ end
+
+ Gem::Security::SigningPolicy.verify @cert_chain, @key
+
+ @key.sign @digest_algorithm.new, data
+ end
+
+ ##
+ # Attempts to re-sign the private key if the signing certificate is expired.
+ #
+ # The key will be re-signed if:
+ # * The expired certificate is self-signed
+ # * The expired certificate is saved at ~/.gem/gem-public_cert.pem
+ # * There is no file matching the expiry date at
+ # ~/.gem/gem-public_cert.pem.expired.%Y%m%d%H%M%S
+ #
+ # If the signing certificate can be re-signed the expired certificate will
+ # be saved as ~/.gem/gem-pubilc_cert.pem.expired.%Y%m%d%H%M%S where the
+ # expiry time (not after) is used for the timestamp.
+
+ def re_sign_key # :nodoc:
+ old_cert = @cert_chain.last
+
+ disk_cert_path = File.join Gem.user_home, 'gem-public_cert.pem'
+ disk_cert = File.read disk_cert_path rescue nil
+ disk_key =
+ File.read File.join(Gem.user_home, 'gem-private_key.pem') rescue nil
+
+ if disk_key == @key.to_pem and disk_cert == old_cert.to_pem then
+ expiry = old_cert.not_after.strftime '%Y%m%d%H%M%S'
+ old_cert_file = "gem-public_cert.pem.expired.#{expiry}"
+ old_cert_path = File.join Gem.user_home, old_cert_file
+
+ unless File.exist? old_cert_path then
+ Gem::Security.write old_cert, old_cert_path
+
+ cert = Gem::Security.re_sign old_cert, @key
+
+ Gem::Security.write cert, disk_cert_path
+
+ @cert_chain = [cert]
+ end
+ end
+ end
+
+end
+
diff --git a/lib/rubygems/security/trust_dir.rb b/lib/rubygems/security/trust_dir.rb
new file mode 100644
index 0000000000..dd51308ee5
--- /dev/null
+++ b/lib/rubygems/security/trust_dir.rb
@@ -0,0 +1,104 @@
+class Gem::Security::TrustDir
+
+ DEFAULT_PERMISSIONS = {
+ :trust_dir => 0700,
+ :trusted_cert => 0600,
+ }
+
+ def initialize dir, permissions = DEFAULT_PERMISSIONS
+ @dir = dir
+ @permissions = permissions
+
+ @digester = Gem::Security::DIGEST_ALGORITHM
+ end
+
+ attr_reader :dir
+
+ ##
+ # Returns the path to the trusted +certificate+
+
+ def cert_path certificate
+ name_path certificate.subject
+ end
+
+ ##
+ # Enumerates trusted certificates.
+
+ def each_certificate
+ return enum_for __method__ unless block_given?
+
+ glob = File.join @dir, '*.pem'
+
+ Dir[glob].each do |certificate_file|
+ begin
+ certificate = load_certificate certificate_file
+
+ yield certificate, certificate_file
+ rescue OpenSSL::X509::CertificateError
+ next # HACK warn
+ end
+ end
+ end
+
+ ##
+ # Returns the issuer certificate of the given +certificate+ if it exists in
+ # the trust directory.
+
+ def issuer_of certificate
+ path = name_path certificate.issuer
+
+ return unless File.exist? path
+
+ load_certificate path
+ end
+
+ ##
+ # Returns the path to the trusted certificate with the given ASN.1 +name+
+
+ def name_path name
+ digest = @digester.hexdigest name.to_s
+
+ File.join @dir, "cert-#{digest}.pem"
+ end
+
+ ##
+ # Loads the given +certificate_file+
+
+ def load_certificate certificate_file
+ pem = File.read certificate_file
+
+ OpenSSL::X509::Certificate.new pem
+ end
+
+ ##
+ # Add a certificate to trusted certificate list.
+
+ def trust_cert certificate
+ verify
+
+ destination = cert_path certificate
+
+ open destination, 'wb', @permissions[:trusted_cert] do |io|
+ io.write certificate.to_pem
+ end
+ end
+
+ ##
+ # Make sure the trust directory exists. If it does exist, make sure it's
+ # actually a directory. If not, then create it with the appropriate
+ # permissions.
+
+ def verify
+ if File.exist? @dir then
+ raise Gem::Security::Exception,
+ "trust directory #{@dir} is not a directory" unless
+ File.directory? @dir
+
+ FileUtils.chmod 0700, @dir
+ else
+ FileUtils.mkdir_p @dir, :mode => @permissions[:trust_dir]
+ end
+ end
+
+end
+
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
index 47fa7c562d..b640186b64 100644
--- a/lib/rubygems/server.rb
+++ b/lib/rubygems/server.rb
@@ -3,7 +3,7 @@ require 'zlib'
require 'erb'
require 'rubygems'
-require 'rubygems/doc_manager'
+require 'rubygems/rdoc'
##
# Gem::Server and allows users to serve gems for consumption by
@@ -17,9 +17,6 @@ require 'rubygems/doc_manager'
# * "/quick/" - Individual gemspecs
# * "/gems" - Direct access to download the installable gems
# * "/rdoc?q=" - Search for installed rdoc documentation
-# * legacy indexes:
-# * "/Marshal.#{Gem.marshal_version}" - Full SourceIndex dump of metadata
-# for installed gems
#
# == Usage
#
@@ -430,53 +427,25 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
options[:launch], options[:addresses]).run
end
- ##
- # Only the first directory in gem_dirs is used for serving gems
-
def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
+ Gem::RDoc.load_rdoc
Socket.do_not_reverse_lookup = true
- @gem_dirs = Array gem_dirs
- @port = port
- @daemon = daemon
- @launch = launch
+ @gem_dirs = Array gem_dirs
+ @port = port
+ @daemon = daemon
+ @launch = launch
@addresses = addresses
- logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
- @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
- @spec_dirs = @gem_dirs.map do |gem_dir|
- 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
+ logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
+ @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
- spec_dir
- end
+ @spec_dirs = @gem_dirs.map { |gem_dir| File.join gem_dir, 'specifications' }
+ @spec_dirs.reject! { |spec_dir| !File.directory? spec_dir }
Gem::Specification.dirs = @gem_dirs
- end
- def Marshal(req, res)
- Gem::Specification.reset
-
- add_date res
-
- index = Gem::Deprecate.skip_during { Marshal.dump Gem.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
+ @have_rdoc_4_plus = nil
end
def add_date res
@@ -485,6 +454,19 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end.max
end
+ def doc_root gem_name
+ if have_rdoc_4_plus? then
+ "/doc_root/#{gem_name}/"
+ else
+ "/doc_root/#{gem_name}/rdoc/index.html"
+ end
+ end
+
+ def have_rdoc_4_plus?
+ @have_rdoc_4_plus ||=
+ Gem::Requirement.new('>= 4').satisfied_by? Gem::RDoc.rdoc_version
+ end
+
def latest_specs(req, res)
Gem::Specification.reset
@@ -614,14 +596,14 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
"authors" => spec.authors.sort.join(", "),
"date" => spec.date.to_s,
"dependencies" => deps,
- "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html",
+ "doc_path" => doc_root(spec.full_name),
"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?,
+ "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?,
"summary" => spec.summary,
"version" => spec.version.to_s,
}
@@ -630,7 +612,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
specs << {
"authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
"dependencies" => [],
- "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html",
+ "doc_path" => doc_root("rubygems-#{Gem::VERSION}"),
"executables" => [{"executable" => 'gem', "is_last" => true}],
"only_one_executable" => true,
"full_name" => "rubygems-#{Gem::VERSION}",
@@ -730,15 +712,15 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
when 1
new_path = File.basename(found_gems[0])
res.status = 302
- res['Location'] = "/doc_root/#{new_path}/rdoc/index.html"
+ res['Location'] = doc_root new_path
return true
else
doc_items = []
found_gems.each do |file_name|
base_name = File.basename(file_name)
doc_items << {
- :name => base_name,
- :url => "/doc_root/#{base_name}/rdoc/index.html",
+ :name => base_name,
+ :url => doc_root(new_path),
:summary => ''
}
end
@@ -756,9 +738,6 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
WEBrick::Daemon.start if @daemon
- @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)
@@ -779,10 +758,21 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
@server.mount_proc "/rdoc", method(:rdoc)
- paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
- paths.each do |mount_point, mount_dir|
- @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
- File.join(@gem_dirs.first, mount_dir), true)
+ file_handlers = {
+ '/gems' => '/cache/',
+ }
+
+ if have_rdoc_4_plus? then
+ @server.mount '/doc_root', RDoc::Servlet, '/doc_root'
+ else
+ file_handlers['/doc_root'] = '/doc/'
+ end
+
+ @gem_dirs.each do |gem_dir|
+ file_handlers.each do |mount_point, mount_dir|
+ @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
+ File.join(gem_dir, mount_dir), true)
+ end
end
trap("INT") { @server.shutdown; exit! }
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
new file mode 100644
index 0000000000..c85a2cf660
--- /dev/null
+++ b/lib/rubygems/source.rb
@@ -0,0 +1,144 @@
+require 'uri'
+require 'fileutils'
+
+class Gem::Source
+ FILES = {
+ :released => 'specs',
+ :latest => 'latest_specs',
+ :prerelease => 'prerelease_specs',
+ }
+
+ def initialize(uri)
+ unless uri.kind_of? URI
+ uri = URI.parse(uri.to_s)
+ end
+
+ @uri = uri
+ @api_uri = nil
+ end
+
+ attr_reader :uri
+
+ def api_uri
+ require 'rubygems/remote_fetcher'
+ @api_uri ||= Gem::RemoteFetcher.fetcher.api_endpoint uri
+ end
+
+ def <=>(other)
+ if !@uri
+ return 0 unless other.uri
+ return -1
+ end
+
+ return 1 if !other.uri
+
+ @uri.to_s <=> other.uri.to_s
+ end
+
+ include Comparable
+
+ def ==(other)
+ case other
+ when self.class
+ @uri == other.uri
+ else
+ false
+ end
+ end
+
+ alias_method :eql?, :==
+
+ def hash
+ @uri.hash
+ end
+
+ ##
+ # Returns the local directory to write +uri+ to.
+
+ def cache_dir(uri)
+ # Correct for windows paths
+ escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
+ root = File.join Gem.user_home, '.gem', 'specs'
+ File.join root, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
+ end
+
+ def update_cache?
+ @update_cache ||= File.stat(Gem.user_home).uid == Process.uid
+ end
+
+ def fetch_spec(name)
+ fetcher = Gem::RemoteFetcher.fetcher
+
+ spec_file_name = name.spec_name
+
+ uri = @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
+ spec = Marshal.load(spec) rescue nil
+ return spec if spec
+ end
+
+ 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
+
+ # TODO: Investigate setting Gem::Specification#loaded_from to a URI
+ Marshal.load spec
+ end
+
+ ##
+ # Loads +type+ kind of specs fetching from +@uri+ if the on-disk cache is
+ # out of date.
+ #
+ # +type+ is one of the following:
+ #
+ # :released => Return the list of all released specs
+ # :latest => Return the list of only the highest version of each gem
+ # :prerelease => Return the list of all prerelease only specs
+ #
+
+ def load_specs(type)
+ file = FILES[type]
+ fetcher = Gem::RemoteFetcher.fetcher
+ file_name = "#{file}.#{Gem.marshal_version}"
+ spec_path = @uri + "#{file_name}.gz"
+ cache_dir = cache_dir spec_path
+ local_file = File.join(cache_dir, file_name)
+ retried = false
+
+ FileUtils.mkdir_p cache_dir if update_cache?
+
+ spec_dump = fetcher.cache_update_path(spec_path, local_file)
+
+ begin
+ Gem::NameTuple.from_list Marshal.load(spec_dump)
+ rescue ArgumentError
+ if update_cache? && !retried
+ FileUtils.rm local_file
+ retried = true
+ retry
+ else
+ raise Gem::Exception.new("Invalid spec cache file in #{local_file}")
+ end
+ end
+ end
+
+ def download(spec, dir=Dir.pwd)
+ fetcher = Gem::RemoteFetcher.fetcher
+ fetcher.download spec, @uri.to_s, dir
+ end
+end
diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb
deleted file mode 100644
index 1fe92c0a80..0000000000
--- a/lib/rubygems/source_index.rb
+++ /dev/null
@@ -1,406 +0,0 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
-require 'rubygems/specification'
-require 'rubygems/deprecate'
-
-##
-# The SourceIndex object indexes all the gems available from a
-# particular source (e.g. a list of gem directories, or a remote
-# source). A SourceIndex maps a gem full name to a gem
-# specification.
-#
-# NOTE:: The class used to be named Cache, but that became
-# confusing when cached source fetchers where introduced. The
-# constant Gem::Cache is an alias for this class to allow old
-# YAMLized source index objects to load properly.
-
-class Gem::SourceIndex
-
- include Enumerable
-
- attr_reader :gems # :nodoc:
-
- ##
- # Directories to use to refresh this SourceIndex when calling refresh!
-
- attr_accessor :spec_dirs
-
- ##
- # Factory method to construct a source index instance for a given
- # path.
- #
- # deprecated::
- # If supplied, from_installed_gems will act just like
- # +from_gems_in+. This argument is deprecated and is provided
- # just for backwards compatibility, and should not generally
- # be used.
- #
- # return::
- # SourceIndex instance
-
- def self.from_installed_gems(*deprecated)
- if deprecated.empty?
- from_gems_in(*installed_spec_directories)
- else
- warn "NOTE: from_installed_gems(arg) is deprecated. From #{caller.first}"
- from_gems_in(*deprecated) # HACK warn
- end
- end
-
- ##
- # Returns a list of directories from Gem.path that contain specifications.
-
- def self.installed_spec_directories
- # TODO: move to Gem::Utils
- Gem.path.collect { |dir| File.join(dir, "specifications") }
- end
-
- ##
- # Creates a new SourceIndex from the ruby format gem specifications in
- # +spec_dirs+.
-
- def self.from_gems_in(*spec_dirs)
- new spec_dirs
- end
-
- ##
- # Loads a ruby-format specification from +file_name+ and returns the
- # loaded spec.
-
- def self.load_specification(file_name)
- Gem::Deprecate.skip_during do
- Gem::Specification.load Gem::Path.new(file_name)
- end
- end
-
- ##
- # Constructs a source index instance from the provided specifications, which
- # is a Hash of gem full names and Gem::Specifications.
-
- def initialize specs_or_dirs = []
- @gems = {}
- @spec_dirs = nil
-
- case specs_or_dirs
- when Hash then
- specs_or_dirs.each do |full_name, spec|
- add_spec spec
- end
- when Array, String then
- self.spec_dirs = Array(specs_or_dirs)
- refresh!
- else
- arg = specs_or_dirs.inspect
- warn "NOTE: SourceIndex.new(#{arg}) is deprecated; From #{caller.first}."
- end
- end
-
- def all_gems
- gems
- end
-
- def prerelease_gems
- @gems.reject { |name, gem| !gem.version.prerelease? }
- end
-
- def released_gems
- @gems.reject { |name, gem| gem.version.prerelease? }
- end
-
- ##
- # Reconstruct the source index from the specifications in +spec_dirs+.
-
- def load_gems_in(*spec_dirs)
- @gems.clear
-
- spec_dirs.reverse_each do |spec_dir|
- spec_files = Dir[File.join(spec_dir, "*.gemspec")]
-
- spec_files.each do |spec_file|
- gemspec = Gem::Deprecate.skip_during do
- Gem::Specification.load spec_file
- end
- add_spec gemspec if gemspec
- end
- end
-
- self
- end
-
- ##
- # Returns an Array specifications for the latest released versions
- # of each gem in this index.
-
- def latest_specs(include_prerelease=false)
- result = Hash.new { |h,k| h[k] = [] }
- latest = {}
-
- sort.each do |_, spec|
- name = spec.name
- curr_ver = spec.version
- prev_ver = latest.key?(name) ? latest[name].version : nil
-
- next if !include_prerelease && curr_ver.prerelease?
- next unless prev_ver.nil? or curr_ver >= prev_ver or
- latest[name].platform != Gem::Platform::RUBY
-
- if prev_ver.nil? or
- (curr_ver > prev_ver and spec.platform == Gem::Platform::RUBY) then
- result[name].clear
- latest[name] = spec
- end
-
- if spec.platform != Gem::Platform::RUBY then
- result[name].delete_if do |result_spec|
- result_spec.platform == spec.platform
- end
- end
-
- result[name] << spec
- end
-
- result.values.flatten
- end
-
- ##
- # An array including only the prerelease gemspecs
-
- def prerelease_specs
- prerelease_gems.values
- end
-
- ##
- # An array including only the released gemspecs
-
- def released_specs
- released_gems.values
- end
-
- ##
- # Add a gem specification to the source index.
-
- def add_spec(gem_spec, name = gem_spec.full_name)
- # No idea why, but the Indexer wants to insert them using original_name
- # instead of full_name. So we make it an optional arg.
- @gems[name] = gem_spec
- end
-
- ##
- # Add gem specifications to the source index.
-
- def add_specs(*gem_specs)
- Gem::Deprecate.skip_during do
- gem_specs.each do |spec|
- add_spec spec
- end
- end
- end
-
- ##
- # Remove a gem specification named +full_name+.
-
- def remove_spec(full_name)
- @gems.delete full_name
- end
-
- ##
- # Iterate over the specifications in the source index.
-
- def each(&block) # :yields: gem.full_name, gem
- @gems.each(&block)
- end
-
- ##
- # The gem specification given a full gem spec name.
-
- def specification(full_name)
- @gems[full_name]
- end
-
- ##
- # The signature for the source index. Changes in the signature indicate a
- # change in the index.
-
- def index_signature
- require 'digest'
-
- Digest::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s
- end
-
- ##
- # The signature for the given gem specification.
-
- def gem_signature(gem_full_name)
- require 'digest'
-
- Digest::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s
- end
-
- def size
- @gems.size
- end
- alias length size
-
- ##
- # Find a gem by an exact match on the short name.
-
- def find_name(gem_name, requirement = Gem::Requirement.default)
- dep = Gem::Dependency.new gem_name, requirement
-
- Gem::Deprecate.skip_during do
- search dep
- end
- end
-
- ##
- # Search for a gem by Gem::Dependency +gem_pattern+. If +only_platform+
- # is true, only gems matching Gem::Platform.local will be returned. An
- # Array of matching Gem::Specification objects is returned.
- #
- # For backwards compatibility, a String or Regexp pattern may be passed as
- # +gem_pattern+, and a Gem::Requirement for +platform_only+. This
- # behavior is deprecated and will be removed.
-
- def search(gem_pattern, platform_or_requirement = false)
- requirement = nil
- only_platform = false # FIX: WTF is this?!?
-
- # TODO - Remove support and warning for legacy arguments after 2008/11
- unless Gem::Dependency === gem_pattern
- warn "#{Gem.location_of_caller.join ':'}:Warning: Gem::SourceIndex#search support for #{gem_pattern.class} patterns is deprecated, use #find_name"
- end
-
- case gem_pattern
- when Regexp then
- requirement = platform_or_requirement || Gem::Requirement.default
- when Gem::Dependency then
- only_platform = platform_or_requirement
- requirement = gem_pattern.requirement
-
- gem_pattern = if Regexp === gem_pattern.name then
- gem_pattern.name
- elsif gem_pattern.name.empty? then
- //
- else
- /^#{Regexp.escape gem_pattern.name}$/
- end
- else
- requirement = platform_or_requirement || Gem::Requirement.default
- gem_pattern = /#{gem_pattern}/i
- end
-
- unless Gem::Requirement === requirement then
- requirement = Gem::Requirement.create requirement
- end
-
- specs = @gems.values.select do |spec|
- spec.name =~ gem_pattern and
- requirement.satisfied_by? spec.version
- end
-
- if only_platform then
- specs = specs.select do |spec|
- Gem::Platform.match spec.platform
- end
- end
-
- specs.sort_by { |s| s.sort_obj }
- end
-
- ##
- # Replaces the gems in the source index from specifications in the
- # 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!
- 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
- outdateds = []
-
- latest_specs.each do |local|
- dependency = Gem::Dependency.new local.name, ">= #{local.version}"
-
- fetcher = Gem::SpecFetcher.fetcher
- remotes = fetcher.find_matching dependency
- remotes = remotes.map { |(_, version, _), _| version }
-
- latest = remotes.sort.last
-
- outdateds << local.name if latest and local.version < latest
- end
-
- outdateds
- end
-
- def ==(other) # :nodoc:
- self.class === other and @gems == other.gems
- end
-
- def dump
- Marshal.dump(self)
- end
-end
-
-# :stopdoc:
-module Gem
-
- ##
- # Cache is an alias for SourceIndex to allow older YAMLized source index
- # objects to load properly.
-
- Cache = SourceIndex
-
-end
-
-class Gem::SourceIndex
- extend Gem::Deprecate
-
- deprecate :all_gems, :none, 2011, 10
-
- deprecate :==, :none, 2011, 11 # noisy
- deprecate :add_specs, :none, 2011, 11 # noisy
- deprecate :each, :none, 2011, 11
- deprecate :gems, :none, 2011, 11
- deprecate :load_gems_in, :none, 2011, 11
- deprecate :refresh!, :none, 2011, 11
- deprecate :spec_dirs=, "Specification.dirs=", 2011, 11 # noisy
- deprecate :add_spec, "Specification.add_spec", 2011, 11
- deprecate :find_name, "Specification.find_by_name", 2011, 11
- deprecate :gem_signature, :none, 2011, 11
- deprecate :index_signature, :none, 2011, 11
- deprecate :initialize, :none, 2011, 11
- deprecate :latest_specs, "Specification.latest_specs", 2011, 11
- deprecate :length, "Specification.all.length", 2011, 11
- deprecate :outdated, :none, 2011, 11
- deprecate :prerelease_gems, :none, 2011, 11
- deprecate :prerelease_specs, :none, 2011, 11
- deprecate :released_gems, :none, 2011, 11
- deprecate :released_specs, :none, 2011, 11
- deprecate :remove_spec, "Specification.remove_spec", 2011, 11
- deprecate :search, :none, 2011, 11
- deprecate :size, "Specification.all.size", 2011, 11
- deprecate :spec_dirs, "Specification.dirs", 2011, 11
- deprecate :specification, "Specification.find", 2011, 11
-
- class << self
- extend Gem::Deprecate
-
- deprecate :from_gems_in, :none, 2011, 10
- deprecate :from_installed_gems, :none, 2011, 10
- deprecate :installed_spec_directories, "Specification.dirs", 2011, 11
- deprecate :load_specification, :none, 2011, 10
- end
-end
-
-# :startdoc:
diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb
new file mode 100644
index 0000000000..7bd8ef0b78
--- /dev/null
+++ b/lib/rubygems/source_list.rb
@@ -0,0 +1,87 @@
+require 'rubygems/source'
+
+class Gem::SourceList
+ def initialize
+ @sources = []
+ end
+
+ attr_reader :sources
+
+ def self.from(ary)
+ list = new
+
+ if ary
+ ary.each do |x|
+ list << x
+ end
+ end
+
+ return list
+ end
+
+ def initialize_copy(other)
+ @sources = @sources.dup
+ end
+
+ def <<(obj)
+ src = case obj
+ when URI
+ Gem::Source.new(obj)
+ when Gem::Source
+ obj
+ else
+ Gem::Source.new(URI.parse(obj))
+ end
+
+ @sources << src
+ src
+ end
+
+ def replace(other)
+ @sources.clear
+
+ other.each do |x|
+ self << x
+ end
+
+ self
+ end
+
+ def each
+ @sources.each { |s| yield s.uri.to_s }
+ end
+
+ def each_source(&b)
+ @sources.each(&b)
+ end
+
+ def ==(other)
+ to_a == other
+ end
+
+ def to_a
+ @sources.map { |x| x.uri.to_s }
+ end
+
+ alias_method :to_ary, :to_a
+
+ def first
+ @sources.first
+ end
+
+ def include?(other)
+ if other.kind_of? Gem::Source
+ @sources.include? other
+ else
+ @sources.find { |x| x.uri.to_s == other.to_s }
+ end
+ end
+
+ def delete(uri)
+ if uri.kind_of? Gem::Source
+ @sources.delete uri
+ else
+ @sources.delete_if { |x| x.uri.to_s == uri.to_s }
+ end
+ end
+end
diff --git a/lib/rubygems/source_local.rb b/lib/rubygems/source_local.rb
new file mode 100644
index 0000000000..44b170c4a4
--- /dev/null
+++ b/lib/rubygems/source_local.rb
@@ -0,0 +1,92 @@
+require 'rubygems/source'
+
+class Gem::Source::Local < Gem::Source
+ def initialize
+ @uri = nil
+ end
+
+ def load_specs(type)
+ names = []
+
+ @specs = {}
+
+ Dir["*.gem"].each do |file|
+ begin
+ pkg = Gem::Package.new(file)
+ rescue SystemCallError, Gem::Package::FormatError
+ # ignore
+ else
+ tup = pkg.spec.name_tuple
+ @specs[tup] = [File.expand_path(file), pkg]
+
+ case type
+ when :released
+ unless pkg.spec.version.prerelease?
+ names << pkg.spec.name_tuple
+ end
+ when :prerelease
+ if pkg.spec.version.prerelease?
+ names << pkg.spec.name_tuple
+ end
+ when :latest
+ tup = pkg.spec.name_tuple
+
+ cur = names.find { |x| x.name == tup.name }
+ if !cur
+ names << tup
+ elsif cur.version < tup.version
+ names.delete cur
+ names << tup
+ end
+ else
+ names << pkg.spec.name_tuple
+ end
+ end
+ end
+
+ names
+ end
+
+ def find_gem(gem_name, version=Gem::Requirement.default,
+ prerelease=false)
+ load_specs :complete
+
+ found = []
+
+ @specs.each do |n, data|
+ if n.name == gem_name
+ s = data[1].spec
+
+ if version.satisfied_by?(s.version)
+ if prerelease
+ found << s
+ elsif !s.version.prerelease?
+ found << s
+ end
+ end
+ end
+ end
+
+ found.sort_by { |s| s.version }.last
+ end
+
+ def fetch_spec(name)
+ load_specs :complete
+
+ if data = @specs[name]
+ data.last.spec
+ else
+ raise Gem::Exception, "Unable to find spec for '#{name}'"
+ end
+ end
+
+ def download(spec, cache_dir=nil)
+ load_specs :complete
+
+ @specs.each do |name, data|
+ return data[0] if data[1].spec == spec
+ end
+
+ raise Gem::Exception, "Unable to find file for '#{spec.full_name}'"
+ end
+end
diff --git a/lib/rubygems/source_specific_file.rb b/lib/rubygems/source_specific_file.rb
new file mode 100644
index 0000000000..d296e617cc
--- /dev/null
+++ b/lib/rubygems/source_specific_file.rb
@@ -0,0 +1,28 @@
+class Gem::Source::SpecificFile < Gem::Source
+ def initialize(file)
+ @uri = nil
+ @path = ::File.expand_path(file)
+
+ @package = Gem::Package.new @path
+ @spec = @package.spec
+ @name = @spec.name_tuple
+ end
+
+ attr_reader :spec
+
+ def load_specs(*a)
+ [@name]
+ end
+
+ def fetch_spec(name)
+ return @spec if name == @name
+ raise Gem::Exception, "Unable to find '#{name}'"
+ @spec
+ end
+
+ def download(spec, dir=nil)
+ return @path if spec == @spec
+ raise Gem::Exception, "Unable to download '#{spec.full_name}'"
+ end
+
+end
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 7302ad9ffa..531d023b2f 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -2,6 +2,7 @@ require 'rubygems/remote_fetcher'
require 'rubygems/user_interaction'
require 'rubygems/errors'
require 'rubygems/text'
+require 'rubygems/name_tuple'
##
# SpecFetcher handles metadata updates from remote gem repositories.
@@ -11,17 +12,6 @@ class Gem::SpecFetcher
include Gem::UserInteraction
include Gem::Text
- FILES = {
- :all => 'specs',
- :latest => 'latest_specs',
- :prerelease => 'prerelease_specs',
- }
-
- ##
- # The SpecFetcher cache dir.
-
- attr_reader :dir # :nodoc:
-
##
# Cache of latest specs
@@ -48,8 +38,6 @@ class Gem::SpecFetcher
end
def initialize
- require 'fileutils'
-
@dir = File.join Gem.user_home, '.gem', 'specs'
@update_cache = File.stat(Gem.user_home).uid == Process.uid
@@ -60,144 +48,124 @@ class Gem::SpecFetcher
@caches = {
:latest => @latest_specs,
:prerelease => @prerelease_specs,
- :all => @specs
+ :released => @specs,
}
@fetcher = Gem::RemoteFetcher.fetcher
end
##
- # Returns the local directory to write +uri+ to.
+ #
+ # Find and fetch gem name tuples that match +dependency+.
+ #
+ # If +matching_platform+ is false, gems for all platforms are returned.
- def cache_dir(uri)
- # Correct for windows paths
- escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
- File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
- end
+ def search_for_dependency(dependency, matching_platform=true)
+ found = {}
- ##
- # Fetch specs matching +dependency+. If +all+ is true, all matching
- # (released) versions are returned. If +matching_platform+ is
- # false, all platforms are returned. If +prerelease+ is true,
- # prerelease versions are included.
-
- def fetch_with_errors(dependency,
- all = false,
- matching_platform = true,
- prerelease = false)
-
- specs_and_sources, errors = find_matching_with_errors(dependency,
- all,
- matching_platform,
- prerelease)
-
- ss = specs_and_sources.map do |spec_tuple, source_uri|
- [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
+ rejected_specs = {}
+
+ if dependency.prerelease?
+ type = :complete
+ elsif dependency.latest_version?
+ type = :latest
+ else
+ type = :released
end
- return [ss, errors]
- end
+ list, errors = available_specs(type)
+ list.each do |source, specs|
+ found[source] = specs.select do |tup|
+ if dependency.match?(tup)
+ if matching_platform and !Gem::Platform.match(tup.platform)
+ pm = (
+ rejected_specs[dependency] ||= \
+ Gem::PlatformMismatch.new(tup.name, tup.version))
+ pm.add_platform tup.platform
+ false
+ else
+ true
+ end
+ end
+ end
+ end
- def fetch(*args)
- fetch_with_errors(*args).first
- end
+ errors += rejected_specs.values
- def fetch_spec(spec, source_uri)
- source_uri = URI.parse source_uri if String === source_uri
- spec = spec - [nil, 'ruby', '']
- spec_file_name = "#{spec.join '-'}.gemspec"
+ tuples = []
- uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
+ found.each do |source, specs|
+ specs.each do |s|
+ tuples << [s, source]
+ end
+ end
- cache_dir = cache_dir uri
+ tuples = tuples.sort_by { |x| x[0] }
- local_spec = File.join cache_dir, spec_file_name
+ return [tuples, errors]
+ end
- 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
+ ##
+ # Return all gem name tuples who's names match +obj+
- if @update_cache then
- FileUtils.mkdir_p cache_dir
+ def detect(type=:complete)
+ tuples = []
- open local_spec, 'wb' do |io|
- io.write spec
+ list, _ = available_specs(type)
+ list.each do |source, specs|
+ specs.each do |tup|
+ if yield(tup)
+ tuples << [tup, source]
end
end
end
- # TODO: Investigate setting Gem::Specification#loaded_from to a URI
- Marshal.load spec
+ tuples
end
+
##
- # Find spec names that match +dependency+. If +all+ is true, all
- # matching released versions are returned. If +matching_platform+
- # is false, gems for all platforms are returned.
-
- def find_matching_with_errors(dependency,
- all = false,
- matching_platform = true,
- prerelease = false)
- found = {}
+ # Find and fetch specs that match +dependency+.
+ #
+ # If +matching_platform+ is false, gems for all platforms are returned.
- rejected_specs = {}
+ def spec_for_dependency(dependency, matching_platform=true)
+ tuples, errors = search_for_dependency(dependency, matching_platform)
- list(all, prerelease).each do |source_uri, specs|
- found[source_uri] = specs.select do |spec_name, version, spec_platform|
- if dependency.match?(spec_name, version)
- if matching_platform and !Gem::Platform.match(spec_platform)
- pm = (rejected_specs[dependency] ||= Gem::PlatformMismatch.new(spec_name, version))
- pm.add_platform spec_platform
- false
- else
- true
- end
- end
+ specs = []
+ tuples.each do |tup, source|
+ begin
+ spec = source.fetch_spec(tup)
+ rescue Gem::RemoteFetcher::FetchError => e
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ specs << [spec, source]
end
end
- errors = rejected_specs.values
-
- specs_and_sources = []
-
- found.each do |source_uri, specs|
- uri_str = source_uri.to_s
- specs_and_sources.concat(specs.map { |spec| [spec, uri_str] })
- end
-
- [specs_and_sources, errors]
- end
-
- def find_matching(*args)
- find_matching_with_errors(*args).first
+ return [specs, errors]
end
##
- # Suggests a gem based on the supplied +gem_name+. Returns a string
- # of the gem name if an approximate match can be found or nil
- # otherwise. NOTE: for performance reasons only gems which exactly
- # match the first character of +gem_name+ are considered.
+ # Suggests gems based on the supplied +gem_name+. Returns an array of
+ # alternative gem names.
def suggest_gems_from_name gem_name
- gem_name = gem_name.downcase
+ gem_name = gem_name.downcase.tr('_-', '')
max = gem_name.size / 2
- specs = list.values.flatten 1
+ names = available_specs(:complete).first.values.flatten(1)
- matches = specs.map { |name, version, platform|
- next unless Gem::Platform.match platform
+ matches = names.map { |n|
+ next unless n.match_platform?
- distance = levenshtein_distance gem_name, name.downcase
+ distance = levenshtein_distance gem_name, n.name.downcase.tr('_-', '')
next if distance >= max
- return [name] if distance == 0
+ return [n.name] if distance == 0
- [name, distance]
+ [n.name, distance]
}.compact
matches = matches.uniq.sort_by { |name, dist| dist }
@@ -206,92 +174,46 @@ class Gem::SpecFetcher
end
##
- # Returns a list of gems available for each source in Gem::sources. If
- # +all+ is true, all released versions are returned instead of only latest
- # versions. If +prerelease+ is true, include prerelease versions.
-
- def list(all = false, prerelease = false)
- # TODO: make type the only argument
- type = if all
- :all
- elsif prerelease
- :prerelease
- else
- :latest
- end
-
- list = {}
- file = FILES[type]
- cache = @caches[type]
-
- Gem.sources.each do |source_uri|
- source_uri = URI.parse source_uri
-
- unless cache.include? source_uri
- cache[source_uri] = load_specs source_uri, file
- end
-
- list[source_uri] = cache[source_uri]
- end
-
- if type == :all
- list.values.map do |gems|
- gems.reject! { |g| !g[1] || g[1].prerelease? }
- end
- end
-
- list
- end
-
- ##
- # Loads specs in +file+, fetching from +source_uri+ if the on-disk cache is
- # out of date.
-
- def load_specs(source_uri, file)
- file_name = "#{file}.#{Gem.marshal_version}"
- spec_path = source_uri + "#{file_name}.gz"
- cache_dir = cache_dir spec_path
- local_file = File.join(cache_dir, file_name)
- loaded = false
-
- if File.exist? local_file then
+ # Returns a list of gems available for each source in Gem::sources.
+ #
+ # +type+ can be one of 3 values:
+ # :released => Return the list of all released specs
+ # :complete => Return the list of all specs
+ # :latest => Return the list of only the highest version of each gem
+ # :prerelease => Return the list of all prerelease only specs
+ #
+
+ def available_specs(type)
+ errors = []
+ list = {}
+
+ Gem.sources.each_source do |source|
begin
- spec_dump =
- @fetcher.fetch_path(spec_path, File.mtime(local_file))
+ names = case type
+ when :latest
+ tuples_for source, :latest
+ when :released
+ tuples_for source, :released
+ when :complete
+ tuples_for(source, :prerelease) + tuples_for(source, :released)
+ when :prerelease
+ tuples_for(source, :prerelease)
+ else
+ raise Gem::Exception, "Unknown type - :#{type}"
+ end
rescue Gem::RemoteFetcher::FetchError => e
- alert_warning "Error fetching data: #{e.message}"
+ errors << Gem::SourceFetchProblem.new(source, e)
+ else
+ list[source] = names
end
-
- loaded = true if spec_dump
-
- spec_dump ||= Gem.read_binary local_file
- else
- spec_dump = @fetcher.fetch_path spec_path
- loaded = true
end
- specs = begin
- Marshal.load spec_dump
- rescue ArgumentError
- spec_dump = @fetcher.fetch_path spec_path
- loaded = true
-
- Marshal.load spec_dump
- end
-
- if loaded and @update_cache then
- begin
- FileUtils.mkdir_p cache_dir
-
- open local_file, 'wb' do |io|
- io << spec_dump
- end
- rescue
- end
- end
-
- specs
+ [list, errors]
end
+ def tuples_for(source, type)
+ cache = @caches[type]
+ cache[source.uri] ||= source.load_specs(type)
+ end
end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 70a3fd09b4..1d290c8af5 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -4,32 +4,56 @@
# See LICENSE.txt for permissions.
#++
+##
+# The Specification class contains the information for a Gem. Typically
+# defined in a .gemspec file or a Rakefile, and looks like this:
+#
+# Gem::Specification.new do |s|
+# s.name = 'example'
+# s.version = '0.1.0'
+# s.summary = "This is an example!"
+# s.description = "Much longer explanation of the example!"
+# s.authors = ["Ruby Coder"]
+# s.email = 'rubycoder@example.com'
+# s.files = ["lib/example.rb"]
+# s.homepage = 'http://rubygems.org/gems/example'
+# end
+#
+# Starting in RubyGems 1.9.0, a Specification can hold arbitrary
+# metadata. This metadata is accessed via Specification#metadata
+# and has the following restrictions:
+#
+# * Must be a Hash object
+# * All keys and values must be Strings
+# * Keys can be a maximum of 128 bytes and values can be a
+# maximum of 1024 bytes
+# * All strings must be UTF8, no binary data is allowed
+#
+# For example, to add metadata for the location of a bugtracker:
+#
+# s.metadata = { "bugtracker" => "http://somewhere.com/blah" }
+#
+
+
require 'rubygems/version'
require 'rubygems/requirement'
require 'rubygems/platform'
-require "rubygems/deprecate"
+require 'rubygems/deprecate'
# :stopdoc:
-class Date; end # for ruby_code if date.rb wasn't required
+# date.rb can't be loaded for `make install` due to miniruby
+# Date is needed for old gems that stored #date as Date instead of Time.
+class Date; end
# :startdoc:
-##
-# The Specification class contains the metadata for a Gem. Typically
-# defined in a .gemspec file or a Rakefile, and looks like this:
-#
-# spec = Gem::Specification.new do |s|
-# s.name = 'example'
-# s.version = '1.0'
-# s.summary = 'Example gem specification'
-# ...
-# end
-#
-# For a great way to package gems, use Hoe.
-
class Gem::Specification
+ # REFACTOR: Consider breaking out this version stuff into a separate
+ # module. There's enough special stuff around it that it may justify
+ # a separate class.
+
##
- # The the version number of a specification that does not specify one
+ # The version number of a specification that does not specify one
# (i.e. RubyGems 0.7 or earlier).
NONEXISTENT_SPECIFICATION_VERSION = -1
@@ -49,17 +73,37 @@ class Gem::Specification
# 2 0.9.5 2007-10-01 Added "required_rubygems_version"
# Now forward-compatible with future versions
# 3 1.3.2 2009-01-03 Added Fixnum validation to specification_version
+ # 4 1.9.0 2011-06-07 Added metadata
#--
# When updating this number, be sure to also update #to_ruby.
#
# NOTE RubyGems < 1.2 cannot load specification versions > 2.
- CURRENT_SPECIFICATION_VERSION = 3
-
- # :stopdoc:
+ CURRENT_SPECIFICATION_VERSION = 4
+
+ ##
+ # 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 => [
+ 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"',
+ '"test_file=x" is a shortcut for "test_files=[x]"'
+ ],
+ 2 => [
+ 'Added "required_rubygems_version"',
+ 'Now forward-compatible with future versions',
+ ],
+ 3 => [
+ 'Added Fixnum validation to the specification_version'
+ ],
+ 4 => [
+ 'Added sandboxed freeform metadata to the specification version.'
+ ]
+ }
- # version => # of fields
- MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 17 }
+ MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16, 3 => 17, 4 => 18 }
today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day)
@@ -95,6 +139,7 @@ class Gem::Specification
:files => [],
:homepage => nil,
:licenses => [],
+ :metadata => {},
:name => nil,
:platform => Gem::Platform::RUBY,
:post_install_message => nil,
@@ -122,19 +167,37 @@ class Gem::Specification
# :section: Required gemspec attributes
##
- # This gem's name
+ # This gem's name.
+ #
+ # Usage:
+ #
+ # spec.name = 'rake'
attr_accessor :name
##
- # This gem's version
+ # This gem's version.
+ #
+ # The version string can contain numbers and periods, such as +1.0.0+.
+ # A gem is a 'prerelease' gem if the version has a letter in it, such as
+ # +1.0.0.pre+.
+ #
+ # Usage:
+ #
+ # spec.version = '0.4.1'
attr_reader :version
##
- # Paths in the gem to add to $LOAD_PATH when this gem is activated.
+ # Paths in the gem to add to <tt>$LOAD_PATH</tt> when this gem is activated.
#
- # The default ['lib'] is typically sufficient.
+ # Usage:
+ #
+ # # If all library files are in the root directory...
+ # spec.require_path = '.'
+ #
+ # # If you have 'lib' and 'ext' directories...
+ # spec.require_paths << 'ext'
attr_accessor :require_paths
@@ -146,32 +209,113 @@ class Gem::Specification
attr_accessor :rubygems_version
##
- # The Gem::Specification version of this gemspec.
+ # A short summary of this gem's description. Displayed in `gem list -d`.
#
- # Do not set this, it is set automatically when the gem is packaged.
+ # The description should be more detailed than the summary.
+ #
+ # Usage:
+ #
+ # spec.summary = "This is a small summary of my gem"
- attr_accessor :specification_version
+ attr_reader :summary
##
- # A short summary of this gem's description. Displayed in `gem list -d`.
+ # The platform this gem runs on.
+ #
+ # This is usually Gem::Platform::RUBY or Gem::Platform::CURRENT.
+ #
+ # Most gems contain pure Ruby code; they should simply leave the default value
+ # in place. Some gems contain C (or other) code to be compiled into a Ruby
+ # “extensionâ€. The should leave the default value in place unless their code
+ # will only compile on a certain type of system. Some gems consist of
+ # pre-compiled code (“binary gemsâ€). It’s especially important that they set
+ # the platform attribute appropriately. A shortcut is to set the platform to
+ # Gem::Platform::CURRENT, which will cause the gem builder to set the platform
+ # to the appropriate value for the system on which the build is being performed.
+ #
+ # If this attribute is set to a non-default value, it will be included in the
+ # filename of the gem when it is built, e.g. fxruby-1.2.0-win32.gem.
+ #
+ # Usage:
#
- # The description should be more detailed than the summary. For example,
- # you might wish to copy the entire README into the description.
+ # spec.platform = Gem::Platform::Win32
- attr_reader :summary
+ def platform= platform
+ if @original_platform.nil? or
+ @original_platform == Gem::Platform::RUBY then
+ @original_platform = platform
+ end
- ######################################################################
- # :section: Optional gemspec attributes
+ case platform
+ when Gem::Platform::CURRENT then
+ @new_platform = Gem::Platform.local
+ @original_platform = @new_platform.to_s
+
+ when Gem::Platform then
+ @new_platform = platform
+
+ # legacy constants
+ when nil, Gem::Platform::RUBY then
+ @new_platform = Gem::Platform::RUBY
+ when 'mswin32' then # was Gem::Platform::WIN32
+ @new_platform = Gem::Platform.new 'x86-mswin32'
+ when 'i586-linux' then # was Gem::Platform::LINUX_586
+ @new_platform = Gem::Platform.new 'x86-linux'
+ when 'powerpc-darwin' then # was Gem::Platform::DARWIN
+ @new_platform = Gem::Platform.new 'ppc-darwin'
+ else
+ @new_platform = Gem::Platform.new platform
+ end
+
+ @platform = @new_platform.to_s
+
+ invalidate_memoized_attributes
+
+ @new_platform
+ end
##
- # Autorequire was used by old RubyGems to automatically require a file.
+ # Files included in this gem. You cannot append to this accessor, you must
+ # assign to it.
#
- # Deprecated: It is neither supported nor functional.
+ # Only add files you can require to this list, not directories, etc.
+ #
+ # Directories are automatically stripped from this list when building a gem,
+ # other non-files cause an error.
+ #
+ # Usage:
+ #
+ # require 'rake'
+ # spec.files = FileList['lib/**/*.rb',
+ # 'bin/*',
+ # '[A-Z]*',
+ # 'test/**/*'].to_a
+ #
+ # # or without Rake...
+ # spec.files = Dir['lib/**/*.rb'] + Dir['bin/*']
+ # spec.files += Dir['[A-Z]*'] + Dir['test/**/*']
+ # spec.files.reject! { |fn| fn.include? "CVS" }
- attr_accessor :autorequire
+ def files
+ # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks)
+ # DOC: Why isn't it normal? Why does it suck? How can we fix this?
+ @files = [@files,
+ @test_files,
+ add_bindir(@executables),
+ @extra_rdoc_files,
+ @extensions,
+ ].flatten.uniq.compact
+ end
+
+ ######################################################################
+ # :section: Optional gemspec attributes
##
# The path in the gem for executable scripts. Usually 'bin'
+ #
+ # Usage:
+ #
+ # spec.bindir = 'bin'
attr_accessor :bindir
@@ -183,39 +327,241 @@ class Gem::Specification
##
# A long description of this gem
+ #
+ # The description should be more detailed than the summary.
+ #
+ # Usage:
+ #
+ # spec.description = <<-EOF
+ # Rake is a Make-like program implemented in Ruby. Tasks and
+ # dependencies are specified in standard Ruby syntax.
+ # EOF
attr_reader :description
##
- # Sets the default executable for this gem.
+ # A contact email for this gem
#
- # Deprecated: You must now specify the executable name to Gem.bin_path.
+ # Usage:
+ #
+ # spec.email = 'john.jones@example.com'
+ # spec.email = ['jack@example.com', 'jill@example.com']
- attr_writer :default_executable
+ attr_accessor :email
##
- # A contact email for this gem
+ # The URL of this gem's home page
+ #
+ # Usage:
#
- # If you are providing multiple authors and multiple emails they should be
- # in the same order such that:
+ # spec.homepage = 'http://rake.rubyforge.org'
+
+ attr_accessor :homepage
+
+ ##
+ # A message that gets displayed after the gem is installed.
#
- # Hash[*spec.authors.zip(spec.emails).flatten]
+ # Usage:
#
- # Gives a hash of author name to email address.
+ # spec.post_install_message = "Thanks for installing!"
- attr_accessor :email
+ attr_accessor :post_install_message
##
- # The URL of this gem's home page
+ # The key used to sign this gem. See Gem::Security for details.
- attr_accessor :homepage
+ attr_accessor :signing_key
##
- # True when this gemspec has been activated. This attribute is not persisted.
+ # :attr_accessor: metadata
+ #
+ # Arbitrary metadata for this gem. An instance of Hash.
+ #
+ # metadata is simply a Symbol => String association that contains arbitary
+ # data that could be useful to other consumers.
- attr_accessor :loaded # :nodoc:
+ attr_accessor :metadata
- alias :loaded? :loaded # :nodoc:
+ ##
+ # Adds a development dependency named +gem+ with +requirements+ to this
+ # gem.
+ #
+ # Usage:
+ #
+ # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4'
+ #
+ # Development dependencies aren't installed by default and aren't
+ # activated when a gem is required.
+
+ def add_development_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :development, *requirements)
+ end
+
+ ##
+ # Adds a runtime dependency named +gem+ with +requirements+ to this gem.
+ #
+ # Usage:
+ #
+ # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'
+
+ def add_runtime_dependency(gem, *requirements)
+ add_dependency_with_type(gem, :runtime, *requirements)
+ end
+
+ ##
+ # Singular writer for #authors
+ #
+ # Usage:
+ #
+ # spec.author = 'John Jones'
+
+ def author= o
+ self.authors = [o]
+ end
+
+ ##
+ # Sets the list of authors, ensuring it is an array.
+ #
+ # Usage:
+ #
+ # spec.authors = ['John Jones', 'Mary Smith']
+
+ def authors= value
+ @authors = Array(value).flatten.grep(String)
+ end
+
+ ##
+ # Executables included in the gem.
+ #
+ # For example, the rake gem has rake as an executable. You don’t specify the
+ # full path (as in bin/rake); all application-style files are expected to be
+ # found in bindir. These files must be executable ruby files. Files that
+ # use bash or other interpreters will not work.
+ #
+ # Usage:
+ #
+ # spec.executables << 'rake'
+
+ def executables
+ @executables ||= []
+ end
+
+ ##
+ # Extensions to build when installing the gem, specifically the paths to
+ # extconf.rb-style files used to compile extensions.
+ #
+ # These files will be run when the gem is installed, causing the C (or
+ # whatever) code to be compiled on the user’s machine.
+ #
+ # Usage:
+ #
+ # spec.extensions << 'ext/rmagic/extconf.rb'
+
+ def extensions
+ @extensions ||= []
+ end
+
+ ##
+ # Extra files to add to RDoc such as README or doc/examples.txt
+ #
+ # When the user elects to generate the RDoc documentation for a gem (typically
+ # at install time), all the library files are sent to RDoc for processing.
+ # This option allows you to have some non-code files included for a more
+ # complete set of documentation.
+ #
+ # Usage:
+ #
+ # spec.extra_rdoc_files = ['README', 'doc/user-guide.txt']
+
+ def extra_rdoc_files
+ @extra_rdoc_files ||= []
+ end
+
+ ##
+ # The license for this gem.
+ #
+ # The license must be a short name, no more than 64 characters.
+ #
+ # This should just be the name of your license. The full
+ # text of the license should be inside of the gem when you build it.
+ #
+ # Usage:
+ # spec.license = 'MIT'
+
+ def license=o
+ self.licenses = [o]
+ end
+
+ ##
+ # The license(s) for the library.
+ #
+ # Each license must be a short name, no more than 64 characters.
+ #
+ # This should just be the name of your license. The full
+ # text of the license should be inside of the gem when you build it.
+ #
+ # Usage:
+ # spec.licenses = ['MIT', 'GPL-2']
+
+ def licenses= licenses
+ @licenses = Array licenses
+ end
+
+ ##
+ # Specifies the rdoc options to be used when generating API documentation.
+ #
+ # Usage:
+ #
+ # spec.rdoc_options << '--title' << 'Rake -- Ruby Make' <<
+ # '--main' << 'README' <<
+ # '--line-numbers'
+
+ def rdoc_options
+ @rdoc_options ||= []
+ end
+
+ ##
+ # The version of ruby required by this gem
+ #
+ # Usage:
+ #
+ # # If it will work with 1.8.6 or greater...
+ # spec.required_ruby_version = '>= 1.8.6'
+ #
+ # # Hopefully by now:
+ # spec.required_ruby_version = '>= 1.9.2'
+
+ def required_ruby_version= req
+ @required_ruby_version = Gem::Requirement.create req
+ end
+
+ ##
+ # Lists the external (to RubyGems) requirements that must be met for this gem
+ # to work. It’s simply information for the user.
+ #
+ # Usage:
+ #
+ # spec.requirements << 'libmagick, v6.0'
+ # spec.requirements << 'A good graphics card'
+
+ def requirements
+ @requirements ||= []
+ end
+
+ ##
+ # A collection of unit test files. They will be loaded as unit tests when
+ # the user requests a gem to be unit tested.
+ #
+ # Usage:
+ # spec.test_files = Dir.glob('test/tc_*.rb')
+ # spec.test_files = ['tests/test-suite.rb']
+
+ def test_files= files
+ @test_files = Array files
+ end
+
+ ######################################################################
+ # :section: Specification internals
##
# True when this gemspec has been activated. This attribute is not persisted.
@@ -225,6 +571,20 @@ class Gem::Specification
alias :activated? :activated
##
+ # Autorequire was used by old RubyGems to automatically require a file.
+ #
+ # Deprecated: It is neither supported nor functional.
+
+ attr_accessor :autorequire
+
+ ##
+ # Sets the default executable for this gem.
+ #
+ # Deprecated: You must now specify the executable name to Gem.bin_path.
+
+ attr_writer :default_executable
+
+ ##
# Path this gemspec was loaded from. This attribute is not persisted.
attr_reader :loaded_from
@@ -235,11 +595,6 @@ class Gem::Specification
attr_writer :original_platform # :nodoc:
##
- # A message that gets displayed after the gem is installed
-
- attr_accessor :post_install_message
-
- ##
# The version of ruby required by this gem
attr_reader :required_ruby_version
@@ -248,7 +603,6 @@ class Gem::Specification
# The RubyGems version required by this gem
attr_reader :required_rubygems_version
-
##
# The rubyforge project this gem lives under. i.e. RubyGems'
# rubyforge_project is "rubygems".
@@ -256,22 +610,49 @@ class Gem::Specification
attr_accessor :rubyforge_project
##
- # The key used to sign this gem. See Gem::Security for details.
+ # The Gem::Specification version of this gemspec.
+ #
+ # Do not set this, it is set automatically when the gem is packaged.
- attr_accessor :signing_key
+ attr_accessor :specification_version
- def self._all # :nodoc:
- unless defined?(@@all) && @@all then
- specs = {}
+ class << self
+ def default_specifications_dir
+ File.join(Gem.default_dir, "specifications", "default")
+ end
- self.dirs.each { |dir|
+ private
+ def each_spec(search_dirs) # :nodoc:
+ search_dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
- specs[spec.full_name] ||= spec if spec
+ yield(spec) if spec
}
}
+ end
+
+ def each_default(&block) # :nodoc:
+ each_spec([default_specifications_dir],
+ &block)
+ end
+
+ def each_normal(&block) # :nodoc:
+ each_spec(dirs, &block)
+ end
+ end
+
+ def self._all # :nodoc:
+ unless defined?(@@all) && @@all then
+
+ specs = {}
+ each_default do |spec|
+ specs[spec.full_name] ||= spec
+ end
+ each_normal do |spec|
+ specs[spec.full_name] ||= spec
+ end
@@all = specs.values
@@ -289,6 +670,15 @@ class Gem::Specification
end
##
+ # Loads the default specifications. It should be called only once.
+
+ def self.load_defaults
+ each_default do |spec|
+ Gem.register_default_spec(spec)
+ end
+ end
+
+ ##
# Adds +spec+ to the known specifications, keeping the collection
# properly sorted.
@@ -380,7 +770,7 @@ class Gem::Specification
def self.dirs
@@dirs ||= Gem.path.collect { |dir|
- File.join dir, "specifications"
+ File.join dir.dup.untaint, "specifications"
}
end
@@ -445,11 +835,21 @@ class Gem::Specification
end
##
+ # Return the best specification that contains the file matching +path+
+ # amongst the specs that are not activated.
+
+ def self.find_inactive_by_path path
+ self.find { |spec|
+ spec.contains_requirable_file? path unless spec.activated?
+ }
+ end
+
+ ##
# Return currently unresolved specs that contain the file matching +path+.
def self.find_in_unresolved path
# TODO: do we need these?? Kill it
- specs = Gem.unresolved_deps.values.map { |dep| dep.to_specs }.flatten
+ specs = unresolved_deps.values.map { |dep| dep.to_specs }.flatten
specs.find_all { |spec| spec.contains_requirable_file? path }
end
@@ -459,7 +859,7 @@ class Gem::Specification
# specs that contain the file matching +path+.
def self.find_in_unresolved_tree path
- specs = Gem.unresolved_deps.values.map { |dep| dep.to_specs }.flatten
+ specs = unresolved_deps.values.map { |dep| dep.to_specs }.flatten
specs.reverse_each do |spec|
trails = []
@@ -498,13 +898,9 @@ class Gem::Specification
raise Gem::Exception, "YAML data doesn't evaluate to gem specification"
end
- unless (spec.instance_variables.include? '@specification_version' or
- spec.instance_variables.include? :@specification_version) and
- spec.instance_variable_get :@specification_version
- spec.instance_variable_set :@specification_version,
- NONEXISTENT_SPECIFICATION_VERSION
- end
-
+ spec.instance_eval { @specification_version ||= NONEXISTENT_SPECIFICATION_VERSION }
+ spec.reset_nil_attributes_to_default
+
spec
end
@@ -533,9 +929,9 @@ class Gem::Specification
# Loads Ruby format gemspec from +file+.
def self.load file
- return unless file && File.file?(file)
-
+ return unless file
file = file.dup.untaint
+ return unless File.file?(file)
code = if defined? Encoding
File.read file, :mode => 'r:UTF-8:-'
@@ -595,9 +991,9 @@ class Gem::Specification
latest_specs.each do |local|
dependency = Gem::Dependency.new local.name, ">= #{local.version}"
- remotes = fetcher.find_matching dependency
- remotes = remotes.map { |(_, version, _), _| version }
- latest = remotes.sort.last
+ remotes, _ = fetcher.search_for_dependency dependency
+ remotes = remotes.map { |n, _| n.version }
+ latest = remotes.sort.last
outdateds << local.name if latest and local.version < latest
end
@@ -635,14 +1031,27 @@ class Gem::Specification
def self.reset
@@dirs = nil
- # from = caller.first(10).reject { |s| s =~ /minitest/ }
- # warn ""
- # warn "NOTE: Specification.reset from #{from.inspect}"
- Gem.pre_reset_hooks.each { |hook| hook.call }
+ Gem.pre_reset_hooks.each { |hook| hook.call }
@@all = nil
+ unresolved = unresolved_deps
+ unless unresolved.empty? then
+ w = "W" + "ARN"
+ warn "#{w}: Unresolved specs during Gem::Specification.reset:"
+ unresolved.values.each do |dep|
+ warn " #{dep}"
+ end
+ warn "#{w}: Clearing out unresolved specs."
+ warn "Please report a bug if this causes problems."
+ unresolved.clear
+ end
Gem.post_reset_hooks.each { |hook| hook.call }
end
+ # DOC: This method needs documented or nodoc'd
+ def self.unresolved_deps
+ @unresolved_deps ||= Hash.new { |h, n| h[n] = Gem::Dependency.new n }
+ end
+
##
# Load custom marshal format, re-initializing defaults as needed
@@ -690,6 +1099,7 @@ class Gem::Specification
spec.instance_variable_set :@new_platform, array[16]
spec.instance_variable_set :@platform, array[16].to_s
spec.instance_variable_set :@license, array[17]
+ spec.instance_variable_set :@metadata, array[18]
spec.instance_variable_set :@loaded, false
spec.instance_variable_set :@activated, false
@@ -732,7 +1142,8 @@ class Gem::Specification
@homepage,
true, # has_rdoc
@new_platform,
- @licenses
+ @licenses,
+ @metadata
]
end
@@ -763,6 +1174,8 @@ class Gem::Specification
# resolved later, as needed.
def activate_dependencies
+ unresolved = Gem::Specification.unresolved_deps
+
self.runtime_dependencies.each do |spec_dep|
if loaded = Gem.loaded_specs[spec_dep.name]
next if spec_dep.matches_spec? loaded
@@ -780,11 +1193,11 @@ class Gem::Specification
specs.first.activate
else
name = spec_dep.name
- Gem.unresolved_deps[name] = Gem.unresolved_deps[name].merge spec_dep
+ unresolved[name] = unresolved[name].merge spec_dep
end
end
- Gem.unresolved_deps.delete self.name
+ unresolved.delete self.name
end
##
@@ -816,8 +1229,7 @@ class Gem::Specification
end
unless dependency.respond_to?(:name) &&
- dependency.respond_to?(:version_requirements)
-
+ dependency.respond_to?(:version_requirements)
dependency = Gem::Dependency.new(dependency, requirements, type)
end
@@ -826,29 +1238,6 @@ class Gem::Specification
private :add_dependency_with_type
- ##
- # Adds a development dependency named +gem+ with +requirements+ to this
- # Gem. For example:
- #
- # spec.add_development_dependency 'example', '~> 1.1', '>= 1.1.4'
- #
- # Development dependencies aren't installed by default and aren't
- # activated when a gem is required.
-
- def add_development_dependency(gem, *requirements)
- add_dependency_with_type(gem, :development, *requirements)
- end
-
- ##
- # Adds a runtime dependency named +gem+ with +requirements+ to this Gem.
- # For example:
- #
- # spec.add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'
-
- def add_runtime_dependency(gem, *requirements)
- add_dependency_with_type(gem, :runtime, *requirements)
- end
-
alias add_dependency add_runtime_dependency
##
@@ -879,34 +1268,13 @@ class Gem::Specification
end
##
- # Singular writer for #authors
-
- def author= o
- self.authors = [o]
- end
-
- ##
# The list of author names who wrote this gem.
- #
- # If you are providing multiple authors and multiple emails they should be
- # in the same order such that:
- #
- # Hash[*spec.authors.zip(spec.emails).flatten]
- #
- # Gives a hash of author name to email address.
def authors
@authors ||= []
end
##
- # Sets the list of authors, ensuring it is an array.
-
- def authors= value
- @authors = Array(value).flatten.grep(String)
- end
-
- ##
# Returns the full path to the base gem directory.
#
# eg: /usr/local/lib/ruby/gems/1.8
@@ -934,6 +1302,32 @@ class Gem::Specification
end
##
+ # Returns the build_args used to install the gem
+
+ def build_args
+ if File.exists? build_info_file
+ File.readlines(build_info_file).map { |x| x.strip }
+ else
+ []
+ end
+ end
+
+ ##
+ # Returns the full path to the build info directory
+
+ def build_info_dir
+ File.join base_dir, "build_info"
+ end
+
+ ##
+ # Returns the full path to the file containing the build
+ # information generated when the gem was installed
+
+ def build_info_file
+ File.join build_info_dir, "#{full_name}.info"
+ end
+
+ ##
# Returns the full path to the cache directory containing this
# spec's cached gem.
@@ -948,8 +1342,6 @@ class Gem::Specification
@cache_file ||= File.join cache_dir, "#{full_name}.gem"
end
- alias :cache_gem :cache_file
-
##
# Return any possible conflicts against the currently loaded specs.
@@ -969,17 +1361,13 @@ class Gem::Specification
# Return true if this spec can require +file+.
def contains_requirable_file? file
- root = full_gem_path
+ root = full_gem_path
+ suffixes = Gem.suffixes
- require_paths.each do |lib|
+ require_paths.any? do |lib|
base = "#{root}/#{lib}/#{file}"
- Gem.suffixes.each do |suf|
- path = "#{base}#{suf}"
- return true if File.file? path
- end
+ suffixes.any? { |suf| File.file? "#{base}#{suf}" }
end
-
- return false
end
##
@@ -989,10 +1377,15 @@ class Gem::Specification
@date ||= TODAY
end
+ DateTimeFormat = /\A
+ (\d{4})-(\d{2})-(\d{2})
+ (\s+ \d{2}:\d{2}:\d{2}\.\d+ \s* (Z | [-+]\d\d:\d\d) )?
+ \Z/x
+
##
# The date this gem was created
#
- # Do not set this, it is set automatically when the gem is packaged.
+ # DO NOT set this, it is set automatically when the gem is packaged.
def date= date
# We want to end up with a Time object with one-day resolution.
@@ -1000,7 +1393,7 @@ class Gem::Specification
# way to do it.
@date = case date
when String then
- if /\A(\d{4})-(\d{2})-(\d{2})\Z/ =~ date then
+ if DateTimeFormat =~ date then
Time.utc($1.to_i, $2.to_i, $3.to_i)
# Workaround for where the date format output from psych isn't
@@ -1060,6 +1453,7 @@ class Gem::Specification
# [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]]
def dependent_gems
+ # REFACTOR: out = []; each; out; ? Really? No #collect love?
out = []
Gem::Specification.each do |spec|
spec.dependencies.each do |dep|
@@ -1097,10 +1491,21 @@ class Gem::Specification
end
##
- # Returns the full path to this spec's documentation directory.
+ # Returns the full path to this spec's documentation directory. If +type+
+ # is given it will be appended to the end. For examlpe:
+ #
+ # spec.doc_dir # => "/path/to/gem_repo/doc/a-1"
+ #
+ # spec.doc_dir 'ri' # => "/path/to/gem_repo/doc/a-1/ri"
- def doc_dir
+ def doc_dir type = nil
@doc_dir ||= File.join base_dir, 'doc', full_name
+
+ if type then
+ File.join @doc_dir, type
+ else
+ @doc_dir
+ end
end
def encode_with coder # :nodoc:
@@ -1143,13 +1548,6 @@ class Gem::Specification
end
##
- # Executables included in the gem.
-
- def executables
- @executables ||= []
- end
-
- ##
# Sets executables to +value+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1159,14 +1557,6 @@ class Gem::Specification
end
##
- # Extensions to build when installing the gem. See
- # Gem::Installer#build_extensions for valid values.
-
- def extensions
- @extensions ||= []
- end
-
- ##
# Sets extensions to +extensions+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1176,13 +1566,6 @@ class Gem::Specification
end
##
- # Extra files to add to RDoc such as README or doc/examples.txt
-
- def extra_rdoc_files
- @extra_rdoc_files ||= []
- end
-
- ##
# Sets extra_rdoc_files to +files+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1201,25 +1584,6 @@ class Gem::Specification
end
##
- # Files included in this gem. You cannot append to this accessor, you must
- # assign to it.
- #
- # Only add files you can require to this list, not directories, etc.
- #
- # Directories are automatically stripped from this list when building a gem,
- # other non-files cause an error.
-
- def files
- # DO NOT CHANGE TO ||= ! This is not a normal accessor. (yes, it sucks)
- @files = [@files,
- @test_files,
- add_bindir(@executables),
- @extra_rdoc_files,
- @extensions,
- ].flatten.uniq.compact
- end
-
- ##
# Sets files to +files+, ensuring it is an array.
def files= files
@@ -1253,11 +1617,14 @@ class Gem::Specification
# The full path to the gem (install path + full name).
def full_gem_path
- # TODO: try to get rid of this... or the awkward
+ # TODO: This is a heavily used method by gems, so we'll need
+ # to aleast just alias it to #gem_dir rather than remove it.
+
# TODO: also, shouldn't it default to full_name if it hasn't been written?
return @full_gem_path if defined?(@full_gem_path) && @full_gem_path
@full_gem_path = File.expand_path File.join(gems_dir, full_name)
+ @full_gem_path.untaint
return @full_gem_path if File.directory? @full_gem_path
@@ -1270,11 +1637,11 @@ class Gem::Specification
# default Ruby platform.
def full_name
- if platform == Gem::Platform::RUBY or platform.nil? then
- "#{@name}-#{@version}"
- else
- "#{@name}-#{@version}-#{platform}"
- end
+ @full_name ||= if platform == Gem::Platform::RUBY or platform.nil? then
+ "#{@name}-#{@version}".untaint
+ else
+ "#{@name}-#{@version}-#{platform}".untaint
+ end
end
##
@@ -1372,13 +1739,9 @@ class Gem::Specification
# Duplicates array_attributes from +other_spec+ so state isn't shared.
def initialize_copy other_spec
- other_ivars = other_spec.instance_variables
- other_ivars = other_ivars.map { |ivar| ivar.intern } if # for 1.9
- String === other_ivars.first
-
self.class.array_attributes.each do |name|
name = :"@#{name}"
- next unless other_ivars.include? name
+ next unless other_spec.instance_variable_defined? name
begin
val = other_spec.instance_variable_get(name)
@@ -1398,11 +1761,22 @@ class Gem::Specification
end
##
- # The directory that this gem was installed into.
- # TODO: rename - horrible. this is the base_dir for a gem path
+ # Expire memoized instance variables that can incorrectly generate, replace
+ # or miss files due changes in certain attributes used to compute them.
- def installation_path
- loaded_from && base_dir
+ def invalidate_memoized_attributes
+ @full_name = nil
+ @cache_file = nil
+ end
+
+ private :invalidate_memoized_attributes
+
+ def inspect
+ if $DEBUG
+ super
+ else
+ "#<#{self.class}:0x#{__id__.to_s(16)} #{full_name}>"
+ end
end
##
@@ -1425,7 +1799,7 @@ class Gem::Specification
def lib_files
@files.select do |file|
require_paths.any? do |path|
- file.index(path) == 0
+ file.start_with? path
end
end
end
@@ -1438,33 +1812,31 @@ class Gem::Specification
end
##
- # Singular accessor for #licenses
-
- def license=o
- self.licenses = [o]
- end
-
- ##
- # The license(s) for the library. Each license must be a short name, no
- # more than 64 characters.
+ # Plural accessor for setting licenses
def licenses
@licenses ||= []
end
##
- # Set licenses to +licenses+, ensuring it is an array.
-
- def licenses= licenses
- @licenses = Array licenses
- end
-
- ##
# Set the location a Specification was loaded from. +obj+ is converted
# to a String.
def loaded_from= path
- @loaded_from = path.to_s
+ @loaded_from = path.to_s
+
+ # reset everything @loaded_from depends upon
+ @base_dir = nil
+ @bin_dir = nil
+ @cache_dir = nil
+ @cache_file = nil
+ @doc_dir = nil
+ @full_gem_path = nil
+ @gem_dir = nil
+ @gems_dir = nil
+ @ri_dir = nil
+ @spec_dir = nil
+ @spec_file = nil
end
##
@@ -1517,6 +1889,13 @@ class Gem::Specification
end
##
+ # Return a NameTuple that represents this Specification
+
+ def name_tuple
+ Gem::NameTuple.new name, version, original_platform
+ end
+
+ ##
# Returns the full name (name-version) of this gemspec using the original
# platform. For use with legacy gems.
@@ -1542,44 +1921,6 @@ class Gem::Specification
@new_platform ||= Gem::Platform::RUBY
end
- ##
- # The platform this gem runs on. See Gem::Platform for details.
- #
- # Setting this to any value other than Gem::Platform::RUBY or
- # Gem::Platform::CURRENT is probably wrong.
-
- def platform= platform
- if @original_platform.nil? or
- @original_platform == Gem::Platform::RUBY then
- @original_platform = platform
- end
-
- case platform
- when Gem::Platform::CURRENT then
- @new_platform = Gem::Platform.local
- @original_platform = @new_platform.to_s
-
- when Gem::Platform then
- @new_platform = platform
-
- # legacy constants
- when nil, Gem::Platform::RUBY then
- @new_platform = Gem::Platform::RUBY
- when 'mswin32' then # was Gem::Platform::WIN32
- @new_platform = Gem::Platform.new 'x86-mswin32'
- when 'i586-linux' then # was Gem::Platform::LINUX_586
- @new_platform = Gem::Platform.new 'x86-linux'
- when 'powerpc-darwin' then # was Gem::Platform::DARWIN
- @new_platform = Gem::Platform.new 'ppc-darwin'
- else
- @new_platform = Gem::Platform.new platform
- end
-
- @platform = @new_platform.to_s
-
- @new_platform
- end
-
def pretty_print(q) # :nodoc:
q.group 2, 'Gem::Specification.new do |s|', 'end' do
q.breakable
@@ -1639,13 +1980,6 @@ class Gem::Specification
end
##
- # An ARGV style array of options to RDoc
-
- def rdoc_options
- @rdoc_options ||= []
- end
-
- ##
# Sets rdoc_options to +value+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1669,13 +2003,6 @@ class Gem::Specification
end
##
- # The version of ruby required by this gem
-
- def required_ruby_version= req
- @required_ruby_version = Gem::Requirement.create req
- end
-
- ##
# The RubyGems version required by this gem
def required_rubygems_version= req
@@ -1683,14 +2010,6 @@ class Gem::Specification
end
##
- # An array or things required by this gem. Not used by anything
- # presently.
-
- def requirements
- @requirements ||= []
- end
-
- ##
# Set requirements to +req+, ensuring it is an array. Don't
# use this, push onto the array instead.
@@ -1714,6 +2033,9 @@ class Gem::Specification
case obj
when String then obj.dump
when Array then '[' + obj.map { |x| ruby_code x }.join(", ") + ']'
+ when Hash then
+ seg = obj.keys.sort.map { |k| "#{k.to_s.dump} => #{obj[k].to_s.dump}" }
+ "{ #{seg.join(', ')} }"
when Gem::Version then obj.to_s.dump
when Date then obj.strftime('%Y-%m-%d').dump
when Time then obj.strftime('%Y-%m-%d').dump
@@ -1800,7 +2122,7 @@ class Gem::Specification
end
##
- # Singular accessor for #test_files
+ # Singular mutator for #test_files
def test_file= file
self.test_files = [file]
@@ -1826,27 +2148,13 @@ class Gem::Specification
end
##
- # Set test_files to +files+, ensuring it is an array.
-
- def test_files= files
- @test_files = Array files
- end
-
- def test_suite_file # :nodoc:
- # TODO: deprecate
- test_files.first
- end
-
- def test_suite_file= file # :nodoc:
- # TODO: deprecate
- @test_files = [] unless defined? @test_files
- @test_files << file
- 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.
+ #
+ # REFACTOR: This, plus stuff like #ruby_code and #pretty_print, should
+ # probably be extracted out into some sort of separate class. SRP, do you
+ # speak it!??!
def to_ruby
mark_version
@@ -1863,6 +2171,10 @@ class Gem::Specification
result << ""
result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version="
+ if metadata and !metadata.empty?
+ result << " s.metadata = #{ruby_code metadata} if s.respond_to? :metadata="
+ end
+
handled = [
:dependencies,
:name,
@@ -1872,6 +2184,7 @@ class Gem::Specification
:version,
:has_rdoc,
:default_executable,
+ :metadata
]
@@attributes.each do |attr_name|
@@ -1883,34 +2196,36 @@ class Gem::Specification
end
end
- result << nil
- result << " if s.respond_to? :specification_version then"
- result << " s.specification_version = #{specification_version}"
- result << nil
+ unless dependencies.empty? then
+ result << nil
+ result << " if s.respond_to? :specification_version then"
+ result << " s.specification_version = #{specification_version}"
+ result << nil
- result << " if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then"
+ result << " if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then"
- dependencies.each do |dep|
- req = dep.requirements_list.inspect
- dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
- result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{req})"
- end
+ dependencies.each do |dep|
+ req = dep.requirements_list.inspect
+ dep.instance_variable_set :@type, :runtime if dep.type.nil? # HACK
+ result << " s.add_#{dep.type}_dependency(%q<#{dep.name}>, #{req})"
+ end
- result << " else"
+ result << " else"
- dependencies.each do |dep|
- version_reqs_param = dep.requirements_list.inspect
- result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
- end
+ 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 << " else"
+ 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"
+ end
result << "end"
result << nil
@@ -1946,6 +2261,7 @@ class Gem::Specification
ast = builder.tree
io = StringIO.new
+ io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
Psych::Visitors::Emitter.new(io).accept(ast)
@@ -2057,12 +2373,42 @@ class Gem::Specification
end
end
+ # FIX: uhhhh single element array.each?
[:authors].each do |field|
val = self.send field
raise Gem::InvalidSpecificationException, "#{field} may not be empty" if
val.empty?
end
+ unless Hash === metadata
+ raise Gem::InvalidSpecificationException,
+ 'metadata must be a hash'
+ end
+
+ metadata.keys.each do |k|
+ if !k.kind_of?(String)
+ raise Gem::InvalidSpecificationException,
+ 'metadata keys must be a String'
+ end
+
+ if k.size > 128
+ raise Gem::InvalidSpecificationException,
+ "metadata key too large (#{k.size} > 128)"
+ end
+ end
+
+ metadata.values.each do |k|
+ if !k.kind_of?(String)
+ raise Gem::InvalidSpecificationException,
+ 'metadata values must be a String'
+ end
+
+ if k.size > 1024
+ raise Gem::InvalidSpecificationException,
+ "metadata value too large (#{k.size} > 1024)"
+ end
+ end
+
licenses.each { |license|
if license.length > 64
raise Gem::InvalidSpecificationException,
@@ -2070,8 +2416,13 @@ class Gem::Specification
end
}
+ alert_warning 'licenses is empty' if licenses.empty?
+
+ validate_permissions
+
# reject lazy developers:
+ # FIX: Doesn't this just evaluate to "FIXME" or "TODO"?
lazy = '"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/, '')
unless authors.grep(/FI XME|TO DO/x).empty? then
@@ -2117,10 +2468,35 @@ class Gem::Specification
alert_warning "#{executable_path} is missing #! line" unless shebang
end
+ dependencies.each do |dep|
+ prerelease_dep = dep.requirements_list.any? do |req|
+ Gem::Requirement.new(req).prerelease?
+ end
+
+ alert_warning "prerelease dependency on #{dep} is not recommended" if
+ prerelease_dep
+ end
+
true
end
##
+ # Checks to see if the files to be packaged are world-readable.
+
+ def validate_permissions
+ files.each do |file|
+ next if File.stat(file).world_readable?
+ alert_warning "#{file} is not world-readable"
+ end
+
+ executables.each do |name|
+ exec = File.join @bindir, name
+ next if File.stat(exec).executable?
+ alert_warning "#{exec} is not executable"
+ end
+ end
+
+ ##
# Set the version to +version+, potentially also setting
# required_rubygems_version if +version+ indicates it is a
# prerelease.
@@ -2128,6 +2504,8 @@ class Gem::Specification
def version= version
@version = Gem::Version.create(version)
self.required_rubygems_version = '> 1.3.1' if @version.prerelease?
+ invalidate_memoized_attributes
+
return @version
end
@@ -2147,15 +2525,33 @@ class Gem::Specification
self.platform = Gem::Platform.new @platform
end
+ ##
+ # Reset nil attributes to their default values to make the spec valid
+
+ def reset_nil_attributes_to_default
+ nil_attributes = self.class.non_nil_attributes.find_all do |name|
+ !instance_variable_defined?("@#{name}") || instance_variable_get("@#{name}").nil?
+ end
+
+ nil_attributes.each do |attribute|
+ default = self.default_value attribute
+
+ value = case default
+ when Time, Numeric, Symbol, true, false, nil then default
+ else default.dup
+ end
+
+ instance_variable_set "@#{attribute}", value
+ end
+ end
+
+ def default_gem?
+ loaded_from &&
+ File.dirname(loaded_from) == self.class.default_specifications_dir
+ end
+
extend Gem::Deprecate
- deprecate :test_suite_file, :test_file, 2011, 10
- deprecate :test_suite_file=, :test_file=, 2011, 10
- deprecate :loaded, :activated, 2011, 10
- deprecate :loaded?, :activated?, 2011, 10
- deprecate :loaded=, :activated=, 2011, 10
- deprecate :installation_path, :base_dir, 2011, 10
- deprecate :cache_gem, :cache_file, 2011, 10
# TODO:
# deprecate :has_rdoc, :none, 2011, 10
# deprecate :has_rdoc?, :none, 2011, 10
@@ -2167,5 +2563,5 @@ class Gem::Specification
# deprecate :full_gem_path, :cache_file, 2011, 10
end
+# DOC: What is this and why is it here, randomly, at the end of this file?
Gem.clear_paths
-
diff --git a/lib/rubygems/ssl_certs/AddTrustExternalCARoot.pem b/lib/rubygems/ssl_certs/AddTrustExternalCARoot.pem
new file mode 100644
index 0000000000..580158f50c
--- /dev/null
+++ b/lib/rubygems/ssl_certs/AddTrustExternalCARoot.pem
@@ -0,0 +1,90 @@
+This CA certificate is for verifying HTTPS connection to;
+ - https://rubygems.org/ (obtained by RubyGems team)
+
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: May 30 10:48:38 2000 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b7:f7:1a:33:e6:f2:00:04:2d:39:e0:4e:5b:ed:
+ 1f:bc:6c:0f:cd:b5:fa:23:b6:ce:de:9b:11:33:97:
+ a4:29:4c:7d:93:9f:bd:4a:bc:93:ed:03:1a:e3:8f:
+ cf:e5:6d:50:5a:d6:97:29:94:5a:80:b0:49:7a:db:
+ 2e:95:fd:b8:ca:bf:37:38:2d:1e:3e:91:41:ad:70:
+ 56:c7:f0:4f:3f:e8:32:9e:74:ca:c8:90:54:e9:c6:
+ 5f:0f:78:9d:9a:40:3c:0e:ac:61:aa:5e:14:8f:9e:
+ 87:a1:6a:50:dc:d7:9a:4e:af:05:b3:a6:71:94:9c:
+ 71:b3:50:60:0a:c7:13:9d:38:07:86:02:a8:e9:a8:
+ 69:26:18:90:ab:4c:b0:4f:23:ab:3a:4f:84:d8:df:
+ ce:9f:e1:69:6f:bb:d7:42:d7:6b:44:e4:c7:ad:ee:
+ 6d:41:5f:72:5a:71:08:37:b3:79:65:a4:59:a0:94:
+ 37:f7:00:2f:0d:c2:92:72:da:d0:38:72:db:14:a8:
+ 45:c4:5d:2a:7d:b7:b4:d6:c4:ee:ac:cd:13:44:b7:
+ c9:2b:dd:43:00:25:fa:61:b9:69:6a:58:23:11:b7:
+ a7:33:8f:56:75:59:f5:cd:29:d7:46:b7:0a:2b:65:
+ b6:d3:42:6f:15:b2:b8:7b:fb:ef:e9:5d:53:d5:34:
+ 5a:27
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+ DirName:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
+ serial:01
+
+ Signature Algorithm: sha1WithRSAEncryption
+ b0:9b:e0:85:25:c2:d6:23:e2:0f:96:06:92:9d:41:98:9c:d9:
+ 84:79:81:d9:1e:5b:14:07:23:36:65:8f:b0:d8:77:bb:ac:41:
+ 6c:47:60:83:51:b0:f9:32:3d:e7:fc:f6:26:13:c7:80:16:a5:
+ bf:5a:fc:87:cf:78:79:89:21:9a:e2:4c:07:0a:86:35:bc:f2:
+ de:51:c4:d2:96:b7:dc:7e:4e:ee:70:fd:1c:39:eb:0c:02:51:
+ 14:2d:8e:bd:16:e0:c1:df:46:75:e7:24:ad:ec:f4:42:b4:85:
+ 93:70:10:67:ba:9d:06:35:4a:18:d3:2b:7a:cc:51:42:a1:7a:
+ 63:d1:e6:bb:a1:c5:2b:c2:36:be:13:0d:e6:bd:63:7e:79:7b:
+ a7:09:0d:40:ab:6a:dd:8f:8a:c3:f6:f6:8c:1a:42:05:51:d4:
+ 45:f5:9f:a7:62:21:68:15:20:43:3c:99:e7:7c:bd:24:d8:a9:
+ 91:17:73:88:3f:56:1b:31:38:18:b4:71:0f:9a:cd:c8:0e:9e:
+ 8e:2e:1b:e1:8c:98:83:cb:1f:31:f1:44:4c:c6:04:73:49:76:
+ 60:0f:c7:f8:bd:17:80:6b:2e:e9:cc:4c:0e:5a:9a:79:0f:20:
+ 0a:2e:d5:9e:63:26:1e:55:92:94:d8:82:17:5a:7b:d0:bc:c7:
+ 8f:4e:86:04
+
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/lib/rubygems/ssl_certs/Entrust_net-Secure-Server-Certification-Authority.pem b/lib/rubygems/ssl_certs/Entrust_net-Secure-Server-Certification-Authority.pem
new file mode 100644
index 0000000000..b48d9cd70b
--- /dev/null
+++ b/lib/rubygems/ssl_certs/Entrust_net-Secure-Server-Certification-Authority.pem
@@ -0,0 +1,90 @@
+This CA certificate is for verifying HTTPS connection to;
+ - https://d2chzxaqi4y7f8.cloudfront.net/ (prepared by AWS)
+
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 927650371 (0x374ad243)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Validity
+ Not Before: May 25 16:09:40 1999 GMT
+ Not After : May 25 16:39:40 2019 GMT
+ Subject: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:cd:28:83:34:54:1b:89:f3:0f:af:37:91:31:ff:
+ af:31:60:c9:a8:e8:b2:10:68:ed:9f:e7:93:36:f1:
+ 0a:64:bb:47:f5:04:17:3f:23:47:4d:c5:27:19:81:
+ 26:0c:54:72:0d:88:2d:d9:1f:9a:12:9f:bc:b3:71:
+ d3:80:19:3f:47:66:7b:8c:35:28:d2:b9:0a:df:24:
+ da:9c:d6:50:79:81:7a:5a:d3:37:f7:c2:4a:d8:29:
+ 92:26:64:d1:e4:98:6c:3a:00:8a:f5:34:9b:65:f8:
+ ed:e3:10:ff:fd:b8:49:58:dc:a0:de:82:39:6b:81:
+ b1:16:19:61:b9:54:b6:e6:43
+ Exponent: 3 (0x3)
+ X509v3 extensions:
+ Netscape Cert Type:
+ SSL CA, S/MIME CA, Object Signing CA
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ DirName: C = US, O = Entrust.net, OU = www.entrust.net/CPS incorp. by ref. (limits liab.), OU = (c) 1999 Entrust.net Limited, CN = Entrust.net Secure Server Certification Authority, CN = CRL1
+
+ Full Name:
+ URI:http://www.entrust.net/CRL/net1.crl
+
+ X509v3 Private Key Usage Period:
+ Not Before: May 25 16:09:40 1999 GMT, Not After: May 25 16:09:40 2019 GMT
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+ X509v3 Subject Key Identifier:
+ F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+ X509v3 Basic Constraints:
+ CA:TRUE
+ 1.2.840.113533.7.65.0:
+ 0
+..V4.0....
+ Signature Algorithm: sha1WithRSAEncryption
+ 90:dc:30:02:fa:64:74:c2:a7:0a:a5:7c:21:8d:34:17:a8:fb:
+ 47:0e:ff:25:7c:8d:13:0a:fb:e4:98:b5:ef:8c:f8:c5:10:0d:
+ f7:92:be:f1:c3:d5:d5:95:6a:04:bb:2c:ce:26:36:65:c8:31:
+ c6:e7:ee:3f:e3:57:75:84:7a:11:ef:46:4f:18:f4:d3:98:bb:
+ a8:87:32:ba:72:f6:3c:e2:3d:9f:d7:1d:d9:c3:60:43:8c:58:
+ 0e:22:96:2f:62:a3:2c:1f:ba:ad:05:ef:ab:32:78:87:a0:54:
+ 73:19:b5:5c:05:f9:52:3e:6d:2d:45:0b:f7:0a:93:ea:ed:06:
+ f9:b2
+
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
diff --git a/lib/rubygems/ssl_certs/VerisignClass3PublicPrimaryCertificationAuthority-G2.pem b/lib/rubygems/ssl_certs/VerisignClass3PublicPrimaryCertificationAuthority-G2.pem
new file mode 100644
index 0000000000..43bad3ebdf
--- /dev/null
+++ b/lib/rubygems/ssl_certs/VerisignClass3PublicPrimaryCertificationAuthority-G2.pem
@@ -0,0 +1,57 @@
+This CA certificate is for verifying HTTPS connection to;
+ - https://s3.amazon.com/ (prepared by AWS)
+
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number:
+ 7d:d9:fe:07:cf:a8:1e:b7:10:79:67:fb:a7:89:34:c6
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network
+ Validity
+ Not Before: May 18 00:00:00 1998 GMT
+ Not After : Aug 1 23:59:59 2028 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:cc:5e:d1:11:5d:5c:69:d0:ab:d3:b9:6a:4c:99:
+ 1f:59:98:30:8e:16:85:20:46:6d:47:3f:d4:85:20:
+ 84:e1:6d:b3:f8:a4:ed:0c:f1:17:0f:3b:f9:a7:f9:
+ 25:d7:c1:cf:84:63:f2:7c:63:cf:a2:47:f2:c6:5b:
+ 33:8e:64:40:04:68:c1:80:b9:64:1c:45:77:c7:d8:
+ 6e:f5:95:29:3c:50:e8:34:d7:78:1f:a8:ba:6d:43:
+ 91:95:8f:45:57:5e:7e:c5:fb:ca:a4:04:eb:ea:97:
+ 37:54:30:6f:bb:01:47:32:33:cd:dc:57:9b:64:69:
+ 61:f8:9b:1d:1c:89:4f:5c:67
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha1WithRSAEncryption
+ 51:4d:cd:be:5c:cb:98:19:9c:15:b2:01:39:78:2e:4d:0f:67:
+ 70:70:99:c6:10:5a:94:a4:53:4d:54:6d:2b:af:0d:5d:40:8b:
+ 64:d3:d7:ee:de:56:61:92:5f:a6:c4:1d:10:61:36:d3:2c:27:
+ 3c:e8:29:09:b9:11:64:74:cc:b5:73:9f:1c:48:a9:bc:61:01:
+ ee:e2:17:a6:0c:e3:40:08:3b:0e:e7:eb:44:73:2a:9a:f1:69:
+ 92:ef:71:14:c3:39:ac:71:a7:91:09:6f:e4:71:06:b3:ba:59:
+ 57:26:79:00:f6:f8:0d:a2:33:30:28:d4:aa:58:a0:9d:9d:69:
+ 91:fd
+
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
+pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
+13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
+U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
+F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
+oJ2daZH9
+-----END CERTIFICATE-----
diff --git a/lib/rubygems/syck_hack.rb b/lib/rubygems/syck_hack.rb
index 9c6f4a2254..5356aa081e 100644
--- a/lib/rubygems/syck_hack.rb
+++ b/lib/rubygems/syck_hack.rb
@@ -46,6 +46,8 @@ module YAML
end
end
end
+
+ SyntaxError = Error unless defined? SyntaxError
end
# Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML
diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb
index 38e09ccd02..ace0177e19 100644
--- a/lib/rubygems/test_case.rb
+++ b/lib/rubygems/test_case.rb
@@ -1,4 +1,4 @@
-at_exit { $SAFE = 1 }
+# TODO: $SAFE = 1
if defined? Gem::QuickLoader
Gem::QuickLoader.load_full_rubygems_library
@@ -11,7 +11,14 @@ begin
rescue Gem::LoadError
end
-require "rubygems/deprecate"
+# We have to load these up front because otherwise we'll try to load
+# them while we're testing rubygems, and thus we can't actually load them.
+unless Gem::Dependency.new('rdoc', '>= 3.10').matching_specs.empty?
+ gem 'rdoc'
+ gem 'json'
+end
+
+require 'rubygems/deprecate'
require 'minitest/autorun'
require 'fileutils'
require 'tmpdir'
@@ -36,16 +43,6 @@ module Gem
end
##
- # Allows setting the default SourceIndex. This method is available when
- # requiring 'rubygems/test_case'
-
- def self.source_index=(si)
- raise "This method is not supported"
- Gem::Specification.reset if si # HACK
- @@source_index = si
- end
-
- ##
# Allows toggling Windows behavior. This method is available when requiring
# 'rubygems/test_case'
@@ -97,7 +94,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase
undef_method :default_test if instance_methods.include? 'default_test' or
instance_methods.include? :default_test
- @@project_dir = Dir.pwd unless defined?(@@project_dir)
+ @@project_dir = Dir.pwd.untaint unless defined?(@@project_dir)
+
+ @@initial_reset = false
##
# #setup prepares a sandboxed location to install gems. All installs are
@@ -119,8 +118,8 @@ class Gem::TestCase < MiniTest::Unit::TestCase
@current_dir = Dir.pwd
@ui = Gem::MockGemUi.new
- tmpdir = nil
- Dir.chdir Dir.tmpdir do tmpdir = Dir.pwd end # HACK OSX /private/tmp
+ # Need to do this in the project because $SAFE fucks up _everything_
+ tmpdir = File.expand_path("tmp/test")
if ENV['KEEP_FILES'] then
@tempdir = File.join(tmpdir, "test_rubygems_#{$$}.#{Time.now.to_i}")
@@ -132,14 +131,14 @@ class Gem::TestCase < MiniTest::Unit::TestCase
@userhome = File.join @tempdir, 'userhome'
@orig_ruby = if ruby = ENV['RUBY'] then
- Gem.class_eval { ruby, @ruby = @ruby, ruby }
+ Gem.class_eval { ruby, @ruby = @ruby, ruby.dup }
ruby
end
Gem.ensure_gem_subdirectories @gemhome
@orig_LOAD_PATH = $LOAD_PATH.dup
- $LOAD_PATH.map! { |s| File.expand_path s }
+ $LOAD_PATH.map! { |s| File.expand_path(s).untaint }
Dir.chdir @tempdir
@@ -150,21 +149,39 @@ class Gem::TestCase < MiniTest::Unit::TestCase
FileUtils.mkdir_p @gemhome
FileUtils.mkdir_p @userhome
+ @default_dir = File.join @tempdir, 'default'
+ @default_spec_dir = File.join @default_dir, "specifications", "default"
+ Gem.instance_variable_set :@default_dir, @default_dir
+ FileUtils.mkdir_p @default_spec_dir
+
+ # We use Gem::Specification.reset the first time only so that if there
+ # are unresolved deps that leak into the whole test suite, they're at least
+ # reported once.
+ if @@initial_reset
+ Gem::Specification.unresolved_deps.clear # done to avoid cross-test warnings
+ else
+ @@initial_reset = true
+ Gem::Specification.reset
+ end
Gem.use_paths(@gemhome)
+ Gem::Security.reset
+
Gem.loaded_specs.clear
- Gem.unresolved_deps.clear
+ Gem.clear_default_specs
+ Gem::Specification.unresolved_deps.clear
Gem.configuration.verbose = true
Gem.configuration.update_sources = true
+ Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new
+
@gem_repo = "http://gems.example.com/"
@uri = URI.parse @gem_repo
Gem.sources.replace [@gem_repo]
Gem.searcher = nil
Gem::SpecFetcher.fetcher = nil
-
@orig_BASERUBY = Gem::ConfigMap[:BASERUBY]
Gem::ConfigMap[:BASERUBY] = Gem::ConfigMap[:ruby_install_name]
@@ -181,8 +198,11 @@ class Gem::TestCase < MiniTest::Unit::TestCase
# TODO: move to installer test cases
Gem.post_build_hooks.clear
Gem.post_install_hooks.clear
+ Gem.done_installing_hooks.clear
+ Gem.post_reset_hooks.clear
Gem.post_uninstall_hooks.clear
Gem.pre_install_hooks.clear
+ Gem.pre_reset_hooks.clear
Gem.pre_uninstall_hooks.clear
# TODO: move to installer test cases
@@ -207,9 +227,6 @@ class Gem::TestCase < MiniTest::Unit::TestCase
Gem.pre_uninstall do |uninstaller|
@pre_uninstall_hook_arg = uninstaller
end
-
- @orig_yamler = YAML::ENGINE.yamler
- YAML::ENGINE.yamler = 'psych' rescue nil
end
##
@@ -217,7 +234,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
# tempdir unless the +KEEP_FILES+ environment variable was set.
def teardown
- $LOAD_PATH.replace @orig_LOAD_PATH
+ $LOAD_PATH.replace @orig_LOAD_PATH if @orig_LOAD_PATH
Gem::ConfigMap[:BASERUBY] = @orig_BASERUBY
Gem::ConfigMap[:arch] = @orig_arch
@@ -242,7 +259,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
ENV.delete 'HOME'
end
- YAML::ENGINE.yamler = @orig_yamler
+ Gem.instance_variable_set :@default_dir, nil
end
##
@@ -251,13 +268,17 @@ class Gem::TestCase < MiniTest::Unit::TestCase
def install_gem spec, options = {}
require 'rubygems/installer'
- use_ui Gem::MockGemUi.new do
- Dir.chdir @tempdir do
- Gem::Builder.new(spec).build
+ gem = File.join @tempdir, "gems", "#{spec.full_name}.gem"
+
+ unless File.exists? gem
+ use_ui Gem::MockGemUi.new do
+ Dir.chdir @tempdir do
+ Gem::Package.build spec
+ end
end
- end
- gem = File.join(@tempdir, File.basename(spec.cache_file)).untaint
+ gem = File.join(@tempdir, File.basename(spec.cache_file)).untaint
+ end
Gem::Installer.new(gem, options.merge({:wrappers => true})).install
end
@@ -280,6 +301,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
##
# creates a temporary directory with hax
+ # TODO: deprecate and remove
def create_tmpdir
tmpdir = nil
@@ -340,8 +362,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
# homepage, summary and description are defaulted. The specification is
# yielded for customization.
#
- # The gem is added to the installed gems in +@gemhome+ and to the current
- # source_index.
+ # The gem is added to the installed gems in +@gemhome+ and the runtime.
#
# Use this with #write_file to build an installed gem.
@@ -414,7 +435,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
use_ui Gem::MockGemUi.new do
- Gem::Builder.new(spec).build
+ Gem::Package.build spec
end
cache = spec.cache_file
@@ -422,6 +443,11 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
end
+ def util_remove_gem(spec)
+ FileUtils.rm_rf spec.cache_file
+ FileUtils.rm_rf spec.spec_file
+ end
+
##
# Removes all installed gems from +@gemhome+.
@@ -440,6 +466,16 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
##
+ # Install the provided default specs
+
+ def install_default_specs(*specs)
+ install_specs(*specs)
+ specs.each do |spec|
+ Gem.register_default_spec(spec)
+ end
+ end
+
+ ##
# Create a new spec (or gem if passed an array of files) and set it
# up properly. Use this instead of util_spec and util_gem.
@@ -483,6 +519,24 @@ class Gem::TestCase < MiniTest::Unit::TestCase
spec
end
+ def new_default_spec(name, version, deps = nil, *files)
+ spec = new_spec(name, version, deps)
+ spec.loaded_from = File.join(@default_spec_dir, spec.spec_name)
+ spec.files = files
+
+ lib_dir = File.join(@tempdir, "default_gems", "lib")
+ $LOAD_PATH.unshift(lib_dir)
+ files.each do |file|
+ rb_path = File.join(lib_dir, file)
+ FileUtils.mkdir_p(File.dirname(rb_path))
+ File.open(rb_path, "w") do |rb|
+ rb << "# #{file}"
+ end
+ end
+
+ spec
+ end
+
##
# Creates a spec with +name+, +version+ and +deps+.
@@ -597,6 +651,12 @@ Also, a list:
@a_evil9 = quick_gem('a_evil', '9', &init)
@b2 = quick_gem('b', '2', &init)
@c1_2 = quick_gem('c', '1.2', &init)
+ @x = quick_gem('x', '1', &init)
+ @dep_x = quick_gem('dep_x', '1') do |s|
+ s.files = %w[lib/code.rb]
+ s.require_paths = %w[lib]
+ s.add_dependency 'x', '>= 1'
+ end
@pl1 = quick_gem 'pl', '1' do |s| # l for legacy
s.files = %w[lib/code.rb]
@@ -617,8 +677,10 @@ Also, a list:
write_file File.join(*%W[gems #{@b2.original_name} lib code.rb])
write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb])
write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@x.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@dep_x.original_name} lib code.rb])
- [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1].each do |spec|
+ [@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1, @x, @dep_x].each do |spec|
util_build_gem spec
end
@@ -664,6 +726,15 @@ Also, a list:
end
##
+ # Add +spec+ to +@fetcher+ serving the data in the file +path+.
+ # +repo+ indicates which repo to make +spec+ appear to be in.
+
+ def add_to_fetcher(spec, path=nil, repo=@gem_repo)
+ path ||= spec.cache_file
+ @fetcher.data["#{@gem_repo}gems/#{spec.file_name}"] = read_binary(path)
+ end
+
+ ##
# Sets up Gem::SpecFetcher to return information from the gems in +specs+.
# Best used with +@all_gems+ from #util_setup_fake_fetcher.
@@ -673,26 +744,23 @@ Also, a list:
spec_fetcher = Gem::SpecFetcher.fetcher
- prerelease, _ = Gem::Specification.partition { |spec|
+ prerelease, all = Gem::Specification.partition { |spec|
spec.version.prerelease?
}
spec_fetcher.specs[@uri] = []
- Gem::Specification.each do |spec|
- spec_tuple = [spec.name, spec.version, spec.original_platform]
- spec_fetcher.specs[@uri] << spec_tuple
+ all.each do |spec|
+ spec_fetcher.specs[@uri] << spec.name_tuple
end
spec_fetcher.latest_specs[@uri] = []
Gem::Specification.latest_specs.each do |spec|
- spec_tuple = [spec.name, spec.version, spec.original_platform]
- spec_fetcher.latest_specs[@uri] << spec_tuple
+ spec_fetcher.latest_specs[@uri] << spec.name_tuple
end
spec_fetcher.prerelease_specs[@uri] = []
prerelease.each do |spec|
- spec_tuple = [spec.name, spec.version, spec.original_platform]
- spec_fetcher.prerelease_specs[@uri] << spec_tuple
+ spec_fetcher.prerelease_specs[@uri] << spec.name_tuple
end
v = Gem.marshal_version
@@ -769,6 +837,14 @@ Also, a list:
system('nmake /? 1>NUL 2>&1')
end
+ # In case we're building docs in a background process, this method waits for
+ # that process to exit (or if it's already been reaped, or never happened,
+ # swallows the Errno::ECHILD error).
+ def wait_for_child_process_to_exit
+ Process.wait if Process.respond_to?(:fork)
+ rescue Errno::ECHILD
+ end
+
##
# Allows tests to use a random (but controlled) port number instead of
# a hardcoded one. This helps CI tools when running parallels builds on
@@ -788,12 +864,13 @@ Also, a list:
##
# Allows the proper version of +rake+ to be used for the test.
- def build_rake_in
+ def build_rake_in(good=true)
gem_ruby = Gem.ruby
Gem.ruby = @@ruby
env_rake = ENV["rake"]
- ENV["rake"] = @@rake
- yield @@rake
+ rake = (good ? @@good_rake : @@bad_rake)
+ ENV["rake"] = rake
+ yield rake
ensure
Gem.ruby = gem_ruby
if env_rake
@@ -833,15 +910,8 @@ Also, a list:
end
@@ruby = rubybin
- env_rake = ENV['rake']
- ruby19_rake = File.expand_path("bin/rake", @@project_dir)
- @@rake = if env_rake then
- ENV["rake"]
- elsif File.exist? ruby19_rake then
- @@ruby + " " + ruby19_rake
- else
- 'rake'
- end
+ @@good_rake = "#{rubybin} #{File.expand_path('../../../test/rubygems/good_rake.rb', __FILE__)}"
+ @@bad_rake = "#{rubybin} #{File.expand_path('../../../test/rubygems/bad_rake.rb', __FILE__)}"
##
# Construct a new Gem::Dependency.
@@ -872,4 +942,73 @@ Also, a list:
Gem::Version.create string
end
+ class StaticSet
+ def initialize(specs)
+ @specs = specs.sort_by { |s| s.full_name }
+ end
+
+ def find_spec(dep)
+ @specs.reverse_each do |s|
+ return s if dep.matches_spec? s
+ end
+ end
+
+ def find_all(dep)
+ @specs.find_all { |s| dep.matches_spec? s }
+ end
+
+ def prefetch(reqs)
+ end
+ end
+
+ ##
+ # Loads certificate named +cert_name+ from <tt>test/rubygems/</tt>.
+
+ def self.load_cert cert_name
+ cert_file = cert_path cert_name
+
+ cert = File.read cert_file
+
+ OpenSSL::X509::Certificate.new cert
+ end
+
+ ##
+ # Returns the path to the certificate named +cert_name+ from
+ # <tt>test/rubygems/</tt>.
+
+ def self.cert_path cert_name
+ if 32 == (Time.at(2**32) rescue 32) then
+ cert_file =
+ File.expand_path "../../../test/rubygems/#{cert_name}_cert_32.pem",
+ __FILE__
+
+ return cert_file if File.exist? cert_file
+ end
+
+ File.expand_path "../../../test/rubygems/#{cert_name}_cert.pem", __FILE__
+ end
+
+ ##
+ # Loads an RSA private key named +key_name+ in <tt>test/rubygems/</tt>
+
+ def self.load_key key_name
+ key_file = key_path key_name
+
+ key = File.read key_file
+
+ OpenSSL::PKey::RSA.new key
+ end
+
+ ##
+ # Returns the path tot he key named +key_name+ from <tt>test/rubygems</tt>
+
+ def self.key_path key_name
+ File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__
+ end
+
+ PRIVATE_KEY = load_key 'private'
+ PUBLIC_KEY = PRIVATE_KEY.public_key
+
+ PUBLIC_CERT = load_cert 'public'
+
end
diff --git a/lib/rubygems/test_utilities.rb b/lib/rubygems/test_utilities.rb
index 1a8fb5a0ad..bd5f2134be 100644
--- a/lib/rubygems/test_utilities.rb
+++ b/lib/rubygems/test_utilities.rb
@@ -24,11 +24,17 @@ class Gem::FakeFetcher
attr_reader :data
attr_reader :last_request
+ attr_reader :api_endpoints
attr_accessor :paths
def initialize
@data = {}
@paths = []
+ @api_endpoints = {}
+ end
+
+ def api_endpoint(uri)
+ @api_endpoints[uri] || uri
end
def find_data(path)
@@ -57,6 +63,15 @@ class Gem::FakeFetcher
end
end
+ def cache_update_path uri, path = nil
+ if data = fetch_path(uri)
+ open(path, 'wb') { |io| io.write data } if path
+ data
+ else
+ Gem.read_binary(path) if path
+ end
+ end
+
# Thanks, FakeWeb!
def open_uri_or_path(path)
data = find_data(path)
@@ -98,7 +113,13 @@ class Gem::FakeFetcher
def download spec, source_uri, install_dir = Gem.dir
name = File.basename spec.cache_file
- path = File.join install_dir, "cache", name
+ path = if Dir.pwd == install_dir then # see fetch_command
+ install_dir
+ else
+ File.join install_dir, "cache"
+ end
+
+ path = File.join path, name
Gem.ensure_gem_subdirectories install_dir
@@ -114,14 +135,13 @@ class Gem::FakeFetcher
end
def download_to_cache dependency
- found = Gem::SpecFetcher.fetcher.fetch dependency, true, true,
- dependency.prerelease?
+ found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
return if found.empty?
- spec, source_uri = found.first
+ spec, source = found.first
- download spec, source_uri
+ download spec, source.uri.to_s
end
end
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index cc32ea48c4..b1500b0748 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -7,7 +7,7 @@
require 'fileutils'
require 'rubygems'
require 'rubygems/dependency_list'
-require 'rubygems/doc_manager'
+require 'rubygems/rdoc'
require 'rubygems/user_interaction'
##
@@ -42,6 +42,7 @@ class Gem::Uninstaller
# Constructs an uninstaller that will uninstall +gem+
def initialize(gem, options = {})
+ # TODO document the valid options
@gem = gem
@version = options[:version] || Gem::Requirement.default
@gem_home = File.expand_path(options[:install_dir] || Gem.dir)
@@ -51,15 +52,19 @@ class Gem::Uninstaller
@bin_dir = options[:bin_dir]
@format_executable = options[:format_executable]
+ # Indicate if development dependencies should be checked when
+ # uninstalling. (default: false)
+ #
+ @check_dev = options[:check_dev]
+
+ if options[:force]
+ @force_all = true
+ @force_ignore = true
+ end
+
# only add user directory if install_dir is not set
@user_install = false
@user_install = options[:user_install] unless options[:install_dir]
-
- if @user_install then
- Gem.use_paths Gem.user_dir, @gem_home
- else
- Gem.use_paths @gem_home
- end
end
##
@@ -69,10 +74,36 @@ class Gem::Uninstaller
def uninstall
list = Gem::Specification.find_all_by_name(@gem, @version)
+ default_specs, list = list.partition do |spec|
+ spec.default_gem?
+ end
+
+ list, other_repo_specs = list.partition do |spec|
+ @gem_home == spec.base_dir or
+ (@user_install and spec.base_dir == Gem.user_dir)
+ end
+
if list.empty? then
- raise Gem::InstallError, "gem #{@gem.inspect} is not installed"
+ if other_repo_specs.empty?
+ if default_specs.empty?
+ raise Gem::InstallError, "gem #{@gem.inspect} is not installed"
+ else
+ message =
+ "gem #{@gem.inspect} cannot be uninstalled " +
+ "because it is a default gem"
+ raise Gem::InstallError, message
+ end
+ end
+
+ other_repos = other_repo_specs.map { |spec| spec.base_dir }.uniq
- elsif list.size > 1 and @force_all then
+ message = ["#{@gem} is not installed in GEM_HOME, try:"]
+ message.concat other_repos.map { |repo|
+ "\tgem uninstall -i #{repo} #{@gem}"
+ }
+
+ raise Gem::InstallError, message.join("\n")
+ elsif @force_all then
remove_all list
elsif list.size > 1 then
@@ -127,12 +158,15 @@ class Gem::Uninstaller
def remove_executables(spec)
return if spec.nil? or spec.executables.empty?
+ executables = spec.executables.clone
+
+ # Leave any executables created by other installed versions
+ # of this gem installed.
+
list = Gem::Specification.find_all { |s|
s.name == spec.name && s.version != spec.version
}
- executables = spec.executables.clone
-
list.each do |s|
s.executables.each do |exe_name|
executables.delete exe_name
@@ -152,9 +186,7 @@ class Gem::Uninstaller
@force_executables
end
- unless remove then
- say "Executables and scripts will remain installed."
- else
+ if remove then
bin_dir = @bin_dir || Gem.bindir(spec.base_dir)
raise Gem::FilePermissionError, bin_dir unless File.writable? bin_dir
@@ -167,6 +199,8 @@ class Gem::Uninstaller
FileUtils.rm_f exe_file
FileUtils.rm_f "#{exe_file}.bat"
end
+ else
+ say "Executables and scripts will remain installed."
end
end
@@ -220,7 +254,7 @@ class Gem::Uninstaller
FileUtils.rm_rf gem
- Gem::DocManager.new(spec).uninstall_doc
+ Gem::RDoc.new(spec).remove
say "Successfully uninstalled #{spec.full_name}"
@@ -241,26 +275,34 @@ class Gem::Uninstaller
return true if @force_ignore
deplist = Gem::DependencyList.from_specs
- deplist.ok_to_remove?(spec.full_name)
+ deplist.ok_to_remove?(spec.full_name, @check_dev)
end
def ask_if_ok(spec)
msg = ['']
msg << 'You have requested to uninstall the gem:'
msg << "\t#{spec.full_name}"
+ msg << ''
+
+ siblings = Gem::Specification.select do |s|
+ s.name == spec.name && s.full_name != spec.full_name
+ end
spec.dependent_gems.each do |dep_spec, dep, satlist|
- msg <<
- ("#{dep_spec.name}-#{dep_spec.version} depends on " +
- "[#{dep.name} (#{dep.requirement})]")
+ unless siblings.any? { |s| s.satisfies_requirement? dep }
+ msg << "#{dep_spec.name}-#{dep_spec.version} depends on #{dep}"
+ end
end
- msg << 'If you remove this gems, one or more dependencies will not be met.'
+ msg << 'If you remove this gem, these dependencies will not be met.'
msg << 'Continue with Uninstall?'
return ask_yes_no(msg.join("\n"), true)
end
def formatted_program_filename(filename)
+ # TODO perhaps the installer should leave a small manifest
+ # of what it did for us to find rather than trying to recreate
+ # it again.
if @format_executable then
require 'rubygems/installer'
Gem::Installer.exec_format % File.basename(filename)
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 8024d37287..0974476507 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -144,6 +144,16 @@ class Gem::StreamUI
end
##
+ # Prints a formatted backtrace to the errors stream if backtraces are
+ # enabled.
+
+ def backtrace exception
+ return unless Gem.configuration.backtrace
+
+ @errs.puts "\t#{exception.backtrace.join "\n\t"}"
+ 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].
diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb
index ffeed88660..f66b2c1f43 100644
--- a/lib/rubygems/validator.rb
+++ b/lib/rubygems/validator.rb
@@ -4,7 +4,7 @@
# See LICENSE.txt for permissions.
#++
-require 'rubygems/format'
+require 'rubygems/package'
require 'rubygems/installer'
##
@@ -16,7 +16,6 @@ class Gem::Validator
def initialize
require 'find'
- require 'digest'
end
##
@@ -24,20 +23,8 @@ class Gem::Validator
# gem_data:: [String] Contents of the gem file
def verify_gem(gem_data)
- raise Gem::VerificationError, 'empty gem file' if gem_data.size == 0
-
- unless gem_data =~ /MD5SUM/ then
- return # Don't worry about it...this sucks. Need to fix MD5 stuff for
- # new format
- # FIXME
- end
-
- sum_data = gem_data.gsub(/MD5SUM = "([a-z0-9]+)"/,
- "MD5SUM = \"#{"F" * 32}\"")
-
- unless Digest::MD5.hexdigest(sum_data) == $1.to_s then
- raise Gem::VerificationError, 'invalid checksum for gem file'
- end
+ # TODO remove me? The code here only validate an MD5SUM that was
+ # in some old formatted gems, but hasn't been for a long time.
end
##
@@ -58,17 +45,27 @@ class Gem::Validator
def find_files_for_gem(gem_directory)
installed_files = []
+
Find.find gem_directory do |file_name|
fn = file_name[gem_directory.size..file_name.size-1].sub(/^\//, "")
installed_files << fn unless
fn =~ /CVS/ || fn.empty? || File.directory?(file_name)
end
+
installed_files
end
public
- ErrorData = Struct.new :path, :problem
+ ErrorData = Struct.new :path, :problem do
+
+ def <=> other
+ return nil unless self.class === other
+
+ [path, problem] <=> [other.path, other.problem]
+ end
+
+ end
##
# Checks the gem directory for the following potential
@@ -80,20 +77,22 @@ class Gem::Validator
# * 1 cache - 1 spec - 1 directory.
#
# returns a hash of ErrorData objects, keyed on the problem gem's name.
+ #--
+ # TODO needs further cleanup
def alien(gems=[])
errors = Hash.new { |h,k| h[k] = {} }
- Gem::SourceIndex.from_installed_gems.each do |gem_name, gem_spec|
- next unless gems.include? gem_spec.name unless gems.empty?
+ Gem::Specification.each do |spec|
+ next unless gems.include? spec.name unless gems.empty?
- install_dir = gem_spec.installation_path
- gem_path = Gem.cache_gem(gem_spec.file_name, install_dir)
- spec_path = File.join install_dir, "specifications", gem_spec.spec_name
- gem_directory = gem_spec.full_gem_path
+ gem_name = spec.file_name
+ gem_path = spec.cache_file
+ spec_path = spec.spec_file
+ gem_directory = spec.full_gem_path
unless File.directory? gem_directory then
- errors[gem_name][gem_spec.full_name] =
+ errors[gem_name][spec.full_name] =
"Gem registered but doesn't exist at #{gem_directory}"
next
end
@@ -108,19 +107,18 @@ class Gem::Validator
good, gone, unreadable = nil, nil, nil, nil
open gem_path, Gem.binary_mode do |file|
- format = Gem::Format.from_file_by_path(gem_path)
+ package = Gem::Package.new gem_path
- good, gone = format.file_entries.partition { |entry, _|
- File.exist? File.join(gem_directory, entry['path'])
+ good, gone = package.contents.partition { |file_name|
+ File.exist? File.join(gem_directory, file_name)
}
- gone.map! { |entry, _| entry['path'] }
gone.sort.each do |path|
errors[gem_name][path] = "Missing file"
end
- good, unreadable = good.partition { |entry, _|
- File.readable? File.join(gem_directory, entry['path'])
+ good, unreadable = good.partition { |file_name|
+ File.readable? File.join(gem_directory, file_name)
}
unreadable.map! { |entry, _| entry['path'] }
@@ -132,9 +130,10 @@ class Gem::Validator
begin
next unless data # HACK `gem check -a mkrf`
- open File.join(gem_directory, entry['path']), Gem.binary_mode do |f|
- unless Digest::MD5.hexdigest(f.read).to_s ==
- Digest::MD5.hexdigest(data).to_s then
+ source = File.join gem_directory, entry['path']
+
+ open source, Gem.binary_mode do |f|
+ unless f.read == data then
errors[gem_name][entry['path']] = "Modified from original"
end
end
@@ -143,7 +142,6 @@ class Gem::Validator
end
installed_files = find_files_for_gem(gem_directory)
- good.map! { |entry, _| entry['path'] }
extras = installed_files - good - unreadable
extras.each do |extra|
@@ -155,15 +153,10 @@ class Gem::Validator
end
errors.each do |name, subhash|
- errors[name] = subhash.map { |path, msg| ErrorData.new(path, msg) }
+ errors[name] = subhash.map { |path, msg| ErrorData.new(path, msg) }.sort
end
errors
end
-
- def remove_leading_dot_dir(path)
- path.sub(/^\.\//, "")
- end
-
end
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 2ced9ccdfb..e983751c17 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -129,8 +129,8 @@
# specify your dependency as ">= 2.0.0" then, you're good, right? What
# happens if fnord 3.0 comes out and it isn't backwards compatible
# with 2.y.z? Your stuff will break as a result of using ">=". The
-# better route is to specify your dependency with a "spermy" version
-# specifier. They're a tad confusing, so here is how the dependency
+# better route is to specify your dependency with an "approximate" version
+# specifier ("~>"). They're a tad confusing, so here is how the dependency
# specifiers work:
#
# Specification From ... To (exclusive)
@@ -145,6 +145,8 @@ class Gem::Version
include Comparable
+ # FIX: These are only used once, in .correct?. Do they deserve to be
+ # constants?
VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc:
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
@@ -169,6 +171,8 @@ class Gem::Version
# ver2 = Version.create(ver1) # -> (ver1)
# ver3 = Version.create(nil) # -> nil
+ # REFACTOR: There's no real reason this should be separate from #initialize.
+
def self.create input
if input.respond_to? :version then
input
@@ -187,8 +191,7 @@ class Gem::Version
raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version)
- @version = version.to_s
- @version.strip!
+ @version = version.to_s.dup.strip
end
##
@@ -248,11 +251,19 @@ class Gem::Version
@hash = nil
end
+ def to_yaml_properties
+ ["@version"]
+ end
+
+ def encode_with coder
+ coder.add 'version', @version
+ end
+
##
# A version is considered a prerelease if it contains a letter.
def prerelease?
- @prerelease ||= @version =~ /[a-zA-Z]/
+ @prerelease ||= !!(@version =~ /[a-zA-Z]/)
end
def pretty_print q # :nodoc:
@@ -284,7 +295,7 @@ class Gem::Version
##
# A recommended version for use with a ~> Requirement.
- def spermy_recommendation
+ def approximate_recommendation
segments = self.segments.dup
segments.pop while segments.any? { |s| String === s }