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 ()
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
-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
@@ -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).
#
- # 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:
#
- # GEM_SKIP=libA:libB ruby-I../libA -I../libB ./mycode.rb
#
- # gem:: [String or Gem::Dependency] The gem name or dependency
- # instance.
#
- # version_requirement:: [default=">= 0"] The version
- # requirement.
#
- # return:: [Boolean] true if the Gem is loaded, otherwise false.
#
- # raises:: [Gem::LoadError] if Gem cannot be found, is listed in
- # GEM_SKIP, or version requirement not met.
#
- 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
- def active_gem_with_options(gem_name, version_requirements, options={})
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)
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"]
)
MUTEX = Mutex.new
RubyGemsPackageVersion = RubyGemsVersion
- DIRECTORIES = %w[cache doc gems specifications] unless defined?(DIRECTORIES)
@@source_index = nil
@@win_platform = nil
@@ -103,10 +101,129 @@ module Gem
@ruby = nil
@sources = []
# 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}"
end
##
- # The directory prefix this RubyGems was installed at.
- def self.prefix
- prefix = File.dirname File.expand_path(__FILE__)
- if prefix == ConfigMap[:sitelibdir] then
- nil
- else
- File.dirname prefix
- end
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
end
##
- # An Array of Regexps that match windows ruby platforms.
- WIN_PATTERNS = [
- /bccwin/i,
- /cygwin/i,
- /djgpp/i,
- /mingw/i,
- /mswin/i,
- /wince/i,
- ]
##
- # 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
- # 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'
- 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
- # 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
- @sources
end
- # Provide an alias for the old name.
- alias cache source_index
- # 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
- # 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
- if defined? RUBY_FRAMEWORK_VERSION then # mac framework support
- '/usr/bin'
- else # generic install
- ConfigMap[:bindir]
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))
end
- @gem_path
end
- # The home directory for the user.
- def user_home
- @user_home ||= find_home
- end
- # Return the path to standard location of the users .gemrc file.
- def config_file
- File.join(Gem.user_home, '.gemrc')
end
- # The standard configuration object for gems.
- def configuration
- return @configuration if @configuration
- require 'rubygems/config_file'
- @configuration = Gem::ConfigFile.new []
- end
- # Use the given configuration object (which implements the
- # ConfigFile protocol) as the standard configuration object.
- def configuration=(config)
- @configuration = config
- end
- # 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
- # Return the searcher object to search for matching gems.
- def searcher
- MUTEX.synchronize do
- @searcher ||= Gem::GemPathSearcher.new
- end
- end
- # 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
- @ruby
- end
- # 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
- index
- end
- # 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
- unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
- 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]
- # 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
- # new load
- spec = matches.last
- if spec.loaded?
- return false unless autorequire
- result = spec.autorequire ? require(spec.autorequire) : false
- return result || false
end
- spec.loaded = true
- @loaded_specs[spec.name] = spec
- # Load dependent gems first
- spec.dependencies.each do |dep_gem|
- activate(dep_gem, autorequire)
- 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
- # Now autorequire
- if autorequire && spec.autorequire then # DEPRECATED
- Array(spec.autorequire).each do |a_lib|
- require a_lib
- end
- end
- return true
- end
- # Report a load error during activation. The message of load
- # error depends on whether it was a version mismatch or if there
- # are not gems of any version by the requested name.
- def report_activate_error(gem)
- matches = Gem.source_index.find_name(gem.name)
- if matches.empty? then
- error = Gem::LoadError.new(
"Could not find RubyGem #{gem.name} (#{gem.version_requirements})\n")
- 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
- # 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
- 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
end
- def suffixes
- ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
- end
- def suffix_pattern
- @suffix_pattern ||= "{#{suffixes.join(',')}}"
end
- # manage_gems is useless and deprecated. Don't call it anymore. This
- # will warn in two releases.
- def manage_gems
- # do nothing
- end
- private
- # Return all the partial paths in the given +gemdir+.
- def all_partials(gemdir)
- Dir[File.join(gemdir, 'gems/*')]
- 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] }
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)
end
end
end
- # Set the Gem home directory (as reported by +dir+).
- def set_home(home)
- @gem_home = home
- ensure_gem_subdirectories(@gem_home)
- 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
- # 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
begin
- File.expand_path("~")
- rescue StandardError => ex
- if File::ALT_SEPARATOR
- "C:/"
- else
- "/"
- end
end
end
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
@@ -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}
end
end
end
@@ -123,6 +123,7 @@ module Gem
end
private
def load_and_instantiate(command_name)
command_name = command_name.to_s
retried = false
@@ -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',
'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
- 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..."
- srcindex = Gem::SourceIndex.from_installed_gems
- primary_gems = {}
- 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
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
- }
- uninstall_command = Gem::CommandManager.instance['uninstall']
- deplist = DependencyList.new
- gems_to_cleanup.uniq.each do |spec| deplist.add(spec) end
- 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}"
- options[:args] = [spec.name]
- options[:version] = "= #{spec.version}"
- options[:executables] = true
- uninstall_command.merge_options(options)
- begin
- uninstall_command.execute
- rescue Gem::DependencyRemovalException => ex
- say "Unable to uninstall #{spec.full_name} ... continuing with remaining gems"
- end
- end
end
-
- say "Clean Up Complete"
end
end
-
end
end
@@ -25,19 +25,18 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
def execute
out = ''
arg = options[:args][0]
- if begins?("packageversion", arg) then
out << Gem::RubyGemsPackageVersion
- elsif begins?("version", arg) then
out << Gem::RubyGemsVersion
- elsif begins?("gemdir", arg) then
out << Gem.dir
- elsif begins?("gempath", arg) then
- out << Gem.path.join("\n")
- elsif begins?("remotesources", arg) then
out << Gem.sources.join("\n")
- elsif arg then
- fail Gem::CommandLineError, "Unknown enviroment option [#{arg}]"
- else
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
end
say out
true
@@ -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
end
- say "Downloaded #{gem_file}"
end
end
@@ -62,13 +62,15 @@ class Gem::Commands::InstallCommand < Gem::Command
:install_dir => options[:install_dir],
:security_policy => options[:security_policy],
:wrappers => options[:wrappers],
}
get_all_gem_names.each do |gem_name|
begin
- inst = Gem::DependencyInstaller.new gem_name, options[:version],
- install_options
- inst.install
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}"
rescue Gem::GemNotFoundException => e
alert_error e.message
# 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
end
end
@@ -6,10 +6,8 @@ module Gem
class ListCommand < QueryCommand
def initialize
- super(
- 'list',
- 'Display all gems whose name starts with STRING'
- )
remove_option('--name-matches')
end
@@ -2,7 +2,7 @@ require 'yaml'
require 'zlib'
require 'rubygems/command'
-require 'rubygems/gem_open_uri'
class Gem::Commands::MirrorCommand < Gem::Command
@@ -1,15 +1,25 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
class Gem::Commands::QueryCommand < Gem::Command
include Gem::LocalRemoteOptions
def initialize(name = 'query',
summary = 'Query gem information in local or remote repositories')
super name, summary,
- :name => /.*/, :domain => :local, :details => false, :versions => true
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_local_remote_options
end
def defaults_str # :nodoc:
- "--local --name-matches '.*' --no-details --versions"
end
def execute
name = options[:name]
if local? then
say
say "*** LOCAL GEMS ***"
say
- output_query_results Gem.cache.search(name)
end
if remote? then
say
say "*** REMOTE GEMS ***"
say
- output_query_results Gem::SourceInfoCache.search(name)
end
end
private
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
@@ -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)
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
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.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
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)
say "*** Removed #{desc} source cache ***"
- elsif ! File.writable?(fn)
say "*** Unable to remove #{desc} source cache (write protected) ***"
else
say "*** Unable to remove #{desc} source cache ***"
@@ -3,6 +3,7 @@ require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
require 'rubygems/source_info_cache'
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]))
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
@@ -35,6 +35,11 @@ module Gem
options[:install_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
end
end
end
@@ -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)
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)
else
- return nil
end
end
@@ -1,8 +1,10 @@
require 'rubygems/command'
require 'rubygems/install_update_options'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
require 'rubygems/version_option'
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..."
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..."
end
- hig = highest_installed_gems = {}
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(//)
- gems_to_update = if options[:args].empty? then
- which_to_update(highest_installed_gems, remote_gemspecs)
- else
- options[:args]
- end
- options[:domain] = :remote # install from remote source
- # HACK use the real API
- install_command = Gem::CommandManager.instance['install']
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
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 ', '}"
end
end
end
@@ -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_original_require path
else
raise load_error
@@ -11,6 +11,9 @@ module Gem
if defined? RUBY_FRAMEWORK_VERSION then
File.join File.dirname(ConfigMap[:sitedir]), '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']
end
# The default system-wide source info cache directory.
@@ -22,8 +22,7 @@ class Gem::DependencyInstaller
}
##
- # Creates a new installer instance that will install +gem_name+ using
- # version requirement +version+ and +options+.
#
# 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 = {})
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]
@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"
- 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
##
@@ -208,9 +126,57 @@ class Gem::DependencyInstaller
@gems_to_install = dependency_list.dependency_order.reverse
end
##
# Installs the gem and all its dependencies.
- def install
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
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
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
spec = inst.install
@@ -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::DocumentError < Gem::Exception; end
@@ -65,3 +68,17 @@ class Gem::RemoteSourceException < Gem::Exception; end
class Gem::VerificationError < Gem::Exception; end
@@ -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?
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.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]
end
end
format
end
@@ -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
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,
"Generating index for #{gem_file_list.size} gems in #{@dest_directory}",
"Loaded all gems"
- gem_file_list.each do |gemfile|
- if File.size(gemfile.to_s) == 0 then
- alert_warning "Skipping zero-length gem: #{gemfile}"
- next
- end
-
- begin
- spec = Gem::Format.from_file_by_path(gemfile).spec
-
- unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
- alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{spec.original_name})"
next
end
- abbreviate spec
- sanitize spec
- @master_index.add spec
- @quick_index.add spec
- @marshal_index.add spec
- progress.updated spec.original_name
- 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)"
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.each do |file|
- relative_name = file[/\A#{Regexp.escape @directory}.(.*)/, 1]
- dest_name = File.join @dest_directory, relative_name
- FileUtils.rm_rf dest_name, :verbose => verbose
- FileUtils.mv file, @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'
@@ -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
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 })
File.open "#{filename}.#{ext}", "wb" do |file|
file.write zipped
end
@@ -0,0 +1,35 @@
@@ -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
- @files << compressed_file_name
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
if data != unzip(compressed_data) then
- fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}"
end
end
@@ -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)
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
end
def add(spec)
@@ -25,6 +25,12 @@ module Gem::InstallUpdateOptions
options[:install_dir] = File.expand_path(value)
end
add_option(:"Install/Update", '-d', '--[no-]rdoc',
'Generate RDoc documentation for the gem on',
'install') do |value, options|
@@ -63,7 +63,8 @@ class Gem::Installer
:force => false,
:install_dir => Gem.dir,
:exec_format => false,
- :env_shebang => false
}.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]
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
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
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"
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
GOTO :EOF
:WinNT
-"%~dp0ruby.exe" "%~dpn0" %*
TEXT
end
@@ -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 uned (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
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
@@ -0,0 +1,24 @@
@@ -0,0 +1,245 @@
@@ -0,0 +1,219 @@
@@ -0,0 +1,143 @@
@@ -0,0 +1,86 @@
@@ -0,0 +1,99 @@
@@ -0,0 +1,180 @@
@@ -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
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)
@proxy_uri =
case proxy
when :no_proxy then nil
@@ -38,6 +43,65 @@ class Gem::RemoteFetcher
end
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}"
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}"
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)
if file_uri?(uri)
open(get_file_uri_path(uri), &block)
else
- connection_options = {
- "User-Agent" => "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}"
- }
- 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)||'']
end
- uri = URI.parse uri unless URI::Generic === uri
unless uri.nil? || uri.user.nil? || uri.user.empty? then
- connection_options[:http_basic_authentication] =
- [unescape(uri.user), unescape(uri.password)]
end
- open(uri, connection_options, &block)
end
end
@@ -16,6 +16,8 @@ class Gem::Requirement
include Comparable
OPS = {
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
@@ -2,5 +2,5 @@
# This file is auto-generated by build scripts.
# See: rake update_version
module Gem
- RubyGemsVersion = '1.0.1'
end
@@ -4,6 +4,7 @@
# See LICENSE.txt for permissions.
#++
require 'rubygems/gem_openssl'
# = Signed Gems README
@@ -1,7 +1,7 @@
require 'webrick'
-require 'rdoc/template'
require 'yaml'
require 'zlib'
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>
-
</div>
- </div>
</div>
-<div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
-</div>
-</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)
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
end
paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
@@ -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.
- #
- class SourceIndex
- include Enumerable
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") }
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}'"
end
- return nil
end
-
end
- # Instance Methods -----------------------------------------------
- # 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
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.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]
- next unless prev_ver.nil? or curr_ver >= prev_ver
- if prev_ver.nil? or curr_ver > prev_ver then
- result[name].clear
- latest[name] = curr_ver
- end
- result[name] << spec
end
- result.values.flatten
- end
- # Add a gem specification to the source index.
- def add_spec(gem_spec)
- @gems[gem_spec.full_name] = gem_spec
end
- # Remove a gem specification named +full_name+.
- def remove_spec(full_name)
- @gems.delete(full_name)
- end
- # Iterate over the specifications in the source index.
- def each(&block) # :yields: gem.full_name, gem
- @gems.each(&block)
- end
- # The gem specification given a full gem spec name.
- def specification(full_name)
- @gems[full_name]
- end
- # The signature for the source index. Changes in the signature
- # indicate a change in the index.
- def index_signature
- require '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'
- 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
- unless Gem::Requirement === version_requirement then
- version_requirement = Gem::Requirement.create version_requirement
end
- specs = @gems.values.select do |spec|
- spec.name =~ gem_pattern and
- version_requirement.satisfied_by? spec.version
- end
- if only_platform then
- specs = specs.select do |spec|
- Gem::Platform.match spec.platform
- end
- end
- specs.sort_by { |s| s.sort_obj }
- end
- # 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.
- #
- def outdated
- dep = Gem::Dependency.new '', Gem::Requirement.default
- remotes = Gem::SourceInfoCache.search dep, true
- outdateds = []
- latest_specs.each do |local|
- name = local.name
- remote = remotes.select { |spec| spec.name == name }.
- sort_by { |spec| spec.version.to_ints }.
- last
- outdateds << name if remote and local.version < remote.version
- end
- outdateds
end
- def update(source_uri)
- use_incremental = false
- begin
- gem_names = fetch_quick_index source_uri
- remove_extra gem_names
- missing_gems = find_missing gem_names
- return false if missing_gems.size.zero?
- say "missing #{missing_gems.size} gems" if
- missing_gems.size > 0 and Gem.configuration.really_verbose
- 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
- 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
- true
- end
- def ==(other) # :nodoc:
- self.class === other and @gems == other.gems
end
- def dump
- Marshal.dump(self)
end
- protected
- attr_reader :gems
- private
- def fetcher
- require 'rubygems/remote_fetcher'
- Gem::RemoteFetcher.fetcher
- end
- def fetch_index_from(source_uri)
- @fetch_error = nil
- 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
end
end
- nil
end
- 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
- # 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
raise Gem::OperationNotSupportedError,
- "No quick index found: " + ex.message
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
- 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
- # 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
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,
"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" \
"\t#{@fetch_error.message}"
- else
- add_spec gemspec
- progress.updated spec_name
- end
- @fetch_error = nil
end
- progress.done
- progress.count
end
-
end
- # Cache is an alias for SourceIndex to allow older YAMLized source
- # index objects to load properly.
Cache = SourceIndex
end
@@ -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
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
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)
end
def initialize # :nodoc:
@cache_data = nil
@cache_file = nil
@dirty = false
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
- sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
- sice.refresh url # HACK may be unnecessary, see ::cache and #refresh
- @cache_data[url] = sice
@cache_data
end
- def reset_cache_data
- @cache_data = {}
- end
- # 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
# 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
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
end
flush
end
- # Searches all source indexes for +pattern+.
- def search(pattern, platform_only = false)
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)
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
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
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)
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
end
- if File.writable?(dir)
- File.open(fn, "wb") { |f| f << Marshal.dump({}) }
- return fn
- end
- nil
end
end
@@ -3,24 +3,31 @@ require 'rubygems/source_index'
require 'rubygems/remote_fetcher'
##
-# Entrys 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
end
- def refresh(source_uri)
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
@size = remote_size
updated
end
@@ -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)
end unless defined? Time.today
end
# :startdoc:
@@ -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)
@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]
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.
- #
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
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
unless answer then
say "Executables and scripts will remain installed."
else
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
#
- # 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.
- #
def remove_all(list)
- list.dup.each { |gem| remove(gem, 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,
"Gem is not installed in directory #{@gem_home}"
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
deplist.ok_to_remove?(spec.full_name) || ask_if_ok(spec)
end
@@ -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
].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)
end
# Return a progress reporter object