summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-03-31 22:40:06 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2008-03-31 22:40:06 +0000
commit8cc45aae947d453acca029e13eb64f3f5f0bf942 (patch)
treef9485a20c99defe1aae3f32555a41d23c2298ad8 /lib
parentdc8359969ec71ece10357ba9396430db7f029e45 (diff)
Import RubyGems 1.1.0
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r--lib/rubygems.rb901
-rw-r--r--lib/rubygems/builder.rb21
-rw-r--r--lib/rubygems/command_manager.rb1
-rw-r--r--lib/rubygems/commands/cleanup_command.rb136
-rw-r--r--lib/rubygems/commands/environment_command.rb20
-rw-r--r--lib/rubygems/commands/fetch_command.rb16
-rw-r--r--lib/rubygems/commands/install_command.rb12
-rw-r--r--lib/rubygems/commands/list_command.rb6
-rw-r--r--lib/rubygems/commands/mirror_command.rb2
-rw-r--r--lib/rubygems/commands/query_command.rb57
-rw-r--r--lib/rubygems/commands/sources_command.rb25
-rw-r--r--lib/rubygems/commands/specification_command.rb12
-rw-r--r--lib/rubygems/commands/uninstall_command.rb13
-rw-r--r--lib/rubygems/commands/unpack_command.rb18
-rw-r--r--lib/rubygems/commands/update_command.rb43
-rwxr-xr-xlib/rubygems/custom_require.rb2
-rw-r--r--lib/rubygems/defaults.rb9
-rw-r--r--lib/rubygems/dependency_installer.rb176
-rw-r--r--lib/rubygems/exceptions.rb19
-rw-r--r--lib/rubygems/format.rb26
-rw-r--r--lib/rubygems/indexer.rb79
-rw-r--r--lib/rubygems/indexer/abstract_index_builder.rb12
-rw-r--r--lib/rubygems/indexer/latest_index_builder.rb35
-rw-r--r--lib/rubygems/indexer/master_index_builder.rb17
-rw-r--r--lib/rubygems/indexer/quick_index_builder.rb8
-rw-r--r--lib/rubygems/install_update_options.rb6
-rw-r--r--lib/rubygems/installer.rb14
-rw-r--r--lib/rubygems/package.rb793
-rw-r--r--lib/rubygems/package/f_sync_dir.rb24
-rw-r--r--lib/rubygems/package/tar_header.rb245
-rw-r--r--lib/rubygems/package/tar_input.rb219
-rw-r--r--lib/rubygems/package/tar_output.rb143
-rw-r--r--lib/rubygems/package/tar_reader.rb86
-rw-r--r--lib/rubygems/package/tar_reader/entry.rb99
-rw-r--r--lib/rubygems/package/tar_writer.rb180
-rw-r--r--lib/rubygems/remote_fetcher.rb147
-rw-r--r--lib/rubygems/requirement.rb2
-rw-r--r--lib/rubygems/rubygems_version.rb2
-rw-r--r--lib/rubygems/security.rb1
-rw-r--r--lib/rubygems/server.rb189
-rw-r--r--lib/rubygems/source_index.rb739
-rw-r--r--lib/rubygems/source_info_cache.rb312
-rw-r--r--lib/rubygems/source_info_cache_entry.rb18
-rw-r--r--lib/rubygems/specification.rb2
-rw-r--r--lib/rubygems/uninstaller.rb60
-rw-r--r--lib/rubygems/user_interaction.rb12
46 files changed, 2883 insertions, 2076 deletions
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 9549b9bdca..3f9657ac1e 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -17,82 +17,80 @@ end
module Kernel
- # Adds a Ruby Gem to the $LOAD_PATH. Before a Gem is loaded, its
- # required Gems are loaded. 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 requirement and/or a required
- # Gem is not found, a Gem::LoadError is raised. More information on
- # version requirements can be found in the Gem::Version
- # documentation.
- #
- # The +gem+ directive should be executed *before* any require
- # statements (otherwise rubygems might select a conflicting library
- # version).
+ ##
+ # Use Kernel#gem to activate a specific version of +gem_name+.
#
- # You can define the environment variable GEM_SKIP as a way to not
- # load specified gems. You might do this to test out changes that
- # haven't been installed yet. Example:
+ # +version_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.
#
- # GEM_SKIP=libA:libB ruby-I../libA -I../libB ./mycode.rb
+ # 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.
#
- # gem:: [String or Gem::Dependency] The gem name or dependency
- # instance.
+ # 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.
#
- # version_requirement:: [default=">= 0"] The version
- # requirement.
+ # Kernel#gem should be called *before* any require statements (otherwise
+ # RubyGems may load a conflicting library version).
#
- # return:: [Boolean] true if the Gem is loaded, otherwise false.
+ # 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.
#
- # raises:: [Gem::LoadError] if Gem cannot be found, is listed in
- # GEM_SKIP, or version requirement not met.
+ # Example:
#
- def gem(gem_name, *version_requirements)
- active_gem_with_options(gem_name, version_requirements)
- end
-
- # Return the file name (string) and line number (integer) of the caller of
- # the caller of this method.
- def location_of_caller
- file, lineno = caller[1].split(':')
- lineno = lineno.to_i
- [file, lineno]
- end
- private :location_of_caller
+ # GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
- def active_gem_with_options(gem_name, version_requirements, options={})
+ def gem(gem_name, *version_requirements)
skip_list = (ENV['GEM_SKIP'] || "").split(/:/)
raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name
- Gem.activate(gem_name, options[:auto_require], *version_requirements)
+ Gem.activate(gem_name, *version_requirements)
end
- private :active_gem_with_options
+
end
+##
# Main module to hold all RubyGem classes/modules.
-#
+
module Gem
ConfigMap = {} unless defined?(ConfigMap)
require 'rbconfig'
RbConfig = Config unless defined? ::RbConfig
+
ConfigMap.merge!(
- :BASERUBY => RbConfig::CONFIG["BASERUBY"],
- :EXEEXT => RbConfig::CONFIG["EXEEXT"],
- :RUBY_INSTALL_NAME => RbConfig::CONFIG["RUBY_INSTALL_NAME"],
- :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"],
- :arch => RbConfig::CONFIG["arch"],
- :bindir => RbConfig::CONFIG["bindir"],
- :libdir => RbConfig::CONFIG["libdir"],
- :ruby_install_name => RbConfig::CONFIG["ruby_install_name"],
- :ruby_version => RbConfig::CONFIG["ruby_version"],
- :sitedir => RbConfig::CONFIG["sitedir"],
- :sitelibdir => RbConfig::CONFIG["sitelibdir"]
+ :BASERUBY => RbConfig::CONFIG["BASERUBY"],
+ :EXEEXT => RbConfig::CONFIG["EXEEXT"],
+ :RUBY_INSTALL_NAME => RbConfig::CONFIG["RUBY_INSTALL_NAME"],
+ :RUBY_SO_NAME => RbConfig::CONFIG["RUBY_SO_NAME"],
+ :arch => RbConfig::CONFIG["arch"],
+ :bindir => RbConfig::CONFIG["bindir"],
+ :libdir => RbConfig::CONFIG["libdir"],
+ :ruby_install_name => RbConfig::CONFIG["ruby_install_name"],
+ :ruby_version => RbConfig::CONFIG["ruby_version"],
+ :sitedir => RbConfig::CONFIG["sitedir"],
+ :sitelibdir => RbConfig::CONFIG["sitelibdir"]
)
+ DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES)
+
MUTEX = Mutex.new
RubyGemsPackageVersion = RubyGemsVersion
- DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES)
+ ##
+ # An Array of Regexps that match windows ruby platforms.
+
+ WIN_PATTERNS = [
+ /bccwin/i,
+ /cygwin/i,
+ /djgpp/i,
+ /mingw/i,
+ /mswin/i,
+ /wince/i,
+ ]
@@source_index = nil
@@win_platform = nil
@@ -103,10 +101,129 @@ module Gem
@ruby = nil
@sources = []
+ ##
+ # Activates an installed gem matching +gem+. The gem must satisfy
+ # +version_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 +gem+ 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(gem, *version_requirements)
+ if version_requirements.empty? then
+ version_requirements = Gem::Requirement.default
+ end
+
+ unless gem.respond_to?(:name) and
+ gem.respond_to?(:version_requirements) then
+ gem = Gem::Dependency.new(gem, version_requirements)
+ end
+
+ matches = Gem.source_index.find_name(gem.name, gem.version_requirements)
+ report_activate_error(gem) if matches.empty?
+
+ if @loaded_specs[gem.name] then
+ # This gem is already loaded. If the currently loaded gem is not in the
+ # list of candidate gems, then we have a version conflict.
+ existing_spec = @loaded_specs[gem.name]
+
+ unless matches.any? { |spec| spec.version == existing_spec.version } then
+ raise Gem::Exception,
+ "can't activate #{gem}, already activated #{existing_spec.full_name}]"
+ end
+
+ return false
+ end
+
+ # new load
+ spec = matches.last
+ return false if spec.loaded?
+
+ spec.loaded = true
+ @loaded_specs[spec.name] = spec
+
+ # Load dependent gems first
+ spec.dependencies.each do |dep_gem|
+ activate dep_gem
+ end
+
+ # bin directory must come before library directories
+ spec.require_paths.unshift spec.bindir if spec.bindir
+
+ require_paths = spec.require_paths.map do |path|
+ File.join spec.full_gem_path, path
+ end
+
+ sitelibdir = ConfigMap[:sitelibdir]
+
+ # gem directories must come after -I and ENV['RUBYLIB']
+ insert_index = load_path_insert_index
+
+ if insert_index then
+ # gem directories must come after -I and ENV['RUBYLIB']
+ $LOAD_PATH.insert(insert_index, *require_paths)
+ else
+ # we are probably testing in core, -I and RUBYLIB don't apply
+ $LOAD_PATH.unshift(*require_paths)
+ end
+
+ return true
+ end
+
+ ##
+ # An Array of all possible load paths for all versions of all gems in the
+ # Gem installation.
+
+ def self.all_load_paths
+ result = []
+
+ Gem.path.each do |gemdir|
+ each_load_path all_partials(gemdir) do |load_path|
+ result << load_path
+ end
+ end
+
+ result
+ end
+
+ ##
+ # Return all the partial paths in +gemdir+.
+
+ def self.all_partials(gemdir)
+ Dir[File.join(gemdir, 'gems/*')]
+ end
+
+ private_class_method :all_partials
+
+ ##
+ # The mode needed to read a file as straight binary.
+
+ def self.binary_mode
+ @binary_mode ||= RUBY_VERSION > '1.9' ? 'rb:ascii-8bit' : 'rb'
+ end
+
+ ##
+ # The path where gem executables are to be installed.
+
+ def self.bindir(install_dir=Gem.dir)
+ return File.join(install_dir, 'bin') unless
+ install_dir.to_s == Gem.default_dir
+ Gem.default_bindir
+ end
+
+ ##
# Reset the +dir+ and +path+ values. The next time +dir+ or +path+
# is requested, the values will be calculated from scratch. This is
# mainly used by the unit tests to provide test isolation.
- #
+
def self.clear_paths
@gem_home = nil
@gem_path = nil
@@ -116,441 +233,430 @@ module Gem
end
end
- # The version of the Marshal format for your Ruby.
- def self.marshal_version
- "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
+ ##
+ # The path to standard location of the user's .gemrc file.
+
+ def self.config_file
+ File.join Gem.user_home, '.gemrc'
end
##
- # The directory prefix this RubyGems was installed at.
+ # The standard configuration object for gems.
- def self.prefix
- prefix = File.dirname File.expand_path(__FILE__)
- if prefix == ConfigMap[:sitelibdir] then
- nil
- else
- File.dirname prefix
- end
+ def self.configuration
+ return @configuration if @configuration
+ require 'rubygems/config_file'
+ @configuration = Gem::ConfigFile.new []
end
- # Returns an Cache of specifications that are in the Gem.path
- #
- # return:: [Gem::SourceIndex] Index of installed Gem::Specifications
- #
- def self.source_index
- @@source_index ||= SourceIndex.from_installed_gems
+ ##
+ # Use the given configuration object (which implements the ConfigFile
+ # protocol) as the standard configuration object.
+
+ def self.configuration=(config)
+ @configuration = config
end
##
- # An Array of Regexps that match windows ruby platforms.
+ # The path the the data directory specified by the gem name. If the
+ # package is not available as a gem, return nil.
- WIN_PATTERNS = [
- /bccwin/i,
- /cygwin/i,
- /djgpp/i,
- /mingw/i,
- /mswin/i,
- /wince/i,
- ]
+ def self.datadir(gem_name)
+ spec = @loaded_specs[gem_name]
+ return nil if spec.nil?
+ File.join(spec.full_gem_path, 'data', gem_name)
+ end
##
- # Is this a windows platform?
+ # The path where gems are to be installed.
- def self.win_platform?
- if @@win_platform.nil? then
- @@win_platform = !!WIN_PATTERNS.find { |r| RUBY_PLATFORM =~ r }
- end
+ def self.dir
+ @gem_home ||= nil
+ set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home
+ @gem_home
+ end
- @@win_platform
+ ##
+ # Expand each partial gem path with each of the required paths specified
+ # in the Gem spec. Each expanded path is yielded.
+
+ def self.each_load_path(partials)
+ partials.each do |gp|
+ base = File.basename(gp)
+ specfn = File.join(dir, "specifications", base + ".gemspec")
+ if File.exist?(specfn)
+ spec = eval(File.read(specfn))
+ spec.require_paths.each do |rp|
+ yield(File.join(gp, rp))
+ end
+ else
+ filename = File.join(gp, 'lib')
+ yield(filename) if File.exist?(filename)
+ end
+ end
end
- class << self
+ private_class_method :each_load_path
- attr_reader :loaded_specs
+ ##
+ # Quietly ensure the named Gem directory contains all the proper
+ # subdirectories. If we can't create a directory due to a permission
+ # problem, then we will silently continue.
- # Quietly ensure the named Gem directory contains all the proper
- # subdirectories. If we can't create a directory due to a permission
- # problem, then we will silently continue.
- def ensure_gem_subdirectories(gemdir)
- require 'fileutils'
+ def self.ensure_gem_subdirectories(gemdir)
+ require 'fileutils'
- Gem::DIRECTORIES.each do |filename|
- fn = File.join gemdir, filename
- FileUtils.mkdir_p fn rescue nil unless File.exist? fn
- end
+ Gem::DIRECTORIES.each do |filename|
+ fn = File.join gemdir, filename
+ FileUtils.mkdir_p fn rescue nil unless File.exist? fn
end
+ end
- def platforms
- @platforms ||= [Gem::Platform::RUBY, Gem::Platform.local]
- end
+ ##
+ # Finds the user's home directory.
+ #--
+ # Some comments from the ruby-talk list regarding finding the home
+ # directory:
+ #
+ # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems
+ # to be depending on HOME in those code samples. I propose that
+ # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at
+ # least on Win32).
- # 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.
- def sources
- if @sources.empty? then
- begin
- gem 'sources', '> 0.0.1'
- require 'sources'
- rescue LoadError
- @sources = default_sources
- end
- end
+ def self.find_home
+ ['HOME', 'USERPROFILE'].each do |homekey|
+ return ENV[homekey] if ENV[homekey]
+ end
- @sources
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
+ return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
end
+ begin
+ File.expand_path("~")
+ rescue
+ if File::ALT_SEPARATOR then
+ "C:/"
+ else
+ "/"
+ end
+ end
+ end
- # Provide an alias for the old name.
- alias cache source_index
+ private_class_method :find_home
- # The directory path where Gems are to be installed.
- #
- # return:: [String] The directory path
- #
- def dir
- @gem_home ||= nil
- set_home(ENV['GEM_HOME'] || default_dir) unless @gem_home
- @gem_home
- end
+ ##
+ # Return a list of all possible load paths for the latest version for all
+ # gems in the Gem installation.
- # The directory path where executables are to be installed.
- #
- def bindir(install_dir=Gem.dir)
- return File.join(install_dir, 'bin') unless
- install_dir.to_s == Gem.default_dir
+ def self.latest_load_paths
+ result = []
- if defined? RUBY_FRAMEWORK_VERSION then # mac framework support
- '/usr/bin'
- else # generic install
- ConfigMap[:bindir]
+ Gem.path.each do |gemdir|
+ each_load_path(latest_partials(gemdir)) do |load_path|
+ result << load_path
end
end
- # List of directory paths to search for Gems.
- #
- # return:: [List<String>] List of directory paths.
- #
- def path
- @gem_path ||= nil
- unless @gem_path
- paths = [ENV['GEM_PATH']]
- paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME
- set_paths(paths.compact.join(File::PATH_SEPARATOR))
+ 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 =~ /(.*)-((\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
- @gem_path
end
+ latest.collect { |k,v| v[1] }
+ end
- # The home directory for the user.
- def user_home
- @user_home ||= find_home
- end
+ private_class_method :latest_partials
- # Return the path to standard location of the users .gemrc file.
- def config_file
- File.join(Gem.user_home, '.gemrc')
+ ##
+ # The index to insert activated gem paths into the $LOAD_PATH.
+ #
+ # Defaults to the site lib directory unless gem_prelude.rb has loaded paths,
+ # then it inserts the activated gem's paths before the gem_prelude.rb paths
+ # so you can override the gem_prelude.rb default $LOAD_PATH paths.
+
+ def self.load_path_insert_index
+ index = $LOAD_PATH.index ConfigMap[:sitelibdir]
+
+ $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
- # The standard configuration object for gems.
- def configuration
- return @configuration if @configuration
- require 'rubygems/config_file'
- @configuration = Gem::ConfigFile.new []
- end
+ index
+ end
- # Use the given configuration object (which implements the
- # ConfigFile protocol) as the standard configuration object.
- def configuration=(config)
- @configuration = config
- end
+ ##
+ # The file name and line number of the caller of the caller of this method.
- # Return the path the the data directory specified by the gem
- # name. If the package is not available as a gem, return nil.
- def datadir(gem_name)
- spec = @loaded_specs[gem_name]
- return nil if spec.nil?
- File.join(spec.full_gem_path, 'data', gem_name)
- end
+ def self.location_of_caller
+ file, lineno = caller[1].split(':')
+ lineno = lineno.to_i
+ [file, lineno]
+ end
- # Return the searcher object to search for matching gems.
- def searcher
- MUTEX.synchronize do
- @searcher ||= Gem::GemPathSearcher.new
- end
- end
+ private_class_method :location_of_caller
- # Return the Ruby command to use to execute the Ruby interpreter.
- def ruby
- if @ruby.nil? then
- @ruby = File.join(ConfigMap[:bindir],
- ConfigMap[:ruby_install_name])
- @ruby << ConfigMap[:EXEEXT]
- end
+ ##
+ # manage_gems is useless and deprecated. Don't call it anymore.
+ #--
+ # TODO warn w/ RubyGems 1.2.x release.
- @ruby
- end
+ def self.manage_gems
+ #file, lineno = location_of_caller
- # Return the index to insert activated gem paths into the $LOAD_PATH
- # Defaults to the site lib directory unless gem_prelude.rb has loaded
- # paths then it inserts the path before those paths so you can override
- # the gem_prelude.rb default $LOAD_PATH paths.
- def load_path_insert_index
- index = $LOAD_PATH.index ConfigMap[:sitelibdir]
-
- $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
+ #warn "#{file}:#{lineno}:Warning: Gem#manage_gems is deprecated and will be removed on or after September 2008."
+ end
- index
- end
+ ##
+ # The version of the Marshal format for your Ruby.
- # Activate a gem (i.e. add it to the Ruby load path). The gem
- # must satisfy all the specified version constraints. If
- # +autorequire+ is true, then automatically require the specified
- # autorequire file in the gem spec.
- #
- # Returns true if the gem is loaded by this call, false if it is
- # already loaded, or an exception otherwise.
- #
- def activate(gem, autorequire, *version_requirements)
- if version_requirements.empty? then
- version_requirements = Gem::Requirement.default
- end
+ def self.marshal_version
+ "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
+ end
- unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
- gem = Gem::Dependency.new(gem, version_requirements)
- end
+ ##
+ # Array of paths to search for Gems.
- matches = Gem.source_index.find_name(gem.name, gem.version_requirements)
- report_activate_error(gem) if matches.empty?
+ def self.path
+ @gem_path ||= nil
- if @loaded_specs[gem.name]
- # This gem is already loaded. If the currently loaded gem is
- # not in the list of candidate gems, then we have a version
- # conflict.
- existing_spec = @loaded_specs[gem.name]
- if ! matches.any? { |spec| spec.version == existing_spec.version }
- fail Gem::Exception, "can't activate #{gem}, already activated #{existing_spec.full_name}]"
- end
- return false
- end
+ unless @gem_path then
+ paths = [ENV['GEM_PATH']] || [default_path]
- # new load
- spec = matches.last
- if spec.loaded?
- return false unless autorequire
- result = spec.autorequire ? require(spec.autorequire) : false
- return result || false
+ if defined?(APPLE_GEM_HOME) and not ENV['GEM_PATH'] then
+ paths << APPLE_GEM_HOME
end
- spec.loaded = true
- @loaded_specs[spec.name] = spec
+ set_paths paths.compact.join(File::PATH_SEPARATOR)
+ end
- # Load dependent gems first
- spec.dependencies.each do |dep_gem|
- activate(dep_gem, autorequire)
- end
+ @gem_path
+ end
- # bin directory must come before library directories
- spec.require_paths.unshift spec.bindir if spec.bindir
+ ##
+ # Array of platforms this RubyGems supports.
- require_paths = spec.require_paths.map do |path|
- File.join spec.full_gem_path, path
- end
+ def self.platforms
+ @platforms ||= [Gem::Platform::RUBY, Gem::Platform.local]
+ end
- sitelibdir = ConfigMap[:sitelibdir]
+ ##
+ # The directory prefix this RubyGems was installed at.
- # gem directories must come after -I and ENV['RUBYLIB']
- insert_index = load_path_insert_index
+ def self.prefix
+ prefix = File.dirname File.expand_path(__FILE__)
- if insert_index then
- # gem directories must come after -I and ENV['RUBYLIB']
- $LOAD_PATH.insert(insert_index, *require_paths)
- else
- # we are probably testing in core, -I and RUBYLIB don't apply
- $LOAD_PATH.unshift(*require_paths)
- end
+ if prefix == File.expand_path(ConfigMap[:sitelibdir]) then
+ nil
+ else
+ File.dirname prefix
+ end
+ end
- # Now autorequire
- if autorequire && spec.autorequire then # DEPRECATED
- Array(spec.autorequire).each do |a_lib|
- require a_lib
- end
- end
+ ##
+ # Safely read a file in binary mode on all platforms.
- return true
- end
+ def self.read_binary(path)
+ File.open path, binary_mode do |f| f.read end
+ 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.
- # 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 report_activate_error(gem)
- matches = Gem.source_index.find_name(gem.name)
+ def self.report_activate_error(gem)
+ matches = Gem.source_index.find_name(gem.name)
- if matches.empty? then
- error = Gem::LoadError.new(
+ if matches.empty? then
+ error = Gem::LoadError.new(
"Could not find RubyGem #{gem.name} (#{gem.version_requirements})\n")
- else
- error = Gem::LoadError.new(
+ else
+ error = Gem::LoadError.new(
"RubyGem version error: " +
"#{gem.name}(#{matches.first.version} not #{gem.version_requirements})\n")
- end
-
- error.name = gem.name
- error.version_requirement = gem.version_requirements
- raise error
- end
- private :report_activate_error
-
- # Use the +home+ and (optional) +paths+ values for +dir+ and +path+.
- # Used mainly by the unit tests to provide environment isolation.
- #
- def use_paths(home, paths=[])
- clear_paths
- set_home(home) if home
- set_paths(paths.join(File::PATH_SEPARATOR)) if paths
end
- # Return a list of all possible load paths for all versions for
- # all gems in the Gem installation.
- #
- def all_load_paths
- result = []
- Gem.path.each do |gemdir|
- each_load_path(all_partials(gemdir)) do |load_path|
- result << load_path
- end
- end
- result
- end
+ error.name = gem.name
+ error.version_requirement = gem.version_requirements
+ raise error
+ end
- # Return a list of all possible load paths for the latest version
- # for all gems in the Gem installation.
- def 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
+ private_class_method :report_activate_error
- def required_location(gemname, libfile, *version_constraints)
- version_constraints = Gem::Requirement.default if version_constraints.empty?
- matches = Gem.source_index.find_name(gemname, version_constraints)
- return nil if matches.empty?
- spec = matches.last
- spec.require_paths.each do |path|
- result = File.join(spec.full_gem_path, path, libfile)
- return result if File.exist?(result)
- end
- nil
+ def self.required_location(gemname, libfile, *version_constraints)
+ version_constraints = Gem::Requirement.default if version_constraints.empty?
+ matches = Gem.source_index.find_name(gemname, version_constraints)
+ return nil if matches.empty?
+ spec = matches.last
+ spec.require_paths.each do |path|
+ result = File.join(spec.full_gem_path, path, libfile)
+ return result if File.exist?(result)
end
+ nil
+ end
- def suffixes
- ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
- end
+ ##
+ # The path to the running Ruby interpreter.
- def suffix_pattern
- @suffix_pattern ||= "{#{suffixes.join(',')}}"
+ def self.ruby
+ if @ruby.nil? then
+ @ruby = File.join(ConfigMap[:bindir],
+ ConfigMap[:ruby_install_name])
+ @ruby << ConfigMap[:EXEEXT]
end
- # manage_gems is useless and deprecated. Don't call it anymore. This
- # will warn in two releases.
- def manage_gems
- # do nothing
- end
+ @ruby
+ end
- private
+ ##
+ # A Gem::Version for the currently running ruby.
- # Return all the partial paths in the given +gemdir+.
- def all_partials(gemdir)
- Dir[File.join(gemdir, 'gems/*')]
- end
+ def self.ruby_version
+ return @ruby_version if defined? @ruby_version
+ version = RUBY_VERSION.dup
+ version << ".#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ @ruby_version = Gem::Version.new version
+ end
- # Return only the latest partial paths in the given +gemdir+.
- def latest_partials(gemdir)
- latest = {}
- all_partials(gemdir).each do |gp|
- base = File.basename(gp)
- if base =~ /(.*)-((\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] }
+ ##
+ # The GemPathSearcher object used to search for matching installed gems.
+
+ def self.searcher
+ MUTEX.synchronize do
+ @searcher ||= Gem::GemPathSearcher.new
end
+ end
+
+ ##
+ # Set the Gem home directory (as reported by Gem.dir).
+
+ def self.set_home(home)
+ home = home.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
+ @gem_home = home
+ ensure_gem_subdirectories(@gem_home)
+ end
- # Expand each partial gem path with each of the required paths
- # specified in the Gem spec. Each expanded path is yielded.
- def each_load_path(partials)
- partials.each do |gp|
- base = File.basename(gp)
- specfn = File.join(dir, "specifications", base + ".gemspec")
- if File.exist?(specfn)
- spec = eval(File.read(specfn))
- spec.require_paths.each do |rp|
- yield(File.join(gp, rp))
- end
- else
- filename = File.join(gp, 'lib')
- yield(filename) if File.exist?(filename)
+ private_class_method :set_home
+
+ ##
+ # Set the Gem search path (as reported by Gem.path).
+
+ def self.set_paths(gpaths)
+ if gpaths
+ @gem_path = gpaths.split(File::PATH_SEPARATOR)
+
+ if File::ALT_SEPARATOR then
+ @gem_path.map! do |path|
+ path.gsub File::ALT_SEPARATOR, File::SEPARATOR
end
end
+
+ @gem_path << Gem.dir
+ else
+ @gem_path = [Gem.dir]
end
- # Set the Gem home directory (as reported by +dir+).
- def set_home(home)
- @gem_home = home
- ensure_gem_subdirectories(@gem_home)
- end
+ @gem_path.uniq!
+ @gem_path.each do |gp| ensure_gem_subdirectories(gp) end
+ end
- # Set the Gem search path (as reported by +path+).
- def set_paths(gpaths)
- if gpaths
- @gem_path = gpaths.split(File::PATH_SEPARATOR)
- @gem_path << Gem.dir
- else
- @gem_path = [Gem.dir]
- end
- @gem_path.uniq!
- @gem_path.each do |gp| ensure_gem_subdirectories(gp) end
- end
+ private_class_method :set_paths
- # Some comments from the ruby-talk list regarding finding the home
- # directory:
- #
- # I have HOME, USERPROFILE and HOMEDRIVE + HOMEPATH. Ruby seems
- # to be depending on HOME in those code samples. I propose that
- # it should fallback to USERPROFILE and HOMEDRIVE + HOMEPATH (at
- # least on Win32).
- #
- def find_home
- ['HOME', 'USERPROFILE'].each do |homekey|
- return ENV[homekey] if ENV[homekey]
- end
- if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
- return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
- end
+ ##
+ # Returns the Gem::SourceIndex of specifications that are in the Gem.path
+
+ def self.source_index
+ @@source_index ||= SourceIndex.from_installed_gems
+ 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.
+
+ def self.sources
+ if @sources.empty? then
begin
- File.expand_path("~")
- rescue StandardError => ex
- if File::ALT_SEPARATOR
- "C:/"
- else
- "/"
- end
+ gem 'sources', '> 0.0.1'
+ require 'sources'
+ rescue LoadError
+ @sources = default_sources
end
end
+ @sources
+ end
+
+ ##
+ # Glob pattern for require-able path suffixes.
+
+ def self.suffix_pattern
+ @suffix_pattern ||= "{#{suffixes.join(',')}}"
+ end
+
+ ##
+ # Suffixes for require-able paths.
+
+ def self.suffixes
+ ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
+ end
+
+ ##
+ # Use the +home+ and +paths+ values for Gem.dir and Gem.path. Used mainly
+ # by the unit tests to provide environment isolation.
+
+ def self.use_paths(home, paths=[])
+ clear_paths
+ set_home(home) if home
+ set_paths(paths.join(File::PATH_SEPARATOR)) if paths
+ end
+
+ ##
+ # The home directory for the user.
+
+ def self.user_home
+ @user_home ||= find_home
+ end
+
+ ##
+ # Is this a windows platform?
+
+ def self.win_platform?
+ if @@win_platform.nil? then
+ @@win_platform = !!WIN_PATTERNS.find { |r| RUBY_PLATFORM =~ r }
+ end
+
+ @@win_platform
+ end
+
+ class << self
+
+ attr_reader :loaded_specs
+
+ # :stopdoc:
+
+ alias cache source_index # an alias for the old name
+
+ # :startdoc:
+
end
end
@@ -558,6 +664,7 @@ end
# Modify the non-gem version of datadir to handle gem package names.
require 'rbconfig/datadir'
+
module Config # :nodoc:
class << self
alias gem_original_datadir datadir
diff --git a/lib/rubygems/builder.rb b/lib/rubygems/builder.rb
index f7f07e86bf..6fd8528f56 100644
--- a/lib/rubygems/builder.rb
+++ b/lib/rubygems/builder.rb
@@ -65,13 +65,20 @@ EOM
end
def write_package
- Package.open(@spec.file_name, "w", @signer) do |pkg|
- pkg.metadata = @spec.to_yaml
- @spec.files.each do |file|
- next if File.directory? file
- pkg.add_file_simple(file, File.stat(@spec.file_name).mode & 0777,
- File.size(file)) do |os|
- os.write File.open(file, "rb"){|f|f.read}
+ open @spec.file_name, 'wb' do |gem_io|
+ Gem::Package.open gem_io, 'w', @signer do |pkg|
+ pkg.metadata = @spec.to_yaml
+
+ @spec.files.each do |file|
+ next if File.directory? file
+
+ 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
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index a80c821c5c..b8aa651f56 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -123,6 +123,7 @@ module Gem
end
private
+
def load_and_instantiate(command_name)
command_name = command_name.to_s
retried = false
diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb
index f6deac9829..40dcb9db34 100644
--- a/lib/rubygems/commands/cleanup_command.rb
+++ b/lib/rubygems/commands/cleanup_command.rb
@@ -2,92 +2,90 @@ require 'rubygems/command'
require 'rubygems/source_index'
require 'rubygems/dependency_list'
-module Gem
- module Commands
- class CleanupCommand < Command
- def initialize
- super(
- 'cleanup',
+class Gem::Commands::CleanupCommand < Gem::Command
+
+ def initialize
+ super 'cleanup',
'Clean up old versions of installed gems in the local repository',
- {
- :force => false,
- :test => false,
- :install_dir => Gem.dir
- })
- add_option('-d', '--dryrun', "") do |value, options|
- options[:dryrun] = true
- end
- end
+ :force => false, :test => false, :install_dir => Gem.dir
- def arguments # :nodoc:
- "GEMNAME name of gem to cleanup"
- end
+ add_option('-d', '--dryrun', "") do |value, options|
+ options[:dryrun] = true
+ end
+ end
- def defaults_str # :nodoc:
- "--no-dryrun"
- end
+ def arguments # :nodoc:
+ "GEMNAME name of gem to cleanup"
+ end
+
+ def defaults_str # :nodoc:
+ "--no-dryrun"
+ end
+
+ def usage # :nodoc:
+ "#{program_name} [GEMNAME ...]"
+ end
+
+ def execute
+ say "Cleaning up installed gems..."
+ primary_gems = {}
- def usage # :nodoc:
- "#{program_name} [GEMNAME ...]"
+ Gem.source_index.each do |name, spec|
+ if primary_gems[spec.name].nil? or
+ primary_gems[spec.name].version < spec.version then
+ primary_gems[spec.name] = spec
end
+ end
- def execute
- say "Cleaning up installed gems..."
- srcindex = Gem::SourceIndex.from_installed_gems
- primary_gems = {}
+ gems_to_cleanup = []
- srcindex.each do |name, spec|
- if primary_gems[spec.name].nil? or primary_gems[spec.name].version < spec.version
- primary_gems[spec.name] = spec
- end
+ unless options[:args].empty? then
+ options[:args].each do |gem_name|
+ specs = Gem.cache.search(/^#{gem_name}$/i)
+ specs.each do |spec|
+ gems_to_cleanup << spec
end
+ end
+ else
+ Gem.source_index.each do |name, spec|
+ gems_to_cleanup << spec
+ end
+ end
- gems_to_cleanup = []
-
- unless options[:args].empty? then
- options[:args].each do |gem_name|
- specs = Gem.cache.search(/^#{gem_name}$/i)
- specs.each do |spec|
- gems_to_cleanup << spec
- end
- end
- else
- srcindex.each do |name, spec|
- gems_to_cleanup << spec
- end
- end
+ gems_to_cleanup = gems_to_cleanup.select { |spec|
+ primary_gems[spec.name].version != spec.version
+ }
- gems_to_cleanup = gems_to_cleanup.select { |spec|
- primary_gems[spec.name].version != spec.version
- }
+ uninstall_command = Gem::CommandManager.instance['uninstall']
+ deplist = Gem::DependencyList.new
+ gems_to_cleanup.uniq.each do |spec| deplist.add spec end
- uninstall_command = Gem::CommandManager.instance['uninstall']
- deplist = DependencyList.new
- gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end
+ deps = deplist.strongly_connected_components.flatten.reverse
- deplist.dependency_order.each do |spec|
- if options[:dryrun] then
- say "Dry Run Mode: Would uninstall #{spec.full_name}"
- else
- say "Attempting uninstall on #{spec.full_name}"
+ deps.each do |spec|
+ if options[:dryrun] then
+ say "Dry Run Mode: Would uninstall #{spec.full_name}"
+ else
+ say "Attempting to uninstall #{spec.full_name}"
- options[:args] = [spec.name]
- options[:version] = "= #{spec.version}"
- options[:executables] = true
+ options[:args] = [spec.name]
+ options[:version] = "= #{spec.version}"
+ options[:executables] = false
- uninstall_command.merge_options(options)
+ uninstaller = Gem::Uninstaller.new spec.name, options
- begin
- uninstall_command.execute
- rescue Gem::DependencyRemovalException => ex
- say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems"
- end
- end
+ begin
+ uninstaller.uninstall
+ rescue Gem::DependencyRemovalException,
+ Gem::GemNotInHomeException => e
+ say "Unable to uninstall #{spec.full_name}:"
+ say "\t#{e.class}: #{e.message}"
end
-
- say "Clean Up Complete"
end
end
-
+
+ say "Clean Up Complete"
end
+
end
+
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index ab85361753..56b373cfbe 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -25,19 +25,18 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
def execute
out = ''
arg = options[:args][0]
- if begins?("packageversion", arg) then
+ case arg
+ when /^packageversion/ then
out << Gem::RubyGemsPackageVersion
- elsif begins?("version", arg) then
+ when /^version/ then
out << Gem::RubyGemsVersion
- elsif begins?("gemdir", arg) then
+ when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then
out << Gem.dir
- elsif begins?("gempath", arg) then
- out << Gem.path.join("\n")
- elsif begins?("remotesources", arg) then
+ when /^gempath/, /^path/, /^GEM_PATH/ then
+ out << Gem.path.join(File::PATH_SEPARATOR)
+ when /^remotesources/ then
out << Gem.sources.join("\n")
- elsif arg then
- fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
- else
+ when nil then
out = "RubyGems Environment:\n"
out << " - RUBYGEMS VERSION: #{Gem::RubyGemsVersion} (#{Gem::RubyGemsPackageVersion})\n"
@@ -75,6 +74,9 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
Gem.sources.each do |s|
out << " - #{s}\n"
end
+
+ else
+ fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
end
say out
true
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index 7db365eba0..ccedc45401 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -44,17 +44,15 @@ class Gem::Commands::FetchCommand < Gem::Command
spec, source_uri = specs_and_sources.last
- gem_file = "#{spec.full_name}.gem"
-
- gem_path = File.join source_uri, 'gems', gem_file
-
- gem = Gem::RemoteFetcher.fetcher.fetch_path gem_path
-
- File.open gem_file, 'wb' do |fp|
- fp.write gem
+ if spec.nil? then
+ alert_error "Could not find #{gem_name} in any repository"
+ next
end
- say "Downloaded #{gem_file}"
+ path = Gem::RemoteFetcher.fetcher.download spec, source_uri
+ FileUtils.mv path, "#{spec.full_name}.gem"
+
+ say "Downloaded #{spec.full_name}"
end
end
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index aa9f480c2a..ce0bc6ba04 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -62,13 +62,15 @@ class Gem::Commands::InstallCommand < Gem::Command
:install_dir => options[:install_dir],
:security_policy => options[:security_policy],
:wrappers => options[:wrappers],
+ :bin_dir => options[:bin_dir]
}
+ exit_code = 0
+
get_all_gem_names.each do |gem_name|
begin
- inst = Gem::DependencyInstaller.new gem_name, options[:version],
- install_options
- inst.install
+ inst = Gem::DependencyInstaller.new install_options
+ inst.install gem_name, options[:version]
inst.installed_gems.each do |spec|
say "Successfully installed #{spec.full_name}"
@@ -77,8 +79,10 @@ class Gem::Commands::InstallCommand < Gem::Command
installed_gems.push(*inst.installed_gems)
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
+ exit_code |= 1
rescue Gem::GemNotFoundException => e
alert_error e.message
+ exit_code |= 2
# rescue => e
# # TODO: Fix this handle to allow the error to propagate to
# # the top level handler. Examine the other errors as
@@ -121,6 +125,8 @@ class Gem::Commands::InstallCommand < Gem::Command
end
end
end
+
+ raise Gem::SystemExitException, exit_code
end
end
diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb
index e179ff57ee..f8b377fcde 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -6,10 +6,8 @@ module Gem
class ListCommand < QueryCommand
def initialize
- super(
- 'list',
- 'Display all gems whose name starts with STRING'
- )
+ super 'list', 'Display gems whose name starts with STRING'
+
remove_option('--name-matches')
end
diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb
index fc4f086ad3..959b8eaec3 100644
--- a/lib/rubygems/commands/mirror_command.rb
+++ b/lib/rubygems/commands/mirror_command.rb
@@ -2,7 +2,7 @@ require 'yaml'
require 'zlib'
require 'rubygems/command'
-require 'rubygems/gem_open_uri'
+require 'open-uri'
class Gem::Commands::MirrorCommand < Gem::Command
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index 4f957625ee..fdc5a6a4ea 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -1,15 +1,25 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
+require 'rubygems/version_option'
class Gem::Commands::QueryCommand < Gem::Command
include Gem::LocalRemoteOptions
+ include Gem::VersionOption
def initialize(name = 'query',
summary = 'Query gem information in local or remote repositories')
super name, summary,
- :name => /.*/, :domain => :local, :details => false, :versions => true
+ :name => //, :domain => :local, :details => false, :versions => true,
+ :installed => false, :version => Gem::Requirement.default
+
+ add_option('-i', '--[no-]installed',
+ 'Check for installed gem') do |value, options|
+ options[:installed] = value
+ end
+
+ add_version_option
add_option('-n', '--name-matches REGEXP',
'Name of gem(s) to query on matches the',
@@ -28,33 +38,70 @@ class Gem::Commands::QueryCommand < Gem::Command
options[:details] = false unless value
end
+ add_option('-a', '--all',
+ 'Display all gem versions') do |value, options|
+ options[:all] = value
+ end
+
add_local_remote_options
end
def defaults_str # :nodoc:
- "--local --name-matches '.*' --no-details --versions"
+ "--local --name-matches // --no-details --versions --no-installed"
end
def execute
+ exit_code = 0
+
name = options[:name]
+ if options[:installed] then
+ if name.source.empty? then
+ alert_error "You must specify a gem name"
+ exit_code |= 4
+ elsif installed? name.source, options[:version] then
+ say "true"
+ else
+ say "false"
+ exit_code |= 1
+ end
+
+ raise Gem::SystemExitException, exit_code
+ end
+
if local? then
say
say "*** LOCAL GEMS ***"
say
- output_query_results Gem.cache.search(name)
+
+ output_query_results Gem.source_index.search(name)
end
if remote? then
say
say "*** REMOTE GEMS ***"
say
- output_query_results Gem::SourceInfoCache.search(name)
+
+ begin
+ Gem::SourceInfoCache.cache.refresh options[:all]
+ rescue Gem::RemoteFetcher::FetchError
+ # no network
+ end
+
+ output_query_results Gem::SourceInfoCache.search(name, false, true)
end
end
private
+ ##
+ # Check if gem +name+ version +version+ is installed.
+
+ def installed?(name, version = Gem::Requirement.default)
+ dep = Gem::Dependency.new name, version
+ !Gem.source_index.search(dep).empty?
+ end
+
def output_query_results(gemspecs)
output = []
gem_list_with_version = {}
@@ -98,7 +145,7 @@ class Gem::Commands::QueryCommand < Gem::Command
##
# Used for wrapping and indenting text
- #
+
def format_text(text, wrap, indent=0)
result = []
work = text.dup
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index a0977f90dc..6d9d5b5b90 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -39,8 +39,11 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:list] = !(options[:add] || options[:remove] || options[:clear_all] || options[:update])
if options[:clear_all] then
- remove_cache_file("user", Gem::SourceInfoCache.user_cache_file)
- remove_cache_file("system", Gem::SourceInfoCache.system_cache_file)
+ sic = Gem::SourceInfoCache
+ remove_cache_file 'user', sic.user_cache_file
+ remove_cache_file 'latest user', sic.latest_user_cache_file
+ remove_cache_file 'system', sic.system_cache_file
+ remove_cache_file 'latest system', sic.latest_system_cache_file
end
if options[:add] then
@@ -48,7 +51,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
sice = Gem::SourceInfoCacheEntry.new nil, nil
begin
- sice.refresh source_uri
+ sice.refresh source_uri, true
Gem::SourceInfoCache.cache_data[source_uri] = sice
Gem::SourceInfoCache.cache.update
@@ -66,7 +69,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
if options[:update] then
- Gem::SourceInfoCache.cache.refresh
+ Gem::SourceInfoCache.cache.refresh true
Gem::SourceInfoCache.cache.flush
say "source cache successfully updated"
@@ -78,6 +81,11 @@ class Gem::Commands::SourcesCommand < Gem::Command
unless Gem.sources.include? source_uri then
say "source #{source_uri} not present in cache"
else
+ begin # HACK figure out how to get the cache w/o update
+ Gem::SourceInfoCache.cache
+ rescue Gem::RemoteFetcher::FetchError
+ end
+
Gem::SourceInfoCache.cache_data.delete source_uri
Gem::SourceInfoCache.cache.update
Gem::SourceInfoCache.cache.flush
@@ -100,11 +108,12 @@ class Gem::Commands::SourcesCommand < Gem::Command
private
- def remove_cache_file(desc, fn)
- FileUtils.rm_rf fn rescue nil
- if ! File.exist?(fn)
+ def remove_cache_file(desc, path)
+ FileUtils.rm_rf path
+
+ if not File.exist?(path) then
say "*** Removed #{desc} source cache ***"
- elsif ! File.writable?(fn)
+ elsif not File.writable?(path) then
say "*** Unable to remove #{desc} source cache (write protected) ***"
else
say "*** Unable to remove #{desc} source cache ***"
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index 1ab2ad9260..7c8598e53b 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -3,6 +3,7 @@ require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
require 'rubygems/source_info_cache'
+require 'rubygems/format'
class Gem::Commands::SpecificationCommand < Gem::Command
@@ -41,13 +42,16 @@ class Gem::Commands::SpecificationCommand < Gem::Command
gem = get_one_gem_name
if local? then
- source_index = Gem::SourceIndex.from_installed_gems
- specs.push(*source_index.search(/\A#{gem}\z/, options[:version]))
+ if File.exist? gem then
+ specs << Gem::Format.from_file_by_path(gem).spec rescue nil
+ end
+
+ if specs.empty? then
+ specs.push(*Gem.source_index.search(/\A#{gem}\z/, options[:version]))
+ end
end
if remote? then
- alert_warning "Remote information is not complete\n\n"
-
Gem::SourceInfoCache.cache_data.each do |_,sice|
specs.push(*sice.source_index.search(gem, options[:version]))
end
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index 2d9c46ee52..3d6e2383bc 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -35,6 +35,11 @@ module Gem
options[:install_dir] = File.expand_path(value)
end
+ add_option('-n', '--bindir DIR',
+ 'Directory to remove binaries from') do |value, options|
+ options[:bin_dir] = File.expand_path(value)
+ end
+
add_version_option
add_platform_option
end
@@ -54,7 +59,13 @@ module Gem
def execute
get_all_gem_names.each do |gem_name|
- Gem::Uninstaller.new(gem_name, options).uninstall
+ begin
+ Gem::Uninstaller.new(gem_name, options).uninstall
+ 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}")
+ end
end
end
end
diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb
index 23ebabc21a..d187f8a9ea 100644
--- a/lib/rubygems/commands/unpack_command.rb
+++ b/lib/rubygems/commands/unpack_command.rb
@@ -38,6 +38,7 @@ class Gem::Commands::UnpackCommand < Gem::Command
def execute
gemname = get_one_gem_name
path = get_path(gemname, options[:version])
+
if path then
basename = File.basename(path).sub(/\.gem$/, '')
target_dir = File.expand_path File.join(options[:target], basename)
@@ -66,16 +67,27 @@ class Gem::Commands::UnpackCommand < Gem::Command
# source directories?
def get_path(gemname, version_req)
return gemname if gemname =~ /\.gem$/i
- specs = Gem::SourceIndex.from_installed_gems.search(/\A#{gemname}\z/, version_req)
+
+ specs = Gem::source_index.search(/\A#{gemname}\z/, version_req)
+
selected = specs.sort_by { |s| s.version }.last
+
return nil if selected.nil?
+
# We expect to find (basename).gem in the 'cache' directory.
# Furthermore, the name match must be exact (ignoring case).
if gemname =~ /^#{selected.name}$/i
filename = selected.full_name + '.gem'
- return File.join(Gem.dir, 'cache', filename)
+ path = nil
+
+ Gem.path.find do |gem_dir|
+ path = File.join gem_dir, 'cache', filename
+ File.exist? path
+ end
+
+ path
else
- return nil
+ nil
end
end
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index 88d48d705e..b8de911e20 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -1,8 +1,10 @@
require 'rubygems/command'
+require 'rubygems/command_manager'
require 'rubygems/install_update_options'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
require 'rubygems/version_option'
+require 'rubygems/commands/install_command'
class Gem::Commands::UpdateCommand < Gem::Command
@@ -45,7 +47,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
def execute
if options[:system] then
- say "Updating RubyGems..."
+ say "Updating RubyGems"
unless options[:args].empty? then
fail "No gem names are allowed with the --system option"
@@ -53,10 +55,10 @@ class Gem::Commands::UpdateCommand < Gem::Command
options[:args] = ["rubygems-update"]
else
- say "Updating installed gems..."
+ say "Updating installed gems"
end
- hig = highest_installed_gems = {}
+ hig = {}
Gem::SourceIndex.from_installed_gems.each do |name, spec|
if hig[spec.name].nil? or hig[spec.name].version < spec.version then
@@ -64,25 +66,28 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
end
- remote_gemspecs = Gem::SourceInfoCache.search(//)
+ pattern = if options[:args].empty? then
+ //
+ else
+ Regexp.union(*options[:args])
+ end
- gems_to_update = if options[:args].empty? then
- which_to_update(highest_installed_gems, remote_gemspecs)
- else
- options[:args]
- end
+ remote_gemspecs = Gem::SourceInfoCache.search pattern
- options[:domain] = :remote # install from remote source
+ gems_to_update = which_to_update hig, remote_gemspecs
- # HACK use the real API
- install_command = Gem::CommandManager.instance['install']
+ updated = []
+ # HACK use the real API
gems_to_update.uniq.sort.each do |name|
- say "Attempting remote update of #{name}"
- options[:args] = [name]
- options[:ignore_dependencies] = true # HACK skip seen gems instead
- install_command.merge_options(options)
- install_command.execute
+ next if updated.any? { |spec| spec.name == name }
+ say "Updating #{name}"
+ installer = Gem::DependencyInstaller.new options
+ installer.install name
+ installer.installed_gems.each do |spec|
+ updated << spec
+ say "Successfully installed #{spec.full_name}"
+ end
end
if gems_to_update.include? "rubygems-update" then
@@ -97,12 +102,10 @@ class Gem::Commands::UpdateCommand < Gem::Command
say "RubyGems system software updated" if installed
else
- updated = gems_to_update.uniq.sort.collect { |g| g.to_s }
-
if updated.empty? then
say "Nothing to update"
else
- say "Gems updated: #{updated.join ', '}"
+ say "Gems updated: #{updated.map { |spec| spec.name }.join ', '}"
end
end
end
diff --git a/lib/rubygems/custom_require.rb b/lib/rubygems/custom_require.rb
index 598ec3ef98..5ff65afb14 100755
--- a/lib/rubygems/custom_require.rb
+++ b/lib/rubygems/custom_require.rb
@@ -28,7 +28,7 @@ module Kernel
rescue LoadError => load_error
if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and
spec = Gem.searcher.find(path) then
- Gem.activate(spec.name, false, "= #{spec.version}")
+ Gem.activate(spec.name, "= #{spec.version}")
gem_original_require path
else
raise load_error
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index 3a6229511b..3864e5faca 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -11,6 +11,9 @@ module Gem
if defined? RUBY_FRAMEWORK_VERSION then
File.join File.dirname(ConfigMap[:sitedir]), 'Gems',
ConfigMap[:ruby_version]
+ elsif defined? RUBY_ENGINE then
+ File.join ConfigMap[:libdir], RUBY_ENGINE, 'gems',
+ ConfigMap[:ruby_version]
else
File.join ConfigMap[:libdir], 'ruby', 'gems', ConfigMap[:ruby_version]
end
@@ -29,7 +32,11 @@ module Gem
# The default directory for binaries
def self.default_bindir
- Config::CONFIG['bindir']
+ if defined? RUBY_FRAMEWORK_VERSION then # mac framework support
+ '/usr/bin'
+ else # generic install
+ ConfigMap[:bindir]
+ end
end
# The default system-wide source info cache directory.
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index ec8a50d912..26ef41b2f1 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -22,8 +22,7 @@ class Gem::DependencyInstaller
}
##
- # Creates a new installer instance that will install +gem_name+ using
- # version requirement +version+ and +options+.
+ # Creates a new installer instance.
#
# Options are:
# :env_shebang:: See Gem::Installer::new.
@@ -36,7 +35,7 @@ class Gem::DependencyInstaller
# :install_dir: See Gem::Installer#install.
# :security_policy: See Gem::Installer::new and Gem::Security.
# :wrappers: See Gem::Installer::new
- def initialize(gem_name, version = nil, options = {})
+ def initialize(options = {})
options = DEFAULT_OPTIONS.merge options
@env_shebang = options[:env_shebang]
@domain = options[:domain]
@@ -46,49 +45,9 @@ class Gem::DependencyInstaller
@install_dir = options[:install_dir] || Gem.dir
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
+ @bin_dir = options[:bin_dir]
@installed_gems = []
-
- spec_and_source = nil
-
- glob = if File::ALT_SEPARATOR then
- gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
- else
- gem_name
- end
-
- local_gems = Dir["#{glob}*"].sort.reverse
-
- unless local_gems.empty? then
- 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
- end
- end
- end
-
- if spec_and_source.nil? then
- version ||= Gem::Requirement.default
- @dep = Gem::Dependency.new gem_name, version
- spec_and_sources = find_gems_with_sources(@dep).reverse
-
- spec_and_source = spec_and_sources.find do |spec, source|
- Gem::Platform.match spec.platform
- end
- end
-
- if spec_and_source.nil? then
- raise Gem::GemNotFoundException,
- "could not find #{gem_name} locally or in a repository"
- end
-
- @specs_and_sources = [spec_and_source]
-
- gather_dependencies
end
##
@@ -107,71 +66,30 @@ class Gem::DependencyInstaller
end
if @domain == :both or @domain == :remote then
- gems_and_sources.push(*Gem::SourceInfoCache.search_with_source(dep, true))
- end
-
- gems_and_sources.sort_by do |gem, source|
- [gem, source !~ /^http:\/\// ? 1 : 0] # local gems win
- end
- end
-
- ##
- # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is
- # already there. If the source_uri is local the gem cache dir copy is
- # always replaced.
- def download(spec, source_uri)
- gem_file_name = "#{spec.full_name}.gem"
- local_gem_path = File.join @install_dir, 'cache', gem_file_name
-
- Gem.ensure_gem_subdirectories @install_dir
-
- source_uri = URI.parse source_uri unless URI::Generic === source_uri
- scheme = source_uri.scheme
-
- # URI.parse gets confused by MS Windows paths with forward slashes.
- scheme = nil if scheme =~ /^[a-z]$/i
-
- case scheme
- when 'http' then
- unless File.exist? local_gem_path then
- begin
- say "Downloading gem #{gem_file_name}" if
- Gem.configuration.really_verbose
-
- remote_gem_path = source_uri + "gems/#{gem_file_name}"
-
- gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
- rescue Gem::RemoteFetcher::FetchError
- raise if spec.original_platform == spec.platform
-
- alternate_name = "#{spec.name}-#{spec.version}-#{spec.original_platform}.gem"
+ begin
+ requirements = dep.version_requirements.requirements.map do |req, ver|
+ req
+ end
- say "Failed, downloading gem #{alternate_name}" if
- Gem.configuration.really_verbose
+ all = requirements.length > 1 ||
+ requirements.first != ">=" || requirements.first != ">"
- remote_gem_path = source_uri + "gems/#{alternate_name}"
+ found = Gem::SourceInfoCache.search_with_source dep, true, all
- gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
- end
+ gems_and_sources.push(*found)
- File.open local_gem_path, 'wb' do |fp|
- fp.write gem
+ rescue Gem::RemoteFetcher::FetchError => e
+ if Gem.configuration.really_verbose then
+ say "Error fetching remote data:\t\t#{e.message}"
+ say "Falling back to local-only install"
end
+ @domain = :local
end
- when nil, 'file' then # TODO test for local overriding cache
- begin
- FileUtils.cp source_uri.to_s, local_gem_path
- rescue Errno::EACCES
- local_gem_path = source_uri.to_s
- end
-
- say "Using local gem #{local_gem_path}" if
- Gem.configuration.really_verbose
- else
- raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
end
- local_gem_path
+ gems_and_sources.sort_by do |gem, source|
+ [gem, source =~ /^http:\/\// ? 0 : 1] # local gems win
+ end
end
##
@@ -208,9 +126,57 @@ class Gem::DependencyInstaller
@gems_to_install = dependency_list.dependency_order.reverse
end
+ def find_spec_by_name_and_version gem_name, version = Gem::Requirement.default
+ spec_and_source = nil
+
+ glob = if File::ALT_SEPARATOR then
+ gem_name.gsub File::ALT_SEPARATOR, File::SEPARATOR
+ else
+ gem_name
+ end
+
+ local_gems = Dir["#{glob}*"].sort.reverse
+
+ unless local_gems.empty? then
+ 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
+ end
+ end
+ end
+
+ if spec_and_source.nil? then
+ dep = Gem::Dependency.new gem_name, version
+ spec_and_sources = find_gems_with_sources(dep).reverse
+
+ spec_and_source = spec_and_sources.find { |spec, source|
+ Gem::Platform.match spec.platform
+ }
+ end
+
+ if spec_and_source.nil? then
+ raise Gem::GemNotFoundException,
+ "could not find #{gem_name} locally or in a repository"
+ end
+
+ @specs_and_sources = [spec_and_source]
+ end
+
##
# Installs the gem and all its dependencies.
- def install
+ def install dep_or_name, version = Gem::Requirement.default
+ if String === dep_or_name then
+ find_spec_by_name_and_version dep_or_name, version
+ else
+ @specs_and_sources = [find_gems_with_sources(dep_or_name).last]
+ end
+
+ gather_dependencies
+
spec_dir = File.join @install_dir, 'specifications'
source_index = Gem::SourceIndex.from_gems_in spec_dir
@@ -219,10 +185,11 @@ class Gem::DependencyInstaller
# HACK is this test for full_name acceptable?
next if source_index.any? { |n,_| n == spec.full_name } and not last
+ # TODO: make this sorta_verbose so other users can benefit from it
say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
_, source_uri = @specs_and_sources.assoc spec
- local_gem_path = download spec, source_uri
+ local_gem_path = Gem::RemoteFetcher.fetcher.download spec, source_uri
inst = Gem::Installer.new local_gem_path,
:env_shebang => @env_shebang,
@@ -231,7 +198,8 @@ class Gem::DependencyInstaller
:ignore_dependencies => @ignore_dependencies,
:install_dir => @install_dir,
:security_policy => @security_policy,
- :wrappers => @wrappers
+ :wrappers => @wrappers,
+ :bin_dir => @bin_dir
spec = inst.install
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index b34bc718ff..c37507c62a 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -13,7 +13,10 @@ class Gem::DependencyRemovalException < Gem::Exception; end
##
# Raised when attempting to uninstall a gem that isn't in GEM_HOME.
-class Gem::GemNotInHomeException < Gem::Exception; end
+
+class Gem::GemNotInHomeException < Gem::Exception
+ attr_accessor :spec
+end
class Gem::DocumentError < Gem::Exception; end
@@ -65,3 +68,17 @@ class Gem::RemoteSourceException < Gem::Exception; end
class Gem::VerificationError < Gem::Exception; end
+##
+# Raised to indicate that a system exit should occur with the specified
+# exit_code
+
+class Gem::SystemExitException < SystemExit
+ attr_accessor :exit_code
+
+ def initialize(exit_code)
+ @exit_code = exit_code
+
+ super "Exiting RubyGems with exit_code #{exit_code}"
+ end
+
+end
diff --git a/lib/rubygems/format.rb b/lib/rubygems/format.rb
index 378a93018c..7dc127d5f4 100644
--- a/lib/rubygems/format.rb
+++ b/lib/rubygems/format.rb
@@ -43,15 +43,12 @@ module Gem
# check for old version gem
if File.read(file_path, 20).include?("MD5SUM =")
- #alert_warning "Gem #{file_path} is in old format."
require 'rubygems/old_format'
+
format = OldFormat.from_file_by_path(file_path)
else
- begin
- f = File.open(file_path, 'rb')
- format = from_io(f, file_path, security_policy)
- ensure
- f.close unless f.closed?
+ open file_path, Gem.binary_mode do |io|
+ format = from_io io, file_path, security_policy
end
end
@@ -65,15 +62,24 @@ module Gem
# io:: [IO] Stream from which to read the gem
#
def self.from_io(io, gem_path="(io)", security_policy = nil)
- format = self.new(gem_path)
- Package.open_from_io(io, 'r', security_policy) do |pkg|
+ format = new gem_path
+
+ Package.open io, 'r', security_policy do |pkg|
format.spec = pkg.metadata
format.file_entries = []
+
pkg.each do |entry|
- format.file_entries << [{"size" => entry.size, "mode" => entry.mode,
- "path" => entry.full_name}, entry.read]
+ 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
diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb
index 272cee3fd3..5496e452cc 100644
--- a/lib/rubygems/indexer.rb
+++ b/lib/rubygems/indexer.rb
@@ -11,6 +11,7 @@ end
##
# Top level class for building the gem repository index.
+
class Gem::Indexer
include Gem::UserInteraction
@@ -25,7 +26,9 @@ class Gem::Indexer
attr_reader :directory
+ ##
# Create an indexer that will index the gems in +directory+.
+
def initialize(directory)
unless ''.respond_to? :to_xs then
fail "Gem::Indexer requires that the XML Builder library be installed:" \
@@ -39,52 +42,60 @@ class Gem::Indexer
@master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory
@marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory
- @quick_index = Gem::Indexer::QuickIndexBuilder.new "index", @directory
+ @quick_index = Gem::Indexer::QuickIndexBuilder.new 'index', @directory
+
+ quick_dir = File.join @directory, 'quick'
+ @latest_index = Gem::Indexer::LatestIndexBuilder.new 'latest_index', quick_dir
end
+ ##
# Build the index.
+
def build_index
@master_index.build do
@quick_index.build do
@marshal_index.build do
- progress = ui.progress_reporter gem_file_list.size,
+ @latest_index.build do
+ progress = ui.progress_reporter gem_file_list.size,
"Generating index for #{gem_file_list.size} gems in #{@dest_directory}",
"Loaded all gems"
- gem_file_list.each do |gemfile|
- if File.size(gemfile.to_s) == 0 then
- alert_warning "Skipping zero-length gem: #{gemfile}"
- next
- end
-
- begin
- spec = Gem::Format.from_file_by_path(gemfile).spec
-
- unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
- alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
+ gem_file_list.each do |gemfile|
+ if File.size(gemfile.to_s) == 0 then
+ alert_warning "Skipping zero-length gem: #{gemfile}"
next
end
- abbreviate spec
- sanitize spec
+ begin
+ spec = Gem::Format.from_file_by_path(gemfile).spec
- @master_index.add spec
- @quick_index.add spec
- @marshal_index.add spec
+ unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
+ alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
+ next
+ end
- progress.updated spec.original_name
+ abbreviate spec
+ sanitize spec
- rescue SignalException => e
- alert_error "Recieved signal, exiting"
- raise
- rescue Exception => e
- alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
- end
- end
+ @master_index.add spec
+ @quick_index.add spec
+ @marshal_index.add spec
+ @latest_index.add spec
+
+ progress.updated spec.original_name
- progress.done
+ rescue SignalException => e
+ alert_error "Recieved signal, exiting"
+ raise
+ rescue Exception => e
+ alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
+ end
+ end
+
+ progress.done
- say "Generating master indexes (this may take a while)"
+ say "Generating master indexes (this may take a while)"
+ end
end
end
end
@@ -95,14 +106,15 @@ class Gem::Indexer
say "Moving index into production dir #{@dest_directory}" if verbose
- files = @master_index.files + @quick_index.files + @marshal_index.files
+ files = @master_index.files + @quick_index.files + @marshal_index.files +
+ @latest_index.files
files.each do |file|
- relative_name = file[/\A#{Regexp.escape @directory}.(.*)/, 1]
- dest_name = File.join @dest_directory, relative_name
+ src_name = File.join @directory, file
+ dst_name = File.join @dest_directory, file
- FileUtils.rm_rf dest_name, :verbose => verbose
- FileUtils.mv file, @dest_directory, :verbose => verbose
+ FileUtils.rm_rf dst_name, :verbose => verbose
+ FileUtils.mv src_name, @dest_directory, :verbose => verbose
end
end
@@ -160,4 +172,5 @@ require 'rubygems/indexer/abstract_index_builder'
require 'rubygems/indexer/master_index_builder'
require 'rubygems/indexer/quick_index_builder'
require 'rubygems/indexer/marshal_index_builder'
+require 'rubygems/indexer/latest_index_builder'
diff --git a/lib/rubygems/indexer/abstract_index_builder.rb b/lib/rubygems/indexer/abstract_index_builder.rb
index f25f21707b..5815dcda87 100644
--- a/lib/rubygems/indexer/abstract_index_builder.rb
+++ b/lib/rubygems/indexer/abstract_index_builder.rb
@@ -22,16 +22,18 @@ class Gem::Indexer::AbstractIndexBuilder
@files = []
end
+ ##
# Build a Gem index. Yields to block to handle the details of the
# actual building. Calls +begin_index+, +end_index+ and +cleanup+ at
# appropriate times to customize basic operations.
+
def build
FileUtils.mkdir_p @directory unless File.exist? @directory
raise "not a directory: #{@directory}" unless File.directory? @directory
file_path = File.join @directory, @filename
- @files << file_path
+ @files << @filename
File.open file_path, "wb" do |file|
@file = file
@@ -39,14 +41,20 @@ class Gem::Indexer::AbstractIndexBuilder
yield
end_index
end
+
cleanup
ensure
@file = nil
end
+ ##
# Compress the given file.
+
def compress(filename, ext="rz")
- zipped = zip(File.open(filename, 'rb'){ |fp| fp.read })
+ data = open filename, 'rb' do |fp| fp.read end
+
+ zipped = zip data
+
File.open "#{filename}.#{ext}", "wb" do |file|
file.write zipped
end
diff --git a/lib/rubygems/indexer/latest_index_builder.rb b/lib/rubygems/indexer/latest_index_builder.rb
new file mode 100644
index 0000000000..a5798580a6
--- /dev/null
+++ b/lib/rubygems/indexer/latest_index_builder.rb
@@ -0,0 +1,35 @@
+require 'rubygems/indexer'
+
+##
+# Construct the latest Gem index file.
+
+class Gem::Indexer::LatestIndexBuilder < Gem::Indexer::AbstractIndexBuilder
+
+ def start_index
+ super
+
+ @index = Gem::SourceIndex.new
+ end
+
+ def end_index
+ super
+
+ latest = @index.latest_specs.sort.map { |spec| spec.original_name }
+
+ @file.write latest.join("\n")
+ end
+
+ def cleanup
+ super
+
+ compress @file.path
+
+ @files.delete 'latest_index' # HACK installed via QuickIndexBuilder :/
+ end
+
+ def add(spec)
+ @index.add_spec(spec)
+ end
+
+end
+
diff --git a/lib/rubygems/indexer/master_index_builder.rb b/lib/rubygems/indexer/master_index_builder.rb
index dbe02370a9..669ea5a1df 100644
--- a/lib/rubygems/indexer/master_index_builder.rb
+++ b/lib/rubygems/indexer/master_index_builder.rb
@@ -1,6 +1,8 @@
require 'rubygems/indexer'
+##
# Construct the master Gem index file.
+
class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def start_index
@@ -10,6 +12,7 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def end_index
super
+
@file.puts "--- !ruby/object:#{@index.class}"
@file.puts "gems:"
@@ -28,11 +31,9 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
index_file_name = File.join @directory, @filename
compress index_file_name, "Z"
- compressed_file_name = "#{index_file_name}.Z"
-
- paranoid index_file_name, compressed_file_name
+ paranoid index_file_name, "#{index_file_name}.Z"
- @files << compressed_file_name
+ @files << "#{@filename}.Z"
end
def add(spec)
@@ -41,12 +42,12 @@ class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
private
- def paranoid(fn, compressed_fn)
- data = File.open(fn, 'rb') do |fp| fp.read end
- compressed_data = File.open(compressed_fn, 'rb') do |fp| fp.read end
+ def paranoid(path, compressed_path)
+ data = Gem.read_binary path
+ compressed_data = Gem.read_binary compressed_path
if data != unzip(compressed_data) then
- fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}"
+ raise "Compressed file #{compressed_path} does not match uncompressed file #{path}"
end
end
diff --git a/lib/rubygems/indexer/quick_index_builder.rb b/lib/rubygems/indexer/quick_index_builder.rb
index 23c7ca696b..dc36179dc5 100644
--- a/lib/rubygems/indexer/quick_index_builder.rb
+++ b/lib/rubygems/indexer/quick_index_builder.rb
@@ -1,7 +1,9 @@
require 'rubygems/indexer'
+##
# Construct a quick index file and all of the individual specs to support
# incremental loading.
+
class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def initialize(filename, directory)
@@ -13,12 +15,12 @@ class Gem::Indexer::QuickIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def cleanup
super
- quick_index_file = File.join(@directory, @filename)
+ quick_index_file = File.join @directory, @filename
compress quick_index_file
# the complete quick index is in a directory, so move it as a whole
- @files.delete quick_index_file
- @files << @directory
+ @files.delete 'index'
+ @files << 'quick'
end
def add(spec)
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index af6be423f6..58807be62a 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -25,6 +25,12 @@ module Gem::InstallUpdateOptions
options[:install_dir] = File.expand_path(value)
end
+ add_option(:"Install/Update", '-n', '--bindir DIR',
+ '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|
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 552a803c12..9dbbca8d08 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -63,7 +63,8 @@ class Gem::Installer
:force => false,
:install_dir => Gem.dir,
:exec_format => false,
- :env_shebang => false
+ :env_shebang => false,
+ :bin_dir => nil
}.merge options
@env_shebang = options[:env_shebang]
@@ -74,6 +75,7 @@ class Gem::Installer
@format_executable = options[:format_executable]
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
+ @bin_dir = options[:bin_dir]
begin
@format = Gem::Format.from_file_by_path @gem, @security_policy
@@ -104,7 +106,7 @@ class Gem::Installer
unless @force then
if rrv = @spec.required_ruby_version then
- unless rrv.satisfied_by? Gem::Version.new(RUBY_VERSION) then
+ unless rrv.satisfied_by? Gem.ruby_version then
raise Gem::InstallError, "#{@spec.name} requires Ruby version #{rrv}"
end
end
@@ -225,7 +227,7 @@ class Gem::Installer
# If the user has asked for the gem to be installed in a directory that is
# the system gem directory, then use the system bin directory, else create
# (or use) a new bin dir under the gem_home.
- bindir = Gem.bindir @gem_home
+ bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home)
Dir.mkdir bindir unless File.exist? bindir
raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir
@@ -303,7 +305,7 @@ class Gem::Installer
# necessary.
def shebang(bin_file_name)
if @env_shebang then
- "#!/usr/bin/env ruby"
+ "#!/usr/bin/env " + Gem::ConfigMap[:ruby_install_name]
else
path = File.join @gem_dir, @spec.bindir, bin_file_name
@@ -352,10 +354,10 @@ TEXT
<<-TEXT
@ECHO OFF
IF NOT "%~f0" == "~f0" GOTO :WinNT
-@"#{Gem.ruby}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9
+@"#{File.basename(Gem.ruby)}" "#{File.join(bindir, bin_file_name)}" %1 %2 %3 %4 %5 %6 %7 %8 %9
GOTO :EOF
:WinNT
-"%~dp0ruby.exe" "%~dpn0" %*
+@"#{File.basename(Gem.ruby)}" "%~dpn0" %*
TEXT
end
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index f15e4feecb..9cb393b0c7 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -45,768 +45,15 @@ module Gem::Package
class TooLongFileName < Error; end
class FormatError < Error; end
- module FSyncDir
- private
- def fsync_dir(dirname)
- # make sure this hits the disc
- begin
- 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
- end
-
- class TarHeader
- FIELDS = [:name, :mode, :uid, :gid, :size, :mtime, :checksum, :typeflag,
- :linkname, :magic, :version, :uname, :gname, :devmajor,
- :devminor, :prefix]
- FIELDS.each {|x| attr_reader x}
-
- def self.new_from_stream(stream)
- data = stream.read(512)
- fields = data.unpack("A100" + # record name
- "A8A8A8" + # mode, uid, gid
- "A12A12" + # size, mtime
- "A8A" + # checksum, typeflag
- "A100" + # linkname
- "A6A2" + # magic, version
- "A32" + # uname
- "A32" + # gname
- "A8A8" + # devmajor, devminor
- "A155") # prefix
- 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 = (data == "\0" * 512)
-
- new(:name=>name, :mode=>mode, :uid=>uid, :gid=>gid, :size=>size,
- :mtime=>mtime, :checksum=>checksum, :typeflag=>typeflag,
- :magic=>magic, :version=>version, :uname=>uname, :gname=>gname,
- :devmajor=>devmajor, :devminor=>devminor, :prefix=>prefix,
- :empty => empty )
- end
-
- def initialize(vals)
- unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
- raise ArgumentError, ":name, :size, :prefix and :mode required"
- end
- vals[:uid] ||= 0
- vals[:gid] ||= 0
- vals[:mtime] ||= 0
- vals[:checksum] ||= ""
- vals[:typeflag] ||= "0"
- vals[:magic] ||= "ustar"
- vals[:version] ||= "00"
- vals[:uname] ||= "wheel"
- vals[:gname] ||= "wheel"
- vals[:devmajor] ||= 0
- vals[:devminor] ||= 0
- FIELDS.each {|x| instance_variable_set "@#{x.to_s}", vals[x]}
- @empty = vals[:empty]
- end
-
- def empty?
- @empty
- end
-
- def to_s
- update_checksum
- header(checksum)
- end
-
- def update_checksum
- h = header(" " * 8)
- @checksum = oct(calculate_checksum(h), 6)
- end
-
- private
- def oct(num, len)
- "%0#{len}o" % num
- end
-
- def calculate_checksum(hdr)
- #hdr.split('').map { |c| c[0] }.inject { |a, b| a + b } # HACK rubinius
- hdr.unpack("C*").inject{|a,b| a+b}
- end
-
- def header(chksum)
- # struct tarfile_entry_posix {
- # char name[100]; # ASCII + (Z unless filled)
- # char mode[8]; # 0 padded, octal, null
- # char uid[8]; # ditto
- # char gid[8]; # ditto
- # char size[12]; # 0 padded, octal, null
- # char mtime[12]; # 0 padded, octal, null
- # char checksum[8]; # 0 padded, octal, null, space
- # char typeflag[1]; # file: "0" dir: "5"
- # char linkname[100]; # ASCII + (Z unless filled)
- # char magic[6]; # "ustar\0"
- # char version[2]; # "00"
- # char uname[32]; # ASCIIZ
- # char gname[32]; # ASCIIZ
- # char devmajor[8]; # 0 padded, octal, null
- # char devminor[8]; # o padded, octal, null
- # char prefix[155]; # ASCII + (Z unless filled)
- # };
- arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
- oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
- uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
- str = arr.pack("a100a8a8a8a12a12" + # name, mode, uid, gid, size, mtime
- "a7aaa100a6a2" + # chksum, typeflag, linkname, magic, version
- "a32a32a8a8a155") # uname, gname, devmajor, devminor, prefix
- str + "\0" * ((512 - str.size) % 512)
- end
- end
-
- class TarWriter
- class FileOverflow < StandardError; end
- class BlockNeeded < StandardError; end
-
- class BoundedStream
- attr_reader :limit, :written
- def initialize(io, limit)
- @io = io
- @limit = limit
- @written = 0
- end
-
- def write(data)
- if data.size + @written > @limit
- raise FileOverflow,
- "You tried to feed more data than fits in the file."
- end
- @io.write data
- @written += data.size
- data.size
- end
- end
-
- class RestrictedStream
- def initialize(anIO)
- @io = anIO
- end
-
- def write(data)
- @io.write data
- end
- end
-
- def self.new(anIO)
- writer = super(anIO)
- return writer unless block_given?
- begin
- yield writer
- ensure
- writer.close
- end
- nil
- end
-
- def initialize(anIO)
- @io = anIO
- @closed = false
- end
-
- def add_file_simple(name, mode, size)
- raise BlockNeeded unless block_given?
- raise ClosedIO if @closed
- name, prefix = split_name(name)
- header = TarHeader.new(:name => name, :mode => mode,
- :size => size, :prefix => prefix).to_s
- @io.write header
- os = BoundedStream.new(@io, size)
- yield os
- #FIXME: what if an exception is raised in the block?
- min_padding = size - os.written
- @io.write("\0" * min_padding)
- remainder = (512 - (size % 512)) % 512
- @io.write("\0" * remainder)
- end
-
- def add_file(name, mode)
- raise BlockNeeded unless block_given?
- raise ClosedIO if @closed
- raise NonSeekableIO unless @io.respond_to? :pos=
- name, prefix = split_name(name)
- init_pos = @io.pos
- @io.write "\0" * 512 # placeholder for the header
- yield RestrictedStream.new(@io)
- #FIXME: what if an exception is raised in the block?
- #FIXME: what if an exception is raised in the block?
- size = @io.pos - init_pos - 512
- remainder = (512 - (size % 512)) % 512
- @io.write("\0" * remainder)
- final_pos = @io.pos
- @io.pos = init_pos
- header = TarHeader.new(:name => name, :mode => mode,
- :size => size, :prefix => prefix).to_s
- @io.write header
- @io.pos = final_pos
- end
-
- def mkdir(name, mode)
- raise ClosedIO if @closed
- name, prefix = split_name(name)
- header = TarHeader.new(:name => name, :mode => mode, :typeflag => "5",
- :size => 0, :prefix => prefix).to_s
- @io.write header
- nil
- end
-
- def flush
- raise ClosedIO if @closed
- @io.flush if @io.respond_to? :flush
- end
-
- def close
- #raise ClosedIO if @closed
- return if @closed
- @io.write "\0" * 1024
- @closed = true
- end
-
- private
- def split_name name
- raise TooLongFileName if name.size > 256
- if name.size <= 100
- prefix = ""
- else
- parts = name.split(/\//)
- newname = parts.pop
- nxt = ""
- loop do
- nxt = parts.pop
- break if newname.size + 1 + nxt.size > 100
- newname = nxt + "/" + newname
- end
- prefix = (parts + [nxt]).join "/"
- name = newname
- raise TooLongFileName if name.size > 100 || prefix.size > 155
- end
- return name, prefix
- end
- end
-
- class TarReader
-
- include Gem::Package
-
- class UnexpectedEOF < StandardError; end
-
- module InvalidEntry
- def read(len=nil); raise ClosedIO; end
- def getc; raise ClosedIO; end
- def rewind; raise ClosedIO; end
- end
-
- class Entry
- TarHeader::FIELDS.each{|x| attr_reader x}
-
- def initialize(header, anIO)
- @io = anIO
- @name = header.name
- @mode = header.mode
- @uid = header.uid
- @gid = header.gid
- @size = header.size
- @mtime = header.mtime
- @checksum = header.checksum
- @typeflag = header.typeflag
- @linkname = header.linkname
- @magic = header.magic
- @version = header.version
- @uname = header.uname
- @gname = header.gname
- @devmajor = header.devmajor
- @devminor = header.devminor
- @prefix = header.prefix
- @read = 0
- @orig_pos = @io.pos
- end
-
- def read(len = nil)
- return nil if @read >= @size
- len ||= @size - @read
- max_read = [len, @size - @read].min
- ret = @io.read(max_read)
- @read += ret.size
- ret
- end
-
- def getc
- return nil if @read >= @size
- ret = @io.getc
- @read += 1 if ret
- ret
- end
-
- def is_directory?
- @typeflag == "5"
- end
-
- def is_file?
- @typeflag == "0"
- end
-
- def eof?
- @read >= @size
- end
-
- def pos
- @read
- end
-
- def rewind
- raise NonSeekableIO unless @io.respond_to? :pos=
- @io.pos = @orig_pos
- @read = 0
- end
-
- alias_method :is_directory, :is_directory?
- alias_method :is_file, :is_file?
-
- def bytes_read
- @read
- end
-
- def full_name
- if @prefix != ""
- File.join(@prefix, @name)
- else
- @name
- end
- end
-
- def close
- invalidate
- end
-
- private
- def invalidate
- extend InvalidEntry
- end
- end
-
- def self.new(anIO)
- reader = super(anIO)
- return reader unless block_given?
- begin
- yield reader
- ensure
- reader.close
- end
- nil
- end
-
- def initialize(anIO)
- @io = anIO
- @init_pos = anIO.pos
- end
-
- def each(&block)
- each_entry(&block)
- end
-
- # do not call this during a #each or #each_entry iteration
- def rewind
- if @init_pos == 0
- raise NonSeekableIO unless @io.respond_to? :rewind
- @io.rewind
- else
- raise NonSeekableIO unless @io.respond_to? :pos=
- @io.pos = @init_pos
- end
- end
-
- def each_entry
- loop do
- return if @io.eof?
- header = TarHeader.new_from_stream(@io)
- return if header.empty?
- entry = Entry.new header, @io
- size = entry.size
- yield entry
- skip = (512 - (size % 512)) % 512
- if @io.respond_to? :seek
- # avoid reading...
- @io.seek(size - entry.bytes_read, IO::SEEK_CUR)
- else
- pending = size - entry.bytes_read
- while pending > 0
- bread = @io.read([pending, 4096].min).size
- raise UnexpectedEOF if @io.eof?
- pending -= bread
- end
- end
- @io.read(skip) # discard trailing zeros
- # make sure nobody can use #read, #getc or #rewind anymore
- entry.close
- end
- end
-
- def close
- end
-
- end
-
- class TarInput
-
- include FSyncDir
- include Enumerable
-
- attr_reader :metadata
-
- class << self; private :new end
-
- def initialize(io, security_policy = nil)
- @io = io
- @tarreader = 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
- break
- 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
-
- gzis = Zlib::GzipReader.new(sio || entry)
- # 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::Policy.key? security_policy then
- # load one of the pre-defined security policies
- security_policy = Gem::Security::Policy[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
- @fileops = Gem::FileOperations.new
- raise FormatError, "No metadata found!" unless has_meta
- 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
-
- def self.open(filename, security_policy = nil, &block)
- open_from_io(File.open(filename, "rb"), security_policy, &block)
- end
-
- def self.open_from_io(io, security_policy = nil, &block)
- raise "Want a block" unless block_given?
- begin
- is = new(io, security_policy)
- yield is
- ensure
- is.close if is
- end
- end
-
- def each(&block)
- @tarreader.each do |entry|
- next unless entry.full_name == "data.tar.gz"
- is = zipped_stream(entry)
- begin
- TarReader.new(is) do |inner|
- inner.each(&block)
- end
- ensure
- is.close if is
- end
- end
- @tarreader.rewind
- 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.
- def zipped_stream(entry)
- if defined? Rubinius then
- zis = Zlib::GzipReader.new entry
- dis = zis.read
- is = StringIO.new(dis)
- else
- # This is Jamis Buck's ZLib workaround for some unknown issue
- entry.read(10) # skip the gzip header
- zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
- is = StringIO.new(zis.inflate(entry.read))
- end
- ensure
- zis.finish if zis
- end
-
- def extract_entry(destdir, entry, expected_md5sum = nil)
- if entry.is_directory?
- dest = File.join(destdir, entry.full_name)
- if file_class.dir? dest
- @fileops.chmod entry.mode, dest, :verbose=>false
- else
- @fileops.mkdir_p(dest, :mode => entry.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))
- @fileops.mkdir_p(destdir, :mode => 0755, :verbose=>false)
- destfile = File.join(destdir, File.basename(entry.full_name))
- @fileops.chmod(0600, destfile, :verbose=>false) rescue nil # Errno::ENOENT
- file_class.open(destfile, "wb", entry.mode) do |os|
- loop do
- data = entry.read(4096)
- break unless data
- md5 << data if expected_md5sum
- os.write(data)
- end
- os.fsync
- end
- @fileops.chmod(entry.mode, destfile, :verbose=>false)
- fsync_dir File.dirname(destfile)
- fsync_dir File.join(File.dirname(destfile), "..")
- if expected_md5sum && expected_md5sum != md5.hexdigest
- raise BadCheckSum
- end
- end
-
- def close
- @io.close
- @tarreader.close
- end
-
- private
-
- def file_class
- File
- end
- end
-
- class TarOutput
-
- class << self; private :new end
-
- def initialize(io)
- @io = io
- @external = TarWriter.new @io
- end
-
- def external_handle
- @external
- end
-
- def self.open(filename, signer = nil, &block)
- io = File.open(filename, "wb")
- open_from_io(io, signer, &block)
- nil
- end
-
- def self.open_from_io(io, signer = nil, &block)
- outputter = new(io)
- metadata = nil
- set_meta = lambda{|x| metadata = x}
- raise "Want a block" unless block_given?
- begin
- data_sig, meta_sig = nil, nil
-
- outputter.external_handle.add_file("data.tar.gz", 0644) do |inner|
- begin
- sio = signer ? StringIO.new : nil
- os = Zlib::GzipWriter.new(sio || inner)
-
- TarWriter.new(os) do |inner_tar_stream|
- klass = class << inner_tar_stream; self end
- klass.send(:define_method, :metadata=, &set_meta)
- block.call inner_tar_stream
- end
- ensure
- os.flush
- os.finish
- #os.close
-
- # if we have a signing key, then sign the data
- # digest and return the signature
- data_sig = nil
- if signer
- dgst_algo = Gem::Security::OPT[:dgst_algo]
- dig = dgst_algo.digest(sio.string)
- data_sig = signer.sign(dig)
- inner.write(sio.string)
- end
- end
- end
-
- # if we have a data signature, then write it to the gem too
- if data_sig
- sig_file = 'data.tar.gz.sig'
- outputter.external_handle.add_file(sig_file, 0644) do |os|
- os.write(data_sig)
- end
- end
-
- outputter.external_handle.add_file("metadata.gz", 0644) do |os|
- begin
- sio = signer ? StringIO.new : nil
- gzos = Zlib::GzipWriter.new(sio || os)
- 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
- dgst_algo = Gem::Security::OPT[:dgst_algo]
- dig = dgst_algo.digest(sio.string)
- meta_sig = signer.sign(dig)
- os.write(sio.string)
- end
- end
- end
-
- # if we have a metadata signature, then write to the gem as
- # well
- if meta_sig
- sig_file = 'metadata.gz.sig'
- outputter.external_handle.add_file(sig_file, 0644) do |os|
- os.write(meta_sig)
- end
- end
-
- ensure
- outputter.close
- end
- nil
- end
-
- def close
- @external.close
- @io.close
- end
-
- end
-
- #FIXME: refactor the following 2 methods
-
- def self.open(dest, mode = "r", signer = nil, &block)
- raise "Block needed" unless block_given?
-
- case mode
- when "r"
- security_policy = signer
- TarInput.open(dest, security_policy, &block)
- when "w"
- TarOutput.open(dest, signer, &block)
- else
- raise "Unknown Package open mode"
- end
- end
-
- def self.open_from_io(io, mode = "r", signer = nil, &block)
- raise "Block needed" unless block_given?
-
- case mode
- when "r"
- security_policy = signer
- TarInput.open_from_io(io, security_policy, &block)
- when "w"
- TarOutput.open_from_io(io, signer, &block)
- else
- raise "Unknown Package open mode"
- end
+ 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)
@@ -836,19 +83,13 @@ module Gem::Package
end
end
- class << self
- def file_class
- File
- end
-
- def dir_class
- Dir
- end
-
- def find_class # HACK kill me
- Find
- end
- end
-
end
+require 'rubygems/package/f_sync_dir'
+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/f_sync_dir.rb b/lib/rubygems/package/f_sync_dir.rb
new file mode 100644
index 0000000000..3e2e4a59a8
--- /dev/null
+++ b/lib/rubygems/package/f_sync_dir.rb
@@ -0,0 +1,24 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+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/tar_header.rb b/lib/rubygems/package/tar_header.rb
new file mode 100644
index 0000000000..c194cc0530
--- /dev/null
+++ b/lib/rubygems/package/tar_header.rb
@@ -0,0 +1,245 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+##
+#--
+# struct tarfile_entry_posix {
+# char name[100]; # ASCII + (Z unless filled)
+# char mode[8]; # 0 padded, octal, null
+# char uid[8]; # ditto
+# char gid[8]; # ditto
+# char size[12]; # 0 padded, octal, null
+# char mtime[12]; # 0 padded, octal, null
+# char checksum[8]; # 0 padded, octal, null, space
+# char typeflag[1]; # file: "0" dir: "5"
+# char linkname[100]; # ASCII + (Z unless filled)
+# char magic[6]; # "ustar\0"
+# char version[2]; # "00"
+# char uname[32]; # ASCIIZ
+# char gname[32]; # ASCIIZ
+# char devmajor[8]; # 0 padded, octal, null
+# char devminor[8]; # o padded, octal, null
+# char prefix[155]; # ASCII + (Z unless filled)
+# };
+#++
+
+class Gem::Package::TarHeader
+
+ FIELDS = [
+ :checksum,
+ :devmajor,
+ :devminor,
+ :gid,
+ :gname,
+ :linkname,
+ :magic,
+ :mode,
+ :mtime,
+ :name,
+ :prefix,
+ :size,
+ :typeflag,
+ :uid,
+ :uname,
+ :version,
+ ]
+
+ PACK_FORMAT = 'a100' + # name
+ 'a8' + # mode
+ 'a8' + # uid
+ 'a8' + # gid
+ 'a12' + # size
+ 'a12' + # mtime
+ 'a7a' + # chksum
+ 'a' + # typeflag
+ 'a100' + # linkname
+ 'a6' + # magic
+ 'a2' + # version
+ 'a32' + # uname
+ 'a32' + # gname
+ 'a8' + # devmajor
+ 'a8' + # devminor
+ 'a155' # prefix
+
+ UNPACK_FORMAT = 'A100' + # name
+ 'A8' + # mode
+ 'A8' + # uid
+ 'A8' + # gid
+ 'A12' + # size
+ 'A12' + # mtime
+ 'A8' + # checksum
+ 'A' + # typeflag
+ 'A100' + # linkname
+ 'A6' + # magic
+ 'A2' + # version
+ 'A32' + # uname
+ 'A32' + # gname
+ 'A8' + # devmajor
+ 'A8' + # devminor
+ 'A155' # prefix
+
+ attr_reader(*FIELDS)
+
+ def self.from(stream)
+ header = stream.read 512
+ empty = (header == "\0" * 512)
+
+ 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
+ end
+
+ def initialize(vals)
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
+ raise ArgumentError, ":name, :size, :prefix and :mode required"
+ end
+
+ vals[:uid] ||= 0
+ vals[:gid] ||= 0
+ vals[:mtime] ||= 0
+ vals[:checksum] ||= ""
+ vals[:typeflag] ||= "0"
+ vals[:magic] ||= "ustar"
+ vals[:version] ||= "00"
+ vals[:uname] ||= "wheel"
+ vals[:gname] ||= "wheel"
+ vals[:devmajor] ||= 0
+ vals[:devminor] ||= 0
+
+ FIELDS.each do |name|
+ instance_variable_set "@#{name}", vals[name]
+ end
+
+ @empty = vals[:empty]
+ end
+
+ def empty?
+ @empty
+ end
+
+ def ==(other)
+ self.class === other and
+ @checksum == other.checksum and
+ @devmajor == other.devmajor and
+ @devminor == other.devminor and
+ @gid == other.gid and
+ @gname == other.gname and
+ @linkname == other.linkname and
+ @magic == other.magic and
+ @mode == other.mode and
+ @mtime == other.mtime and
+ @name == other.name and
+ @prefix == other.prefix and
+ @size == other.size and
+ @typeflag == other.typeflag and
+ @uid == other.uid and
+ @uname == other.uname and
+ @version == other.version
+ end
+
+ def to_s
+ update_checksum
+ header
+ end
+
+ def update_checksum
+ header = header " " * 8
+ @checksum = oct calculate_checksum(header), 6
+ end
+
+ private
+
+ def calculate_checksum(header)
+ header.unpack("C*").inject { |a, b| a + b }
+ end
+
+ def header(checksum = @checksum)
+ header = [
+ name,
+ oct(mode, 7),
+ oct(uid, 7),
+ oct(gid, 7),
+ oct(size, 11),
+ oct(mtime, 11),
+ checksum,
+ " ",
+ typeflag,
+ linkname,
+ magic,
+ oct(version, 2),
+ uname,
+ gname,
+ oct(devmajor, 7),
+ oct(devminor, 7),
+ prefix
+ ]
+
+ header = header.pack PACK_FORMAT
+
+ header << ("\0" * ((512 - header.size) % 512))
+ end
+
+ def oct(num, len)
+ "%0#{len}o" % num
+ end
+
+end
+
diff --git a/lib/rubygems/package/tar_input.rb b/lib/rubygems/package/tar_input.rb
new file mode 100644
index 0000000000..2ed3d6b772
--- /dev/null
+++ b/lib/rubygems/package/tar_input.rb
@@ -0,0 +1,219 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+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
+
+ gzis = Zlib::GzipReader.new(sio || entry)
+ # 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::Policy.key? security_policy then
+ # load one of the pre-defined security policies
+ security_policy = Gem::Security::Policy[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
+ @fileops = Gem::FileOperations.new
+
+ raise Gem::Package::FormatError, "No metadata found!" unless has_meta
+ 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.dir? dest then
+ @fileops.chmod entry.header.mode, dest, :verbose=>false
+ else
+ @fileops.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)
+ @fileops.mkdir_p destdir, :mode => 0755, :verbose => false
+ destfile = File.join destdir, File.basename(entry.full_name)
+ @fileops.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
+
+ @fileops.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.
+
+ def zipped_stream(entry)
+ if defined? Rubinius then
+ zis = Zlib::GzipReader.new entry
+ dis = zis.read
+ is = StringIO.new(dis)
+ else
+ # This is Jamis Buck's Zlib workaround for some unknown issue
+ entry.read(10) # skip the gzip header
+ zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+ is = StringIO.new(zis.inflate(entry.read))
+ end
+ ensure
+ zis.finish if zis
+ end
+
+end
+
diff --git a/lib/rubygems/package/tar_output.rb b/lib/rubygems/package/tar_output.rb
new file mode 100644
index 0000000000..b22f7dd86b
--- /dev/null
+++ b/lib/rubygems/package/tar_output.rb
@@ -0,0 +1,143 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+##
+# 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|
+ def data_tar_writer.metadata() @metadata end
+ def data_tar_writer.metadata=(metadata) @metadata = metadata end
+
+ 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
+ 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
+ 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
new file mode 100644
index 0000000000..8359399207
--- /dev/null
+++ b/lib/rubygems/package/tar_reader.rb
@@ -0,0 +1,86 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+class Gem::Package::TarReader
+
+ include Gem::Package
+
+ class UnexpectedEOF < StandardError; end
+
+ def self.new(io)
+ reader = super
+
+ return reader unless block_given?
+
+ begin
+ yield reader
+ ensure
+ reader.close
+ end
+
+ nil
+ end
+
+ def initialize(io)
+ @io = io
+ @init_pos = io.pos
+ end
+
+ def close
+ end
+
+ def each
+ loop do
+ return if @io.eof?
+
+ header = Gem::Package::TarHeader.from @io
+ return if header.empty?
+
+ entry = Gem::Package::TarReader::Entry.new header, @io
+ size = entry.header.size
+
+ yield entry
+
+ skip = (512 - (size % 512)) % 512
+
+ if @io.respond_to? :seek then
+ # avoid reading...
+ @io.seek(size - entry.bytes_read, IO::SEEK_CUR)
+ else
+ pending = size - entry.bytes_read
+
+ while pending > 0 do
+ bread = @io.read([pending, 4096].min).size
+ raise UnexpectedEOF if @io.eof?
+ pending -= bread
+ end
+ end
+
+ @io.read skip # discard trailing zeros
+
+ # make sure nobody can use #read, #getc or #rewind anymore
+ entry.close
+ end
+ end
+
+ alias each_entry each
+
+ ##
+ # NOTE: Do not call #rewind during #each
+
+ def rewind
+ if @init_pos == 0 then
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind
+ @io.rewind
+ else
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+ @io.pos = @init_pos
+ end
+ end
+
+end
+
diff --git a/lib/rubygems/package/tar_reader/entry.rb b/lib/rubygems/package/tar_reader/entry.rb
new file mode 100644
index 0000000000..dcc66153d8
--- /dev/null
+++ b/lib/rubygems/package/tar_reader/entry.rb
@@ -0,0 +1,99 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+class Gem::Package::TarReader::Entry
+
+ attr_reader :header
+
+ def initialize(header, io)
+ @closed = false
+ @header = header
+ @io = io
+ @orig_pos = @io.pos
+ @read = 0
+ end
+
+ def check_closed # :nodoc:
+ raise IOError, "closed #{self.class}" if closed?
+ end
+
+ def bytes_read
+ @read
+ end
+
+ def close
+ @closed = true
+ end
+
+ def closed?
+ @closed
+ end
+
+ def eof?
+ check_closed
+
+ @read >= @header.size
+ end
+
+ def full_name
+ if @header.prefix != "" then
+ File.join @header.prefix, @header.name
+ else
+ @header.name
+ end
+ end
+
+ def getc
+ check_closed
+
+ return nil if @read >= @header.size
+
+ ret = @io.getc
+ @read += 1 if ret
+
+ ret
+ end
+
+ def directory?
+ @header.typeflag == "5"
+ end
+
+ def file?
+ @header.typeflag == "0"
+ end
+
+ def pos
+ check_closed
+
+ bytes_read
+ end
+
+ def read(len = nil)
+ check_closed
+
+ return nil if @read >= @header.size
+
+ len ||= @header.size - @read
+ max_read = [len, @header.size - @read].min
+
+ ret = @io.read max_read
+ @read += ret.size
+
+ ret
+ end
+
+ def rewind
+ check_closed
+
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+
+ @io.pos = @orig_pos
+ @read = 0
+ end
+
+end
+
diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb
new file mode 100644
index 0000000000..6e11440e22
--- /dev/null
+++ b/lib/rubygems/package/tar_writer.rb
@@ -0,0 +1,180 @@
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+require 'rubygems/package'
+
+class Gem::Package::TarWriter
+
+ class FileOverflow < StandardError; end
+
+ class BoundedStream
+
+ attr_reader :limit, :written
+
+ def initialize(io, limit)
+ @io = io
+ @limit = limit
+ @written = 0
+ end
+
+ def write(data)
+ if data.size + @written > @limit
+ raise FileOverflow, "You tried to feed more data than fits in the file."
+ end
+ @io.write data
+ @written += data.size
+ data.size
+ end
+
+ end
+
+ class RestrictedStream
+
+ def initialize(io)
+ @io = io
+ end
+
+ def write(data)
+ @io.write data
+ end
+
+ end
+
+ def self.new(io)
+ writer = super
+
+ return writer unless block_given?
+
+ begin
+ yield writer
+ ensure
+ writer.close
+ end
+
+ nil
+ end
+
+ def initialize(io)
+ @io = io
+ @closed = false
+ end
+
+ def add_file(name, mode)
+ check_closed
+
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+
+ name, prefix = split_name name
+
+ init_pos = @io.pos
+ @io.write "\0" * 512 # placeholder for the header
+
+ yield RestrictedStream.new(@io) if block_given?
+
+ size = @io.pos - init_pos - 512
+
+ remainder = (512 - (size % 512)) % 512
+ @io.write "\0" * remainder
+
+ final_pos = @io.pos
+ @io.pos = init_pos
+
+ header = Gem::Package::TarHeader.new :name => name, :mode => mode,
+ :size => size, :prefix => prefix
+
+ @io.write header
+ @io.pos = final_pos
+
+ self
+ end
+
+ def add_file_simple(name, mode, size)
+ check_closed
+
+ name, prefix = split_name name
+
+ header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
+ :size => size, :prefix => prefix).to_s
+
+ @io.write header
+ os = BoundedStream.new @io, size
+
+ yield os if block_given?
+
+ min_padding = size - os.written
+ @io.write("\0" * min_padding)
+
+ remainder = (512 - (size % 512)) % 512
+ @io.write("\0" * remainder)
+
+ self
+ end
+
+ def check_closed
+ raise IOError, "closed #{self.class}" if closed?
+ end
+
+ def close
+ check_closed
+
+ @io.write "\0" * 1024
+ flush
+
+ @closed = true
+ end
+
+ def closed?
+ @closed
+ end
+
+ def flush
+ check_closed
+
+ @io.flush if @io.respond_to? :flush
+ end
+
+ def mkdir(name, mode)
+ check_closed
+
+ name, prefix = split_name(name)
+
+ header = Gem::Package::TarHeader.new :name => name, :mode => mode,
+ :typeflag => "5", :size => 0,
+ :prefix => prefix
+
+ @io.write header
+
+ self
+ end
+
+ def split_name(name) # :nodoc:
+ raise Gem::Package::TooLongFileName if name.size > 256
+
+ if name.size <= 100 then
+ prefix = ""
+ else
+ parts = name.split(/\//)
+ newname = parts.pop
+ nxt = ""
+
+ loop do
+ nxt = parts.pop
+ break if newname.size + 1 + nxt.size > 100
+ newname = nxt + "/" + newname
+ end
+
+ prefix = (parts + [nxt]).join "/"
+ name = newname
+
+ if name.size > 100 or prefix.size > 155 then
+ raise Gem::Package::TooLongFileName
+ end
+ end
+
+ return name, prefix
+ end
+
+end
+
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index cb22e1f1b1..f49ee2f4a1 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -2,7 +2,6 @@ require 'net/http'
require 'uri'
require 'rubygems'
-require 'rubygems/gem_open_uri'
##
# RemoteFetcher handles the details of fetching gems and gem information from
@@ -10,6 +9,8 @@ require 'rubygems/gem_open_uri'
class Gem::RemoteFetcher
+ include Gem::UserInteraction
+
class FetchError < Gem::Exception; end
@fetcher = nil
@@ -29,6 +30,10 @@ class Gem::RemoteFetcher
# HTTP_PROXY_PASS)
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
def initialize(proxy)
+ Socket.do_not_reverse_lookup = true
+
+ @connections = {}
+ @requests = Hash.new 0
@proxy_uri =
case proxy
when :no_proxy then nil
@@ -38,6 +43,65 @@ class Gem::RemoteFetcher
end
end
+ ##
+ # Moves the gem +spec+ from +source_uri+ to the cache dir unless it is
+ # already there. If the source_uri is local the gem cache dir copy is
+ # always replaced.
+ def download(spec, source_uri, install_dir = Gem.dir)
+ gem_file_name = "#{spec.full_name}.gem"
+ local_gem_path = File.join install_dir, 'cache', gem_file_name
+
+ Gem.ensure_gem_subdirectories install_dir
+
+ source_uri = URI.parse source_uri unless URI::Generic === source_uri
+ scheme = source_uri.scheme
+
+ # URI.parse gets confused by MS Windows paths with forward slashes.
+ scheme = nil if scheme =~ /^[a-z]$/i
+
+ case scheme
+ when 'http' then
+ unless File.exist? local_gem_path then
+ begin
+ say "Downloading gem #{gem_file_name}" if
+ Gem.configuration.really_verbose
+
+ remote_gem_path = source_uri + "gems/#{gem_file_name}"
+
+ gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
+ rescue Gem::RemoteFetcher::FetchError
+ raise if spec.original_platform == spec.platform
+
+ alternate_name = "#{spec.original_name}.gem"
+
+ say "Failed, downloading gem #{alternate_name}" if
+ Gem.configuration.really_verbose
+
+ remote_gem_path = source_uri + "gems/#{alternate_name}"
+
+ gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path
+ end
+
+ File.open local_gem_path, 'wb' do |fp|
+ fp.write gem
+ end
+ end
+ when nil, 'file' then # TODO test for local overriding cache
+ begin
+ FileUtils.cp source_uri.to_s, local_gem_path
+ rescue Errno::EACCES
+ local_gem_path = source_uri.to_s
+ end
+
+ say "Using local gem #{local_gem_path}" if
+ Gem.configuration.really_verbose
+ else
+ raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}"
+ end
+
+ local_gem_path
+ end
+
# Downloads +uri+.
def fetch_path(uri)
open_uri_or_path(uri) do |input|
@@ -47,9 +111,8 @@ class Gem::RemoteFetcher
raise FetchError, "timed out fetching #{uri}"
rescue IOError, SocketError, SystemCallError => e
raise FetchError, "#{e.class}: #{e} reading #{uri}"
- rescue OpenURI::HTTPError => e
- body = e.io.readlines.join "\n\t"
- message = "#{e.class}: #{e} reading #{uri}\n\t#{body}"
+ rescue => e
+ message = "#{e.class}: #{e} reading #{uri}"
raise FetchError, message
end
@@ -83,7 +146,8 @@ class Gem::RemoteFetcher
end
rescue SocketError, SystemCallError, Timeout::Error => e
- raise FetchError, "#{e.message} (#{e.class})\n\tgetting size of #{uri}"
+ raise Gem::RemoteFetcher::FetchError,
+ "#{e.message} (#{e.class})\n\tgetting size of #{uri}"
end
private
@@ -131,26 +195,77 @@ class Gem::RemoteFetcher
# 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, &block)
+ def open_uri_or_path(uri, depth = 0, &block)
if file_uri?(uri)
open(get_file_uri_path(uri), &block)
else
- connection_options = {
- "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
- }
+ uri = URI.parse uri unless URI::Generic === uri
+ net_http_args = [uri.host, uri.port]
+
+ if @proxy_uri then
+ net_http_args += [ @proxy_uri.host,
+ @proxy_uri.port,
+ @proxy_uri.user,
+ @proxy_uri.password
+ ]
+ end
+
+ connection_id = net_http_args.join ':'
+ @connections[connection_id] ||= Net::HTTP.new(*net_http_args)
+ connection = @connections[connection_id]
- if @proxy_uri
- http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}"
- connection_options[:proxy_http_basic_authentication] = [http_proxy_url, unescape(@proxy_uri.user)||'', unescape(@proxy_uri.password)||'']
+ if uri.scheme == 'https' && ! connection.started?
+ http_obj.use_ssl = true
+ http_obj.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
- uri = URI.parse uri unless URI::Generic === uri
+ connection.start unless connection.started?
+
+ request = Net::HTTP::Get.new(uri.request_uri)
unless uri.nil? || uri.user.nil? || uri.user.empty? then
- connection_options[:http_basic_authentication] =
- [unescape(uri.user), unescape(uri.password)]
+ request.basic_auth(uri.user, uri.password)
end
- open(uri, connection_options, &block)
+ ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
+ ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
+ ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ ua << ")"
+
+ request.add_field 'User-Agent', ua
+ request.add_field 'Connection', 'keep-alive'
+ request.add_field 'Keep-Alive', '30'
+
+ # HACK work around EOFError bug in Net::HTTP
+ retried = false
+ begin
+ @requests[connection_id] += 1
+ response = connection.request(request)
+ rescue EOFError
+ requests = @requests[connection_id]
+ say "connection reset after #{requests} requests, retrying" if
+ Gem.configuration.really_verbose
+
+ raise Gem::RemoteFetcher::FetchError, 'too many connection resets' if
+ retried
+
+ @requests[connection_id] = 0
+
+ connection.finish
+ connection.start
+ retried = true
+ retry
+ end
+
+ case response
+ when Net::HTTPOK then
+ block.call(StringIO.new(response.body)) if block
+ when Net::HTTPRedirection then
+ raise Gem::RemoteFetcher::FetchError, "too many redirects" if depth > 10
+ open_uri_or_path(response['Location'], depth + 1, &block)
+ else
+ raise Gem::RemoteFetcher::FetchError,
+ "bad response #{response.message} #{response.code}"
+ end
end
end
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index 4dfba4fa61..209bd432f0 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -16,6 +16,8 @@ class Gem::Requirement
include Comparable
+ attr_reader :requirements
+
OPS = {
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
diff --git a/lib/rubygems/rubygems_version.rb b/lib/rubygems/rubygems_version.rb
index 146e2cfee4..cc7269d622 100644
--- a/lib/rubygems/rubygems_version.rb
+++ b/lib/rubygems/rubygems_version.rb
@@ -2,5 +2,5 @@
# This file is auto-generated by build scripts.
# See: rake update_version
module Gem
- RubyGemsVersion = '1.0.1'
+ RubyGemsVersion = '1.1.0'
end
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index 415e98ffc0..345d36bbd3 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -4,6 +4,7 @@
# See LICENSE.txt for permissions.
#++
+require 'rubygems'
require 'rubygems/gem_openssl'
# = Signed Gems README
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
index ed957dd38a..dab449894f 100644
--- a/lib/rubygems/server.rb
+++ b/lib/rubygems/server.rb
@@ -1,7 +1,7 @@
require 'webrick'
-require 'rdoc/template'
require 'yaml'
require 'zlib'
+require 'erb'
require 'rubygems'
@@ -27,107 +27,87 @@ class Gem::Server
include Gem::UserInteraction
- DOC_TEMPLATE = <<-WEBPAGE
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
- <title>RubyGems Documentation Index</title>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
-</head>
-<body>
- <div id="fileHeader">
- <h1>RubyGems Documentation Index</h1>
- </div>
- <!-- banner header -->
-
-<div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <h1>Summary</h1>
-<p>There are %gem_count% gems installed:</p>
-<p>
-START:specs
-IFNOT:is_last
-<a href="#%name%">%name%</a>,
-ENDIF:is_last
-IF:is_last
-<a href="#%name%">%name%</a>.
-ENDIF:is_last
-END:specs
-<h1>Gems</h1>
-
-<dl>
-START:specs
-<dt>
-IF:first_name_entry
- <a name="%name%"></a>
-ENDIF:first_name_entry
-<b>%name% %version%</b>
-IF:rdoc_installed
- <a href="%doc_path%">[rdoc]</a>
-ENDIF:rdoc_installed
-IFNOT:rdoc_installed
- <span title="rdoc not installed">[rdoc]</span>
-ENDIF:rdoc_installed
-IF:homepage
-<a href="%homepage%" title="%homepage%">[www]</a>
-ENDIF:homepage
-IFNOT:homepage
-<span title="no homepage available">[www]</span>
-ENDIF:homepage
-IF:has_deps
- - depends on
-START:dependencies
-IFNOT:is_last
-<a href="#%name%" title="%version%">%name%</a>,
-ENDIF:is_last
-IF:is_last
-<a href="#%name%" title="%version%">%name%</a>.
-ENDIF:is_last
-END:dependencies
-ENDIF:has_deps
-</dt>
-<dd>
-%summary%
-IF:executables
- <br/>
-
-IF:only_one_executable
- Executable is
-ENDIF:only_one_executable
-
-IFNOT:only_one_executable
- Executables are
-ENDIF:only_one_executable
-
-START:executables
-IFNOT:is_last
- <span class="context-item-name">%executable%</span>,
-ENDIF:is_last
-IF:is_last
- <span class="context-item-name">%executable%</span>.
-ENDIF:is_last
-END:executables
-ENDIF:executables
-<br/>
-<br/>
-</dd>
-END:specs
-</dl>
-
+ DOC_TEMPLATE = <<-'WEBPAGE'
+ <?xml version="1.0" encoding="iso-8859-1"?>
+ <!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>RubyGems Documentation Index</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
+ </head>
+ <body>
+ <div id="fileHeader">
+ <h1>RubyGems Documentation Index</h1>
+ </div>
+ <!-- banner header -->
+
+ <div id="bodyContent">
+ <div id="contextContent">
+ <div id="description">
+ <h1>Summary</h1>
+ <p>There are <%=values["gem_count"]%> gems installed:</p>
+ <p>
+ <%= values["specs"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
+ <h1>Gems</h1>
+
+ <dl>
+ <% values["specs"].each do |spec| %>
+ <dt>
+ <% if spec["first_name_entry"] then %>
+ <a name="<%=spec["name"]%>"></a>
+ <% end %>
+
+ <b><%=spec["name"]%> <%=spec["version"]%></b>
+
+ <% if spec["rdoc_installed"] then %>
+ <a href="<%=spec["doc_path"]%>">[rdoc]</a>
+ <% else %>
+ <span title="rdoc not installed">[rdoc]</span>
+ <% end %>
+
+ <% if spec["homepage"] then %>
+ <a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a>
+ <% else %>
+ <span title="no homepage available">[www]</span>
+ <% end %>
+
+ <% if spec["has_deps"] then %>
+ - depends on
+ <%= spec["dependencies"].map { |v| "<a href=\"#{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
+ <% end %>
+ </dt>
+ <dd>
+ <%=spec["summary"]%>
+ <% if spec["executables"] then %>
+ <br/>
+
+ <% if spec["only_one_executable"] then %>
+ Executable is
+ <% else %>
+ Executables are
+ <%end%>
+
+ <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>.
+
+ <%end%>
+ <br/>
+ <br/>
+ </dd>
+ <% end %>
+ </dl>
+
+ </div>
+ </div>
</div>
- </div>
+ <div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
</div>
-<div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
-</div>
-</body>
-</html>
+ </body>
+ </html>
WEBPAGE
# CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
@@ -496,11 +476,12 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
end
# create page from template
- template = TemplatePage.new(DOC_TEMPLATE)
+ template = ERB.new(DOC_TEMPLATE)
res['content-type'] = 'text/html'
- template.write_html_on res.body,
- "gem_count" => specs.size.to_s, "specs" => specs,
- "total_file_count" => total_file_count.to_s
+ values = { "gem_count" => specs.size.to_s, "specs" => specs,
+ "total_file_count" => total_file_count.to_s }
+ result = template.result binding
+ res.body = result
end
paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
diff --git a/lib/rubygems/source_index.rb b/lib/rubygems/source_index.rb
index 1283f8f904..61f5324a95 100644
--- a/lib/rubygems/source_index.rb
+++ b/lib/rubygems/source_index.rb
@@ -8,437 +8,512 @@ require 'rubygems'
require 'rubygems/user_interaction'
require 'rubygems/specification'
-module Gem
+##
+# 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.
- # 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 SourceIndex
+class Gem::SourceIndex
- include Enumerable
+ include Enumerable
+ include Gem::UserInteraction
+
+ class << self
include Gem::UserInteraction
- # Class Methods. -------------------------------------------------
- class << self
- include Gem::UserInteraction
-
- # 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 from_installed_gems(*deprecated)
- if deprecated.empty?
- from_gems_in(*installed_spec_directories)
- else
- from_gems_in(*deprecated) # HACK warn
- end
- end
-
- # Return a list of directories in the current gem path that
- # contain specifications.
- #
- # return::
- # List of directory paths (all ending in "../specifications").
- #
- def installed_spec_directories
- Gem.path.collect { |dir| File.join(dir, "specifications") }
+ ##
+ # 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 from_installed_gems(*deprecated)
+ if deprecated.empty?
+ from_gems_in(*installed_spec_directories)
+ else
+ from_gems_in(*deprecated) # HACK warn
end
+ end
- # Factory method to construct a source index instance for a
- # given path.
- #
- # spec_dirs::
- # List of directories to search for specifications. Each
- # directory should have a "specifications" subdirectory
- # containing the gem specifications.
- #
- # return::
- # SourceIndex instance
- #
- def from_gems_in(*spec_dirs)
- self.new.load_gems_in(*spec_dirs)
- end
-
- # Load a specification from a file (eval'd Ruby code)
- #
- # file_name:: [String] The .gemspec file
- # return:: Specification instance or nil if an error occurs
- #
- def load_specification(file_name)
- begin
- spec_code = File.read(file_name).untaint
- gemspec = eval spec_code, binding, file_name
- if gemspec.is_a?(Gem::Specification)
- gemspec.loaded_from = file_name
- return gemspec
- end
- alert_warning "File '#{file_name}' does not evaluate to a gem specification"
- rescue SyntaxError => e
- alert_warning e
- alert_warning spec_code
- rescue Exception => e
- alert_warning(e.inspect.to_s + "\n" + spec_code)
- alert_warning "Invalid .gemspec format in '#{file_name}'"
+ ##
+ # Return a list of directories in the current gem path that
+ # contain specifications.
+ #
+ # return::
+ # List of directory paths (all ending in "../specifications").
+
+ def installed_spec_directories
+ Gem.path.collect { |dir| File.join(dir, "specifications") }
+ end
+
+ ##
+ # Creates a new SourceIndex from the ruby format gem specifications in
+ # +spec_dirs+.
+
+ def from_gems_in(*spec_dirs)
+ self.new.load_gems_in(*spec_dirs)
+ end
+
+ ##
+ # Loads a ruby-format specification from +file_name+ and returns the
+ # loaded spec.
+
+ def load_specification(file_name)
+ begin
+ spec_code = File.read(file_name).untaint
+ gemspec = eval spec_code, binding, file_name
+ if gemspec.is_a?(Gem::Specification)
+ gemspec.loaded_from = file_name
+ return gemspec
end
- return nil
+ alert_warning "File '#{file_name}' does not evaluate to a gem specification"
+ rescue SyntaxError => e
+ alert_warning e
+ alert_warning spec_code
+ rescue Exception => e
+ alert_warning(e.inspect.to_s + "\n" + spec_code)
+ alert_warning "Invalid .gemspec format in '#{file_name}'"
end
-
+ return nil
end
- # Instance Methods -----------------------------------------------
+ end
- # Constructs a source index instance from the provided
- # specifications
- #
- # specifications::
- # [Hash] hash of [Gem name, Gem::Specification] pairs
- #
- def initialize(specifications={})
- @gems = specifications
- end
-
- # Reconstruct the source index from the list of source
- # directories.
- def load_gems_in(*spec_dirs)
- @gems.clear
- specs = Dir.glob File.join("{#{spec_dirs.join(',')}}", "*.gemspec")
-
- specs.each do |file_name|
- gemspec = self.class.load_specification(file_name.untaint)
- add_spec(gemspec) if gemspec
+ ##
+ # Constructs a source index instance from the provided
+ # specifications
+ #
+ # specifications::
+ # [Hash] hash of [Gem name, Gem::Specification] pairs
+
+ def initialize(specifications={})
+ @gems = specifications
+ 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.glob File.join(spec_dir, '*.gemspec')
+
+ spec_files.each do |spec_file|
+ gemspec = self.class.load_specification spec_file.untaint
+ add_spec gemspec if gemspec
end
- self
end
- # Returns a Hash of name => Specification of the latest versions of each
- # gem in this index.
- def latest_specs
- result, latest = Hash.new { |h,k| h[k] = [] }, {}
+ self
+ end
- self.each do |_, spec| # SourceIndex is not a hash, so we're stuck with each
- name = spec.name
- curr_ver = spec.version
- prev_ver = latest[name]
+ ##
+ # Returns a Hash of name => Specification of the latest versions of each
+ # gem in this index.
- next unless prev_ver.nil? or curr_ver >= prev_ver
+ def latest_specs
+ result = Hash.new { |h,k| h[k] = [] }
+ latest = {}
- if prev_ver.nil? or curr_ver > prev_ver then
- result[name].clear
- latest[name] = curr_ver
- end
+ sort.each do |_, spec|
+ name = spec.name
+ curr_ver = spec.version
+ prev_ver = latest.key?(name) ? latest[name].version : nil
+
+ next unless prev_ver.nil? or curr_ver >= prev_ver or
+ latest[name].platform != Gem::Platform::RUBY
- result[name] << spec
+ if prev_ver.nil? or
+ (curr_ver > prev_ver and spec.platform == Gem::Platform::RUBY) then
+ result[name].clear
+ latest[name] = spec
end
- result.values.flatten
- end
+ if spec.platform != Gem::Platform::RUBY then
+ result[name].delete_if do |result_spec|
+ result_spec.platform == spec.platform
+ end
+ end
- # Add a gem specification to the source index.
- def add_spec(gem_spec)
- @gems[gem_spec.full_name] = gem_spec
+ result[name] << spec
end
- # Remove a gem specification named +full_name+.
- def remove_spec(full_name)
- @gems.delete(full_name)
- end
+ result.values.flatten
+ end
- # Iterate over the specifications in the source index.
- def each(&block) # :yields: gem.full_name, gem
- @gems.each(&block)
- end
+ ##
+ # Add a gem specification to the source index.
- # The gem specification given a full gem spec name.
- def specification(full_name)
- @gems[full_name]
- end
+ def add_spec(gem_spec)
+ @gems[gem_spec.full_name] = gem_spec
+ end
- # The signature for the source index. Changes in the signature
- # indicate a change in the index.
- def index_signature
- require 'rubygems/digest/sha2'
+ ##
+ # Add gem specifications to the source index.
- Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s
+ def add_specs(*gem_specs)
+ gem_specs.each do |spec|
+ add_spec spec
end
+ end
- # The signature for the given gem specification.
- def gem_signature(gem_full_name)
- require 'rubygems/digest/sha2'
+ ##
+ # Remove a gem specification named +full_name+.
- Gem::SHA256.new.hexdigest(@gems[gem_full_name].to_yaml).to_s
- end
+ 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 'rubygems/digest/sha2'
+
+ Gem::SHA256.new.hexdigest(@gems.keys.sort.join(',')).to_s
+ end
+
+ ##
+ # The signature for the given gem specification.
+
+ def gem_signature(gem_full_name)
+ require 'rubygems/digest/sha2'
- def size
- @gems.size
+ Gem::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, version_requirement = Gem::Requirement.default)
+ search(/^#{gem_name}$/, version_requirement)
+ 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_only = false)
+ version_requirement = nil
+ only_platform = false
+
+ case gem_pattern # TODO warn after 2008/03, remove three months after
+ when Regexp then
+ version_requirement = platform_only || Gem::Requirement.default
+ when Gem::Dependency then
+ only_platform = platform_only
+ version_requirement = gem_pattern.version_requirements
+ gem_pattern = if gem_pattern.name.empty? then
+ //
+ else
+ /^#{Regexp.escape gem_pattern.name}$/
+ end
+ else
+ version_requirement = platform_only || Gem::Requirement.default
+ gem_pattern = /#{gem_pattern}/i
end
- alias length size
- # Find a gem by an exact match on the short name.
- def find_name(gem_name, version_requirement = Gem::Requirement.default)
- search(/^#{gem_name}$/, version_requirement)
+ unless Gem::Requirement === version_requirement then
+ version_requirement = Gem::Requirement.create version_requirement
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_only = false)
- version_requirement = nil
- only_platform = false
-
- case gem_pattern # TODO warn after 2008/03, remove three months after
- when Regexp then
- version_requirement = platform_only || Gem::Requirement.default
- when Gem::Dependency then
- only_platform = platform_only
- version_requirement = gem_pattern.version_requirements
- gem_pattern = if gem_pattern.name.empty? then
- //
- else
- /^#{Regexp.escape gem_pattern.name}$/
- end
- else
- version_requirement = platform_only || Gem::Requirement.default
- gem_pattern = /#{gem_pattern}/i
- end
+ specs = @gems.values.select do |spec|
+ spec.name =~ gem_pattern and
+ version_requirement.satisfied_by? spec.version
+ end
- unless Gem::Requirement === version_requirement then
- version_requirement = Gem::Requirement.create version_requirement
+ if only_platform then
+ specs = specs.select do |spec|
+ Gem::Platform.match spec.platform
end
+ end
- specs = @gems.values.select do |spec|
- spec.name =~ gem_pattern and
- version_requirement.satisfied_by? spec.version
- end
+ specs.sort_by { |s| s.sort_obj }
+ end
- if only_platform then
- specs = specs.select do |spec|
- Gem::Platform.match spec.platform
- end
- end
+ ##
+ # Refresh the source index from the local file system.
+ #
+ # return:: Returns a pointer to itself.
- specs.sort_by { |s| s.sort_obj }
- end
+ def refresh!
+ load_gems_in(self.class.installed_spec_directories)
+ end
- # Refresh the source index from the local file system.
- #
- # return:: Returns a pointer to itself.
- #
- def refresh!
- load_gems_in(self.class.installed_spec_directories)
- end
+ ##
+ # Returns an Array of Gem::Specifications that are not up to date.
- # Returns an Array of Gem::Specifications that are not up to date.
- #
- def outdated
- dep = Gem::Dependency.new '', Gem::Requirement.default
+ def outdated
+ dep = Gem::Dependency.new '', Gem::Requirement.default
- remotes = Gem::SourceInfoCache.search dep, true
+ remotes = Gem::SourceInfoCache.search dep, true
- outdateds = []
+ outdateds = []
- latest_specs.each do |local|
- name = local.name
- remote = remotes.select { |spec| spec.name == name }.
- sort_by { |spec| spec.version.to_ints }.
- last
- outdateds << name if remote and local.version < remote.version
- end
+ latest_specs.each do |local|
+ name = local.name
+ remote = remotes.select { |spec| spec.name == name }.
+ sort_by { |spec| spec.version.to_ints }.
+ last
- outdateds
+ outdateds << name if remote and local.version < remote.version
end
- def update(source_uri)
- use_incremental = false
+ outdateds
+ end
- begin
- gem_names = fetch_quick_index source_uri
- remove_extra gem_names
- missing_gems = find_missing gem_names
+ ##
+ # Updates this SourceIndex from +source_uri+. If +all+ is false, only the
+ # latest gems are fetched.
- return false if missing_gems.size.zero?
+ def update(source_uri, all)
+ source_uri = URI.parse source_uri unless URI::Generic === source_uri
+ source_uri.path += '/' unless source_uri.path =~ /\/$/
- say "missing #{missing_gems.size} gems" if
- missing_gems.size > 0 and Gem.configuration.really_verbose
+ use_incremental = false
- use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold
- rescue Gem::OperationNotSupportedError => ex
- alert_error "Falling back to bulk fetch: #{ex.message}" if
- Gem.configuration.really_verbose
- use_incremental = false
- end
+ begin
+ gem_names = fetch_quick_index source_uri, all
+ remove_extra gem_names
+ missing_gems = find_missing gem_names
- if use_incremental then
- update_with_missing(source_uri, missing_gems)
- else
- new_index = fetch_bulk_index(source_uri)
- @gems.replace(new_index.gems)
- end
+ return false if missing_gems.size.zero?
- true
- end
+ say "Missing metadata for #{missing_gems.size} gems" if
+ missing_gems.size > 0 and Gem.configuration.really_verbose
- def ==(other) # :nodoc:
- self.class === other and @gems == other.gems
+ use_incremental = missing_gems.size <= Gem.configuration.bulk_threshold
+ rescue Gem::OperationNotSupportedError => ex
+ alert_error "Falling back to bulk fetch: #{ex.message}" if
+ Gem.configuration.really_verbose
+ use_incremental = false
end
- def dump
- Marshal.dump(self)
+ if use_incremental then
+ update_with_missing(source_uri, missing_gems)
+ else
+ new_index = fetch_bulk_index(source_uri)
+ @gems.replace(new_index.gems)
end
- protected
+ true
+ end
- attr_reader :gems
+ def ==(other) # :nodoc:
+ self.class === other and @gems == other.gems
+ end
- private
+ def dump
+ Marshal.dump(self)
+ end
- def fetcher
- require 'rubygems/remote_fetcher'
+ protected
- Gem::RemoteFetcher.fetcher
- end
+ attr_reader :gems
- def fetch_index_from(source_uri)
- @fetch_error = nil
+ private
+
+ def fetcher
+ require 'rubygems/remote_fetcher'
+
+ Gem::RemoteFetcher.fetcher
+ end
+
+ def fetch_index_from(source_uri)
+ @fetch_error = nil
- indexes = %W[
+ indexes = %W[
Marshal.#{Gem.marshal_version}.Z
Marshal.#{Gem.marshal_version}
yaml.Z
yaml
]
- indexes.each do |name|
- spec_data = nil
- begin
- spec_data = fetcher.fetch_path("#{source_uri}/#{name}")
- spec_data = unzip(spec_data) if name =~ /\.Z$/
- if name =~ /Marshal/ then
- return Marshal.load(spec_data)
- else
- return YAML.load(spec_data)
- end
- rescue => e
- if Gem.configuration.really_verbose then
- alert_error "Unable to fetch #{name}: #{e.message}"
- end
- @fetch_error = e
+ indexes.each do |name|
+ spec_data = nil
+ index = source_uri + name
+ begin
+ spec_data = fetcher.fetch_path index
+ spec_data = unzip(spec_data) if name =~ /\.Z$/
+
+ if name =~ /Marshal/ then
+ return Marshal.load(spec_data)
+ else
+ return YAML.load(spec_data)
+ end
+ rescue => e
+ if Gem.configuration.really_verbose then
+ alert_error "Unable to fetch #{name}: #{e.message}"
end
+
+ @fetch_error = e
end
- nil
end
- def fetch_bulk_index(source_uri)
- say "Bulk updating Gem source index for: #{source_uri}"
+ nil
+ end
- index = fetch_index_from(source_uri)
- if index.nil? then
- raise Gem::RemoteSourceException,
+ def fetch_bulk_index(source_uri)
+ say "Bulk updating Gem source index for: #{source_uri}"
+
+ index = fetch_index_from(source_uri)
+ if index.nil? then
+ raise Gem::RemoteSourceException,
"Error fetching remote gem cache: #{@fetch_error}"
- end
- @fetch_error = nil
- index
end
+ @fetch_error = nil
+ index
+ end
+
+ ##
+ # Get the quick index needed for incremental updates.
+
+ def fetch_quick_index(source_uri, all)
+ index = all ? 'index' : 'latest_index'
- # Get the quick index needed for incremental updates.
- def fetch_quick_index(source_uri)
- zipped_index = fetcher.fetch_path source_uri + '/quick/index.rz'
- unzip(zipped_index).split("\n")
- rescue ::Exception => ex
+ zipped_index = fetcher.fetch_path source_uri + "quick/#{index}.rz"
+
+ unzip(zipped_index).split("\n")
+ rescue ::Exception => e
+ unless all then
+ say "Latest index not found, using quick index" if
+ Gem.configuration.really_verbose
+
+ fetch_quick_index source_uri, true
+ else
raise Gem::OperationNotSupportedError,
- "No quick index found: " + ex.message
+ "No quick index found: #{e.message}"
end
+ end
- # Make a list of full names for all the missing gemspecs.
- def find_missing(spec_names)
- spec_names.find_all { |full_name|
- specification(full_name).nil?
- }
- end
+ ##
+ # Make a list of full names for all the missing gemspecs.
- def remove_extra(spec_names)
- dictionary = spec_names.inject({}) { |h, k| h[k] = true; h }
- each do |name, spec|
- remove_spec name unless dictionary.include? name
- end
- end
+ def find_missing(spec_names)
+ spec_names.find_all { |full_name|
+ specification(full_name).nil?
+ }
+ end
- # Unzip the given string.
- def unzip(string)
- require 'zlib'
- Zlib::Inflate.inflate(string)
+ def remove_extra(spec_names)
+ dictionary = spec_names.inject({}) { |h, k| h[k] = true; h }
+ each do |name, spec|
+ remove_spec name unless dictionary.include? name
end
+ end
- # Tries to fetch Marshal representation first, then YAML
- def fetch_single_spec(source_uri, spec_name)
- @fetch_error = nil
- begin
- marshal_uri = source_uri + "/quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz"
- zipped = fetcher.fetch_path marshal_uri
- return Marshal.load(unzip(zipped))
- rescue => ex
- @fetch_error = ex
- if Gem.configuration.really_verbose then
- say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}"
- end
+ ##
+ # Unzip the given string.
+
+ def unzip(string)
+ require 'zlib'
+ Zlib::Inflate.inflate(string)
+ end
+
+ ##
+ # Tries to fetch Marshal representation first, then YAML
+
+ def fetch_single_spec(source_uri, spec_name)
+ @fetch_error = nil
+
+ begin
+ marshal_uri = source_uri + "quick/Marshal.#{Gem.marshal_version}/#{spec_name}.gemspec.rz"
+ zipped = fetcher.fetch_path marshal_uri
+ return Marshal.load(unzip(zipped))
+ rescue => ex
+ @fetch_error = ex
+
+ if Gem.configuration.really_verbose then
+ say "unable to fetch marshal gemspec #{marshal_uri}: #{ex.class} - #{ex}"
end
+ end
- begin
- yaml_uri = source_uri + "/quick/#{spec_name}.gemspec.rz"
- zipped = fetcher.fetch_path yaml_uri
- return YAML.load(unzip(zipped))
- rescue => ex
- @fetch_error = ex
- if Gem.configuration.really_verbose then
- say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}"
- end
+ begin
+ yaml_uri = source_uri + "quick/#{spec_name}.gemspec.rz"
+ zipped = fetcher.fetch_path yaml_uri
+ return YAML.load(unzip(zipped))
+ rescue => ex
+ @fetch_error = ex
+ if Gem.configuration.really_verbose then
+ say "unable to fetch YAML gemspec #{yaml_uri}: #{ex.class} - #{ex}"
end
- nil
end
- # Update the cached source index with the missing names.
- def update_with_missing(source_uri, missing_names)
- progress = ui.progress_reporter(missing_names.size,
+ nil
+ end
+
+ ##
+ # Update the cached source index with the missing names.
+
+ def update_with_missing(source_uri, missing_names)
+ progress = ui.progress_reporter(missing_names.size,
"Updating metadata for #{missing_names.size} gems from #{source_uri}")
- missing_names.each do |spec_name|
- gemspec = fetch_single_spec(source_uri, spec_name)
- if gemspec.nil? then
- ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \
+ missing_names.each do |spec_name|
+ gemspec = fetch_single_spec(source_uri, spec_name)
+ if gemspec.nil? then
+ ui.say "Failed to download spec #{spec_name} from #{source_uri}:\n" \
"\t#{@fetch_error.message}"
- else
- add_spec gemspec
- progress.updated spec_name
- end
- @fetch_error = nil
+ else
+ add_spec gemspec
+ progress.updated spec_name
end
- progress.done
- progress.count
+ @fetch_error = nil
end
-
+ progress.done
+ progress.count
end
- # Cache is an alias for SourceIndex to allow older YAMLized source
- # index objects to load properly.
+end
+
+module Gem
+
+ # :stopdoc:
+
+ # Cache is an alias for SourceIndex to allow older YAMLized source index
+ # objects to load properly.
Cache = SourceIndex
+ # :starddoc:
+
end
diff --git a/lib/rubygems/source_info_cache.rb b/lib/rubygems/source_info_cache.rb
index c84868a5f5..9383f6362e 100644
--- a/lib/rubygems/source_info_cache.rb
+++ b/lib/rubygems/source_info_cache.rb
@@ -4,6 +4,7 @@ require 'rubygems'
require 'rubygems/source_info_cache_entry'
require 'rubygems/user_interaction'
+##
# SourceInfoCache stores a copy of the gem index for each gem source.
#
# There are two possible cache locations, the system cache and the user cache:
@@ -25,7 +26,7 @@ require 'rubygems/user_interaction'
# @source_index => Gem::SourceIndex
# ...
# }
-#
+
class Gem::SourceInfoCache
include Gem::UserInteraction
@@ -37,7 +38,7 @@ class Gem::SourceInfoCache
def self.cache
return @cache if @cache
@cache = new
- @cache.refresh if Gem.configuration.update_sources
+ @cache.refresh false if Gem.configuration.update_sources
@cache
end
@@ -45,86 +46,178 @@ class Gem::SourceInfoCache
cache.cache_data
end
- # Search all source indexes for +pattern+.
- def self.search(pattern, platform_only = false)
- cache.search pattern, platform_only
+ ##
+ # The name of the system cache file.
+
+ def self.latest_system_cache_file
+ File.join File.dirname(system_cache_file),
+ "latest_#{File.basename system_cache_file}"
+ end
+
+ ##
+ # The name of the latest user cache file.
+
+ def self.latest_user_cache_file
+ File.join File.dirname(user_cache_file),
+ "latest_#{File.basename user_cache_file}"
+ end
+
+ ##
+ # Search all source indexes. See Gem::SourceInfoCache#search.
+
+ def self.search(*args)
+ cache.search(*args)
end
- # Search all source indexes for +pattern+. Only returns gems matching
- # Gem.platforms when +only_platform+ is true. See #search_with_source.
- def self.search_with_source(pattern, only_platform = false)
- cache.search_with_source(pattern, only_platform)
+ ##
+ # Search all source indexes returning the source_uri. See
+ # Gem::SourceInfoCache#search_with_source.
+
+ def self.search_with_source(*args)
+ cache.search_with_source(*args)
+ end
+
+ ##
+ # The name of the system cache file. (class method)
+
+ def self.system_cache_file
+ @system_cache_file ||= Gem.default_system_source_cache_dir
+ end
+
+ ##
+ # The name of the user cache file.
+
+ def self.user_cache_file
+ @user_cache_file ||=
+ ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
end
def initialize # :nodoc:
@cache_data = nil
@cache_file = nil
@dirty = false
+ @only_latest = true
end
+ ##
# The most recent cache data.
+
def cache_data
return @cache_data if @cache_data
cache_file # HACK writable check
- begin
- # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
- data = File.open cache_file, 'rb' do |fp| fp.read end
- @cache_data = Marshal.load data
-
- @cache_data.each do |url, sice|
- next unless sice.is_a?(Hash)
- update
- cache = sice['cache']
- size = sice['size']
- if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
- new_sice = Gem::SourceInfoCacheEntry.new cache, size
- @cache_data[url] = new_sice
- else # irreperable, force refetch.
- reset_cache_for(url)
- end
- end
- @cache_data
- rescue => e
- if Gem.configuration.really_verbose then
- say "Exception during cache_data handling: #{ex.class} - #{ex}"
- say "Cache file was: #{cache_file}"
- say "\t#{e.backtrace.join "\n\t"}"
- end
- reset_cache_data
- end
- end
-
- def reset_cache_for(url)
- say "Reseting cache for #{url}" if Gem.configuration.really_verbose
+ @only_latest = true
- sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
- sice.refresh url # HACK may be unnecessary, see ::cache and #refresh
+ @cache_data = read_cache_data latest_cache_file
- @cache_data[url] = sice
@cache_data
end
- def reset_cache_data
- @cache_data = {}
- end
+ ##
+ # The name of the cache file.
- # The name of the cache file to be read
def cache_file
return @cache_file if @cache_file
@cache_file = (try_file(system_cache_file) or
try_file(user_cache_file) or
raise "unable to locate a writable cache file")
end
+
+ ##
+ # Force cache file to be reset, useful for integration testing of rubygems
+
+ def reset_cache_file
+ @cache_file = nil
+ end
+ ##
# Write the cache to a local file (if it is dirty).
+
def flush
write_cache if @dirty
@dirty = false
end
- # Refreshes each source in the cache from its repository.
- def refresh
+ def latest_cache_data
+ latest_cache_data = {}
+
+ cache_data.each do |repo, sice|
+ latest = sice.source_index.latest_specs
+
+ new_si = Gem::SourceIndex.new
+ new_si.add_specs(*latest)
+
+ latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size
+ latest_cache_data[repo] = latest_sice
+ end
+
+ latest_cache_data
+ end
+
+ ##
+ # The name of the latest cache file.
+
+ def latest_cache_file
+ File.join File.dirname(cache_file), "latest_#{File.basename cache_file}"
+ end
+
+ ##
+ # The name of the latest system cache file.
+
+ def latest_system_cache_file
+ self.class.latest_system_cache_file
+ end
+
+ ##
+ # The name of the latest user cache file.
+
+ def latest_user_cache_file
+ self.class.latest_user_cache_file
+ end
+
+ def read_all_cache_data
+ if @only_latest then
+ @only_latest = false
+ @cache_data = read_cache_data cache_file
+ end
+ end
+
+ def read_cache_data(file)
+ # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
+ data = open file, 'rb' do |fp| fp.read end
+ cache_data = Marshal.load data
+
+ cache_data.each do |url, sice|
+ next unless sice.is_a?(Hash)
+ update
+
+ cache = sice['cache']
+ size = sice['size']
+
+ if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
+ new_sice = Gem::SourceInfoCacheEntry.new cache, size
+ cache_data[url] = new_sice
+ else # irreperable, force refetch.
+ reset_cache_for url, cache_data
+ end
+ end
+
+ cache_data
+ rescue => e
+ if Gem.configuration.really_verbose then
+ say "Exception during cache_data handling: #{e.class} - #{e}"
+ say "Cache file was: #{file}"
+ say "\t#{e.backtrace.join "\n\t"}"
+ end
+
+ {}
+ end
+
+ ##
+ # Refreshes each source in the cache from its repository. If +all+ is
+ # false, only latest gems are updated.
+
+ def refresh(all)
Gem.sources.each do |source_uri|
cache_entry = cache_data[source_uri]
if cache_entry.nil? then
@@ -132,14 +225,34 @@ class Gem::SourceInfoCache
cache_data[source_uri] = cache_entry
end
- update if cache_entry.refresh source_uri
+ update if cache_entry.refresh source_uri, all
end
flush
end
- # Searches all source indexes for +pattern+.
- def search(pattern, platform_only = false)
+ def reset_cache_for(url, cache_data)
+ say "Reseting cache for #{url}" if Gem.configuration.really_verbose
+
+ sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
+ sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh
+
+ cache_data[url] = sice
+ cache_data
+ end
+
+ def reset_cache_data
+ @cache_data = nil
+ end
+
+ ##
+ # Searches all source indexes. See Gem::SourceIndex#search for details on
+ # +pattern+ and +platform_only+. If +all+ is set to true, the full index
+ # will be loaded before searching.
+
+ def search(pattern, platform_only = false, all = false)
+ read_all_cache_data if all
+
cache_data.map do |source_uri, sic_entry|
next unless Gem.sources.include? source_uri
sic_entry.source_index.search pattern, platform_only
@@ -150,7 +263,9 @@ class Gem::SourceInfoCache
# only gems matching Gem.platforms will be selected. Returns an Array of
# pairs containing the Gem::Specification found and the source_uri it was
# found at.
- def search_with_source(pattern, only_platform = false)
+ def search_with_source(pattern, only_platform = false, all = false)
+ read_all_cache_data if all
+
results = []
cache_data.map do |source_uri, sic_entry|
@@ -164,68 +279,75 @@ class Gem::SourceInfoCache
results
end
- # Mark the cache as updated (i.e. dirty).
- def update
- @dirty = true
+ ##
+ # Set the source info cache data directly. This is mainly used for unit
+ # testing when we don't want to read a file system to grab the cached source
+ # index information. The +hash+ should map a source URL into a
+ # SourceInfoCacheEntry.
+
+ def set_cache_data(hash)
+ @cache_data = hash
+ update
end
+ ##
# The name of the system cache file.
+
def system_cache_file
self.class.system_cache_file
end
- # The name of the system cache file. (class method)
- def self.system_cache_file
- @system_cache_file ||= Gem.default_system_source_cache_dir
+ ##
+ # Determine if +path+ is a candidate for a cache file. Returns +path+ if
+ # it is, nil if not.
+
+ def try_file(path)
+ return path if File.writable? path
+ return nil if File.exist? path
+
+ dir = File.dirname path
+
+ unless File.exist? dir then
+ begin
+ FileUtils.mkdir_p dir
+ rescue RuntimeError, SystemCallError
+ return nil
+ end
+ end
+
+ if File.writable? dir then
+ open path, "wb" do |io| io.write Marshal.dump({}) end
+ return path
+ end
+
+ nil
+ end
+
+ ##
+ # Mark the cache as updated (i.e. dirty).
+
+ def update
+ @dirty = true
end
+ ##
# The name of the user cache file.
+
def user_cache_file
self.class.user_cache_file
end
- # The name of the user cache file. (class method)
- def self.user_cache_file
- @user_cache_file ||=
- ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
- end
-
+ ##
# Write data to the proper cache.
+
def write_cache
- open cache_file, "wb" do |f|
- f.write Marshal.dump(cache_data)
+ open cache_file, 'wb' do |io|
+ io.write Marshal.dump(cache_data)
end
- end
- # Set the source info cache data directly. This is mainly used for unit
- # testing when we don't want to read a file system to grab the cached source
- # index information. The +hash+ should map a source URL into a
- # SourceInfoCacheEntry.
- def set_cache_data(hash)
- @cache_data = hash
- update
- end
-
- private
-
- # Determine if +fn+ is a candidate for a cache file. Return fn if
- # it is. Return nil if it is not.
- def try_file(fn)
- return fn if File.writable?(fn)
- return nil if File.exist?(fn)
- dir = File.dirname(fn)
- unless File.exist? dir then
- begin
- FileUtils.mkdir_p(dir)
- rescue RuntimeError, SystemCallError
- return nil
- end
+ open latest_cache_file, 'wb' do |io|
+ io.write Marshal.dump(latest_cache_data)
end
- if File.writable?(dir)
- File.open(fn, "wb") { |f| f << Marshal.dump({}) }
- return fn
- end
- nil
end
end
diff --git a/lib/rubygems/source_info_cache_entry.rb b/lib/rubygems/source_info_cache_entry.rb
index 02e03ca9db..c3f75e5b99 100644
--- a/lib/rubygems/source_info_cache_entry.rb
+++ b/lib/rubygems/source_info_cache_entry.rb
@@ -3,24 +3,31 @@ require 'rubygems/source_index'
require 'rubygems/remote_fetcher'
##
-# Entrys held by a SourceInfoCache.
+# Entries held by a SourceInfoCache.
class Gem::SourceInfoCacheEntry
+ ##
# The source index for this cache entry.
+
attr_reader :source_index
+ ##
# The size of the of the source entry. Used to determine if the
# source index has changed.
+
attr_reader :size
+ ##
# Create a cache entry.
+
def initialize(si, size)
@source_index = si || Gem::SourceIndex.new({})
@size = size
+ @all = false
end
- def refresh(source_uri)
+ def refresh(source_uri, all)
begin
marshal_uri = URI.join source_uri.to_s, "Marshal.#{Gem.marshal_version}"
remote_size = Gem::RemoteFetcher.fetcher.fetch_size marshal_uri
@@ -29,9 +36,12 @@ class Gem::SourceInfoCacheEntry
remote_size = Gem::RemoteFetcher.fetcher.fetch_size yaml_uri
end
- return false if @size == remote_size # TODO Use index_signature instead of size?
- updated = @source_index.update source_uri
+ # TODO Use index_signature instead of size?
+ return false if @size == remote_size and @all
+
+ updated = @source_index.update source_uri, all
@size = remote_size
+ @all = all
updated
end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index be03150c96..de37a08b60 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -13,7 +13,7 @@ require 'rubygems/platform'
if RUBY_VERSION < '1.9' then
def Time.today
t = Time.now
- t - ((t.to_i + t.gmt_offset) % 86400)
+ t - ((t.to_f + t.gmt_offset) % 86400)
end unless defined? Time.today
end
# :startdoc:
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index 1c979d8573..e2b5e5372b 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -12,7 +12,7 @@ require 'rubygems/user_interaction'
##
# An Uninstaller.
-#
+
class Gem::Uninstaller
include Gem::UserInteraction
@@ -21,8 +21,8 @@ class Gem::Uninstaller
# Constructs an Uninstaller instance
#
# gem:: [String] The Gem name to uninstall
- #
- def initialize(gem, options)
+
+ def initialize(gem, options = {})
@gem = gem
@version = options[:version] || Gem::Requirement.default
gem_home = options[:install_dir] || Gem.dir
@@ -30,12 +30,13 @@ class Gem::Uninstaller
@force_executables = options[:executables]
@force_all = options[:all]
@force_ignore = options[:ignore]
+ @bin_dir = options[:bin_dir]
end
##
# Performs the uninstall of the Gem. This removes the spec, the
# Gem directory, and the cached .gem file,
- #
+
def uninstall
list = Gem.source_index.search(/^#{@gem}$/, @version)
@@ -66,18 +67,14 @@ class Gem::Uninstaller
end
##
- # Remove executables and batch files (windows only) for the gem as
- # it is being installed
- #
- # gemspec::[Specification] the gem whose executables need to be removed.
- #
+ # Removes installed executables and batch files (windows only) for
+ # +gemspec+.
+
def remove_executables(gemspec)
return if gemspec.nil?
if gemspec.executables.size > 0 then
- bindir = Gem.bindir @gem_home
-
- raise Gem::FilePermissionError, bindir unless File.writable? bindir
+ bindir = @bin_dir ? @bin_dir : (Gem.bindir @gem_home)
list = Gem.source_index.search(gemspec.name).delete_if { |spec|
spec.version == gemspec.version
@@ -93,14 +90,19 @@ class Gem::Uninstaller
return if executables.size == 0
- answer = @force_executables || ask_yes_no(
- "Remove executables:\n" \
- "\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?",
- true) # " # appease ruby-mode - don't ask
+ answer = if @force_executables.nil? then
+ ask_yes_no("Remove executables:\n" \
+ "\t#{gemspec.executables.join(", ")}\n\nin addition to the gem?",
+ true) # " # appease ruby-mode - don't ask
+ else
+ @force_executables
+ end
unless answer then
say "Executables and scripts will remain installed."
else
+ raise Gem::FilePermissionError, bindir unless File.writable? bindir
+
gemspec.executables.each do |exe_name|
say "Removing #{exe_name}"
FileUtils.rm_f File.join(bindir, exe_name)
@@ -110,23 +112,22 @@ class Gem::Uninstaller
end
end
+ ##
+ # Removes all gems in +list+.
#
- # list:: the list of all gems to remove
- #
- # Warning: this method modifies the +list+ parameter. Once it has
- # uninstalled a gem, it is removed from that list.
- #
+ # NOTE: removes uninstalled gems from +list+.
+
def remove_all(list)
- list.dup.each { |gem| remove(gem, list) }
+ list.dup.each { |spec| remove spec, list }
end
- #
+ ##
# spec:: the spec of the gem to be uninstalled
# list:: the list of all such gems
#
# Warning: this method modifies the +list+ parameter. Once it has
# uninstalled a gem, it is removed from that list.
- #
+
def remove(spec, list)
unless dependencies_ok? spec then
raise Gem::DependencyRemovalException,
@@ -134,10 +135,11 @@ class Gem::Uninstaller
end
unless path_ok? spec then
- alert("In order to remove #{spec.name}, please execute:\n" \
- "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
- raise Gem::GemNotInHomeException,
+ e = Gem::GemNotInHomeException.new \
"Gem is not installed in directory #{@gem_home}"
+ e.spec = spec
+
+ raise e
end
raise Gem::FilePermissionError, spec.installation_path unless
@@ -182,8 +184,8 @@ class Gem::Uninstaller
def dependencies_ok?(spec)
return true if @force_ignore
- srcindex = Gem::SourceIndex.from_installed_gems
- deplist = Gem::DependencyList.from_source_index srcindex
+ source_index = Gem::SourceIndex.from_installed_gems
+ deplist = Gem::DependencyList.from_source_index source_index
deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec)
end
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 4970f33c00..ffccb60314 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -68,7 +68,7 @@ module Gem
include DefaultUserInteraction
[
:choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning,
- :alert_error, :terminate_interaction!, :terminate_interaction
+ :alert_error, :terminate_interaction
].each do |methname|
class_eval %{
def #{methname}(*args)
@@ -182,16 +182,10 @@ module Gem
ask(question) if question
end
- # Terminate the application immediately without running any exit
- # handlers.
- def terminate_interaction!(status=-1)
- exit!(status)
- end
-
# Terminate the appliation normally, running any exit handlers
# that might have been defined.
- def terminate_interaction(status=0)
- exit(status)
+ def terminate_interaction(status = 0)
+ raise Gem::SystemExitException, status
end
# Return a progress reporter object