1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Import RubyGems trunk revision 1493.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@13862 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2007-11-10 07:48:56 +00:00
parent 7a4aad7535
commit fbf59bdbea
144 changed files with 21330 additions and 0 deletions

View file

@ -1,3 +1,15 @@
Sat Nov 10 16:37:07 2007 Eric Hodel <drbrain@segment7.net>
* lib/rubygems: Import RubyGems revision 1493.
* lib/rubygems.rb: ditto.
* lib/ubygems.rb: ditto.
* lib/rbconfig/datadir.rb: ditto.
* test/rubygems: ditto.
Sat Nov 10 16:34:21 2007 Eric Hodel <drbrain@segment7.net>
* lib/soap/property.rb: Don't override Enumerable#inject for 1.9.

24
lib/rbconfig/datadir.rb Normal file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
module Config
# Only define datadir if it doesn't already exist.
unless Config.respond_to?(:datadir)
# Return the path to the data directory associated with the given
# package name. Normally this is just
# "#{Config::CONFIG['datadir']}/#{package_name}", but may be
# modified by packages like RubyGems to handle versioned data
# directories.
def Config.datadir(package_name)
File.join(CONFIG['datadir'], package_name)
end
end
end

561
lib/rubygems.rb Normal file
View file

@ -0,0 +1,561 @@
# -*- ruby -*-
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rbconfig'
require 'rubygems/rubygems_version'
require 'thread'
module Gem
class LoadError < ::LoadError
attr_accessor :name, :version_requirement
end
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 intsalled 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
# Same as the +gem+ command, but will also require a file if the gem
# provides an auto-required file name.
#
# DEPRECATED! Use +gem+ instead.
#
def require_gem(gem_name, *version_requirements)
file, lineno = location_of_caller
warn "#{file}:#{lineno}:Warning: require_gem is obsolete. Use gem instead."
active_gem_with_options(gem_name, version_requirements, :auto_require=>true)
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
MUTEX = Mutex.new
RubyGemsPackageVersion = RubyGemsVersion
DIRECTORIES = %w[cache doc gems specifications]
@@source_index = nil
@@win_platform = nil
@configuration = nil
@loaded_specs = {}
@platforms = nil
@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
@@source_index = nil
MUTEX.synchronize do
@searcher = nil
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 == Config::CONFIG['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 = [/mswin/i, /mingw/i, /bccwin/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
# An Array of the default sources that come with RubyGems.
def default_sources
%w[http://gems.rubyforge.org]
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
File.join(File.dirname(Config::CONFIG["sitedir"]),
File.basename(Config::CONFIG["bindir"]))
else # generic install
Config::CONFIG['bindir']
end
end
# List of directory paths to search for Gems.
#
# return:: [List<String>] List of directory paths.
#
def path
@gem_path ||= nil
set_paths(ENV['GEM_PATH']) unless @gem_path
@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(Config::CONFIG['bindir'],
Config::CONFIG['ruby_install_name'])
@ruby << Config::CONFIG['EXEEXT']
end
@ruby
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 = Config::CONFIG['sitelibdir']
# gem directories must come after -I and ENV['RUBYLIB']
$:.insert($:.index(sitelibdir), *require_paths)
# 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
public
# Default home directory path to be used if an alternate value is
# not specified in the environment.
def default_dir
if defined? RUBY_FRAMEWORK_VERSION
return File.join(File.dirname(Config::CONFIG["sitedir"]), "Gems")
else
File.join(Config::CONFIG['libdir'], 'ruby', 'gems', Config::CONFIG['ruby_version'])
end
end
end
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
# Return the path to the data directory associated with the named
# package. If the package is loaded as a gem, return the gem
# specific data directory. Otherwise return a path to the share
# area as define by "#{Config::CONFIG['datadir']}/#{package_name}".
def datadir(package_name)
Gem.datadir(package_name) || Config.gem_original_datadir(package_name)
end
end
end
require 'rubygems/exceptions'
require 'rubygems/version'
require 'rubygems/requirement'
require 'rubygems/dependency'
require 'rubygems/gem_path_searcher' # Needed for Kernel#gem
require 'rubygems/source_index' # Needed for Kernel#gem
require 'rubygems/platform'
require 'rubygems/builder' # HACK: Needed for rake's package task.
if RUBY_VERSION < '1.9' then
require 'rubygems/custom_require'
end

81
lib/rubygems/builder.rb Normal file
View file

@ -0,0 +1,81 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
module Gem
##
# The Builder class processes RubyGem specification files
# to produce a .gem file.
#
class Builder
include UserInteraction
##
# Constructs a builder instance for the provided specification
#
# spec:: [Gem::Specification] The specification instance
#
def initialize(spec)
require "yaml"
require "rubygems/package"
require "rubygems/security"
@spec = spec
end
##
# Builds the gem from the specification. Returns the name of the file
# written.
#
def build
@spec.mark_version
@spec.validate
@signer = sign
write_package
say success
@spec.file_name
end
def success
<<-EOM
Successfully built RubyGem
Name: #{@spec.name}
Version: #{@spec.version}
File: #{@spec.full_name+'.gem'}
EOM
end
private
def sign
# if the signing key was specified, then load the file, and swap
# to the public key (TODO: we should probably just omit the
# signing key in favor of the signing certificate, but that's for
# the future, also the signature algorithm should be configurable)
signer = nil
if @spec.respond_to?(:signing_key) && @spec.signing_key
signer = Gem::Security::Signer.new(@spec.signing_key, @spec.cert_chain)
@spec.signing_key = nil
@spec.cert_chain = signer.cert_chain.map { |cert| cert.to_s }
end
signer
end
def write_package
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
end
end
end

406
lib/rubygems/command.rb Normal file
View file

@ -0,0 +1,406 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'optparse'
require 'rubygems/user_interaction'
module Gem
# Base class for all Gem commands. When creating a new gem command, define
# #arguments, #defaults_str, #description and #usage (as appropriate).
class Command
include UserInteraction
# The name of the command.
attr_reader :command
# The options for the command.
attr_reader :options
# The default options for the command.
attr_accessor :defaults
# The name of the command for command-line invocation.
attr_accessor :program_name
# A short description of the command.
attr_accessor :summary
# Initializes a generic gem command named +command+. +summary+ is a short
# description displayed in `gem help commands`. +defaults+ are the
# default options. Defaults should be mirrored in #defaults_str, unless
# there are none.
#
# Use add_option to add command-line switches.
def initialize(command, summary=nil, defaults={})
@command = command
@summary = summary
@program_name = "gem #{command}"
@defaults = defaults
@options = defaults.dup
@option_groups = Hash.new { |h,k| h[k] = [] }
@parser = nil
@when_invoked = nil
end
# True if +long+ begins with the characters from +short+.
def begins?(long, short)
return false if short.nil?
long[0, short.length] == short
end
# Override to provide command handling.
def execute
fail "Generic command has no actions"
end
# Get all gem names from the command line.
def get_all_gem_names
args = options[:args]
if args.nil? or args.empty? then
raise Gem::CommandLineError,
"Please specify at least one gem name (e.g. gem build GEMNAME)"
end
gem_names = args.select { |arg| arg !~ /^-/ }
end
# Get the single gem name from the command line. Fail if there is no gem
# name or if there is more than one gem name given.
def get_one_gem_name
args = options[:args]
if args.nil? or args.empty? then
raise Gem::CommandLineError,
"Please specify a gem name on the command line (e.g. gem build GEMNAME)"
end
if args.size > 1 then
raise Gem::CommandLineError,
"Too many gem names (#{args.join(', ')}); please specify only one"
end
args.first
end
# Get a single optional argument from the command line. If more than one
# argument is given, return only the first. Return nil if none are given.
def get_one_optional_argument
args = options[:args] || []
args.first
end
# Override to provide details of the arguments a command takes.
# It should return a left-justified string, one argument per line.
def arguments
""
end
# Override to display the default values of the command
# options. (similar to +arguments+, but displays the default
# values).
def defaults_str
""
end
# Override to display a longer description of what this command does.
def description
nil
end
# Override to display the usage for an individual gem command.
def usage
program_name
end
# Display the help message for the command.
def show_help
parser.program_name = usage
say parser
end
# Invoke the command with the given list of arguments.
def invoke(*args)
handle_options(args)
if options[:help]
show_help
elsif @when_invoked
@when_invoked.call(options)
else
execute
end
end
# Call the given block when invoked.
#
# Normal command invocations just executes the +execute+ method of
# the command. Specifying an invocation block allows the test
# methods to override the normal action of a command to determine
# that it has been invoked correctly.
def when_invoked(&block)
@when_invoked = block
end
# Add a command-line option and handler to the command.
#
# See OptionParser#make_switch for an explanation of +opts+.
#
# +handler+ will be called with two values, the value of the argument and
# the options hash.
def add_option(*opts, &handler) # :yields: value, options
group_name = Symbol === opts.first ? opts.shift : :options
@option_groups[group_name] << [opts, handler]
end
# Remove previously defined command-line argument +name+.
def remove_option(name)
@option_groups.each do |_, option_list|
option_list.reject! { |args, _| args.any? { |x| x =~ /^#{name}/ } }
end
end
# Merge a set of command options with the set of default options
# (without modifying the default option hash).
def merge_options(new_options)
@options = @defaults.clone
new_options.each do |k,v| @options[k] = v end
end
# True if the command handles the given argument list.
def handles?(args)
begin
parser.parse!(args.dup)
return true
rescue
return false
end
end
# Handle the given list of arguments by parsing them and recording
# the results.
def handle_options(args)
args = add_extra_args(args)
@options = @defaults.clone
parser.parse!(args)
@options[:args] = args
end
def add_extra_args(args)
result = []
s_extra = Command.specific_extra_args(@command)
extra = Command.extra_args + s_extra
while ! extra.empty?
ex = []
ex << extra.shift
ex << extra.shift if extra.first.to_s =~ /^[^-]/
result << ex if handles?(ex)
end
result.flatten!
result.concat(args)
result
end
private
# Create on demand parser.
def parser
create_option_parser if @parser.nil?
@parser
end
def create_option_parser
@parser = OptionParser.new
@parser.separator("")
regular_options = @option_groups.delete :options
configure_options "", regular_options
@option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list|
configure_options group_name, option_list
end
configure_options "Common", Command.common_options
@parser.separator("")
unless arguments.empty?
@parser.separator(" Arguments:")
arguments.split(/\n/).each do |arg_desc|
@parser.separator(" #{arg_desc}")
end
@parser.separator("")
end
@parser.separator(" Summary:")
wrap(@summary, 80 - 4).split("\n").each do |line|
@parser.separator(" #{line.strip}")
end
if description then
formatted = description.split("\n\n").map do |chunk|
wrap(chunk, 80 - 4)
end.join("\n")
@parser.separator ""
@parser.separator " Description:"
formatted.split("\n").each do |line|
@parser.separator " #{line.rstrip}"
end
end
unless defaults_str.empty?
@parser.separator("")
@parser.separator(" Defaults:")
defaults_str.split(/\n/).each do |line|
@parser.separator(" #{line}")
end
end
end
def configure_options(header, option_list)
return if option_list.nil? or option_list.empty?
header = header.to_s.empty? ? '' : "#{header} "
@parser.separator " #{header}Options:"
option_list.each do |args, handler|
dashes = args.select { |arg| arg =~ /^-/ }
@parser.on(*args) do |value|
handler.call(value, @options)
end
end
@parser.separator ''
end
# Wraps +text+ to +width+
def wrap(text, width)
text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
end
##################################################################
# Class methods for Command.
class << self
def common_options
@common_options ||= []
end
def add_common_option(*args, &handler)
Gem::Command.common_options << [args, handler]
end
def extra_args
@extra_args ||= []
end
def extra_args=(value)
case value
when Array
@extra_args = value
when String
@extra_args = value.split
end
end
# Return an array of extra arguments for the command. The extra
# arguments come from the gem configuration file read at program
# startup.
def specific_extra_args(cmd)
specific_extra_args_hash[cmd]
end
# Add a list of extra arguments for the given command. +args+
# may be an array or a string to be split on white space.
def add_specific_extra_args(cmd,args)
args = args.split(/\s+/) if args.kind_of? String
specific_extra_args_hash[cmd] = args
end
# Accessor for the specific extra args hash (self initializing).
def specific_extra_args_hash
@specific_extra_args_hash ||= Hash.new do |h,k|
h[k] = Array.new
end
end
end
# ----------------------------------------------------------------
# Add the options common to all commands.
add_common_option('-h', '--help',
'Get help on this command') do
|value, options|
options[:help] = true
end
add_common_option('-V', '--[no-]verbose',
'Set the verbose level of output') do |value, options|
# Set us to "really verbose" so the progess meter works
if Gem.configuration.verbose and value then
Gem.configuration.verbose = 1
else
Gem.configuration.verbose = value
end
end
add_common_option('-q', '--quiet', 'Silence commands') do |value, options|
Gem.configuration.verbose = false
end
# Backtrace and config-file are added so they show up in the help
# commands. Both options are actually handled before the other
# options get parsed.
add_common_option('--config-file FILE',
"Use this config file instead of default") do
end
add_common_option('--backtrace',
'Show stack backtrace on errors') do
end
add_common_option('--debug',
'Turn on Ruby debugging') do
end
# :stopdoc:
HELP = %{
RubyGems is a sophisticated package manager for Ruby. This is a
basic help message containing pointers to more information.
Usage:
gem -h/--help
gem -v/--version
gem command [arguments...] [options...]
Examples:
gem install rake
gem list --local
gem build package.gemspec
gem help install
Further help:
gem help commands list all 'gem' commands
gem help examples show some examples of usage
gem help platforms show information about platforms
gem help <COMMAND> show help on COMMAND
(e.g. 'gem help install')
Further information:
http://rubygems.rubyforge.org
}.gsub(/^ /, "")
# :startdoc:
end # class
# This is where Commands will be placed in the namespace
module Commands; end
end

View file

@ -0,0 +1,144 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'timeout'
require 'rubygems/command'
require 'rubygems/user_interaction'
module Gem
####################################################################
# The command manager registers and installs all the individual
# sub-commands supported by the gem command.
class CommandManager
include UserInteraction
# Return the authoratative instance of the command manager.
def self.instance
@command_manager ||= CommandManager.new
end
# Register all the subcommands supported by the gem command.
def initialize
@commands = {}
register_command :build
register_command :cert
register_command :check
register_command :cleanup
register_command :contents
register_command :dependency
register_command :environment
register_command :fetch
register_command :generate_index
register_command :help
register_command :install
register_command :list
register_command :lock
register_command :mirror
register_command :outdated
register_command :pristine
register_command :query
register_command :rdoc
register_command :search
register_command :server
register_command :sources
register_command :specification
register_command :uninstall
register_command :unpack
register_command :update
register_command :which
end
# Register the command object.
def register_command(command_obj)
@commands[command_obj] = false
end
# Return the registered command from the command name.
def [](command_name)
command_name = command_name.intern
return nil if @commands[command_name].nil?
@commands[command_name] ||= load_and_instantiate(command_name)
end
# Return a list of all command names (as strings).
def command_names
@commands.keys.collect {|key| key.to_s}.sort
end
# Run the config specificed by +args+.
def run(args)
process_args(args)
rescue StandardError, Timeout::Error => ex
alert_error "While executing gem ... (#{ex.class})\n #{ex.to_s}"
ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if
Gem.configuration.backtrace
terminate_interaction(1)
rescue Interrupt
alert_error "Interrupted"
terminate_interaction(1)
end
def process_args(args)
args = args.to_str.split(/\s+/) if args.respond_to?(:to_str)
if args.size == 0
say Gem::Command::HELP
terminate_interaction(1)
end
case args[0]
when '-h', '--help'
say Gem::Command::HELP
terminate_interaction(0)
when '-v', '--version'
say Gem::RubyGemsPackageVersion
terminate_interaction(0)
when /^-/
alert_error "Invalid option: #{args[0]}. See 'gem --help'."
terminate_interaction(1)
else
cmd_name = args.shift.downcase
cmd = find_command(cmd_name)
cmd.invoke(*args)
end
end
def find_command(cmd_name)
possibilities = find_command_possibilities(cmd_name)
if possibilities.size > 1
raise "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]"
end
if possibilities.size < 1
raise "Unknown command #{cmd_name}"
end
self[possibilities.first]
end
def find_command_possibilities(cmd_name)
len = cmd_name.length
self.command_names.select { |n| cmd_name == n[0,len] }
end
private
def load_and_instantiate(command_name)
command_name = command_name.to_s
retried = false
begin
const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase }
Gem::Commands.const_get("#{const_name}Command").new
rescue NameError
if retried then
raise
else
retried = true
require "rubygems/commands/#{command_name}_command"
retry
end
end
end
end
end

View file

@ -0,0 +1,53 @@
require 'rubygems/command'
require 'rubygems/builder'
class Gem::Commands::BuildCommand < Gem::Command
def initialize
super('build', 'Build a gem from a gemspec')
end
def arguments # :nodoc:
"GEMSPEC_FILE gemspec file name to build a gem for"
end
def usage # :nodoc:
"#{program_name} GEMSPEC_FILE"
end
def execute
gemspec = get_one_gem_name
if File.exist?(gemspec)
specs = load_gemspecs(gemspec)
specs.each do |spec|
Gem::Builder.new(spec).build
end
else
alert_error "Gemspec file not found: #{gemspec}"
end
end
def load_gemspecs(filename)
if yaml?(filename)
result = []
open(filename) do |f|
begin
while not f.eof? and spec = Gem::Specification.from_yaml(f)
result << spec
end
rescue Gem::EndOfYAMLException => e
# OK
end
end
else
result = [Gem::Specification.load(filename)]
end
result
end
def yaml?(filename)
line = open(filename) { |f| line = f.gets }
result = line =~ %r{^--- *!ruby/object:Gem::Specification}
result
end
end

View file

@ -0,0 +1,86 @@
require 'rubygems/command'
require 'rubygems/security'
class Gem::Commands::CertCommand < Gem::Command
def initialize
super 'cert', 'Manage RubyGems certificates and signing settings'
add_option('-a', '--add CERT',
'Add a trusted certificate.') do |value, options|
cert = OpenSSL::X509::Certificate.new(File.read(value))
Gem::Security.add_trusted_cert(cert)
say "Added '#{cert.subject.to_s}'"
end
add_option('-l', '--list',
'List trusted certificates.') do |value, options|
glob_str = File::join(Gem::Security::OPT[:trust_dir], '*.pem')
Dir::glob(glob_str) do |path|
begin
cert = OpenSSL::X509::Certificate.new(File.read(path))
# this could proably be formatted more gracefully
say cert.subject.to_s
rescue OpenSSL::X509::CertificateError
next
end
end
end
add_option('-r', '--remove STRING',
'Remove trusted certificates containing',
'STRING.') do |value, options|
trust_dir = Gem::Security::OPT[:trust_dir]
glob_str = File::join(trust_dir, '*.pem')
Dir::glob(glob_str) do |path|
begin
cert = OpenSSL::X509::Certificate.new(File.read(path))
if cert.subject.to_s.downcase.index(value)
say "Removed '#{cert.subject.to_s}'"
File.unlink(path)
end
rescue OpenSSL::X509::CertificateError
next
end
end
end
add_option('-b', '--build EMAIL_ADDR',
'Build private key and self-signed',
'certificate for EMAIL_ADDR.') do |value, options|
vals = Gem::Security.build_self_signed_cert(value)
File.chmod 0600, vals[:key_path]
say "Public Cert: #{vals[:cert_path]}"
say "Private Key: #{vals[:key_path]}"
say "Don't forget to move the key file to somewhere private..."
end
add_option('-C', '--certificate CERT',
'Certificate for --sign command.') do |value, options|
cert = OpenSSL::X509::Certificate.new(File.read(value))
Gem::Security::OPT[:issuer_cert] = cert
end
add_option('-K', '--private-key KEY',
'Private key for --sign command.') do |value, options|
key = OpenSSL::PKey::RSA.new(File.read(value))
Gem::Security::OPT[:issuer_key] = key
end
add_option('-s', '--sign NEWCERT',
'Sign a certificate with my key and',
'certificate.') do |value, options|
cert = OpenSSL::X509::Certificate.new(File.read(value))
my_cert = Gem::Security::OPT[:issuer_cert]
my_key = Gem::Security::OPT[:issuer_key]
cert = Gem::Security.sign_cert(cert, my_key, my_cert)
File.open(value, 'wb') { |file| file.write(cert.to_pem) }
end
end
def execute
end
end

View file

@ -0,0 +1,74 @@
require 'rubygems/command'
require 'rubygems/version_option'
require 'rubygems/validator'
class Gem::Commands::CheckCommand < Gem::Command
include Gem::VersionOption
def initialize
super 'check', 'Check installed gems',
:verify => false, :alien => false
add_option( '--verify FILE',
'Verify gem file against its internal',
'checksum') do |value, options|
options[:verify] = value
end
add_option('-a', '--alien', "Report 'unmanaged' or rogue files in the",
"gem repository") do |value, options|
options[:alien] = true
end
add_option('-t', '--test', "Run unit tests for gem") do |value, options|
options[:test] = true
end
add_version_option 'run tests for'
end
def execute
if options[:test]
version = options[:version] || Gem::Requirement.default
gem_spec = Gem::SourceIndex.from_installed_gems.search(get_one_gem_name, version).first
Gem::Validator.new.unit_test(gem_spec)
end
if options[:alien]
say "Performing the 'alien' operation"
Gem::Validator.new.alien.each do |key, val|
if(val.size > 0)
say "#{key} has #{val.size} problems"
val.each do |error_entry|
say "\t#{error_entry.path}:"
say "\t#{error_entry.problem}"
say
end
else
say "#{key} is error-free"
end
say
end
end
if options[:verify]
gem_name = options[:verify]
unless gem_name
alert_error "Must specify a .gem file with --verify NAME"
return
end
unless File.exist?(gem_name)
alert_error "Unknown file: #{gem_name}."
return
end
say "Verifying gem: '#{gem_name}'"
begin
Gem::Validator.new.verify_gem_file(gem_name)
rescue Exception => e
alert_error "#{gem_name} is invalid."
end
end
end
end

View file

@ -0,0 +1,93 @@
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

View file

@ -0,0 +1,74 @@
require 'rubygems/command'
require 'rubygems/version_option'
class Gem::Commands::ContentsCommand < Gem::Command
include Gem::VersionOption
def initialize
super 'contents', 'Display the contents of the installed gems',
:specdirs => [], :lib_only => false
add_version_option
add_option('-s', '--spec-dir a,b,c', Array,
"Search for gems under specific paths") do |spec_dirs, options|
options[:specdirs] = spec_dirs
end
add_option('-l', '--[no-]lib-only',
"Only return files in the Gem's lib_dirs") do |lib_only, options|
options[:lib_only] = lib_only
end
end
def arguments # :nodoc:
"GEMNAME name of gem to list contents for"
end
def defaults_str # :nodoc:
"--no-lib-only"
end
def usage # :nodoc:
"#{program_name} GEMNAME"
end
def execute
version = options[:version] || Gem::Requirement.default
gem = get_one_gem_name
s = options[:specdirs].map do |i|
[i, File.join(i, "specifications")]
end.flatten
path_kind = if s.empty? then
s = Gem::SourceIndex.installed_spec_directories
"default gem paths"
else
"specified path"
end
si = Gem::SourceIndex.from_gems_in(*s)
gem_spec = si.search(/\A#{gem}\z/, version).last
unless gem_spec then
say "Unable to find gem '#{gem}' in #{path_kind}"
if Gem.configuration.verbose then
say "\nDirectories searched:"
s.each { |dir| say dir }
end
terminate_interaction
end
files = options[:lib_only] ? gem_spec.lib_files : gem_spec.files
files.each do |f|
say File.join(gem_spec.full_gem_path, f)
end
end
end

View file

@ -0,0 +1,150 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
require 'rubygems/source_info_cache'
class Gem::Commands::DependencyCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
super 'dependency',
'Show the dependencies of an installed gem',
:version => Gem::Requirement.default, :domain => :local
add_version_option
add_platform_option
add_option('-R', '--[no-]reverse-dependencies',
'Include reverse dependencies in the output') do
|value, options|
options[:reverse_dependencies] = value
end
add_option('-p', '--pipe',
"Pipe Format (name --version ver)") do |value, options|
options[:pipe_format] = value
end
add_local_remote_options
end
def arguments # :nodoc:
"GEMNAME name of gem to show dependencies for"
end
def defaults_str # :nodoc:
"--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies"
end
def usage # :nodoc:
"#{program_name} GEMNAME"
end
def execute
options[:args] << '.' if options[:args].empty?
specs = {}
source_indexes = []
if local? then
source_indexes << Gem::SourceIndex.from_installed_gems
end
if remote? then
Gem::SourceInfoCache.cache_data.map do |_, sice|
source_indexes << sice.source_index
end
end
options[:args].each do |name|
new_specs = nil
source_indexes.each do |source_index|
new_specs = find_gems(name, source_index)
end
say "No match found for #{name} (#{options[:version]})" if
new_specs.empty?
specs = specs.merge new_specs
end
terminate_interaction 1 if specs.empty?
reverse = Hash.new { |h, k| h[k] = [] }
if options[:reverse_dependencies] then
specs.values.each do |source_index, spec|
reverse[spec.full_name] = find_reverse_dependencies spec, source_index
end
end
if options[:pipe_format] then
specs.values.sort_by { |_, spec| spec }.each do |_, spec|
unless spec.dependencies.empty?
spec.dependencies.each do |dep|
say "#{dep.name} --version '#{dep.version_requirements}'"
end
end
end
else
response = ''
specs.values.sort_by { |_, spec| spec }.each do |_, spec|
response << print_dependencies(spec)
unless reverse[spec.full_name].empty? then
response << " Used by\n"
reverse[spec.full_name].each do |sp, dep|
response << " #{sp} (#{dep})\n"
end
end
response << "\n"
end
say response
end
end
def print_dependencies(spec, level = 0)
response = ''
response << ' ' * level + "Gem #{spec.full_name}\n"
unless spec.dependencies.empty? then
spec.dependencies.each do |dep|
response << ' ' * level + " #{dep}\n"
end
end
response
end
# Retuns list of [specification, dep] that are satisfied by spec.
def find_reverse_dependencies(spec, source_index)
result = []
source_index.each do |name, sp|
sp.dependencies.each do |dep|
dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep
if spec.name == dep.name and
dep.version_requirements.satisfied_by?(spec.version) then
result << [sp.full_name, dep]
end
end
end
result
end
def find_gems(name, source_index)
specs = {}
spec_list = source_index.search name, options[:version]
spec_list.each do |spec|
specs[spec.full_name] = [source_index, spec]
end
specs
end
end

View file

@ -0,0 +1,80 @@
require 'rubygems/command'
class Gem::Commands::EnvironmentCommand < Gem::Command
def initialize
super 'environment', 'Display information about the RubyGems environment'
end
def arguments # :nodoc:
args = <<-EOF
packageversion display the package version
gemdir display the path where gems are installed
gempath display path used to search for gems
version display the gem format version
remotesources display the remote gem servers
<omitted> display everything
EOF
return args.gsub(/^\s+/, '')
end
def usage # :nodoc:
"#{program_name} [arg]"
end
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"
out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
out << ") [#{RUBY_PLATFORM}]\n"
out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil?
out << " - RUBY EXECUTABLE: #{Gem.ruby}\n"
out << " - RUBYGEMS PLATFORMS:\n"
Gem.platforms.each do |platform|
out << " - #{platform}\n"
end
out << " - GEM PATHS:\n"
Gem.path.each do |p|
out << " - #{p}\n"
end
out << " - GEM CONFIGURATION:\n"
Gem.configuration.each do |name, value|
out << " - #{name.inspect} => #{value.inspect}\n"
end
out << " - REMOTE SOURCES:\n"
Gem.sources.each do |s|
out << " - #{s}\n"
end
end
say out
true
end
end

View file

@ -0,0 +1,62 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
require 'rubygems/source_info_cache'
class Gem::Commands::FetchCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
super 'fetch', 'Download a gem and place it in the current directory'
add_bulk_threshold_option
add_proxy_option
add_source_option
add_version_option
add_platform_option
end
def arguments # :nodoc:
'GEMNAME name of gem to download'
end
def defaults_str # :nodoc:
"--version '#{Gem::Requirement.default}'"
end
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
def execute
version = options[:version] || Gem::Requirement.default
gem_names = get_all_gem_names
gem_names.each do |gem_name|
dep = Gem::Dependency.new gem_name, version
specs_and_sources = Gem::SourceInfoCache.search_with_source dep, true
specs_and_sources.sort_by { |spec,| spec.version }
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
end

View file

@ -0,0 +1,57 @@
require 'rubygems/command'
require 'rubygems/indexer'
class Gem::Commands::GenerateIndexCommand < Gem::Command
def initialize
super 'generate_index',
'Generates the index files for a gem server directory',
:directory => '.'
add_option '-d', '--directory=DIRNAME',
'repository base dir containing gems subdir' do |dir, options|
options[:directory] = File.expand_path dir
end
end
def defaults_str # :nodoc:
"--directory ."
end
def description # :nodoc:
<<-EOF
The generate_index command creates a set of indexes for serving gems
statically. The command expects a 'gems' directory under the path given to
the --directory option. When done, it will generate a set of files like this:
gems/ # .gem files you want to index
quick/index
quick/index.rz # quick index manifest
quick/<gemname>.gemspec.rz # legacy YAML quick index file
quick/Marshal.<version>/<gemname>.gemspec.rz # Marshal quick index file
Marshal.<version>
Marshal.<version>.Z # Marshal full index
yaml
yaml.Z # legacy YAML full index
The .Z and .rz extension files are compressed with the inflate algorithm. The
Marshal version number comes from ruby's Marshal::MAJOR_VERSION and
Marshal::MINOR_VERSION constants. It is used to ensure compatibility. The
yaml indexes exist for legacy RubyGems clients and fallback in case of Marshal
version changes.
EOF
end
def execute
if not File.exist?(options[:directory]) or
not File.directory?(options[:directory]) then
alert_error "unknown directory name #{directory}."
terminate_interaction 1
else
indexer = Gem::Indexer.new options[:directory]
indexer.generate_index
end
end
end

View file

@ -0,0 +1,172 @@
require 'rubygems/command'
class Gem::Commands::HelpCommand < Gem::Command
# :stopdoc:
EXAMPLES = <<-EOF
Some examples of 'gem' usage.
* Install 'rake', either from local directory or remote server:
gem install rake
* Install 'rake', only from remote server:
gem install rake --remote
* Install 'rake' from remote server, and run unit tests,
and generate RDocs:
gem install --remote rake --test --rdoc --ri
* Install 'rake', but only version 0.3.1, even if dependencies
are not met, and into a specific directory:
gem install rake --version 0.3.1 --force --install-dir $HOME/.gems
* List local gems whose name begins with 'D':
gem list D
* List local and remote gems whose name contains 'log':
gem search log --both
* List only remote gems whose name contains 'log':
gem search log --remote
* Uninstall 'rake':
gem uninstall rake
* Create a gem:
See http://rubygems.rubyforge.org/wiki/wiki.pl?CreateAGemInTenMinutes
* See information about RubyGems:
gem environment
* Update all gems on your system:
gem update
EOF
PLATFORMS = <<-'EOF'
RubyGems platforms are composed of three parts, a CPU, an OS, and a
version. These values are taken from values in rbconfig.rb. You can view
your current platform by running `gem environment`.
RubyGems matches platforms as follows:
* The CPU must match exactly, unless one of the platforms has
"universal" as the CPU.
* The OS must match exactly.
* The versions must match exactly unless one of the versions is nil.
For commands that install, uninstall and list gems, you can override what
RubyGems thinks your platform is with the --platform option. The platform
you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin
platforms, the version is the compiler version, not the OS version. (Ruby
compiled with VC6 uses "60" as the compiler version, VC8 uses "80".)
Example platforms:
x86-freebsd # Any FreeBSD version on an x86 CPU
universal-darwin-8 # Darwin 8 only gems that run on any CPU
x86-mswin32-80 # Windows gems compiled with VC8
When building platform gems, set the platform in the gem specification to
Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
platform.
EOF
# :startdoc:
def initialize
super 'help', "Provide help on the 'gem' command"
end
def arguments # :nodoc:
args = <<-EOF
commands List all 'gem' commands
examples Show examples of 'gem' usage
<command> Show specific help for <command>
EOF
return args.gsub(/^\s+/, '')
end
def usage # :nodoc:
"#{program_name} ARGUMENT"
end
def execute
command_manager = Gem::CommandManager.instance
arg = options[:args][0]
if begins? "commands", arg then
out = []
out << "GEM commands are:"
out << nil
margin_width = 4
desc_width = command_manager.command_names.map { |n| n.size }.max + 4
summary_width = 80 - margin_width - desc_width
wrap_indent = ' ' * (margin_width + desc_width)
format = "#{' ' * margin_width}%-#{desc_width}s%s"
command_manager.command_names.each do |cmd_name|
summary = command_manager[cmd_name].summary
summary = wrap(summary, summary_width).split "\n"
out << sprintf(format, cmd_name, summary.shift)
until summary.empty? do
out << "#{wrap_indent}#{summary.shift}"
end
end
out << nil
out << "For help on a particular command, use 'gem help COMMAND'."
out << nil
out << "Commands may be abbreviated, so long as they are unambiguous."
out << "e.g. 'gem i rake' is short for 'gem install rake'."
say out.join("\n")
elsif begins? "options", arg then
say Gem::Command::HELP
elsif begins? "examples", arg then
say EXAMPLES
elsif begins? "platforms", arg then
say PLATFORMS
elsif options[:help] then
command = command_manager[options[:help]]
if command
# help with provided command
command.invoke("--help")
else
alert_error "Unknown command #{options[:help]}. Try 'gem help commands'"
end
elsif arg then
possibilities = command_manager.find_command_possibilities(arg.downcase)
if possibilities.size == 1
command = command_manager[possibilities.first]
command.invoke("--help")
elsif possibilities.size > 1
alert_warning "Ambiguous command #{arg} (#{possibilities.join(', ')})"
else
alert_warning "Unknown command #{arg}. Try gem help commands"
end
else
say Gem::Command::HELP
end
end
end

View file

@ -0,0 +1,125 @@
require 'rubygems/command'
require 'rubygems/doc_manager'
require 'rubygems/install_update_options'
require 'rubygems/dependency_installer'
require 'rubygems/local_remote_options'
require 'rubygems/validator'
require 'rubygems/version_option'
class Gem::Commands::InstallCommand < Gem::Command
include Gem::VersionOption
include Gem::LocalRemoteOptions
include Gem::InstallUpdateOptions
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
:generate_rdoc => true,
:generate_ri => true,
:install_dir => Gem.dir,
:test => false,
:version => Gem::Requirement.default,
})
super 'install', 'Install a gem into the local repository', defaults
add_install_update_options
add_local_remote_options
add_platform_option
add_version_option
end
def arguments # :nodoc:
"GEMNAME name of gem to install"
end
def defaults_str # :nodoc:
"--both --version '#{Gem::Requirement.default}' --rdoc --ri --no-force\n" \
"--no-test --install-dir #{Gem.dir}"
end
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags"
end
def execute
if options[:include_dependencies] then
alert "`gem install -y` is now default and will be removed"
alert "use --ignore-dependencies to install only the gems you list"
end
installed_gems = []
ENV['GEM_PATH'] = options[:install_dir] # HACK what does this do?
install_options = {
:env_shebang => options[:env_shebang],
:domain => options[:domain],
:force => options[:force],
:ignore_dependencies => options[:ignore_dependencies],
: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}"
end
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
# # well. This implementation here looks suspicious to me --
# # JimWeirich (4/Jan/05)
# alert_error "Error installing gem #{gem_name}: #{e.message}"
# return
end
end
unless installed_gems.empty? then
gems = installed_gems.length == 1 ? 'gem' : 'gems'
say "#{installed_gems.length} #{gems} installed"
end
# NOTE: *All* of the RI documents must be generated first.
# For some reason, RI docs cannot be generated after any RDoc
# documents are generated.
if options[:generate_ri] then
installed_gems.each do |gem|
Gem::DocManager.new(gem, options[:rdoc_args]).generate_ri
end
end
if options[:generate_rdoc] then
installed_gems.each do |gem|
Gem::DocManager.new(gem, options[:rdoc_args]).generate_rdoc
end
end
if options[:test] then
installed_gems.each do |spec|
gem_spec = Gem::SourceIndex.from_installed_gems.search(spec.name, spec.version.version).first
result = Gem::Validator.new.unit_test(gem_spec)
if result and not result.passed?
unless ask_yes_no("...keep Gem?", true) then
Gem::Uninstaller.new(spec.name, :version => spec.version.version).uninstall
end
end
end
end
end
end

View file

@ -0,0 +1,35 @@
require 'rubygems/command'
require 'rubygems/commands/query_command'
module Gem
module Commands
class ListCommand < QueryCommand
def initialize
super(
'list',
'Display all gems whose name starts with STRING'
)
remove_option('--name-matches')
end
def arguments # :nodoc:
"STRING start of gem name to look for"
end
def defaults_str # :nodoc:
"--local --no-details"
end
def usage # :nodoc:
"#{program_name} [STRING]"
end
def execute
string = get_one_optional_argument || ''
options[:name] = /^#{string}/i
super
end
end
end
end

View file

@ -0,0 +1,101 @@
require 'rubygems/command'
class Gem::Commands::LockCommand < Gem::Command
def initialize
super 'lock', 'Generate a lockdown list of gems',
:strict => false
add_option '-s', '--[no-]strict',
'fail if unable to satisfy a dependency' do |strict, options|
options[:strict] = strict
end
end
def arguments # :nodoc:
"GEMNAME name of gem to lock\nVERSION version of gem to lock"
end
def defaults_str # :nodoc:
"--no-strict"
end
def description # :nodoc:
<<-EOF
The lock command will generate a list of +gem+ statements that will lock down
the versions for the gem given in the command line. It will specify exact
versions in the requirements list to ensure that the gems loaded will always
be consistent. A full recursive search of all effected gems will be
generated.
Example:
gemlock rails-1.0.0 > lockdown.rb
will produce in lockdown.rb:
require "rubygems"
gem 'rails', '= 1.0.0'
gem 'rake', '= 0.7.0.1'
gem 'activesupport', '= 1.2.5'
gem 'activerecord', '= 1.13.2'
gem 'actionpack', '= 1.11.2'
gem 'actionmailer', '= 1.1.5'
gem 'actionwebservice', '= 1.0.0'
Just load lockdown.rb from your application to ensure that the current
versions are loaded. Make sure that lockdown.rb is loaded *before* any
other require statements.
Notice that rails 1.0.0 only requires that rake 0.6.2 or better be used.
Rake-0.7.0.1 is the most recent version installed that satisfies that, so we
lock it down to the exact version.
EOF
end
def usage # :nodoc:
"#{program_name} GEMNAME-VERSION [GEMNAME-VERSION ...]"
end
def complain(message)
if options.strict then
raise message
else
say "# #{message}"
end
end
def execute
say 'require "rubygems"'
locked = {}
pending = options[:args]
until pending.empty? do
full_name = pending.shift
spec = Gem::SourceIndex.load_specification spec_path(full_name)
say "gem '#{spec.name}', '= #{spec.version}'" unless locked[spec.name]
locked[spec.name] = true
spec.dependencies.each do |dep|
next if locked[dep.name]
candidates = Gem.source_index.search dep.name, dep.requirement_list
if candidates.empty? then
complain "Unable to satisfy '#{dep}' from currently installed gems."
else
pending << candidates.last.full_name
end
end
end
end
def spec_path(gem_full_name)
File.join Gem.path, "specifications", "#{gem_full_name }.gemspec"
end
end

View file

@ -0,0 +1,105 @@
require 'yaml'
require 'zlib'
require 'rubygems/command'
require 'rubygems/gem_open_uri'
class Gem::Commands::MirrorCommand < Gem::Command
def initialize
super 'mirror', 'Mirror a gem repository'
end
def description # :nodoc:
<<-EOF
The mirror command uses the ~/.gemmirrorrc config file to mirror remote gem
repositories to a local path. The config file is a YAML document that looks
like this:
---
- from: http://gems.example.com # source repository URI
to: /path/to/mirror # destination directory
Multiple sources and destinations may be specified.
EOF
end
def execute
config_file = File.join Gem.user_home, '.gemmirrorrc'
raise "Config file #{config_file} not found" unless File.exist? config_file
mirrors = YAML.load_file config_file
raise "Invalid config file #{config_file}" unless mirrors.respond_to? :each
mirrors.each do |mir|
raise "mirror missing 'from' field" unless mir.has_key? 'from'
raise "mirror missing 'to' field" unless mir.has_key? 'to'
get_from = mir['from']
save_to = File.expand_path mir['to']
raise "Directory not found: #{save_to}" unless File.exist? save_to
raise "Not a directory: #{save_to}" unless File.directory? save_to
gems_dir = File.join save_to, "gems"
if File.exist? gems_dir then
raise "Not a directory: #{gems_dir}" unless File.directory? gems_dir
else
Dir.mkdir gems_dir
end
sourceindex_data = ''
say "fetching: #{get_from}/Marshal.#{Gem.marshal_version}.Z"
get_from = URI.parse get_from
if get_from.scheme.nil? then
get_from = get_from.to_s
elsif get_from.scheme == 'file' then
get_from = get_from.to_s[5..-1]
end
open File.join(get_from, "Marshal.#{Gem.marshal_version}.Z"), "rb" do |y|
sourceindex_data = Zlib::Inflate.inflate y.read
open File.join(save_to, "Marshal.#{Gem.marshal_version}"), "wb" do |out|
out.write sourceindex_data
end
end
sourceindex = Marshal.load(sourceindex_data)
progress = ui.progress_reporter sourceindex.size,
"Fetching #{sourceindex.size} gems"
sourceindex.each do |fullname, gem|
gem_file = "#{fullname}.gem"
gem_dest = File.join gems_dir, gem_file
unless File.exist? gem_dest then
begin
open "#{get_from}/gems/#{gem_file}", "rb" do |g|
contents = g.read
open gem_dest, "wb" do |out|
out.write contents
end
end
rescue
old_gf = gem_file
gem_file = gem_file.downcase
retry if old_gf != gem_file
alert_error $!
end
end
progress.updated gem_file
end
progress.done
end
end
end

View file

@ -0,0 +1,30 @@
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
require 'rubygems/version_option'
class Gem::Commands::OutdatedCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
super 'outdated', 'Display all gems that need updates'
add_local_remote_options
add_platform_option
end
def execute
locals = Gem::SourceIndex.from_installed_gems
locals.outdated.sort.each do |name|
local = locals.search(/^#{name}$/).last
remotes = Gem::SourceInfoCache.search_with_source(/^#{name}$/, true)
remote = remotes.last.first
say "#{local.name} (#{local.version} < #{remote.version})"
end
end
end

View file

@ -0,0 +1,133 @@
require 'fileutils'
require 'rubygems/command'
require 'rubygems/format'
require 'rubygems/installer'
require 'rubygems/version_option'
class Gem::Commands::PristineCommand < Gem::Command
include Gem::VersionOption
def initialize
super 'pristine',
'Restores installed gems to pristine condition from files located in the gem cache',
:version => Gem::Requirement.default
add_option('--all',
'Restore all installed gems to pristine',
'condition') do |value, options|
options[:all] = value
end
add_version_option('restore to', 'pristine condition')
end
def arguments # :nodoc:
"GEMNAME gem to restore to pristine condition (unless --all)"
end
def defaults_str # :nodoc:
"--all"
end
def description # :nodoc:
<<-EOF
The pristine command compares the installed gems with the contents of the
cached gem and restores any files that don't match the cached gem's copy.
If you have made modifications to your installed gems, the pristine command
will revert them. After all the gem's files have been checked all bin stubs
for the gem are regenerated.
If the cached gem cannot be found, you will need to use `gem install` to
revert the gem.
EOF
end
def usage # :nodoc:
"#{program_name} [args]"
end
def execute
gem_name = nil
specs = if options[:all] then
Gem::SourceIndex.from_installed_gems.map do |name, spec|
spec
end
else
gem_name = get_one_gem_name
Gem::SourceIndex.from_installed_gems.search(gem_name,
options[:version])
end
if specs.empty? then
raise Gem::Exception,
"Failed to find gem #{gem_name} #{options[:version]}"
end
install_dir = Gem.dir # TODO use installer option
raise Gem::FilePermissionError.new(install_dir) unless
File.writable?(install_dir)
say "Restoring gem(s) to pristine condition..."
specs.each do |spec|
gem = Dir[File.join(Gem.dir, 'cache', "#{spec.full_name}.gem")].first
if gem.nil? then
alert_error "Cached gem for #{spec.full_name} not found, use `gem install` to restore"
next
end
# TODO use installer options
installer = Gem::Installer.new gem, :wrappers => true
gem_file = File.join install_dir, "cache", "#{spec.full_name}.gem"
security_policy = nil # TODO use installer option
format = Gem::Format.from_file_by_path gem_file, security_policy
target_directory = File.join(install_dir, "gems", format.spec.full_name)
target_directory.untaint
pristine_files = format.file_entries.collect { |data| data[0]["path"] }
file_map = {}
format.file_entries.each do |entry, file_data|
file_map[entry["path"]] = file_data
end
Dir.chdir target_directory do
deployed_files = Dir.glob(File.join("**", "*")) +
Dir.glob(File.join("**", ".*"))
pristine_files = pristine_files.map { |f| File.expand_path f }
deployed_files = deployed_files.map { |f| File.expand_path f }
to_redeploy = (pristine_files - deployed_files)
to_redeploy = to_redeploy.map { |path| path.untaint}
if to_redeploy.length > 0 then
say "Restoring #{to_redeploy.length} file#{to_redeploy.length == 1 ? "" : "s"} to #{spec.full_name}..."
to_redeploy.each do |path|
say " #{path}"
FileUtils.mkdir_p File.dirname(path)
File.open(path, "wb") do |out|
out.write file_map[path]
end
end
else
say "#{spec.full_name} is in pristine condition"
end
end
installer.generate_bin
end
end
end

View file

@ -0,0 +1,118 @@
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',
'provided REGEXP') do |value, options|
options[:name] = /#{value}/i
end
add_option('-d', '--[no-]details',
'Display detailed information of gem(s)') do |value, options|
options[:details] = value
end
add_option( '--[no-]versions',
'Display only gem names') do |value, options|
options[:versions] = value
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 = {}
gemspecs.flatten.each do |gemspec|
gem_list_with_version[gemspec.name] ||= []
gem_list_with_version[gemspec.name] << gemspec
end
gem_list_with_version = gem_list_with_version.sort_by do |name, spec|
name.downcase
end
gem_list_with_version.each do |gem_name, list_of_matching|
list_of_matching = list_of_matching.sort_by { |x| x.version.to_ints }.reverse
seen_versions = {}
list_of_matching.delete_if do |item|
if seen_versions[item.version] then
true
else
seen_versions[item.version] = true
false
end
end
entry = gem_name.dup
if options[:versions] then
entry << " (#{list_of_matching.map{|gem| gem.version.to_s}.join(", ")})"
end
entry << "\n" << format_text(list_of_matching[0].summary, 68, 4) if
options[:details]
output << entry
end
say output.join(options[:details] ? "\n\n" : "\n")
end
##
# Used for wrapping and indenting text
#
def format_text(text, wrap, indent=0)
result = []
work = text.dup
while work.length > wrap
if work =~ /^(.{0,#{wrap}})[ \n]/o then
result << $1
work.slice!(0, $&.length)
else
result << work.slice!(0, wrap)
end
end
result << work if work.length.nonzero?
result.join("\n").gsub(/^/, " " * indent)
end
end

View file

@ -0,0 +1,78 @@
require 'rubygems/command'
require 'rubygems/version_option'
require 'rubygems/doc_manager'
module Gem
module Commands
class RdocCommand < Command
include VersionOption
def initialize
super('rdoc',
'Generates RDoc for pre-installed gems',
{
:version => Gem::Requirement.default,
:include_rdoc => true,
:include_ri => true,
})
add_option('--all',
'Generate RDoc/RI documentation for all',
'installed gems') do |value, options|
options[:all] = value
end
add_option('--[no-]rdoc',
'Include RDoc generated documents') do
|value, options|
options[:include_rdoc] = value
end
add_option('--[no-]ri',
'Include RI generated documents'
) do |value, options|
options[:include_ri] = value
end
add_version_option
end
def arguments # :nodoc:
"GEMNAME gem to generate documentation for (unless --all)"
end
def defaults_str # :nodoc:
"--version '#{Gem::Requirement.default}' --rdoc --ri"
end
def usage # :nodoc:
"#{program_name} [args]"
end
def execute
if options[:all]
specs = Gem::SourceIndex.from_installed_gems.collect { |name, spec|
spec
}
else
gem_name = get_one_gem_name
specs = Gem::SourceIndex.from_installed_gems.search(
gem_name, options[:version])
end
if specs.empty?
fail "Failed to find gem #{gem_name} to generate RDoc for #{options[:version]}"
end
if options[:include_ri]
specs.each do |spec|
Gem::DocManager.new(spec).generate_ri
end
end
if options[:include_rdoc]
specs.each do |spec|
Gem::DocManager.new(spec).generate_rdoc
end
end
true
end
end
end
end

View file

@ -0,0 +1,37 @@
require 'rubygems/command'
require 'rubygems/commands/query_command'
module Gem
module Commands
class SearchCommand < QueryCommand
def initialize
super(
'search',
'Display all gems whose name contains STRING'
)
remove_option('--name-matches')
end
def arguments # :nodoc:
"STRING fragment of gem name to search for"
end
def defaults_str # :nodoc:
"--local --no-details"
end
def usage # :nodoc:
"#{program_name} [STRING]"
end
def execute
string = get_one_optional_argument
options[:name] = /#{string}/i
super
end
end
end
end

View file

@ -0,0 +1,48 @@
require 'rubygems/command'
require 'rubygems/server'
class Gem::Commands::ServerCommand < Gem::Command
def initialize
super 'server', 'Documentation and gem repository HTTP server',
:port => 8808, :gemdir => Gem.dir, :daemon => false
add_option '-p', '--port=PORT',
'port to listen on' do |port, options|
options[:port] = port
end
add_option '-d', '--dir=GEMDIR',
'directory from which to serve gems' do |gemdir, options|
options[:gemdir] = gemdir
end
add_option '--[no]-daemon', 'run as a daemon' do |daemon, options|
options[:daemon] = daemon
end
end
def defaults_str # :nodoc:
"--port 8808 --dir #{Gem.dir} --no-daemon"
end
def description # :nodoc:
<<-EOF
The server command starts up a web server that hosts the RDoc for your
installed gems and can operate as a server for installation of gems on other
machines.
The cache files for installed gems must exist to use the server as a source
for gem installation.
To install gems from a running server, use `gem install GEMNAME --source
http://gem_server_host:8808`
EOF
end
def execute
Gem::Server.run options
end
end

View file

@ -0,0 +1,115 @@
require 'rubygems/command'
require 'rubygems/remote_fetcher'
require 'rubygems/source_info_cache'
require 'rubygems/source_info_cache_entry'
class Gem::Commands::SourcesCommand < Gem::Command
def initialize
super 'sources',
'Manage the sources and cache file RubyGems uses to search for gems'
add_option '-a', '--add SOURCE_URI', 'Add source' do |value, options|
options[:add] = value
end
add_option '-l', '--list', 'List sources' do |value, options|
options[:list] = value
end
add_option '-r', '--remove SOURCE_URI', 'Remove source' do |value, options|
options[:remove] = value
end
add_option '-u', '--update', 'Update source cache' do |value, options|
options[:update] = value
end
add_option '-c', '--clear-all',
'Remove all sources (clear the cache)' do |value, options|
options[:clear_all] = value
end
end
def defaults_str
'--list'
end
def execute
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
source_uri = options[:add]
sice = Gem::SourceInfoCacheEntry.new nil, nil
begin
sice.refresh source_uri
Gem::SourceInfoCache.cache_data[source_uri] = sice
Gem::SourceInfoCache.cache.update
Gem::SourceInfoCache.cache.flush
Gem.sources << source_uri
Gem.configuration.write
say "#{source_uri} added to sources"
rescue URI::Error, ArgumentError
say "#{source_uri} is not a URI"
rescue Gem::RemoteFetcher::FetchError => e
say "Error fetching #{source_uri}:\n\t#{e.message}"
end
end
if options[:update] then
Gem::SourceInfoCache.cache.refresh
Gem::SourceInfoCache.cache.flush
say "source cache successfully updated"
end
if options[:remove] then
source_uri = options[:remove]
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
Gem.sources.delete source_uri
Gem.configuration.write
say "#{source_uri} removed from sources"
end
end
if options[:list] then
say "*** CURRENT SOURCES ***"
say
Gem.sources.each do |source_uri|
say source_uri
end
end
end
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 ***"
end
end
end

View file

@ -0,0 +1,72 @@
require 'yaml'
require 'rubygems/command'
require 'rubygems/local_remote_options'
require 'rubygems/version_option'
require 'rubygems/source_info_cache'
class Gem::Commands::SpecificationCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
super 'specification', 'Display gem specification (in yaml)',
:domain => :local, :version => Gem::Requirement.default
add_version_option('examine')
add_platform_option
add_option('--all', 'Output specifications for all versions of',
'the gem') do |value, options|
options[:all] = true
end
add_local_remote_options
end
def arguments # :nodoc:
"GEMFILE name of gem to show the gemspec for"
end
def defaults_str # :nodoc:
"--local --version '#{Gem::Requirement.default}'"
end
def usage # :nodoc:
"#{program_name} [GEMFILE]"
end
def execute
specs = []
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
end
if specs.empty? then
alert_error "Unknown gem '#{gem}'"
terminate_interaction 1
end
output = lambda { |spec| say spec.to_yaml; say "\n" }
if options[:all] then
specs.each(&output)
else
spec = specs.sort_by { |spec| spec.version }.last
output[spec]
end
end
end

View file

@ -0,0 +1,56 @@
require 'rubygems/command'
require 'rubygems/version_option'
require 'rubygems/uninstaller'
module Gem
module Commands
class UninstallCommand < Command
include VersionOption
def initialize
super 'uninstall', 'Uninstall gems from the local repository',
:version => Gem::Requirement.default
add_option('-a', '--[no-]all',
'Uninstall all matching versions'
) do |value, options|
options[:all] = value
end
add_option('-i', '--[no-]ignore-dependencies',
'Ignore dependency requirements while',
'uninstalling') do |value, options|
options[:ignore] = value
end
add_option('-x', '--[no-]executables',
'Uninstall applicable executables without',
'confirmation') do |value, options|
options[:executables] = value
end
add_version_option
add_platform_option
end
def arguments # :nodoc:
"GEMNAME name of gem to uninstall"
end
def defaults_str # :nodoc:
"--version '#{Gem::Requirement.default}' --no-force"
end
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
def execute
get_all_gem_names.each do |gem_name|
Gem::Uninstaller.new(gem_name, options).uninstall
end
end
end
end
end

View file

@ -0,0 +1,76 @@
require 'fileutils'
require 'rubygems/command'
require 'rubygems/installer'
require 'rubygems/version_option'
class Gem::Commands::UnpackCommand < Gem::Command
include Gem::VersionOption
def initialize
super 'unpack', 'Unpack an installed gem to the current directory',
:version => Gem::Requirement.default
add_version_option
end
def arguments # :nodoc:
"GEMNAME name of gem to unpack"
end
def defaults_str # :nodoc:
"--version '#{Gem::Requirement.default}'"
end
def usage # :nodoc:
"#{program_name} GEMNAME"
end
#--
# TODO: allow, e.g., 'gem unpack rake-0.3.1'. Find a general solution for
# this, so that it works for uninstall as well. (And check other commands
# at the same time.)
def execute
gemname = get_one_gem_name
path = get_path(gemname, options[:version])
if path
target_dir = File.basename(path).sub(/\.gem$/, '')
FileUtils.mkdir_p target_dir
Gem::Installer.new(path).unpack(File.expand_path(target_dir))
say "Unpacked gem: '#{target_dir}'"
else
alert_error "Gem '#{gemname}' not installed."
end
end
# Return the full path to the cached gem file matching the given
# name and version requirement. Returns 'nil' if no match.
#
# Example:
#
# get_path('rake', '> 0.4') # -> '/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem'
# get_path('rake', '< 0.1') # -> nil
# get_path('rak') # -> nil (exact name required)
#--
# TODO: This should be refactored so that it's a general service. I don't
# think any of our existing classes are the right place though. Just maybe
# 'Cache'?
#
# TODO: It just uses Gem.dir for now. What's an easy way to get the list of
# 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
end

View file

@ -0,0 +1,149 @@
require 'rubygems/command'
require 'rubygems/install_update_options'
require 'rubygems/local_remote_options'
require 'rubygems/source_info_cache'
require 'rubygems/version_option'
module Gem
module Commands
class UpdateCommand < Command
include Gem::InstallUpdateOptions
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
super(
'update',
'Update the named gems (or all installed gems) in the local repository',
{
:generate_rdoc => true,
:generate_ri => true,
:force => false,
:test => false,
:install_dir => Gem.dir
})
add_install_update_options
add_option('--system',
'Update the RubyGems system software') do |value, options|
options[:system] = value
end
add_local_remote_options
add_platform_option
end
def arguments # :nodoc:
"GEMNAME name of gem to update"
end
def defaults_str # :nodoc:
"--rdoc --ri --no-force --no-test\n" +
"--install-dir #{Gem.dir}"
end
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
def execute
if options[:system] then
say "Updating RubyGems..."
unless options[:args].empty? then
fail "No gem names are allowed with the --system option"
end
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
hig[spec.name] = spec
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
latest_ruby_gem = remote_gemspecs.select { |s|
s.name == 'rubygems-update'
}.sort_by { |s|
s.version
}.last
say "Updating version of RubyGems to #{latest_ruby_gem.version}"
installed = do_rubygems_update(latest_ruby_gem.version.to_s)
say "RubyGems system software updated" if installed
else
say "Gems: [#{gems_to_update.uniq.sort.collect{|g| g.to_s}.join(', ')}] updated"
end
end
def do_rubygems_update(version_string)
args = []
args.push '--prefix', Gem.prefix unless Gem.prefix.nil?
args << '--no-rdoc' unless options[:generate_rdoc]
args << '--no-ri' unless options[:generate_ri]
update_dir = File.join(Gem.dir, 'gems',
"rubygems-update-#{version_string}")
success = false
Dir.chdir update_dir do
say "Installing RubyGems #{version_string}"
setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}"
# Make sure old rubygems isn't loaded
if Gem.win_platform? then
system "set RUBYOPT= & #{setup_cmd}"
else
system "RUBYOPT=\"\" #{setup_cmd}"
end
end
end
def which_to_update(highest_installed_gems, remote_gemspecs)
result = []
highest_installed_gems.each do |l_name, l_spec|
highest_remote_gem =
remote_gemspecs.select { |spec| spec.name == l_name }.
sort_by { |spec| spec.version }.
last
if highest_remote_gem and l_spec.version < highest_remote_gem.version
result << l_name
end
end
result
end
end
end
end

View file

@ -0,0 +1,86 @@
require 'rubygems/command'
require 'rubygems/gem_path_searcher'
class Gem::Commands::WhichCommand < Gem::Command
EXT = %w[.rb .rbw .so .dll] # HACK
def initialize
super 'which', 'Find the location of a library',
:search_gems_first => false, :show_all => false
add_option '-a', '--[no-]all', 'show all matching files' do |show_all, options|
options[:show_all] = show_all
end
add_option '-g', '--[no-]gems-first',
'search gems before non-gems' do |gems_first, options|
options[:search_gems_first] = gems_first
end
end
def arguments # :nodoc:
"FILE name of file to find"
end
def defaults_str # :nodoc:
"--no-gems-first --no-all"
end
def usage # :nodoc:
"#{program_name} FILE [FILE ...]"
end
def execute
searcher = Gem::GemPathSearcher.new
options[:args].each do |arg|
dirs = $LOAD_PATH
spec = searcher.find arg
if spec then
if options[:search_gems_first] then
dirs = gem_paths(spec) + $LOAD_PATH
else
dirs = $LOAD_PATH + gem_paths(spec)
end
say "(checking gem #{spec.full_name} for #{arg})" if
Gem.configuration.verbose
end
paths = find_paths arg, dirs
if paths.empty? then
say "Can't find #{arg}"
else
say paths
end
end
end
def find_paths(package_name, dirs)
result = []
dirs.each do |dir|
EXT.each do |ext|
full_path = File.join dir, "#{package_name}#{ext}"
if File.exist? full_path then
result << full_path
return result unless options[:show_all]
end
end
end
result
end
def gem_paths(spec)
spec.require_paths.collect { |d| File.join spec.full_gem_path, d }
end
def usage # :nodoc:
"#{program_name} FILE [...]"
end
end

224
lib/rubygems/config_file.rb Normal file
View file

@ -0,0 +1,224 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'yaml'
require 'rubygems'
# Store the gem command options specified in the configuration file. The
# config file object acts much like a hash.
class Gem::ConfigFile
DEFAULT_BACKTRACE = false
DEFAULT_BENCHMARK = false
DEFAULT_BULK_THRESHOLD = 1000
DEFAULT_VERBOSITY = true
DEFAULT_UPDATE_SOURCES = true
# List of arguments supplied to the config file object.
attr_reader :args
# True if we print backtraces on errors.
attr_writer :backtrace
# True if we are benchmarking this run.
attr_accessor :benchmark
# Bulk threshold value. If the number of missing gems are above
# this threshold value, then a bulk download technique is used.
attr_accessor :bulk_threshold
# Verbose level of output:
# * false -- No output
# * true -- Normal output
# * :loud -- Extra output
attr_accessor :verbose
# True if we want to update the SourceInfoCache every time, false otherwise
attr_accessor :update_sources
# Create the config file object. +args+ is the list of arguments
# from the command line.
#
# The following command line options are handled early here rather
# than later at the time most command options are processed.
#
# * --config-file and --config-file==NAME -- Obviously these need
# to be handled by the ConfigFile object to ensure we get the
# right config file.
#
# * --backtrace -- Backtrace needs to be turned on early so that
# errors before normal option parsing can be properly handled.
#
# * --debug -- Enable Ruby level debug messages. Handled early
# for the same reason as --backtrace.
#
def initialize(arg_list)
@config_file_name = nil
need_config_file_name = false
arg_list = arg_list.map do |arg|
if need_config_file_name then
@config_file_name = arg
nil
elsif arg =~ /^--config-file=(.*)/ then
@config_file_name = $1
nil
elsif arg =~ /^--config-file$/ then
need_config_file_name = true
nil
else
arg
end
end.compact
@backtrace = DEFAULT_BACKTRACE
@benchmark = DEFAULT_BENCHMARK
@bulk_threshold = DEFAULT_BULK_THRESHOLD
@verbose = DEFAULT_VERBOSITY
@update_sources = DEFAULT_UPDATE_SOURCES
begin
# HACK $SAFE ok?
@hash = open(config_file_name.dup.untaint) {|f| YAML.load(f) }
rescue ArgumentError
warn "Failed to load #{config_file_name}"
rescue Errno::ENOENT
# Ignore missing config file error.
rescue Errno::EACCES
warn "Failed to load #{config_file_name} due to permissions problem."
end
@hash ||= {}
# HACK these override command-line args, which is bad
@backtrace = @hash[:backtrace] if @hash.key? :backtrace
@benchmark = @hash[:benchmark] if @hash.key? :benchmark
@bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold
Gem.sources.replace @hash[:sources] if @hash.key? :sources
@verbose = @hash[:verbose] if @hash.key? :verbose
@update_sources = @hash[:update_sources] if @hash.key? :update_sources
handle_arguments arg_list
end
# True if the backtrace option has been specified, or debug is on.
def backtrace
@backtrace or $DEBUG
end
# The name of the configuration file.
def config_file_name
@config_file_name || Gem.config_file
end
# Delegates to @hash
def each(&block)
hash = @hash.dup
hash.delete :update_sources
hash.delete :verbose
hash.delete :benchmark
hash.delete :backtrace
hash.delete :bulk_threshold
yield :update_sources, @update_sources
yield :verbose, @verbose
yield :benchmark, @benchmark
yield :backtrace, @backtrace
yield :bulk_threshold, @bulk_threshold
yield 'config_file_name', @config_file_name if @config_file_name
hash.each(&block)
end
# Handle the command arguments.
def handle_arguments(arg_list)
@args = []
arg_list.each do |arg|
case arg
when /^--(backtrace|traceback)$/ then
@backtrace = true
when /^--bench(mark)?$/ then
@benchmark = true
when /^--debug$/ then
$DEBUG = true
else
@args << arg
end
end
end
# Really verbose mode gives you extra output.
def really_verbose
case verbose
when true, false, nil then false
else true
end
end
# to_yaml only overwrites things you can't override on the command line.
def to_yaml # :nodoc:
yaml_hash = {}
yaml_hash[:backtrace] = @hash.key?(:backtrace) ? @hash[:backtrace] :
DEFAULT_BACKTRACE
yaml_hash[:benchmark] = @hash.key?(:benchmark) ? @hash[:benchmark] :
DEFAULT_BENCHMARK
yaml_hash[:bulk_threshold] = @hash.key?(:bulk_threshold) ?
@hash[:bulk_threshold] : DEFAULT_BULK_THRESHOLD
yaml_hash[:sources] = Gem.sources
yaml_hash[:update_sources] = @hash.key?(:update_sources) ?
@hash[:update_sources] : DEFAULT_UPDATE_SOURCES
yaml_hash[:verbose] = @hash.key?(:verbose) ? @hash[:verbose] :
DEFAULT_VERBOSITY
keys = yaml_hash.keys.map { |key| key.to_s }
keys << 'debug'
re = Regexp.union(*keys)
@hash.each do |key, value|
key = key.to_s
next if key =~ re
yaml_hash[key.to_s] = value
end
yaml_hash.to_yaml
end
# Writes out this config file, replacing its source.
def write
File.open config_file_name, 'w' do |fp|
fp.write self.to_yaml
end
end
# Return the configuration information for +key+.
def [](key)
@hash[key.to_s]
end
# Set configuration option +key+ to +value+.
def []=(key, value)
@hash[key.to_s] = value
end
def ==(other) # :nodoc:
self.class === other and
@backtrace == other.backtrace and
@benchmark == other.benchmark and
@bulk_threshold == other.bulk_threshold and
@verbose == other.verbose and
@update_sources == other.update_sources and
@hash == other.hash
end
protected
attr_reader :hash
end

38
lib/rubygems/custom_require.rb Executable file
View file

@ -0,0 +1,38 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
module Kernel
alias gem_original_require require # :nodoc:
#
# We replace Ruby's require with our own, which is capable of
# loading gems on demand.
#
# When you call <tt>require 'x'</tt>, this is what happens:
# * If the file can be loaded from the existing Ruby loadpath, it
# is.
# * Otherwise, installed gems are searched for a file that matches.
# If it's found in gem 'y', that gem is activated (added to the
# loadpath).
#
# The normal <tt>require</tt> functionality of returning false if
# that file has already been loaded is preserved.
#
def require(path) # :nodoc:
gem_original_require path
rescue LoadError => load_error
if load_error.message =~ /\A[Nn]o such file to load -- #{Regexp.escape path}\z/ and
spec = Gem.searcher.find(path) then
Gem.activate(spec.name, false, "= #{spec.version}")
gem_original_require path
else
raise load_error
end
end
end # module Kernel

View file

@ -0,0 +1,65 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
##
# The Dependency class holds a Gem name and a Gem::Requirement
class Gem::Dependency
attr_accessor :name
attr_writer :version_requirements
def <=>(other)
[@name] <=> [other.name]
end
##
# Constructs the dependency
#
# name:: [String] name of the Gem
# version_requirements:: [String Array] version requirement (e.g. ["> 1.2"])
#
def initialize(name, version_requirements)
@name = name
@version_requirements = Gem::Requirement.create version_requirements
@version_requirement = nil # Avoid warnings.
end
def version_requirements
normalize if defined? @version_requirement and @version_requirement
@version_requirements
end
def requirement_list
version_requirements.as_list
end
alias requirements_list requirement_list
def normalize
ver = @version_requirement.instance_eval { @version }
@version_requirements = Gem::Requirement.new([ver])
@version_requirement = nil
end
def to_s # :nodoc:
"#{name} (#{version_requirements})"
end
def ==(other) # :nodoc:
self.class === other &&
self.name == other.name &&
self.version_requirements == other.version_requirements
end
def hash
name.hash + version_requirements.hash
end
end

View file

@ -0,0 +1,219 @@
require 'rubygems'
require 'rubygems/dependency_list'
require 'rubygems/installer'
require 'rubygems/source_info_cache'
require 'rubygems/user_interaction'
class Gem::DependencyInstaller
include Gem::UserInteraction
attr_reader :gems_to_install
attr_reader :installed_gems
DEFAULT_OPTIONS = {
:env_shebang => false,
:domain => :both, # HACK dup
:force => false,
:ignore_dependencies => false,
:security_policy => Gem::Security::NoSecurity, # HACK AlmostNo? Low?
:wrappers => true
}
##
# Creates a new installer instance that will install +gem_name+ using
# version requirement +version+ and +options+.
#
# Options are:
# :env_shebang:: See Gem::Installer::new.
# :domain:: :local, :remote, or :both. :local only searches gems in the
# current directory. :remote searches only gems in Gem::sources.
# :both searches both.
# :force:: See Gem::Installer#install.
# :ignore_dependencies: Don't install any dependencies.
# :install_dir: See Gem::Installer#install.
# :security_policy: See Gem::Installer::new and Gem::Security.
# :wrappers: See Gem::Installer::new
def initialize(gem_name, version = nil, options = {})
options = DEFAULT_OPTIONS.merge options
@env_shebang = options[:env_shebang]
@domain = options[:domain]
@force = options[:force]
@ignore_dependencies = options[:ignore_dependencies]
@install_dir = options[:install_dir] || Gem.dir
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
@installed_gems = []
spec_and_source = nil
local_gems = Dir["#{gem_name}*"].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
##
# Returns a list of pairs of gemspecs and source_uris that match
# Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
# sources. Gems are sorted with newer gems prefered over older gems, and
# local gems prefered over remote gems.
def find_gems_with_sources(dep)
gems_and_sources = []
if @domain == :both or @domain == :local then
Dir[File.join(Dir.pwd, "#{dep.name}-[0-9]*.gem")].each do |gem_file|
spec = Gem::Format.from_file_by_path(gem_file).spec
gems_and_sources << [spec, gem_file] if spec.name == dep.name
end
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
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
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
##
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
def gather_dependencies
specs = @specs_and_sources.map { |spec,_| spec }
dependency_list = Gem::DependencyList.new
dependency_list.add(*specs)
unless @ignore_dependencies then
to_do = specs.dup
seen = {}
until to_do.empty? do
spec = to_do.shift
next if spec.nil? or seen[spec.name]
seen[spec.name] = true
spec.dependencies.each do |dep|
results = find_gems_with_sources(dep).reverse # local gems first
results.each do |dep_spec, source_uri|
next if seen[dep_spec.name]
@specs_and_sources << [dep_spec, source_uri]
dependency_list.add dep_spec
to_do.push dep_spec
end
end
end
end
@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
@gems_to_install.each do |spec|
last = spec == @gems_to_install.last
# HACK is this test for full_name acceptable?
next if source_index.any? { |n,_| n == spec.full_name } and not last
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,
:force => @force,
:ignore_dependencies => @ignore_dependencies,
:install_dir => @install_dir,
:security_policy => @security_policy,
:wrappers => @wrappers
spec = inst.install
@installed_gems << spec
end
end
end

View file

@ -0,0 +1,165 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'tsort'
class Gem::DependencyList
include TSort
def self.from_source_index(src_index)
deps = new
src_index.each do |full_name, spec|
deps.add spec
end
deps
end
def initialize
@specs = []
end
# Adds +gemspecs+ to the dependency list.
def add(*gemspecs)
@specs.push(*gemspecs)
end
# Return a list of the specifications in the dependency list,
# sorted in order so that no spec in the list depends on a gem
# earlier in the list.
#
# This is useful when removing gems from a set of installed gems.
# By removing them in the returned order, you don't get into as
# many dependency issues.
#
# If there are circular dependencies (yuck!), then gems will be
# returned in order until only the circular dependents and anything
# they reference are left. Then arbitrary gemspecs will be returned
# until the circular dependency is broken, after which gems will be
# returned in dependency order again.
def dependency_order
sorted = strongly_connected_components.flatten
result = []
seen = {}
sorted.each do |spec|
if index = seen[spec.name] then
if result[index].version < spec.version then
result[index] = spec
end
else
seen[spec.name] = result.length
result << spec
end
end
result.reverse
end
def find_name(full_name)
@specs.find { |spec| spec.full_name == full_name }
end
# Are all the dependencies in the list satisfied?
def ok?
@specs.all? do |spec|
spec.dependencies.all? do |dep|
@specs.find { |s| s.satisfies_requirement? dep }
end
end
end
# Is is ok to remove a gem from the dependency list?
#
# If removing the gemspec creates breaks a currently ok dependency,
# then it is NOT ok to remove the gem.
def ok_to_remove?(full_name)
gem_to_remove = find_name full_name
siblings = @specs.find_all { |s|
s.name == gem_to_remove.name &&
s.full_name != gem_to_remove.full_name
}
deps = []
@specs.each do |spec|
spec.dependencies.each do |dep|
deps << dep if gem_to_remove.satisfies_requirement?(dep)
end
end
deps.all? { |dep|
siblings.any? { |s|
s.satisfies_requirement? dep
}
}
end
def remove_by_name(full_name)
@specs.delete_if { |spec| spec.full_name == full_name }
end
# Return a hash of predecessors. <tt>result[spec]</tt> is an
# Array of gemspecs that have a dependency satisfied by the named
# spec.
def spec_predecessors
result = Hash.new { |h,k| h[k] = [] }
specs = @specs.sort.reverse
specs.each do |spec|
specs.each do |other|
next if spec == other
other.dependencies.each do |dep|
if spec.satisfies_requirement? dep then
result[spec] << other
end
end
end
end
result
end
def tsort_each_node(&block)
@specs.each(&block)
end
def tsort_each_child(node, &block)
specs = @specs.sort.reverse
node.dependencies.each do |dep|
specs.each do |spec|
if spec.satisfies_requirement? dep then
begin
yield spec
rescue TSort::Cyclic
end
break
end
end
end
end
private
# Count the number of gemspecs in the list +specs+ that are not in
# +ignored+.
def active_count(specs, ignored)
result = 0
specs.each do |spec|
result += 1 unless ignored[spec.full_name]
end
result
end
end

View file

@ -0,0 +1,40 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
module Gem
# There is an incompatibility between the way Ruby 1.8.5 and 1.8.6
# handles digests. This DigestAdapter will take a pre-1.8.6 digest
# and adapt it to the 1.8.6 API.
#
# Note that only the digest and hexdigest methods are adapted,
# since these are the only functions used by Gems.
#
class DigestAdapter
# Initialize a digest adapter.
def initialize(digest_class)
@digest_class = digest_class
end
# Return a new digester. Since we are only implementing the stateless
# methods, we will return ourself as the instance.
def new
self
end
# Return the digest of +string+ as a hex string.
def hexdigest(string)
@digest_class.new(string).hexdigest
end
# Return the digest of +string+ as a binary string.
def digest(string)
@digest_class.new(string).digest
end
end
end

23
lib/rubygems/digest/md5.rb Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'digest/md5'
# :stopdoc:
module Gem
if RUBY_VERSION >= '1.8.6'
MD5 = Digest::MD5
else
require 'rubygems/digest/digest_adapter'
MD5 = DigestAdapter.new(Digest::MD5)
def MD5.md5(string)
self.hexdigest(string)
end
end
end
# :startdoc:

17
lib/rubygems/digest/sha1.rb Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'digest/sha1'
module Gem
if RUBY_VERSION >= '1.8.6'
SHA1 = Digest::SHA1
else
require 'rubygems/digest/digest_adapter'
SHA1 = DigestAdapter.new(Digest::SHA1)
end
end

17
lib/rubygems/digest/sha2.rb Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'digest/sha2'
module Gem
if RUBY_VERSION >= '1.8.6'
SHA256 = Digest::SHA256
else
require 'rubygems/digest/digest_adapter'
SHA256 = DigestAdapter.new(Digest::SHA256)
end
end

161
lib/rubygems/doc_manager.rb Normal file
View file

@ -0,0 +1,161 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
module Gem
class DocManager
include UserInteraction
# Create a document manager for the given gem spec.
#
# spec:: The Gem::Specification object representing the gem.
# rdoc_args:: Optional arguments for RDoc (template etc.) as a String.
#
def initialize(spec, rdoc_args="")
@spec = spec
@doc_dir = File.join(spec.installation_path, "doc", spec.full_name)
@rdoc_args = rdoc_args.nil? ? [] : rdoc_args.split
end
# Is the RDoc documentation installed?
def rdoc_installed?
return File.exist?(File.join(@doc_dir, "rdoc"))
end
# Generate the RI documents for this gem spec.
#
# Note that if both RI and RDoc documents are generated from the
# same process, the RI docs should be done first (a likely bug in
# RDoc will cause RI docs generation to fail if run after RDoc).
def generate_ri
if @spec.has_rdoc then
load_rdoc
install_ri # RDoc bug, ri goes first
end
FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
end
# Generate the RDoc documents for this gem spec.
#
# Note that if both RI and RDoc documents are generated from the
# same process, the RI docs should be done first (a likely bug in
# RDoc will cause RI docs generation to fail if run after RDoc).
def generate_rdoc
if @spec.has_rdoc then
load_rdoc
install_rdoc
end
FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
end
# Load the RDoc documentation generator library.
def load_rdoc
if File.exist?(@doc_dir) && !File.writable?(@doc_dir) then
raise Gem::FilePermissionError.new(@doc_dir)
end
FileUtils.mkdir_p @doc_dir unless File.exist?(@doc_dir)
begin
require 'rdoc/rdoc'
rescue LoadError => e
raise Gem::DocumentError,
"ERROR: RDoc documentation generator not installed!"
end
end
def install_rdoc
rdoc_dir = File.join @doc_dir, 'rdoc'
FileUtils.rm_rf rdoc_dir
say "Installing RDoc documentation for #{@spec.full_name}..."
run_rdoc '--op', rdoc_dir
end
def install_ri
ri_dir = File.join @doc_dir, 'ri'
FileUtils.rm_rf ri_dir
say "Installing ri documentation for #{@spec.full_name}..."
run_rdoc '--ri', '--op', ri_dir
end
def run_rdoc(*args)
args << @spec.rdoc_options
args << DocManager.configured_args
args << '--quiet'
args << @spec.require_paths.clone
args << @spec.extra_rdoc_files
args.flatten!
r = RDoc::RDoc.new
old_pwd = Dir.pwd
Dir.chdir(@spec.full_gem_path)
begin
r.document args
rescue Errno::EACCES => e
dirname = File.dirname e.message.split("-")[1].strip
raise Gem::FilePermissionError.new(dirname)
rescue RuntimeError => ex
alert_error "While generating documentation for #{@spec.full_name}"
ui.errs.puts "... MESSAGE: #{ex}"
ui.errs.puts "... RDOC args: #{args.join(' ')}"
ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if
Gem.configuration.backtrace
ui.errs.puts "(continuing with the rest of the installation)"
ensure
Dir.chdir(old_pwd)
end
end
def uninstall_doc
raise Gem::FilePermissionError.new(@spec.installation_path) unless
File.writable? @spec.installation_path
original_name = [
@spec.name, @spec.version, @spec.original_platform].join '-'
doc_dir = File.join @spec.installation_path, 'doc', @spec.full_name
unless File.directory? doc_dir then
doc_dir = File.join @spec.installation_path, 'doc', original_name
end
FileUtils.rm_rf doc_dir
ri_dir = File.join @spec.installation_path, 'ri', @spec.full_name
unless File.directory? ri_dir then
ri_dir = File.join @spec.installation_path, 'ri', original_name
end
FileUtils.rm_rf ri_dir
end
class << self
def configured_args
@configured_args ||= []
end
def configured_args=(args)
case args
when Array
@configured_args = args
when String
@configured_args = args.split
end
end
end
end
end

View file

@ -0,0 +1,63 @@
require 'rubygems'
##
# Base exception class for RubyGems. All exception raised by RubyGems are a
# subclass of this one.
class Gem::Exception < RuntimeError; end
class Gem::CommandLineError < Gem::Exception; end
class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end
class Gem::DocumentError < Gem::Exception; end
##
# Potentially raised when a specification is validated.
class Gem::EndOfYAMLException < Gem::Exception; end
##
# Signals that a file permission error is preventing the user from
# installing in the requested directories.
class Gem::FilePermissionError < Gem::Exception
def initialize(path)
super("You don't have write permissions into the #{path} directory.")
end
end
##
# Used to raise parsing and loading errors
class Gem::FormatException < Gem::Exception
attr_accessor :file_path
end
class Gem::GemNotFoundException < Gem::Exception; end
class Gem::InstallError < Gem::Exception; end
##
# Potentially raised when a specification is validated.
class Gem::InvalidSpecificationException < Gem::Exception; end
class Gem::OperationNotSupportedError < Gem::Exception; end
##
# Signals that a remote operation cannot be conducted, probably due to not
# being connected (or just not finding host).
#--
# TODO: create a method that tests connection to the preferred gems server.
# All code dealing with remote operations will want this. Failure in that
# method should raise this error.
class Gem::RemoteError < Gem::Exception; end
class Gem::RemoteInstallationCancelled < Gem::Exception; end
class Gem::RemoteInstallationSkipped < Gem::Exception; end
##
# Represents an error communicating via HTTP.
class Gem::RemoteSourceException < Gem::Exception; end
class Gem::VerificationError < Gem::Exception; end

18
lib/rubygems/ext.rb Normal file
View file

@ -0,0 +1,18 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
##
# Classes for building C extensions live here.
module Gem::Ext; end
require 'rubygems/ext/builder'
require 'rubygems/ext/configure_builder'
require 'rubygems/ext/ext_conf_builder'
require 'rubygems/ext/rake_builder'

View file

@ -0,0 +1,56 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/ext'
class Gem::Ext::Builder
def self.class_name
name =~ /Ext::(.*)Builder/
$1.downcase
end
def self.make(dest_path, results)
unless File.exist? 'Makefile' then
raise Gem::InstallError, "Makefile not found:\n\n#{results.join "\n"}"
end
mf = File.read('Makefile')
mf = mf.gsub(/^RUBYARCHDIR\s*=\s*\$[^$]*/, "RUBYARCHDIR = #{dest_path}")
mf = mf.gsub(/^RUBYLIBDIR\s*=\s*\$[^$]*/, "RUBYLIBDIR = #{dest_path}")
File.open('Makefile', 'wb') {|f| f.print mf}
make_program = ENV['make']
unless make_program then
make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end
['', ' install'].each do |target|
cmd = "#{make_program}#{target}"
results << cmd
results << `#{cmd} #{redirector}`
raise Gem::InstallError, "make#{target} failed:\n\n#{results}" unless
$?.exitstatus.zero?
end
end
def self.redirector
'2>&1'
end
def self.run(command, results)
results << command
results << `#{command} #{redirector}`
unless $?.exitstatus.zero? then
raise Gem::InstallError, "#{class_name} failed:\n\n#{results.join "\n"}"
end
end
end

View file

@ -0,0 +1,24 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/ext/builder'
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
def self.build(extension, directory, dest_path, results)
unless File.exist?('Makefile') then
cmd = "sh ./configure --prefix=#{dest_path}"
run cmd, results
end
make dest_path, results
results
end
end

View file

@ -0,0 +1,23 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/ext/builder'
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
def self.build(extension, directory, dest_path, results)
cmd = "#{Gem.ruby} #{File.basename extension}"
cmd << " #{ARGV.join ' '}" unless ARGV.empty?
run cmd, results
make dest_path, results
results
end
end

View file

@ -0,0 +1,27 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/ext/builder'
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
def self.build(extension, directory, dest_path, results)
if File.basename(extension) =~ /mkrf_conf/i then
cmd = "#{Gem.ruby} #{File.basename extension}"
cmd << " #{ARGV.join " "}" unless ARGV.empty?
run cmd, results
end
cmd = ENV['rake'] || 'rake'
cmd << " RUBYARCHDIR=#{dest_path} RUBYLIBDIR=#{dest_path}"
run cmd, results
results
end
end

81
lib/rubygems/format.rb Normal file
View file

@ -0,0 +1,81 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
require 'rubygems/package'
module Gem
##
# The format class knows the guts of the RubyGem .gem file format
# and provides the capability to read gem files
#
class Format
attr_accessor :spec, :file_entries, :gem_path
extend Gem::UserInteraction
##
# Constructs an instance of a Format object, representing the gem's
# data structure.
#
# gem:: [String] The file name of the gem
#
def initialize(gem_path)
@gem_path = gem_path
end
##
# Reads the named gem file and returns a Format object, representing
# the data from the gem file
#
# file_path:: [String] Path to the gem file
#
def self.from_file_by_path(file_path, security_policy = nil)
format = nil
unless File.exist?(file_path)
raise Gem::Exception, "Cannot load gem at [#{file_path}] in #{Dir.pwd}"
end
# 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
return format
end
##
# Reads a gem from an io stream and returns a Format object, representing
# the data from the gem file
#
# io:: [IO] Stream from which to read the gem
#
def self.from_io(io, gem_path="(io)", 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
end
end

View file

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
if RUBY_VERSION < "1.9"
require 'rubygems/open-uri'
else
require 'open-uri'
end

View file

@ -0,0 +1,83 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
# Some system might not have OpenSSL installed, therefore the core
# library file openssl might not be available. We localize testing
# for the presence of OpenSSL in this file.
module Gem
class << self
# Is SSL (used by the signing commands) available on this
# platform?
def ssl_available?
require 'rubygems/gem_openssl'
@ssl_available
end
# Set the value of the ssl_avilable flag.
attr_writer :ssl_available
# Ensure that SSL is available. Throw an exception if it is not.
def ensure_ssl_available
unless ssl_available?
fail Gem::Exception, "SSL is not installed on this system"
end
end
end
end
begin
require 'openssl'
# Reference a constant defined in the .rb portion of ssl (just to
# make sure that part is loaded too).
dummy = OpenSSL::Digest::SHA1
Gem.ssl_available = true
class OpenSSL::X509::Certificate # :nodoc:
# Check the validity of this certificate.
def check_validity(issuer_cert = nil, time = Time.now)
ret = if @not_before && @not_before > time
[false, :expired, "not valid before '#@not_before'"]
elsif @not_after && @not_after < time
[false, :expired, "not valid after '#@not_after'"]
elsif issuer_cert && !verify(issuer_cert.public_key)
[false, :issuer, "#{issuer_cert.subject} is not issuer"]
else
[true, :ok, 'Valid certificate']
end
# return hash
{ :is_valid => ret[0], :error => ret[1], :desc => ret[2] }
end
end
rescue LoadError, StandardError
Gem.ssl_available = false
end
module Gem::SSL
# We make our own versions of the constants here. This allows us
# to reference the constants, even though some systems might not
# have SSL installed in the Ruby core package.
#
# These constants are only used during load time. At runtime, any
# method that makes a direct reference to SSL software must be
# protected with a Gem.ensure_ssl_available call.
#
if Gem.ssl_available? then
PKEY_RSA = OpenSSL::PKey::RSA
DIGEST_SHA1 = OpenSSL::Digest::SHA1
else
PKEY_RSA = :rsa
DIGEST_SHA1 = :sha1
end
end

View file

@ -0,0 +1,84 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
#
# GemPathSearcher has the capability to find loadable files inside
# gems. It generates data up front to speed up searches later.
#
class Gem::GemPathSearcher
#
# Initialise the data we need to make searches later.
#
def initialize
# We want a record of all the installed gemspecs, in the order
# we wish to examine them.
@gemspecs = init_gemspecs
# Map gem spec to glob of full require_path directories.
# Preparing this information may speed up searches later.
@lib_dirs = {}
@gemspecs.each do |spec|
@lib_dirs[spec.object_id] = lib_dirs_for(spec)
end
end
#
# Look in all the installed gems until a matching _path_ is found.
# Return the _gemspec_ of the gem where it was found. If no match
# is found, return nil.
#
# The gems are searched in alphabetical order, and in reverse
# version order.
#
# For example:
#
# find('log4r') # -> (log4r-1.1 spec)
# find('log4r.rb') # -> (log4r-1.1 spec)
# find('rake/rdoctask') # -> (rake-0.4.12 spec)
# find('foobarbaz') # -> nil
#
# Matching paths can have various suffixes ('.rb', '.so', and
# others), which may or may not already be attached to _file_.
# This method doesn't care about the full filename that matches;
# only that there is a match.
#
def find(path)
@gemspecs.each do |spec|
return spec if matching_file(spec, path)
end
nil
end
private
# Attempts to find a matching path using the require_paths of the
# given _spec_.
#
# Some of the intermediate results are cached in @lib_dirs for
# speed.
def matching_file(spec, path) # :doc:
glob = File.join @lib_dirs[spec.object_id], "#{path}#{Gem.suffix_pattern}"
return true unless Dir[glob].select { |f| File.file?(f.untaint) }.empty?
end
# Return a list of all installed gemspecs, sorted by alphabetical
# order and in reverse version order.
def init_gemspecs
Gem.source_index.map { |_, spec| spec }.sort { |a,b|
(a.name <=> b.name).nonzero? || (b.version <=> a.version)
}
end
# Returns library directories glob for a gemspec. For example,
# '/usr/local/lib/ruby/gems/1.8/gems/foobar-1.0/{lib,ext}'
def lib_dirs_for(spec)
"#{spec.full_gem_path}/{#{spec.require_paths.join(',')}}"
end
end

View file

@ -0,0 +1,58 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/command_manager'
require 'rubygems/config_file'
require 'rubygems/doc_manager'
module Gem
####################################################################
# Run an instance of the gem program.
#
class GemRunner
def initialize(options={})
@command_manager_class = options[:command_manager] || Gem::CommandManager
@config_file_class = options[:config_file] || Gem::ConfigFile
@doc_manager_class = options[:doc_manager] || Gem::DocManager
end
# Run the gem command with the following arguments.
def run(args)
start_time = Time.now
do_configuration(args)
cmd = @command_manager_class.instance
cmd.command_names.each do |command_name|
config_args = Gem.configuration[command_name]
config_args = case config_args
when String
config_args.split ' '
else
Array(config_args)
end
Command.add_specific_extra_args command_name, config_args
end
cmd.run(Gem.configuration.args)
end_time = Time.now
if Gem.configuration.benchmark
printf "\nExecution time: %0.2f seconds.\n", end_time-start_time
puts "Press Enter to finish"
STDIN.gets
end
end
private
def do_configuration(args)
Gem.configuration = @config_file_class.new(args)
Gem.use_paths(Gem.configuration[:gemhome], Gem.configuration[:gempath])
Gem::Command.extra_args = Gem.configuration[:gem]
@doc_manager_class.configured_args = Gem.configuration[:rdoc]
end
end # class
end # module

171
lib/rubygems/indexer.rb Normal file
View file

@ -0,0 +1,171 @@
require 'fileutils'
require 'tmpdir'
require 'rubygems'
require 'rubygems/format'
begin
require 'builder/xchar'
rescue LoadError
end
##
# Top level class for building the gem repository index.
class Gem::Indexer
include Gem::UserInteraction
##
# Index install location
attr_reader :dest_directory
##
# Index build directory
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:" \
"\n\tgem install builder"
end
@dest_directory = directory
@directory = File.join Dir.tmpdir, "gem_generate_index_#{$$}"
marshal_name = "Marshal.#{Gem.marshal_version}"
@master_index = Gem::Indexer::MasterIndexBuilder.new "yaml", @directory
@marshal_index = Gem::Indexer::MarshalIndexBuilder.new marshal_name, @directory
@quick_index = Gem::Indexer::QuickIndexBuilder.new "index", @directory
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
original_name = if spec.platform == Gem::Platform::RUBY or
spec.platform.nil? then
spec.full_name
else
"#{spec.name}-#{spec.version}-#{spec.original_platform}"
end
unless gemfile =~ /\/#{Regexp.escape spec.full_name}.*\.gem\z/i or
gemfile =~ /\/#{Regexp.escape original_name}.*\.gem\z/i then
alert_warning "Skipping misnamed gem: #{gemfile} => #{spec.full_name} (#{original_name})"
next
end
abbreviate spec
sanitize spec
@master_index.add spec
@quick_index.add spec
@marshal_index.add spec
progress.updated spec.full_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
end
def install_index
verbose = Gem.configuration.really_verbose
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#{@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
def generate_index
FileUtils.rm_rf @directory
FileUtils.mkdir_p @directory, :mode => 0700
build_index
install_index
rescue SignalException
ensure
FileUtils.rm_rf @directory
end
# List of gem file names to index.
def gem_file_list
Dir.glob(File.join(@dest_directory, "gems", "*.gem"))
end
# Abbreviate the spec for downloading. Abbreviated specs are only
# used for searching, downloading and related activities and do not
# need deployment specific information (e.g. list of files). So we
# abbreviate the spec, making it much smaller for quicker downloads.
def abbreviate(spec)
spec.files = []
spec.test_files = []
spec.rdoc_options = []
spec.extra_rdoc_files = []
spec.cert_chain = []
spec
end
# Sanitize the descriptive fields in the spec. Sometimes non-ASCII
# characters will garble the site index. Non-ASCII characters will
# be replaced by their XML entity equivalent.
def sanitize(spec)
spec.summary = sanitize_string(spec.summary)
spec.description = sanitize_string(spec.description)
spec.post_install_message = sanitize_string(spec.post_install_message)
spec.authors = spec.authors.collect { |a| sanitize_string(a) }
spec
end
# Sanitize a single string.
def sanitize_string(string)
# HACK the #to_s is in here because RSpec has an Array of Arrays of
# Strings for authors. Need a way to disallow bad values on gempsec
# generation. (Probably won't happen.)
string ? string.to_s.to_xs : string
end
end
require 'rubygems/indexer/abstract_index_builder'
require 'rubygems/indexer/master_index_builder'
require 'rubygems/indexer/quick_index_builder'
require 'rubygems/indexer/marshal_index_builder'

View file

@ -0,0 +1,80 @@
require 'zlib'
require 'rubygems/indexer'
# Abstract base class for building gem indicies. Uses the template pattern
# with subclass specialization in the +begin_index+, +end_index+ and +cleanup+
# methods.
class Gem::Indexer::AbstractIndexBuilder
# Directory to put index files in
attr_reader :directory
# File name of the generated index
attr_reader :filename
# List of written files/directories to move into production
attr_reader :files
def initialize(filename, directory)
@filename = filename
@directory = directory
@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
start_index
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
end
# Called immediately before the yield in build. The index file is open and
# available as @file.
def start_index
end
# Called immediately after the yield in build. The index file is still open
# and available as @file.
def end_index
end
# Called from within builder after the index file has been closed.
def cleanup
end
# Return an uncompressed version of a compressed string.
def unzip(string)
Zlib::Inflate.inflate(string)
end
# Return a compressed version of the given string.
def zip(string)
Zlib::Deflate.deflate(string)
end
end

View file

@ -0,0 +1,8 @@
require 'rubygems/indexer'
# Construct the master Gem index file.
class Gem::Indexer::MarshalIndexBuilder < Gem::Indexer::MasterIndexBuilder
def end_index
@file.write @index.dump
end
end

View file

@ -0,0 +1,44 @@
require 'rubygems/indexer'
# Construct the master Gem index file.
class Gem::Indexer::MasterIndexBuilder < Gem::Indexer::AbstractIndexBuilder
def start_index
super
@index = Gem::SourceIndex.new
end
def end_index
super
@file.puts @index.to_yaml
end
def cleanup
super
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)
@index.add_spec(spec)
end
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
end

View file

@ -0,0 +1,48 @@
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)
directory = File.join directory, 'quick'
super filename, directory
end
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)
@file.puts spec.full_name
add_yaml(spec)
add_marshal(spec)
end
def add_yaml(spec)
fn = File.join @directory, "#{spec.full_name}.gemspec.rz"
zipped = zip spec.to_yaml
File.open fn, "wb" do |gsfile| gsfile.write zipped end
end
def add_marshal(spec)
# HACK why does this not work in #initialize?
FileUtils.mkdir_p File.join(@directory, "Marshal.#{Gem.marshal_version}")
fn = File.join @directory, "Marshal.#{Gem.marshal_version}",
"#{spec.full_name}.gemspec.rz"
zipped = zip Marshal.dump(spec)
File.open fn, "wb" do |gsfile| gsfile.write zipped end
end
end

View file

@ -0,0 +1,87 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
require 'rubygems/security'
##
# Mixin methods for install and update options for Gem::Commands
module Gem::InstallUpdateOptions
# Add the install/update options to the option parser.
def add_install_update_options
OptionParser.accept Gem::Security::Policy do |value|
value = Gem::Security::Policies[value]
raise OptionParser::InvalidArgument, value if value.nil?
value
end
add_option(:"Install/Update", '-i', '--install-dir DIR',
'Gem repository directory to get installed',
'gems') do |value, options|
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|
options[:generate_rdoc] = value
end
add_option(:"Install/Update", '--[no-]ri',
'Generate RI documentation for the gem on',
'install') do |value, options|
options[:generate_ri] = value
end
add_option(:"Install/Update", '-E', '--env-shebang',
"Rewrite the shebang line on installed",
"scripts to use /usr/bin/env") do |value, options|
options[:env_shebang] = value
end
add_option(:"Install/Update", '-f', '--[no-]force',
'Force gem to install, bypassing dependency',
'checks') do |value, options|
options[:force] = value
end
add_option(:"Install/Update", '-t', '--[no-]test',
'Run unit tests prior to installation') do |value, options|
options[:test] = value
end
add_option(:"Install/Update", '-w', '--[no-]wrappers',
'Use bin wrappers for executables',
'Not available on dosish platforms') do |value, options|
options[:wrappers] = value
end
add_option(:"Install/Update", '-P', '--trust-policy POLICY',
Gem::Security::Policy,
'Specify gem trust policy') do |value, options|
options[:security_policy] = value
end
add_option(:"Install/Update", '--ignore-dependencies',
'Do not install any required dependent gems') do |value, options|
options[:ignore_dependencies] = value
end
add_option(:"Install/Update", '-y', '--include-dependencies',
'Unconditionally install the required',
'dependent gems') do |value, options|
options[:include_dependencies] = value
end
end
# Default options for the gem install command.
def install_update_defaults_str
'--rdoc --no-force --no-test --wrappers'
end
end

421
lib/rubygems/installer.rb Normal file
View file

@ -0,0 +1,421 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
require 'pathname'
require 'rbconfig'
require 'rubygems/format'
require 'rubygems/ext'
##
# The installer class processes RubyGem .gem files and installs the
# files contained in the .gem into the Gem.path.
#
# Gem::Installer does the work of putting files in all the right places on the
# filesystem including unpacking the gem into its gem dir, installing the
# gemspec in the specifications dir, storing the cached gem in the cache dir,
# and installing either wrappers or symlinks for executables.
class Gem::Installer
##
# Raised when there is an error while building extensions.
#
class ExtensionBuildError < Gem::InstallError; end
include Gem::UserInteraction
##
# Constructs an Installer instance that will install the gem located at
# +gem+. +options+ is a Hash with the following keys:
#
# :env_shebang:: Use /usr/bin/env in bin wrappers.
# :force:: Overrides all version checks and security policy checks, except
# for a signed-gems-only policy.
# :ignore_dependencies:: Don't raise if a dependency is missing.
# :install_dir:: The directory to install the gem into.
# :security_policy:: Use the specified security policy. See Gem::Security
# :wrappers:: Install wrappers if true, symlinks if false.
def initialize(gem, options={})
@gem = gem
options = { :force => false, :install_dir => Gem.dir }.merge options
@env_shebang = options[:env_shebang]
@force = options[:force]
gem_home = options[:install_dir]
@gem_home = Pathname.new(gem_home).expand_path
@ignore_dependencies = options[:ignore_dependencies]
@security_policy = options[:security_policy]
@wrappers = options[:wrappers]
begin
@format = Gem::Format.from_file_by_path @gem, @security_policy
rescue Gem::Package::FormatError
raise Gem::InstallError, "invalid gem format for #{@gem}"
end
@spec = @format.spec
@gem_dir = File.join(@gem_home, "gems", @spec.full_name).untaint
end
##
# Installs the gem and returns a loaded Gem::Specification for the installed
# gem.
#
# The gem will be installed with the following structure:
#
# @gem_home/
# cache/<gem-version>.gem #=> a cached copy of the installed gem
# gems/<gem-version>/... #=> extracted files
# specifications/<gem-version>.gemspec #=> the Gem::Specification
def install
# If we're forcing the install then disable security unless the security
# policy says that we only install singed gems.
@security_policy = nil if @force and @security_policy and
not @security_policy.only_signed
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
if rrgv = @spec.required_rubygems_version then
unless rrgv.satisfied_by? Gem::Version.new(Gem::RubyGemsVersion) then
raise Gem::InstallError,
"#{@spec.name} requires RubyGems version #{rrgv}"
end
end
unless @ignore_dependencies then
@spec.dependencies.each do |dep_gem|
ensure_dependency @spec, dep_gem
end
end
end
FileUtils.mkdir_p @gem_home unless File.directory? @gem_home
raise Gem::FilePermissionError, @gem_home unless File.writable? @gem_home
Gem.ensure_gem_subdirectories @gem_home
FileUtils.mkdir_p @gem_dir
extract_files
generate_bin
build_extensions
write_spec
# HACK remove? Isn't this done in multiple places?
cached_gem = File.join @gem_home, "cache", @gem.split(/\//).pop
unless File.exist? cached_gem then
FileUtils.cp @gem, File.join(@gem_home, "cache")
end
say @spec.post_install_message unless @spec.post_install_message.nil?
@spec.loaded_from = File.join(@gem_home, 'specifications',
"#{@spec.full_name}.gemspec")
return @spec
rescue Zlib::GzipFile::Error
raise Gem::InstallError, "gzip error installing #{@gem}"
end
##
# Ensure that the dependency is satisfied by the current installation of
# gem. If it is not an exception is raised.
#
# spec :: Gem::Specification
# dependency :: Gem::Dependency
def ensure_dependency(spec, dependency)
unless installation_satisfies_dependency? dependency then
raise Gem::InstallError, "#{spec.name} requires #{dependency}"
end
true
end
##
# True if the current installed gems satisfy the given dependency.
#
# dependency :: Gem::Dependency
def installation_satisfies_dependency?(dependency)
current_index = Gem::SourceIndex.from_installed_gems
current_index.find_name(dependency.name, dependency.version_requirements).size > 0
end
##
# Unpacks the gem into the given directory.
#
def unpack(directory)
@gem_dir = directory
@format = Gem::Format.from_file_by_path @gem, @security_policy
extract_files
end
##
# Writes the .gemspec specification (in Ruby) to the supplied
# spec_path.
#
# spec:: [Gem::Specification] The Gem specification to output
# spec_path:: [String] The location (path) to write the gemspec to
#
def write_spec
rubycode = @spec.to_ruby
file_name = File.join @gem_home, 'specifications',
"#{@spec.full_name}.gemspec"
file_name.untaint
File.open(file_name, "w") do |file|
file.puts rubycode
end
end
##
# Creates windows .bat files for easy running of commands
#
def generate_windows_script(bindir, filename)
if Gem.win_platform? then
script_name = filename + ".bat"
File.open(File.join(bindir, File.basename(script_name)), "w") do |file|
file.puts windows_stub_script(bindir, filename)
end
end
end
def generate_bin
return if @spec.executables.nil? or @spec.executables.empty?
# If the user has asked for the gem to be installed in a directory that is
# the system gem directory, then use the system bin directory, else create
# (or use) a new bin dir under the gem_home.
bindir = Gem.bindir @gem_home
Dir.mkdir bindir unless File.exist? bindir
raise Gem::FilePermissionError.new(bindir) unless File.writable? bindir
@spec.executables.each do |filename|
filename.untaint
bin_path = File.join @gem_dir, 'bin', filename
mode = File.stat(bin_path).mode | 0111
File.chmod mode, bin_path
if @wrappers then
generate_bin_script filename, bindir
else
generate_bin_symlink filename, bindir
end
end
end
##
# Creates the scripts to run the applications in the gem.
#--
# The Windows script is generated in addition to the regular one due to a
# bug or misfeature in the Windows shell's pipe. See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/193379
#
def generate_bin_script(filename, bindir)
File.open(File.join(bindir, File.basename(filename)), "w", 0755) do |file|
file.print app_script_text(filename)
end
generate_windows_script bindir, filename
end
##
# Creates the symlinks to run the applications in the gem. Moves
# the symlink if the gem being installed has a newer version.
#
def generate_bin_symlink(filename, bindir)
if Config::CONFIG["arch"] =~ /dos|win32/i then
alert_warning "Unable to use symlinks on win32, installing wrapper"
generate_bin_script filename, bindir
return
end
src = File.join @gem_dir, 'bin', filename
dst = File.join bindir, File.basename(filename)
if File.exist? dst then
if File.symlink? dst then
link = File.readlink(dst).split File::SEPARATOR
cur_version = Gem::Version.create(link[-3].sub(/^.*-/, ''))
return if @spec.version < cur_version
end
File.unlink dst
end
File.symlink src, dst
end
##
# Generates a #! line for +bin_file_name+'s wrapper copying arguments if
# necessary.
def shebang(bin_file_name)
if @env_shebang then
"#!/usr/bin/env ruby"
else
path = File.join @gem_dir, @spec.bindir, bin_file_name
File.open(path, "rb") do |file|
first_line = file.gets
if first_line =~ /^#!/ then
# Preserve extra words on shebang line, like "-w". Thanks RPA.
shebang = first_line.sub(/\A\#!.*?ruby\S*/, "#!#{Gem.ruby}")
else
# Create a plain shebang line.
shebang = "#!#{Gem.ruby}"
end
shebang.strip # Avoid nasty ^M issues.
end
end
end
# Return the text for an application file.
def app_script_text(bin_file_name)
<<-TEXT
#{shebang bin_file_name}
#
# This file was generated by RubyGems.
#
# The application '#{@spec.name}' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = "#{Gem::Requirement.default}"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem '#{@spec.name}', version
load '#{bin_file_name}'
TEXT
end
# return the stub script text used to launch the true ruby script
def windows_stub_script(bindir, bin_file_name)
<<-TEXT
@ECHO OFF
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
# Builds extensions. Valid types of extensions are extconf.rb files,
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
return if @spec.extensions.empty?
say "Building native extensions. This could take a while..."
start_dir = Dir.pwd
dest_path = File.join @gem_dir, @spec.require_paths.first
ran_rake = false # only run rake once
@spec.extensions.each do |extension|
break if ran_rake
results = []
builder = case extension
when /extconf/ then
Gem::Ext::ExtConfBuilder
when /configure/ then
Gem::Ext::ConfigureBuilder
when /rakefile/i, /mkrf_conf/i then
ran_rake = true
Gem::Ext::RakeBuilder
else
results = ["No builder for extension '#{extension}'"]
nil
end
begin
Dir.chdir File.join(@gem_dir, File.dirname(extension))
results = builder.build(extension, @gem_dir, dest_path, results)
rescue => ex
results = results.join "\n"
File.open('gem_make.out', 'wb') { |f| f.puts results }
message = <<-EOF
ERROR: Failed to build gem native extension.
#{results}
Gem files will remain installed in #{@gem_dir} for inspection.
Results logged to #{File.join(Dir.pwd, 'gem_make.out')}
EOF
raise ExtensionBuildError, message
ensure
Dir.chdir start_dir
end
end
end
##
# Reads the file index and extracts each file into the gem directory.
#
# Ensures that files can't be installed outside the gem directory.
def extract_files
expand_and_validate_gem_dir
raise ArgumentError, "format required to extract from" if @format.nil?
@format.file_entries.each do |entry, file_data|
path = entry['path'].untaint
if path =~ /\A\// then # for extra sanity
raise Gem::InstallError,
"attempt to install file into #{entry['path'].inspect}"
end
path = File.expand_path File.join(@gem_dir, path)
if path !~ /\A#{Regexp.escape @gem_dir}/ then
msg = "attempt to install file into %p under %p" %
[entry['path'], @gem_dir]
raise Gem::InstallError, msg
end
FileUtils.mkdir_p File.dirname(path)
File.open(path, "wb") do |out|
out.write file_data
end
end
end
private
# HACK Pathname is broken on windows.
def absolute_path? pathname
pathname.absolute? or (Gem.win_platform? and pathname.to_s =~ /\A[a-z]:/i)
end
def expand_and_validate_gem_dir
@gem_dir = Pathname.new(@gem_dir).expand_path
unless absolute_path?(@gem_dir) then # HACK is this possible after #expand_path?
raise ArgumentError, "install directory %p not absolute" % @gem_dir
end
@gem_dir = @gem_dir.to_s
end
end

View file

@ -0,0 +1,106 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
# Mixin methods for local and remote Gem::Command options.
module Gem::LocalRemoteOptions
# Allows OptionParser to handle HTTP URIs.
def accept_uri_http
OptionParser.accept URI::HTTP do |value|
begin
value = URI.parse value
rescue URI::InvalidURIError
raise OptionParser::InvalidArgument, value
end
raise OptionParser::InvalidArgument, value unless value.scheme == 'http'
value
end
end
# Add local/remote options to the command line parser.
def add_local_remote_options
add_option(:"Local/Remote", '-l', '--local',
'Restrict operations to the LOCAL domain') do |value, options|
options[:domain] = :local
end
add_option(:"Local/Remote", '-r', '--remote',
'Restrict operations to the REMOTE domain') do |value, options|
options[:domain] = :remote
end
add_option(:"Local/Remote", '-b', '--both',
'Allow LOCAL and REMOTE operations') do |value, options|
options[:domain] = :both
end
add_bulk_threshold_option
add_source_option
add_proxy_option
add_update_sources_option
end
# Add the --bulk-threshold option
def add_bulk_threshold_option
add_option(:"Local/Remote", '-B', '--bulk-threshold COUNT',
"Threshold for switching to bulk",
"synchronization (default #{Gem.configuration.bulk_threshold})") do
|value, options|
Gem.configuration.bulk_threshold = value.to_i
end
end
# Add the --http-proxy option
def add_proxy_option
accept_uri_http
add_option(:"Local/Remote", '-p', '--[no-]http-proxy [URL]', URI::HTTP,
'Use HTTP proxy for remote operations') do |value, options|
options[:http_proxy] = (value == false) ? :no_proxy : value
Gem.configuration[:http_proxy] = options[:http_proxy]
end
end
# Add the --source option
def add_source_option
accept_uri_http
add_option(:"Local/Remote", '--source URL', URI::HTTP,
'Use URL as the remote source for gems') do |value, options|
if options[:added_source] then
Gem.sources << value
else
options[:added_source] = true
Gem.sources.replace [value]
end
end
end
# Add the --source option
def add_update_sources_option
add_option(:"Local/Remote", '-u', '--[no-]update-sources',
'Update local source cache') do |value, options|
Gem.configuration.update_sources = value
end
end
# Is local fetching enabled?
def local?
options[:domain] == :local || options[:domain] == :both
end
# Is remote fetching enabled?
def remote?
options[:domain] == :remote || options[:domain] == :both
end
end

148
lib/rubygems/old_format.rb Normal file
View file

@ -0,0 +1,148 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
require 'yaml'
require 'zlib'
module Gem
##
# The format class knows the guts of the RubyGem .gem file format
# and provides the capability to read gem files
#
class OldFormat
attr_accessor :spec, :file_entries, :gem_path
##
# Constructs an instance of a Format object, representing the gem's
# data structure.
#
# gem:: [String] The file name of the gem
#
def initialize(gem_path)
@gem_path = gem_path
end
##
# Reads the named gem file and returns a Format object, representing
# the data from the gem file
#
# file_path:: [String] Path to the gem file
#
def self.from_file_by_path(file_path)
unless File.exist?(file_path)
raise Gem::Exception, "Cannot load gem file [#{file_path}]"
end
File.open(file_path, 'rb') do |file|
from_io(file, file_path)
end
end
##
# Reads a gem from an io stream and returns a Format object, representing
# the data from the gem file
#
# io:: [IO] Stream from which to read the gem
#
def self.from_io(io, gem_path="(io)")
format = self.new(gem_path)
skip_ruby(io)
format.spec = read_spec(io)
format.file_entries = []
read_files_from_gem(io) do |entry, file_data|
format.file_entries << [entry, file_data]
end
format
end
private
##
# Skips the Ruby self-install header. After calling this method, the
# IO index will be set after the Ruby code.
#
# file:: [IO] The IO to process (skip the Ruby code)
#
def self.skip_ruby(file)
end_seen = false
loop {
line = file.gets
if(line == nil || line.chomp == "__END__") then
end_seen = true
break
end
}
if(end_seen == false) then
raise Gem::Exception.new("Failed to find end of ruby script while reading gem")
end
end
##
# Reads the specification YAML from the supplied IO and constructs
# a Gem::Specification from it. After calling this method, the
# IO index will be set after the specification header.
#
# file:: [IO] The IO to process
#
def self.read_spec(file)
yaml = ''
begin
read_until_dashes(file) do |line|
yaml << line
end
Specification.from_yaml(yaml)
rescue YAML::Error => e
raise Gem::Exception.new("Failed to parse gem specification out of gem file")
rescue ArgumentError => e
raise Gem::Exception.new("Failed to parse gem specification out of gem file")
end
end
##
# Reads lines from the supplied IO until a end-of-yaml (---) is
# reached
#
# file:: [IO] The IO to process
# block:: [String] The read line
#
def self.read_until_dashes(file)
while((line = file.gets) && line.chomp.strip != "---") do
yield line
end
end
##
# Reads the embedded file data from a gem file, yielding an entry
# containing metadata about the file and the file contents themselves
# for each file that's archived in the gem.
# NOTE: Many of these methods should be extracted into some kind of
# Gem file read/writer
#
# gem_file:: [IO] The IO to process
#
def self.read_files_from_gem(gem_file)
errstr = "Error reading files from gem"
header_yaml = ''
begin
self.read_until_dashes(gem_file) do |line|
header_yaml << line
end
header = YAML.load(header_yaml)
raise Gem::Exception.new(errstr) unless header
header.each do |entry|
file_data = ''
self.read_until_dashes(gem_file) do |line|
file_data << line
end
yield [entry, Zlib::Inflate.inflate(file_data.strip.unpack("m")[0])]
end
rescue Exception,Zlib::DataError => e
raise Gem::Exception.new(errstr)
end
end
end
end

773
lib/rubygems/open-uri.rb Normal file
View file

@ -0,0 +1,773 @@
require 'uri'
require 'stringio'
require 'time'
# :stopdoc:
module Kernel
private
alias rubygems_open_uri_original_open open # :nodoc:
# makes possible to open various resources including URIs.
# If the first argument respond to `open' method,
# the method is called with the rest arguments.
#
# If the first argument is a string which begins with xxx://,
# it is parsed by URI.parse. If the parsed object respond to `open' method,
# the method is called with the rest arguments.
#
# Otherwise original open is called.
#
# Since open-uri.rb provides URI::HTTP#open, URI::HTTPS#open and
# URI::FTP#open,
# Kernel[#.]open can accepts such URIs and strings which begins with
# http://, https:// and ftp://.
# In these case, the opened file object is extended by OpenURI::Meta.
def open(name, *rest, &block) # :doc:
if name.respond_to?(:open)
name.open(*rest, &block)
elsif name.respond_to?(:to_str) &&
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
(uri = URI.parse(name)).respond_to?(:open)
uri.open(*rest, &block)
else
rubygems_open_uri_original_open(name, *rest, &block)
end
end
module_function :open
end
# OpenURI is an easy-to-use wrapper for net/http, net/https and net/ftp.
#
#== Example
#
# It is possible to open http/https/ftp URL as usual like opening a file:
#
# open("http://www.ruby-lang.org/") {|f|
# f.each_line {|line| p line}
# }
#
# The opened file has several methods for meta information as follows since
# it is extended by OpenURI::Meta.
#
# open("http://www.ruby-lang.org/en") {|f|
# f.each_line {|line| p line}
# p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
# p f.content_type # "text/html"
# p f.charset # "iso-8859-1"
# p f.content_encoding # []
# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
# }
#
# Additional header fields can be specified by an optional hash argument.
#
# open("http://www.ruby-lang.org/en/",
# "User-Agent" => "Ruby/#{RUBY_VERSION}",
# "From" => "foo@bar.invalid",
# "Referer" => "http://www.ruby-lang.org/") {|f|
# # ...
# }
#
# The environment variables such as http_proxy, https_proxy and ftp_proxy
# are in effect by default. :proxy => nil disables proxy.
#
# open("http://www.ruby-lang.org/en/raa.html", :proxy => nil) {|f|
# # ...
# }
#
# URI objects can be opened in a similar way.
#
# uri = URI.parse("http://www.ruby-lang.org/en/")
# uri.open {|f|
# # ...
# }
#
# URI objects can be read directly. The returned string is also extended by
# OpenURI::Meta.
#
# str = uri.read
# p str.base_uri
#
# Author:: Tanaka Akira <akr@m17n.org>
module OpenURI
Options = {
:proxy => true,
:proxy_http_basic_authentication => true,
:progress_proc => true,
:content_length_proc => true,
:http_basic_authentication => true,
:read_timeout => true,
:ssl_ca_cert => nil,
:ssl_verify_mode => nil,
}
def OpenURI.check_options(options) # :nodoc:
options.each {|k, v|
next unless Symbol === k
unless Options.include? k
raise ArgumentError, "unrecognized option: #{k}"
end
}
end
def OpenURI.scan_open_optional_arguments(*rest) # :nodoc:
if !rest.empty? && (String === rest.first || Integer === rest.first)
mode = rest.shift
if !rest.empty? && Integer === rest.first
perm = rest.shift
end
end
return mode, perm, rest
end
def OpenURI.open_uri(name, *rest) # :nodoc:
uri = URI::Generic === name ? name : URI.parse(name)
mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest)
options = rest.shift if !rest.empty? && Hash === rest.first
raise ArgumentError.new("extra arguments") if !rest.empty?
options ||= {}
OpenURI.check_options(options)
unless mode == nil ||
mode == 'r' || mode == 'rb' ||
mode == File::RDONLY
raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
end
io = open_loop(uri, options)
if block_given?
begin
yield io
ensure
io.close
end
else
io
end
end
def OpenURI.open_loop(uri, options) # :nodoc:
proxy_opts = []
proxy_opts << :proxy_http_basic_authentication if options.include? :proxy_http_basic_authentication
proxy_opts << :proxy if options.include? :proxy
proxy_opts.compact!
if 1 < proxy_opts.length
raise ArgumentError, "multiple proxy options specified"
end
case proxy_opts.first
when :proxy_http_basic_authentication
opt_proxy, proxy_user, proxy_pass = options.fetch(:proxy_http_basic_authentication)
proxy_user = proxy_user.to_str
proxy_pass = proxy_pass.to_str
if opt_proxy == true
raise ArgumentError.new("Invalid authenticated proxy option: #{options[:proxy_http_basic_authentication].inspect}")
end
when :proxy
opt_proxy = options.fetch(:proxy)
proxy_user = nil
proxy_pass = nil
when nil
opt_proxy = true
proxy_user = nil
proxy_pass = nil
end
case opt_proxy
when true
find_proxy = lambda {|u| pxy = u.find_proxy; pxy ? [pxy, nil, nil] : nil}
when nil, false
find_proxy = lambda {|u| nil}
when String
opt_proxy = URI.parse(opt_proxy)
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
when URI::Generic
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
else
raise ArgumentError.new("Invalid proxy option: #{opt_proxy}")
end
uri_set = {}
buf = nil
while true
redirect = catch(:open_uri_redirect) {
buf = Buffer.new
uri.buffer_open(buf, find_proxy.call(uri), options)
nil
}
if redirect
if redirect.relative?
# Although it violates RFC2616, Location: field may have relative
# URI. It is converted to absolute URI using uri as a base URI.
redirect = uri + redirect
end
unless OpenURI.redirectable?(uri, redirect)
raise "redirection forbidden: #{uri} -> #{redirect}"
end
if options.include? :http_basic_authentication
# send authentication only for the URI directly specified.
options = options.dup
options.delete :http_basic_authentication
end
uri = redirect
raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s
uri_set[uri.to_s] = true
else
break
end
end
io = buf.io
io.base_uri = uri
io
end
def OpenURI.redirectable?(uri1, uri2) # :nodoc:
# This test is intended to forbid a redirection from http://... to
# file:///etc/passwd.
# However this is ad hoc. It should be extensible/configurable.
uri1.scheme.downcase == uri2.scheme.downcase ||
(/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme)
end
def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
if proxy
proxy_uri, proxy_user, proxy_pass = proxy
raise "Non-HTTP proxy URI: #{proxy_uri}" if proxy_uri.class != URI::HTTP
end
if target.userinfo && "1.9.0" <= RUBY_VERSION
# don't raise for 1.8 because compatibility.
raise ArgumentError, "userinfo not supported. [RFC3986]"
end
header = {}
options.each {|k, v| header[k] = v if String === k }
require 'net/http'
klass = Net::HTTP
if URI::HTTP === target
# HTTP or HTTPS
if proxy
if proxy_user && proxy_pass
klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_user, proxy_pass)
else
klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port)
end
end
target_host = target.host
target_port = target.port
request_uri = target.request_uri
else
# FTP over HTTP proxy
target_host = proxy_uri.host
target_port = proxy_uri.port
request_uri = target.to_s
if proxy_user && proxy_pass
header["Proxy-Authorization"] = 'Basic ' + ["#{proxy_user}:#{proxy_pass}"].pack('m').delete("\r\n")
end
end
http = klass.new(target_host, target_port)
if target.class == URI::HTTPS
require 'net/https'
http.use_ssl = true
http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
store = OpenSSL::X509::Store.new
if options[:ssl_ca_cert]
if File.directory? options[:ssl_ca_cert]
store.add_path options[:ssl_ca_cert]
else
store.add_file options[:ssl_ca_cert]
end
else
store.set_default_paths
end
store.set_default_paths
http.cert_store = store
end
if options.include? :read_timeout
http.read_timeout = options[:read_timeout]
end
resp = nil
http.start {
if target.class == URI::HTTPS
# xxx: information hiding violation
sock = http.instance_variable_get(:@socket)
if sock.respond_to?(:io)
sock = sock.io # 1.9
else
sock = sock.instance_variable_get(:@socket) # 1.8
end
sock.post_connection_check(target_host)
end
req = Net::HTTP::Get.new(request_uri, header)
if options.include? :http_basic_authentication
user, pass = options[:http_basic_authentication]
req.basic_auth user, pass
end
http.request(req) {|response|
resp = response
if options[:content_length_proc] && Net::HTTPSuccess === resp
if resp.key?('Content-Length')
options[:content_length_proc].call(resp['Content-Length'].to_i)
else
options[:content_length_proc].call(nil)
end
end
resp.read_body {|str|
buf << str
if options[:progress_proc] && Net::HTTPSuccess === resp
options[:progress_proc].call(buf.size)
end
}
}
}
io = buf.io
io.rewind
io.status = [resp.code, resp.message]
resp.each {|name,value| buf.io.meta_add_field name, value }
case resp
when Net::HTTPSuccess
when Net::HTTPMovedPermanently, # 301
Net::HTTPFound, # 302
Net::HTTPSeeOther, # 303
Net::HTTPTemporaryRedirect # 307
throw :open_uri_redirect, URI.parse(resp['location'])
else
raise OpenURI::HTTPError.new(io.status.join(' '), io)
end
end
class HTTPError < StandardError
def initialize(message, io)
super(message)
@io = io
end
attr_reader :io
end
class Buffer # :nodoc:
def initialize
@io = StringIO.new
@size = 0
end
attr_reader :size
StringMax = 10240
def <<(str)
@io << str
@size += str.length
if StringIO === @io && StringMax < @size
require 'tempfile'
io = Tempfile.new('open-uri')
io.binmode
Meta.init io, @io if @io.respond_to? :meta
io << @io.string
@io = io
end
end
def io
Meta.init @io unless @io.respond_to? :meta
@io
end
end
# Mixin for holding meta-information.
module Meta
def Meta.init(obj, src=nil) # :nodoc:
obj.extend Meta
obj.instance_eval {
@base_uri = nil
@meta = {}
}
if src
obj.status = src.status
obj.base_uri = src.base_uri
src.meta.each {|name, value|
obj.meta_add_field(name, value)
}
end
end
# returns an Array which consists status code and message.
attr_accessor :status
# returns a URI which is base of relative URIs in the data.
# It may differ from the URI supplied by a user because redirection.
attr_accessor :base_uri
# returns a Hash which represents header fields.
# The Hash keys are downcased for canonicalization.
attr_reader :meta
def meta_add_field(name, value) # :nodoc:
@meta[name.downcase] = value
end
# returns a Time which represents Last-Modified field.
def last_modified
if v = @meta['last-modified']
Time.httpdate(v)
else
nil
end
end
RE_LWS = /[\r\n\t ]+/n
RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n
RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n
RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n
def content_type_parse # :nodoc:
v = @meta['content-type']
# The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045.
if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v
type = $1.downcase
subtype = $2.downcase
parameters = []
$3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval|
val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval
parameters << [att.downcase, val]
}
["#{type}/#{subtype}", *parameters]
else
nil
end
end
# returns "type/subtype" which is MIME Content-Type.
# It is downcased for canonicalization.
# Content-Type parameters are stripped.
def content_type
type, *parameters = content_type_parse
type || 'application/octet-stream'
end
# returns a charset parameter in Content-Type field.
# It is downcased for canonicalization.
#
# If charset parameter is not given but a block is given,
# the block is called and its result is returned.
# It can be used to guess charset.
#
# If charset parameter and block is not given,
# nil is returned except text type in HTTP.
# In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1.
def charset
type, *parameters = content_type_parse
if pair = parameters.assoc('charset')
pair.last.downcase
elsif block_given?
yield
elsif type && %r{\Atext/} =~ type &&
@base_uri && /\Ahttp\z/i =~ @base_uri.scheme
"iso-8859-1" # RFC2616 3.7.1
else
nil
end
end
# returns a list of encodings in Content-Encoding field
# as an Array of String.
# The encodings are downcased for canonicalization.
def content_encoding
v = @meta['content-encoding']
if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v
v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase}
else
[]
end
end
end
# Mixin for HTTP and FTP URIs.
module OpenRead
# OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
#
# OpenURI::OpenRead#open takes optional 3 arguments as:
# OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
#
# `mode', `perm' is same as Kernel#open.
#
# However, `mode' must be read mode because OpenURI::OpenRead#open doesn't
# support write mode (yet).
# Also `perm' is just ignored because it is meaningful only for file
# creation.
#
# `options' must be a hash.
#
# Each pairs which key is a string in the hash specify a extra header
# field for HTTP.
# I.e. it is ignored for FTP without HTTP proxy.
#
# The hash may include other options which key is a symbol:
#
# [:proxy]
# Synopsis:
# :proxy => "http://proxy.foo.com:8000/"
# :proxy => URI.parse("http://proxy.foo.com:8000/")
# :proxy => true
# :proxy => false
# :proxy => nil
#
# If :proxy option is specified, the value should be String, URI,
# boolean or nil.
# When String or URI is given, it is treated as proxy URI.
# When true is given or the option itself is not specified,
# environment variable `scheme_proxy' is examined.
# `scheme' is replaced by `http', `https' or `ftp'.
# When false or nil is given, the environment variables are ignored and
# connection will be made to a server directly.
#
# [:proxy_http_basic_authentication]
# Synopsis:
# :proxy_http_basic_authentication => ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"]
# :proxy_http_basic_authentication => [URI.parse("http://proxy.foo.com:8000/"), "proxy-user", "proxy-password"]
#
# If :proxy option is specified, the value should be an Array with 3 elements.
# It should contain a proxy URI, a proxy user name and a proxy password.
# The proxy URI should be a String, an URI or nil.
# The proxy user name and password should be a String.
#
# If nil is given for the proxy URI, this option is just ignored.
#
# If :proxy and :proxy_http_basic_authentication is specified,
# ArgumentError is raised.
#
# [:http_basic_authentication]
# Synopsis:
# :http_basic_authentication=>[user, password]
#
# If :http_basic_authentication is specified,
# the value should be an array which contains 2 strings:
# username and password.
# It is used for HTTP Basic authentication defined by RFC 2617.
#
# [:content_length_proc]
# Synopsis:
# :content_length_proc => lambda {|content_length| ... }
#
# If :content_length_proc option is specified, the option value procedure
# is called before actual transfer is started.
# It takes one argument which is expected content length in bytes.
#
# If two or more transfer is done by HTTP redirection, the procedure
# is called only one for a last transfer.
#
# When expected content length is unknown, the procedure is called with
# nil.
# It is happen when HTTP response has no Content-Length header.
#
# [:progress_proc]
# Synopsis:
# :progress_proc => lambda {|size| ...}
#
# If :progress_proc option is specified, the proc is called with one
# argument each time when `open' gets content fragment from network.
# The argument `size' `size' is a accumulated transfered size in bytes.
#
# If two or more transfer is done by HTTP redirection, the procedure
# is called only one for a last transfer.
#
# :progress_proc and :content_length_proc are intended to be used for
# progress bar.
# For example, it can be implemented as follows using Ruby/ProgressBar.
#
# pbar = nil
# open("http://...",
# :content_length_proc => lambda {|t|
# if t && 0 < t
# pbar = ProgressBar.new("...", t)
# pbar.file_transfer_mode
# end
# },
# :progress_proc => lambda {|s|
# pbar.set s if pbar
# }) {|f| ... }
#
# [:read_timeout]
# Synopsis:
# :read_timeout=>nil (no timeout)
# :read_timeout=>10 (10 second)
#
# :read_timeout option specifies a timeout of read for http connections.
#
# [:ssl_ca_cert]
# Synopsis:
# :ssl_ca_cert=>filename
#
# :ssl_ca_cert is used to specify CA certificate for SSL.
# If it is given, default certificates are not used.
#
# [:ssl_verify_mode]
# Synopsis:
# :ssl_verify_mode=>mode
#
# :ssl_verify_mode is used to specify openssl verify mode.
#
# OpenURI::OpenRead#open returns an IO like object if block is not given.
# Otherwise it yields the IO object and return the value of the block.
# The IO object is extended with OpenURI::Meta.
def open(*rest, &block)
OpenURI.open_uri(self, *rest, &block)
end
# OpenURI::OpenRead#read([options]) reads a content referenced by self and
# returns the content as string.
# The string is extended with OpenURI::Meta.
# The argument `options' is same as OpenURI::OpenRead#open.
def read(options={})
self.open(options) {|f|
str = f.read
Meta.init str, f
str
}
end
end
end
module URI
class Generic
# returns a proxy URI.
# The proxy URI is obtained from environment variables such as http_proxy,
# ftp_proxy, no_proxy, etc.
# If there is no proper proxy, nil is returned.
#
# Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.)
# are examined too.
#
# But http_proxy and HTTP_PROXY is treated specially under CGI environment.
# It's because HTTP_PROXY may be set by Proxy: header.
# So HTTP_PROXY is not used.
# http_proxy is not used too if the variable is case insensitive.
# CGI_HTTP_PROXY can be used instead.
def find_proxy
name = self.scheme.downcase + '_proxy'
proxy_uri = nil
if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI?
# HTTP_PROXY conflicts with *_proxy for proxy settings and
# HTTP_* for header information in CGI.
# So it should be careful to use it.
pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k }
case pairs.length
when 0 # no proxy setting anyway.
proxy_uri = nil
when 1
k, v = pairs.shift
if k == 'http_proxy' && ENV[k.upcase] == nil
# http_proxy is safe to use because ENV is case sensitive.
proxy_uri = ENV[name]
else
proxy_uri = nil
end
else # http_proxy is safe to use because ENV is case sensitive.
proxy_uri = ENV[name]
end
if !proxy_uri
# Use CGI_HTTP_PROXY. cf. libwww-perl.
proxy_uri = ENV["CGI_#{name.upcase}"]
end
elsif name == 'http_proxy'
unless proxy_uri = ENV[name]
if proxy_uri = ENV[name.upcase]
warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.'
end
end
else
proxy_uri = ENV[name] || ENV[name.upcase]
end
if proxy_uri && self.host
require 'socket'
begin
addr = IPSocket.getaddress(self.host)
proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr
rescue SocketError
end
end
if proxy_uri
proxy_uri = URI.parse(proxy_uri)
name = 'no_proxy'
if no_proxy = ENV[name] || ENV[name.upcase]
no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port|
if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host &&
(!port || self.port == port.to_i)
proxy_uri = nil
break
end
}
end
proxy_uri
else
nil
end
end
end
class HTTP
def buffer_open(buf, proxy, options) # :nodoc:
OpenURI.open_http(buf, self, proxy, options)
end
include OpenURI::OpenRead
end
class FTP
def buffer_open(buf, proxy, options) # :nodoc:
if proxy
OpenURI.open_http(buf, self, proxy, options)
return
end
require 'net/ftp'
directories = self.path.split(%r{/}, -1)
directories.shift if directories[0] == '' # strip a field before leading slash
directories.each {|d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
}
unless filename = directories.pop
raise ArgumentError, "no filename: #{self.inspect}"
end
directories.each {|d|
if /[\r\n]/ =~ d
raise ArgumentError, "invalid directory: #{d.inspect}"
end
}
if /[\r\n]/ =~ filename
raise ArgumentError, "invalid filename: #{filename.inspect}"
end
typecode = self.typecode
if typecode && /\A[aid]\z/ !~ typecode
raise ArgumentError, "invalid typecode: #{typecode.inspect}"
end
# The access sequence is defined by RFC 1738
ftp = Net::FTP.open(self.host)
# todo: extract user/passwd from .netrc.
user = 'anonymous'
passwd = nil
user, passwd = self.userinfo.split(/:/) if self.userinfo
ftp.login(user, passwd)
directories.each {|cwd|
ftp.voidcmd("CWD #{cwd}")
}
if typecode
# xxx: typecode D is not handled.
ftp.voidcmd("TYPE #{typecode.upcase}")
end
if options[:content_length_proc]
options[:content_length_proc].call(ftp.size(filename))
end
ftp.retrbinary("RETR #{filename}", 4096) { |str|
buf << str
options[:progress_proc].call(buf.size) if options[:progress_proc]
}
ftp.close
buf.io.rewind
end
include OpenURI::OpenRead
end
end
# :startdoc:

851
lib/rubygems/package.rb Normal file
View file

@ -0,0 +1,851 @@
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
require 'fileutils'
require 'find'
require 'stringio'
require 'yaml'
require 'zlib'
require 'rubygems/digest/md5'
require 'rubygems/security'
require 'rubygems/specification'
# Wrapper for FileUtils meant to provide logging and additional operations if
# needed.
class Gem::FileOperations
def initialize(logger = nil)
@logger = logger
end
def method_missing(meth, *args, &block)
case
when FileUtils.respond_to?(meth)
@logger.log "#{meth}: #{args}" if @logger
FileUtils.send meth, *args, &block
when Gem::FileOperations.respond_to?(meth)
@logger.log "#{meth}: #{args}" if @logger
Gem::FileOperations.send meth, *args, &block
else
super
end
end
end
module Gem::Package
class Error < StandardError; end
class NonSeekableIO < Error; end
class ClosedIO < Error; end
class BadCheckSum < Error; end
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.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)
# This is Jamis Buck's ZLib workaround. The original code is
# commented out while we evaluate this patch.
entry.read(10) # skip the gzip header
zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
is = StringIO.new(zis.inflate(entry.read))
# zis = Zlib::GzipReader.new entry
# dis = zis.read
# is = StringIO.new(dis)
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)
TarOutput.open(destname, signer) do |outp|
dir_class.chdir(src) do
outp.metadata = (file_class.read("RPA/metadata") rescue nil)
find_class.find('.') do |entry|
case
when file_class.file?(entry)
entry.sub!(%r{\./}, "")
next if entry =~ /\ARPA\//
stat = File.stat(entry)
outp.add_file_simple(entry, stat.mode, stat.size) do |os|
file_class.open(entry, "rb") do |f|
os.write(f.read(4096)) until f.eof?
end
end
when file_class.dir?(entry)
entry.sub!(%r{\./}, "")
next if entry == "RPA"
outp.mkdir(entry, file_class.stat(entry).mode)
else
raise "Don't know how to pack this yet!"
end
end
end
end
end
class << self
def file_class
File
end
def dir_class
Dir
end
def find_class # HACK kill me
Find
end
end
end

187
lib/rubygems/platform.rb Normal file
View file

@ -0,0 +1,187 @@
require 'rubygems'
# Available list of platforms for targeting Gem installations.
#
class Gem::Platform
@local = nil
attr_accessor :cpu
attr_accessor :os
attr_accessor :version
def self.local
arch = Config::CONFIG['arch']
arch = "#{arch}_60" if arch =~ /mswin32$/
@local ||= new(arch)
end
def self.match(platform)
Gem.platforms.any? do |local_platform|
platform.nil? or local_platform == platform or
(local_platform != Gem::Platform::RUBY and local_platform =~ platform)
end
end
def self.new(arch) # :nodoc:
case arch
when Gem::Platform::RUBY, nil then
Gem::Platform::RUBY
else
super
end
end
def initialize(arch)
case arch
when Array then
@cpu, @os, @version = arch
when String then
arch = arch.split '-'
if arch.length > 2 and arch.last !~ /\d/ then # reassemble x86-linux-gnu
extra = arch.pop
arch.last << "-#{extra}"
end
cpu = arch.shift
@cpu = case cpu
when /i\d86/ then 'x86'
else cpu
end
if arch.length == 2 and arch.last =~ /^\d+$/ then # for command-line
@os, @version = arch
return
end
os, = arch
@cpu, os = nil, cpu if os.nil? # legacy jruby
@os, @version = case os
when /aix(\d+)/ then [ 'aix', $1 ]
when /cygwin/ then [ 'cygwin', nil ]
when /darwin(\d+)?/ then [ 'darwin', $1 ]
when /freebsd(\d+)/ then [ 'freebsd', $1 ]
when /hpux(\d+)/ then [ 'hpux', $1 ]
when /^java$/, /^jruby$/ then [ 'java', nil ]
when /^java([\d.]*)/ then [ 'java', $1 ]
when /linux/ then [ 'linux', $1 ]
when /mingw32/ then [ 'mingw32', nil ]
when /(mswin\d+)(\_(\d+))?/ then [ $1, $3 ]
when /netbsdelf/ then [ 'netbsdelf', nil ]
when /openbsd(\d+\.\d+)/ then [ 'openbsd', $1 ]
when /solaris(\d+\.\d+)/ then [ 'solaris', $1 ]
# test
when /^(\w+_platform)(\d+)/ then [ $1, $2 ]
else [ 'unknown', nil ]
end
when Gem::Platform then
@cpu = arch.cpu
@os = arch.os
@version = arch.version
else
raise ArgumentError, "invalid argument #{arch.inspect}"
end
end
def inspect
"#<%s:0x%x @cpu=%p, @os=%p, @version=%p>" % [self.class, object_id, *to_a]
end
def to_a
[@cpu, @os, @version]
end
def to_s
to_a.compact.join '-'
end
def ==(other)
self.class === other and
@cpu == other.cpu and @os == other.os and @version == other.version
end
def ===(other)
return nil unless Gem::Platform === other
# cpu
(@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu) and
# os
@os == other.os and
# version
(@version.nil? or other.version.nil? or @version == other.version)
end
def =~(other)
case other
when Gem::Platform then # nop
when String then
# This data is from http://gems.rubyforge.org/gems/yaml on 19 Aug 2007
other = case other
when /^i686-darwin(\d)/ then ['x86', 'darwin', $1]
when /^i\d86-linux/ then ['x86', 'linux', nil]
when 'java', 'jruby' then [nil, 'java', nil]
when /mswin32(\_(\d+))?/ then ['x86', 'mswin32', $2]
when 'powerpc-darwin' then ['powerpc', 'darwin', nil]
when /powerpc-darwin(\d)/ then ['powerpc', 'darwin', $1]
when /sparc-solaris2.8/ then ['sparc', 'solaris', '2.8']
when /universal-darwin(\d)/ then ['universal', 'darwin', $1]
else other
end
other = Gem::Platform.new other
else
return nil
end
self === other
end
##
# A pure-ruby gem that may use Gem::Specification#extensions to build
# binary files.
RUBY = 'ruby'
##
# A platform-specific gem that is built for the packaging ruby's platform.
# This will be replaced with Gem::Platform::local.
CURRENT = 'current'
##
# A One Click Installer-compatible gem, built with VC6 for 32 bit Windows.
#
# CURRENT is preferred over this constant, avoid its use at all costs.
MSWIN32 = new ['x86', 'mswin32', '60']
##
# An x86 Linux-compatible gem
#
# CURRENT is preferred over this constant, avoid its use at all costs.
X86_LINUX = new ['x86', 'linux', nil]
##
# A PowerPC Darwin-compatible gem
#
# CURRENT is preferred over this constant, avoid its use at all costs.
PPC_DARWIN = new ['ppc', 'darwin', nil]
# :stopdoc:
# Here lie legacy constants. These are deprecated.
WIN32 = 'mswin32'
LINUX_586 = 'i586-linux'
DARWIN = 'powerpc-darwin'
# :startdoc:
end

View file

@ -0,0 +1,164 @@
require 'net/http'
require 'uri'
require 'rubygems'
require 'rubygems/gem_open_uri'
##
# RemoteFetcher handles the details of fetching gems and gem information from
# a remote source.
class Gem::RemoteFetcher
class FetchError < Gem::Exception; end
@fetcher = nil
# Cached RemoteFetcher instance.
def self.fetcher
@fetcher ||= new Gem.configuration[:http_proxy]
end
# Initialize a remote fetcher using the source URI and possible proxy
# information.
#
# +proxy+
# * [String]: explicit specification of proxy; overrides any environment
# variable setting
# * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
# HTTP_PROXY_PASS)
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
def initialize(proxy)
@proxy_uri =
case proxy
when :no_proxy then nil
when nil then get_proxy_from_env
when URI::HTTP then proxy
else URI.parse(proxy)
end
end
# Downloads +uri+.
def fetch_path(uri)
open_uri_or_path(uri) do |input|
input.read
end
rescue Timeout::Error
raise FetchError, "timed out fetching #{uri}"
rescue OpenURI::HTTPError, IOError, SocketError, SystemCallError => e
raise FetchError, "#{e.class}: #{e} reading #{uri}"
end
# Returns the size of +uri+ in bytes.
def fetch_size(uri)
return File.size(get_file_uri_path(uri)) if file_uri? uri
uri = URI.parse uri unless URI::Generic === uri
raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === uri
http = connect_to uri.host, uri.port
request = Net::HTTP::Head.new uri.request_uri
request.basic_auth unescape(uri.user), unescape(uri.password) unless
uri.user.nil? or uri.user.empty?
resp = http.request request
if resp.code !~ /^2/ then
raise Gem::RemoteSourceException,
"HTTP Response #{resp.code} fetching #{uri}"
end
if resp['content-length'] then
return resp['content-length'].to_i
else
resp = http.get uri.request_uri
return resp.body.size
end
rescue SocketError, SystemCallError, Timeout::Error => e
raise FetchError, "#{e.message} (#{e.class})"
end
private
def escape(str)
return unless str
URI.escape(str)
end
def unescape(str)
return unless str
URI.unescape(str)
end
# Returns an HTTP proxy URI if one is set in the environment variables.
def get_proxy_from_env
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
return nil if env_proxy.nil? or env_proxy.empty?
uri = URI.parse env_proxy
if uri and uri.user.nil? and uri.password.nil? then
# Probably we have http_proxy_* variables?
uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
end
uri
end
# Normalize the URI by adding "http://" if it is missing.
def normalize_uri(uri)
(uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
end
# Connect to the source host/port, using a proxy if needed.
def connect_to(host, port)
if @proxy_uri
Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port)
else
Net::HTTP.new(host, port)
end
end
# Read the data from the (source based) URI, but if it is a file:// URI,
# read from the filesystem instead.
def open_uri_or_path(uri, &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
# Checks if the provided string is a file:// URI.
def file_uri?(uri)
uri =~ %r{\Afile://}
end
# Given a file:// URI, returns its local path.
def get_file_uri_path(uri)
uri.sub(%r{\Afile://}, '')
end
end

View file

@ -0,0 +1,195 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
require 'rubygems'
require 'rubygems/installer'
require 'rubygems/source_info_cache'
module Gem
class RemoteInstaller
include UserInteraction
# <tt>options[:http_proxy]</tt>::
# * [String]: explicit specification of proxy; overrides any
# environment variable setting
# * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS)
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_
# use a proxy
#
# * <tt>:cache_dir</tt>: override where downloaded gems are cached.
def initialize(options={})
@options = options
@source_index_hash = nil
end
# This method will install package_name onto the local system.
#
# gem_name::
# [String] Name of the Gem to install
#
# version_requirement::
# [default = ">= 0"] Gem version requirement to install
#
# Returns::
# an array of Gem::Specification objects, one for each gem installed.
#
def install(gem_name, version_requirement = Gem::Requirement.default,
force = false, install_dir = Gem.dir)
unless version_requirement.respond_to?(:satisfied_by?)
version_requirement = Gem::Requirement.new [version_requirement]
end
installed_gems = []
begin
spec, source = find_gem_to_install(gem_name, version_requirement)
dependencies = find_dependencies_not_installed(spec.dependencies)
installed_gems << install_dependencies(dependencies, force, install_dir)
cache_dir = @options[:cache_dir] || File.join(install_dir, "cache")
destination_file = File.join(cache_dir, spec.full_name + ".gem")
download_gem(destination_file, source, spec)
installer = new_installer(destination_file)
installed_gems.unshift installer.install(force, install_dir)
rescue RemoteInstallationSkipped => e
alert_error e.message
end
installed_gems.flatten
end
# Return a hash mapping the available source names to the source
# index of that source.
def source_index_hash
return @source_index_hash if @source_index_hash
@source_index_hash = {}
Gem::SourceInfoCache.cache_data.each do |source_uri, sic_entry|
@source_index_hash[source_uri] = sic_entry.source_index
end
@source_index_hash
end
# Finds the Gem::Specification objects and the corresponding source URI
# for gems matching +gem_name+ and +version_requirement+
def specs_n_sources_matching(gem_name, version_requirement)
specs_n_sources = []
source_index_hash.each do |source_uri, source_index|
specs = source_index.search(/^#{Regexp.escape gem_name}$/i,
version_requirement)
# TODO move to SourceIndex#search?
ruby_version = Gem::Version.new RUBY_VERSION
specs = specs.select do |spec|
spec.required_ruby_version.nil? or
spec.required_ruby_version.satisfied_by? ruby_version
end
specs.each { |spec| specs_n_sources << [spec, source_uri] }
end
if specs_n_sources.empty? then
raise GemNotFoundException, "Could not find #{gem_name} (#{version_requirement}) in any repository"
end
specs_n_sources = specs_n_sources.sort_by { |gs,| gs.version }.reverse
specs_n_sources
end
# Find a gem to be installed by interacting with the user.
def find_gem_to_install(gem_name, version_requirement)
specs_n_sources = specs_n_sources_matching gem_name, version_requirement
top_3_versions = specs_n_sources.map{|gs| gs.first.version}.uniq[0..3]
specs_n_sources.reject!{|gs| !top_3_versions.include?(gs.first.version)}
binary_gems = specs_n_sources.reject { |item|
item[0].platform.nil? || item[0].platform==Platform::RUBY
}
# only non-binary gems...return latest
return specs_n_sources.first if binary_gems.empty?
list = specs_n_sources.collect { |spec, source_uri|
"#{spec.name} #{spec.version} (#{spec.platform})"
}
list << "Skip this gem"
list << "Cancel installation"
string, index = choose_from_list(
"Select which gem to install for your platform (#{RUBY_PLATFORM})",
list)
if index.nil? or index == (list.size - 1) then
raise RemoteInstallationCancelled, "Installation of #{gem_name} cancelled."
end
if index == (list.size - 2) then
raise RemoteInstallationSkipped, "Installation of #{gem_name} skipped."
end
specs_n_sources[index]
end
def find_dependencies_not_installed(dependencies)
to_install = []
dependencies.each do |dependency|
srcindex = Gem::SourceIndex.from_installed_gems
matches = srcindex.find_name(dependency.name, dependency.requirement_list)
to_install.push dependency if matches.empty?
end
to_install
end
# Install all the given dependencies. Returns an array of
# Gem::Specification objects, one for each dependency installed.
#
# TODO: For now, we recursively install, but this is not the right
# way to do things (e.g. if a package fails to download, we
# shouldn't install anything).
def install_dependencies(dependencies, force, install_dir)
return if @options[:ignore_dependencies]
installed_gems = []
dependencies.each do |dep|
if @options[:include_dependencies] ||
ask_yes_no("Install required dependency #{dep.name}?", true)
remote_installer = RemoteInstaller.new @options
installed_gems << remote_installer.install(dep.name,
dep.version_requirements,
force, install_dir)
elsif force then
# ignore
else
raise DependencyError, "Required dependency #{dep.name} not installed"
end
end
installed_gems
end
def download_gem(destination_file, source, spec)
return if File.exist? destination_file
uri = source + "/gems/#{spec.full_name}.gem"
response = Gem::RemoteFetcher.fetcher.fetch_path uri
write_gem_to_file response, destination_file
end
def write_gem_to_file(body, destination_file)
FileUtils.mkdir_p(File.dirname(destination_file)) unless File.exist?(destination_file)
File.open(destination_file, 'wb') do |out|
out.write(body)
end
end
def new_installer(gem)
return Installer.new(gem, @options)
end
end
end

157
lib/rubygems/requirement.rb Normal file
View file

@ -0,0 +1,157 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/version'
##
# Requirement version includes a prefaced comparator in addition
# to a version number.
#
# A Requirement object can actually contain multiple, er,
# requirements, as in (> 1.2, < 2.0).
class Gem::Requirement
include Comparable
OPS = {
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
">" => lambda { |v, r| v > r },
"<" => lambda { |v, r| v < r },
">=" => lambda { |v, r| v >= r },
"<=" => lambda { |v, r| v <= r },
"~>" => lambda { |v, r| v >= r && v < r.bump }
}
OP_RE = /#{OPS.keys.map{ |k| Regexp.quote k }.join '|'}/o
##
# Factory method to create a Gem::Requirement object. Input may be a
# Version, a String, or nil. Intended to simplify client code.
#
# If the input is "weird", the default version requirement is returned.
#
def self.create(input)
case input
when Gem::Requirement then
input
when Gem::Version, Array then
new input
else
if input.respond_to? :to_str then
self.new [input.to_str]
else
self.default
end
end
end
##
# A default "version requirement" can surely _only_ be '>= 0'.
#--
# This comment once said:
#
# "A default "version requirement" can surely _only_ be '> 0'."
def self.default
self.new ['>= 0']
end
##
# Constructs a Requirement from +requirements+ which can be a String, a
# Gem::Version, or an Array of those. See parse for details on the
# formatting of requirement strings.
def initialize(requirements)
@requirements = case requirements
when Array then
requirements.map do |requirement|
parse(requirement)
end
else
[parse(requirements)]
end
@version = nil # Avoid warnings.
end
# Marshal raw requirements, rather than the full object
def marshal_dump
[@requirements]
end
# Load custom marshal format
def marshal_load(array)
@requirements = array[0]
@version = nil
end
def to_s # :nodoc:
as_list.join(", ")
end
def as_list
normalize
@requirements.collect { |req|
"#{req[0]} #{req[1]}"
}
end
def normalize
return if not defined? @version or @version.nil?
@requirements = [parse(@version)]
@nums = nil
@version = nil
@op = nil
end
##
# Is the requirement satifised by +version+.
#
# version:: [Gem::Version] the version to compare against
# return:: [Boolean] true if this requirement is satisfied by
# the version, otherwise false
#
def satisfied_by?(version)
normalize
@requirements.all? { |op, rv| satisfy?(op, version, rv) }
end
##
# Is "version op required_version" satisfied?
#
def satisfy?(op, version, required_version)
OPS[op].call(version, required_version)
end
##
# Parse the version requirement obj returning the operator and version.
#
# The requirement can be a String or a Gem::Version. A String can be an
# operator (<, <=, =, =>, >, !=, ~>), a version number, or both, operator
# first.
def parse(obj)
case obj
when /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/o then
[$1, Gem::Version.new($2)]
when /^\s*([0-9.]+)\s*$/ then
['=', Gem::Version.new($1)]
when /^\s*(#{OP_RE})\s*$/o then
[$1, Gem::Version.new('0')]
when Gem::Version then
['=', obj]
else
fail ArgumentError, "Illformed requirement [#{obj.inspect}]"
end
end
def <=>(other)
to_s <=> other.to_s
end
def hash # :nodoc:
to_s.hash
end
end

View file

@ -0,0 +1,6 @@
# DO NOT EDIT
# This file is auto-generated by build scripts.
# See: rake update_version
module Gem
RubyGemsVersion = '0.9.4.6'
end

785
lib/rubygems/security.rb Normal file
View file

@ -0,0 +1,785 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems/gem_openssl'
# = Signed Gems README
#
# == Table of Contents
# * Overview
# * Walkthrough
# * Command-Line Options
# * OpenSSL Reference
# * Bugs/TODO
# * About the Author
#
# == Overview
#
# Gem::Security implements cryptographic signatures in RubyGems. The section
# below is a step-by-step guide to using signed gems and generating your own.
#
# == Walkthrough
#
# In order to start signing your gems, you'll need to build a private key and
# a self-signed certificate. Here's how:
#
# # build a private key and certificate for gemmaster@example.com
# $ gem cert --build gemmaster@example.com
#
# This could take anywhere from 5 seconds to 10 minutes, depending on the
# speed of your computer (public key algorithms aren't exactly the speediest
# crypto algorithms in the world). When it's finished, you'll see the files
# "gem-private_key.pem" and "gem-public_cert.pem" in the current directory.
#
# First things first: take the "gem-private_key.pem" file and move it
# somewhere private, preferably a directory only you have access to, a floppy
# (yuck!), a CD-ROM, or something comparably secure. Keep your private key
# hidden; if it's compromised, someone can sign packages as you (note: PKI has
# ways of mitigating the risk of stolen keys; more on that later).
#
# Now, let's sign an existing gem. I'll be using my Imlib2-Ruby bindings, but
# you can use whatever gem you'd like. Open up your existing gemspec file and
# add the following lines:
#
# # signing key and certificate chain
# s.signing_key = '/mnt/floppy/gem-private_key.pem'
# s.cert_chain = ['gem-public_cert.pem']
#
# (Be sure to replace "/mnt/floppy" with the ultra-secret path to your private
# key).
#
# After that, go ahead and build your gem as usual. Congratulations, you've
# just built your first signed gem! If you peek inside your gem file, you'll
# see a couple of new files have been added:
#
# $ tar tf tar tf Imlib2-Ruby-0.5.0.gem
# data.tar.gz
# data.tar.gz.sig
# metadata.gz
# metadata.gz.sig
#
# Now let's verify the signature. Go ahead and install the gem, but add the
# following options: "-P HighSecurity", like this:
#
# # install the gem with using the security policy "HighSecurity"
# $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
#
# The -P option sets your security policy -- we'll talk about that in just a
# minute. Eh, what's this?
#
# Attempting local installation of 'Imlib2-Ruby-0.5.0.gem'
# ERROR: Error installing gem Imlib2-Ruby-0.5.0.gem[.gem]: Couldn't
# verify data signature: Untrusted Signing Chain Root: cert =
# '/CN=gemmaster/DC=example/DC=com', error = 'path
# "/root/.rubygems/trust/cert-15dbb43a6edf6a70a85d4e784e2e45312cff7030.pem"
# does not exist'
#
# The culprit here is the security policy. RubyGems has several different
# security policies. Let's take a short break and go over the security
# policies. Here's a list of the available security policies, and a brief
# description of each one:
#
# * NoSecurity - Well, no security at all. Signed packages are treated like
# unsigned packages.
# * LowSecurity - Pretty much no security. If a package is signed then
# RubyGems will make sure the signature matches the signing
# certificate, and that the signing certificate hasn't expired, but
# that's it. A malicious user could easily circumvent this kind of
# security.
# * MediumSecurity - Better than LowSecurity and NoSecurity, but still
# fallible. Package contents are verified against the signing
# certificate, and the signing certificate is checked for validity,
# and checked against the rest of the certificate chain (if you don't
# know what a certificate chain is, stay tuned, we'll get to that).
# The biggest improvement over LowSecurity is that MediumSecurity
# won't install packages that are signed by untrusted sources.
# Unfortunately, MediumSecurity still isn't totally secure -- a
# malicious user can still unpack the gem, strip the signatures, and
# distribute the gem unsigned.
# * HighSecurity - Here's the bugger that got us into this mess.
# The HighSecurity policy is identical to the MediumSecurity policy,
# except that it does not allow unsigned gems. A malicious user
# doesn't have a whole lot of options here; he can't modify the
# package contents without invalidating the signature, and he can't
# modify or remove signature or the signing certificate chain, or
# RubyGems will simply refuse to install the package. Oh well, maybe
# he'll have better luck causing problems for CPAN users instead :).
#
# So, the reason RubyGems refused to install our shiny new signed gem was
# because it was from an untrusted source. Well, my code is infallible
# (hah!), so I'm going to add myself as a trusted source.
#
# Here's how:
#
# # add trusted certificate
# gem cert --add gem-public_cert.pem
#
# I've added my public certificate as a trusted source. Now I can install
# packages signed my private key without any hassle. Let's try the install
# command above again:
#
# # install the gem with using the HighSecurity policy (and this time
# # without any shenanigans)
# $ sudo gem install Imlib2-Ruby-0.5.0.gem -P HighSecurity
#
# This time RubyGems should accept your signed package and begin installing.
# While you're waiting for RubyGems to work it's magic, have a look at some of
# the other security commands:
#
# Usage: gem cert [options]
#
# Options:
# -a, --add CERT Add a trusted certificate.
# -l, --list List trusted certificates.
# -r, --remove STRING Remove trusted certificates containing STRING.
# -b, --build EMAIL_ADDR Build private key and self-signed certificate
# for EMAIL_ADDR.
# -C, --certificate CERT Certificate for --sign command.
# -K, --private-key KEY Private key for --sign command.
# -s, --sign NEWCERT Sign a certificate with my key and certificate.
#
# (By the way, you can pull up this list any time you'd like by typing "gem
# cert --help")
#
# Hmm. We've already covered the "--build" option, and the "--add", "--list",
# and "--remove" commands seem fairly straightforward; they allow you to add,
# list, and remove the certificates in your trusted certificate list. But
# what's with this "--sign" option?
#
# To answer that question, let's take a look at "certificate chains", a
# concept I mentioned earlier. There are a couple of problems with
# self-signed certificates: first of all, self-signed certificates don't offer
# a whole lot of security. Sure, the certificate says Yukihiro Matsumoto, but
# how do I know it was actually generated and signed by matz himself unless he
# gave me the certificate in person?
#
# The second problem is scalability. Sure, if there are 50 gem authors, then
# I have 50 trusted certificates, no problem. What if there are 500 gem
# authors? 1000? Having to constantly add new trusted certificates is a
# pain, and it actually makes the trust system less secure by encouraging
# RubyGems users to blindly trust new certificates.
#
# Here's where certificate chains come in. A certificate chain establishes an
# arbitrarily long chain of trust between an issuing certificate and a child
# certificate. So instead of trusting certificates on a per-developer basis,
# we use the PKI concept of certificate chains to build a logical hierarchy of
# trust. Here's a hypothetical example of a trust hierarchy based (roughly)
# on geography:
#
#
# --------------------------
# | rubygems@rubyforge.org |
# --------------------------
# |
# -----------------------------------
# | |
# ---------------------------- -----------------------------
# | seattle.rb@zenspider.com | | dcrubyists@richkilmer.com |
# ---------------------------- -----------------------------
# | | | |
# --------------- ---------------- ----------- --------------
# | alf@seattle | | bob@portland | | pabs@dc | | tomcope@dc |
# --------------- ---------------- ----------- --------------
#
#
# Now, rather than having 4 trusted certificates (one for alf@seattle,
# bob@portland, pabs@dc, and tomecope@dc), a user could actually get by with 1
# certificate: the "rubygems@rubyforge.org" certificate. Here's how it works:
#
# I install "Alf2000-Ruby-0.1.0.gem", a package signed by "alf@seattle". I've
# never heard of "alf@seattle", but his certificate has a valid signature from
# the "seattle.rb@zenspider.com" certificate, which in turn has a valid
# signature from the "rubygems@rubyforge.org" certificate. Voila! At this
# point, it's much more reasonable for me to trust a package signed by
# "alf@seattle", because I can establish a chain to "rubygems@rubyforge.org",
# which I do trust.
#
# And the "--sign" option allows all this to happen. A developer creates
# their build certificate with the "--build" option, then has their
# certificate signed by taking it with them to their next regional Ruby meetup
# (in our hypothetical example), and it's signed there by the person holding
# the regional RubyGems signing certificate, which is signed at the next
# RubyConf by the holder of the top-level RubyGems certificate. At each point
# the issuer runs the same command:
#
# # sign a certificate with the specified key and certificate
# # (note that this modifies client_cert.pem!)
# $ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
# --sign client_cert.pem
#
# Then the holder of issued certificate (in this case, our buddy
# "alf@seattle"), can start using this signed certificate to sign RubyGems.
# By the way, in order to let everyone else know about his new fancy signed
# certificate, "alf@seattle" would change his gemspec file to look like this:
#
# # signing key (still kept in an undisclosed location!)
# s.signing_key = '/mnt/floppy/alf-private_key.pem'
#
# # certificate chain (includes the issuer certificate now too)
# s.cert_chain = ['/home/alf/doc/seattlerb-public_cert.pem',
# '/home/alf/doc/alf_at_seattle-public_cert.pem']
#
# Obviously, this RubyGems trust infrastructure doesn't exist yet. Also, in
# the "real world" issuers actually generate the child certificate from a
# certificate request, rather than sign an existing certificate. And our
# hypothetical infrastructure is missing a certificate revocation system.
# These are that can be fixed in the future...
#
# I'm sure your new signed gem has finished installing by now (unless you're
# installing rails and all it's dependencies, that is ;D). At this point you
# should know how to do all of these new and interesting things:
#
# * build a gem signing key and certificate
# * modify your existing gems to support signing
# * adjust your security policy
# * modify your trusted certificate list
# * sign a certificate
#
# If you've got any questions, feel free to contact me at the email address
# below. The next couple of sections
#
#
# == Command-Line Options
#
# Here's a brief summary of the certificate-related command line options:
#
# gem install
# -P, --trust-policy POLICY Specify gem trust policy.
#
# gem cert
# -a, --add CERT Add a trusted certificate.
# -l, --list List trusted certificates.
# -r, --remove STRING Remove trusted certificates containing
# STRING.
# -b, --build EMAIL_ADDR Build private key and self-signed
# certificate for EMAIL_ADDR.
# -C, --certificate CERT Certificate for --sign command.
# -K, --private-key KEY Private key for --sign command.
# -s, --sign NEWCERT Sign a certificate with my key and
# certificate.
#
# A more detailed description of each options is available in the walkthrough
# above.
#
#
# == OpenSSL Reference
#
# The .pem files generated by --build and --sign are just basic OpenSSL PEM
# files. Here's a couple of useful commands for manipulating them:
#
# # convert a PEM format X509 certificate into DER format:
# # (note: Windows .cer files are X509 certificates in DER format)
# $ openssl x509 -in input.pem -outform der -out output.der
#
# # print out the certificate in a human-readable format:
# $ openssl x509 -in input.pem -noout -text
#
# And you can do the same thing with the private key file as well:
#
# # convert a PEM format RSA key into DER format:
# $ openssl rsa -in input_key.pem -outform der -out output_key.der
#
# # print out the key in a human readable format:
# $ openssl rsa -in input_key.pem -noout -text
#
# == Bugs/TODO
#
# * There's no way to define a system-wide trust list.
# * custom security policies (from a YAML file, etc)
# * Simple method to generate a signed certificate request
# * Support for OCSP, SCVP, CRLs, or some other form of cert
# status check (list is in order of preference)
# * Support for encrypted private keys
# * Some sort of semi-formal trust hierarchy (see long-winded explanation
# above)
# * Path discovery (for gem certificate chains that don't have a self-signed
# root) -- by the way, since we don't have this, THE ROOT OF THE CERTIFICATE
# CHAIN MUST BE SELF SIGNED if Policy#verify_root is true (and it is for the
# MediumSecurity and HighSecurity policies)
# * Better explanation of X509 naming (ie, we don't have to use email
# addresses)
# * Possible alternate signing mechanisms (eg, via PGP). this could be done
# pretty easily by adding a :signing_type attribute to the gemspec, then add
# the necessary support in other places
# * Honor AIA field (see note about OCSP above)
# * Maybe honor restriction extensions?
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
# file, instead of an array embedded in the metadata. ideas?
# * Possibly embed signature and key algorithms into metadata (right now
# they're assumed to be the same as what's set in Gem::Security::OPT)
#
# == About the Author
#
# Paul Duncan <pabs@pablotron.org>
# http://pablotron.org/
module Gem::Security
class Exception < Exception; end
#
# default options for most of the methods below
#
OPT = {
# private key options
:key_algo => Gem::SSL::PKEY_RSA,
:key_size => 2048,
# public cert options
:cert_age => 365 * 24 * 3600, # 1 year
:dgst_algo => Gem::SSL::DIGEST_SHA1,
# x509 certificate extensions
:cert_exts => {
'basicConstraints' => 'CA:FALSE',
'subjectKeyIdentifier' => 'hash',
'keyUsage' => 'keyEncipherment,dataEncipherment,digitalSignature',
},
# save the key and cert to a file in build_self_signed_cert()?
:save_key => true,
:save_cert => true,
# if you define either of these, then they'll be used instead of
# the output_fmt macro below
:save_key_path => nil,
:save_cert_path => nil,
# output name format for self-signed certs
:output_fmt => 'gem-%s.pem',
:munge_re => Regexp.new(/[^a-z0-9_.-]+/),
# output directory for trusted certificate checksums
:trust_dir => File::join(Gem.user_home, '.gem', 'trust'),
# default permissions for trust directory and certs
:perms => {
:trust_dir => 0700,
:trusted_cert => 0600,
:signing_cert => 0600,
:signing_key => 0600,
},
}
#
# A Gem::Security::Policy object encapsulates the settings for verifying
# signed gem files. This is the base class. You can either declare an
# instance of this or use one of the preset security policies below.
#
class Policy
attr_accessor :verify_data, :verify_signer, :verify_chain,
:verify_root, :only_trusted, :only_signed
#
# Create a new Gem::Security::Policy object with the given mode and
# options.
#
def initialize(policy = {}, opt = {})
# set options
@opt = Gem::Security::OPT.merge(opt)
# build policy
policy.each_pair do |key, val|
case key
when :verify_data then @verify_data = val
when :verify_signer then @verify_signer = val
when :verify_chain then @verify_chain = val
when :verify_root then @verify_root = val
when :only_trusted then @only_trusted = val
when :only_signed then @only_signed = val
end
end
end
#
# Get the path to the file for this cert.
#
def self.trusted_cert_path(cert, opt = {})
opt = Gem::Security::OPT.merge(opt)
# get digest algorithm, calculate checksum of root.subject
algo = opt[:dgst_algo]
dgst = algo.hexdigest(cert.subject.to_s)
# build path to trusted cert file
name = "cert-#{dgst}.pem"
# join and return path components
File::join(opt[:trust_dir], name)
end
#
# Verify that the gem data with the given signature and signing chain
# matched this security policy at the specified time.
#
def verify_gem(signature, data, chain, time = Time.now)
Gem.ensure_ssl_available
cert_class = OpenSSL::X509::Certificate
exc = Gem::Security::Exception
chain ||= []
chain = chain.map{ |str| cert_class.new(str) }
signer, ch_len = chain[-1], chain.size
# make sure signature is valid
if @verify_data
# get digest algorithm (TODO: this should be configurable)
dgst = @opt[:dgst_algo]
# verify the data signature (this is the most important part, so don't
# screw it up :D)
v = signer.public_key.verify(dgst.new, signature, data)
raise exc, "Invalid Gem Signature" unless v
# make sure the signer is valid
if @verify_signer
# make sure the signing cert is valid right now
v = signer.check_validity(nil, time)
raise exc, "Invalid Signature: #{v[:desc]}" unless v[:is_valid]
end
end
# make sure the certificate chain is valid
if @verify_chain
# iterate down over the chain and verify each certificate against it's
# issuer
(ch_len - 1).downto(1) do |i|
issuer, cert = chain[i - 1, 2]
v = cert.check_validity(issuer, time)
raise exc, "%s: cert = '%s', error = '%s'" % [
'Invalid Signing Chain', cert.subject, v[:desc]
] unless v[:is_valid]
end
# verify root of chain
if @verify_root
# make sure root is self-signed
root = chain[0]
raise exc, "%s: %s (subject = '%s', issuer = '%s')" % [
'Invalid Signing Chain Root',
'Subject does not match Issuer for Gem Signing Chain',
root.subject.to_s,
root.issuer.to_s,
] unless root.issuer.to_s == root.subject.to_s
# make sure root is valid
v = root.check_validity(root, time)
raise exc, "%s: cert = '%s', error = '%s'" % [
'Invalid Signing Chain Root', root.subject, v[:desc]
] unless v[:is_valid]
# verify that the chain root is trusted
if @only_trusted
# get digest algorithm, calculate checksum of root.subject
algo = @opt[:dgst_algo]
path = Gem::Security::Policy.trusted_cert_path(root, @opt)
# check to make sure trusted path exists
raise exc, "%s: cert = '%s', error = '%s'" % [
'Untrusted Signing Chain Root',
root.subject.to_s,
"path \"#{path}\" does not exist",
] unless File.exist?(path)
# load calculate digest from saved cert file
save_cert = OpenSSL::X509::Certificate.new(File.read(path))
save_dgst = algo.digest(save_cert.public_key.to_s)
# create digest of public key
pkey_str = root.public_key.to_s
cert_dgst = algo.digest(pkey_str)
# now compare the two digests, raise exception
# if they don't match
raise exc, "%s: %s (saved = '%s', root = '%s')" % [
'Invalid Signing Chain Root',
"Saved checksum doesn't match root checksum",
save_dgst, cert_dgst,
] unless save_dgst == cert_dgst
end
end
# return the signing chain
chain.map { |cert| cert.subject }
end
end
end
#
# No security policy: all package signature checks are disabled.
#
NoSecurity = Policy.new(
:verify_data => false,
:verify_signer => false,
:verify_chain => false,
:verify_root => false,
:only_trusted => false,
:only_signed => false
)
#
# AlmostNo security policy: only verify that the signing certificate is the
# one that actually signed the data. Make no attempt to verify the signing
# certificate chain.
#
# This policy is basically useless. better than nothing, but can still be
# easily spoofed, and is not recommended.
#
AlmostNoSecurity = Policy.new(
:verify_data => true,
:verify_signer => false,
:verify_chain => false,
:verify_root => false,
:only_trusted => false,
:only_signed => false
)
#
# Low security policy: only verify that the signing certificate is actually
# the gem signer, and that the signing certificate is valid.
#
# This policy is better than nothing, but can still be easily spoofed, and
# is not recommended.
#
LowSecurity = Policy.new(
:verify_data => true,
:verify_signer => true,
:verify_chain => false,
:verify_root => false,
:only_trusted => false,
:only_signed => false
)
#
# Medium security policy: verify the signing certificate, verify the signing
# certificate chain all the way to the root certificate, and only trust root
# certificates that we have explicity allowed trust for.
#
# This security policy is reasonable, but it allows unsigned packages, so a
# malicious person could simply delete the package signature and pass the
# gem off as unsigned.
#
MediumSecurity = Policy.new(
:verify_data => true,
:verify_signer => true,
:verify_chain => true,
:verify_root => true,
:only_trusted => true,
:only_signed => false
)
#
# High security policy: only allow signed gems to be installed, verify the
# signing certificate, verify the signing certificate chain all the way to
# the root certificate, and only trust root certificates that we have
# explicity allowed trust for.
#
# This security policy is significantly more difficult to bypass, and offers
# a reasonable guarantee that the contents of the gem have not been altered.
#
HighSecurity = Policy.new(
:verify_data => true,
:verify_signer => true,
:verify_chain => true,
:verify_root => true,
:only_trusted => true,
:only_signed => true
)
#
# Hash of configured security policies
#
Policies = {
'NoSecurity' => NoSecurity,
'AlmostNoSecurity' => AlmostNoSecurity,
'LowSecurity' => LowSecurity,
'MediumSecurity' => MediumSecurity,
'HighSecurity' => HighSecurity,
}
#
# Sign the cert cert with @signing_key and @signing_cert, using the digest
# algorithm opt[:dgst_algo]. Returns the newly signed certificate.
#
def self.sign_cert(cert, signing_key, signing_cert, opt = {})
opt = OPT.merge(opt)
# set up issuer information
cert.issuer = signing_cert.subject
cert.sign(signing_key, opt[:dgst_algo].new)
cert
end
#
# Make sure the trust directory exists. If it does exist, make sure it's
# actually a directory. If not, then create it with the appropriate
# permissions.
#
def self.verify_trust_dir(path, perms)
# if the directory exists, then make sure it is in fact a directory. if
# it doesn't exist, then create it with the appropriate permissions
if File.exist?(path)
# verify that the trust directory is actually a directory
unless File.directory?(path)
err = "trust directory #{path} isn't a directory"
raise Gem::Security::Exception, err
end
else
# trust directory doesn't exist, so create it with permissions
FileUtils.mkdir_p(path)
FileUtils.chmod(perms, path)
end
end
#
# Build a certificate from the given DN and private key.
#
def self.build_cert(name, key, opt = {})
Gem.ensure_ssl_available
opt = OPT.merge(opt)
# create new cert
ret = OpenSSL::X509::Certificate.new
# populate cert attributes
ret.version = 2
ret.serial = 0
ret.public_key = key.public_key
ret.not_before = Time.now
ret.not_after = Time.now + opt[:cert_age]
ret.subject = name
# add certificate extensions
ef = OpenSSL::X509::ExtensionFactory.new(nil, ret)
ret.extensions = opt[:cert_exts].map { |k, v| ef.create_extension(k, v) }
# sign cert
i_key, i_cert = opt[:issuer_key] || key, opt[:issuer_cert] || ret
ret = sign_cert(ret, i_key, i_cert, opt)
# return cert
ret
end
#
# Build a self-signed certificate for the given email address.
#
def self.build_self_signed_cert(email_addr, opt = {})
Gem.ensure_ssl_available
opt = OPT.merge(opt)
path = { :key => nil, :cert => nil }
# split email address up
cn, dcs = email_addr.split('@')
dcs = dcs.split('.')
# munge email CN and DCs
cn = cn.gsub(opt[:munge_re], '_')
dcs = dcs.map { |dc| dc.gsub(opt[:munge_re], '_') }
# create DN
name = "CN=#{cn}/" << dcs.map { |dc| "DC=#{dc}" }.join('/')
name = OpenSSL::X509::Name::parse(name)
# build private key
key = opt[:key_algo].new(opt[:key_size])
# method name pretty much says it all :)
verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
# if we're saving the key, then write it out
if opt[:save_key]
path[:key] = opt[:save_key_path] || (opt[:output_fmt] % 'private_key')
File.open(path[:key], 'wb') do |file|
file.chmod(opt[:perms][:signing_key])
file.write(key.to_pem)
end
end
# build self-signed public cert from key
cert = build_cert(name, key, opt)
# if we're saving the cert, then write it out
if opt[:save_cert]
path[:cert] = opt[:save_cert_path] || (opt[:output_fmt] % 'public_cert')
File.open(path[:cert], 'wb') do |file|
file.chmod(opt[:perms][:signing_cert])
file.write(cert.to_pem)
end
end
# return key, cert, and paths (if applicable)
{ :key => key, :cert => cert,
:key_path => path[:key], :cert_path => path[:cert] }
end
#
# Add certificate to trusted cert list.
#
# Note: At the moment these are stored in OPT[:trust_dir], although that
# directory may change in the future.
#
def self.add_trusted_cert(cert, opt = {})
opt = OPT.merge(opt)
# get destination path
path = Gem::Security::Policy.trusted_cert_path(cert, opt)
# verify trust directory (can't write to nowhere, you know)
verify_trust_dir(opt[:trust_dir], opt[:perms][:trust_dir])
# write cert to output file
File.open(path, 'wb') do |file|
file.chmod(opt[:perms][:trusted_cert])
file.write(cert.to_pem)
end
# return nil
nil
end
#
# Basic OpenSSL-based package signing class.
#
class Signer
attr_accessor :key, :cert_chain
def initialize(key, cert_chain)
Gem.ensure_ssl_available
@algo = Gem::Security::OPT[:dgst_algo]
@key, @cert_chain = key, cert_chain
# check key, if it's a file, and if it's key, leave it alone
if @key && !@key.kind_of?(OpenSSL::PKey::PKey)
@key = OpenSSL::PKey::RSA.new(File.read(@key))
end
# check cert chain, if it's a file, load it, if it's cert data, convert
# it into a cert object, and if it's a cert object, leave it alone
if @cert_chain
@cert_chain = @cert_chain.map do |cert|
# check cert, if it's a file, load it, if it's cert data, convert it
# into a cert object, and if it's a cert object, leave it alone
if cert && !cert.kind_of?(OpenSSL::X509::Certificate)
cert = File.read(cert) if File::exist?(cert)
cert = OpenSSL::X509::Certificate.new(cert)
end
cert
end
end
end
#
# Sign data with given digest algorithm
#
def sign(data)
@key.sign(@algo.new, data)
end
end
end

504
lib/rubygems/server.rb Normal file
View file

@ -0,0 +1,504 @@
require 'webrick'
require 'rdoc/template'
require 'yaml'
require 'zlib'
require 'rubygems'
##
# Gem::Server and allows users to serve gems for consumption by
# `gem --remote-install`.
#
# gem_server starts an HTTP server on the given port and serves the folowing:
# * "/" - Browsing of gem spec files for installed gems
# * "/Marshal" - Full SourceIndex dump of metadata for installed gems
# * "/yaml" - YAML dump of metadata for installed gems - deprecated
# * "/gems" - Direct access to download the installable gems
#
# == Usage
#
# gem server [-p portnum] [-d gem_path]
#
# port_num:: The TCP port the HTTP server will bind to
# gem_path::
# Root gem directory containing both "cache" and "specifications"
# subdirectories.
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
RDOC_CSS = <<-RDOCCSS
body {
font-family: Verdana,Arial,Helvetica,sans-serif;
font-size: 90%;
margin: 0;
margin-left: 40px;
padding: 0;
background: white;
}
h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
h1 { font-size: 150%; }
h2,h3,h4 { margin-top: 1em; }
a { background: #eef; color: #039; text-decoration: none; }
a:hover { background: #039; color: #eef; }
/* Override the base stylesheets Anchor inside a table cell */
td > a {
background: transparent;
color: #039;
text-decoration: none;
}
/* and inside a section title */
.section-title > a {
background: transparent;
color: #eee;
text-decoration: none;
}
/* === Structural elements =================================== */
div#index {
margin: 0;
margin-left: -40px;
padding: 0;
font-size: 90%;
}
div#index a {
margin-left: 0.7em;
}
div#index .section-bar {
margin-left: 0px;
padding-left: 0.7em;
background: #ccc;
font-size: small;
}
div#classHeader, div#fileHeader {
width: auto;
color: white;
padding: 0.5em 1.5em 0.5em 1.5em;
margin: 0;
margin-left: -40px;
border-bottom: 3px solid #006;
}
div#classHeader a, div#fileHeader a {
background: inherit;
color: white;
}
div#classHeader td, div#fileHeader td {
background: inherit;
color: white;
}
div#fileHeader {
background: #057;
}
div#classHeader {
background: #048;
}
.class-name-in-header {
font-size: 180%;
font-weight: bold;
}
div#bodyContent {
padding: 0 1.5em 0 1.5em;
}
div#description {
padding: 0.5em 1.5em;
background: #efefef;
border: 1px dotted #999;
}
div#description h1,h2,h3,h4,h5,h6 {
color: #125;;
background: transparent;
}
div#validator-badges {
text-align: center;
}
div#validator-badges img { border: 0; }
div#copyright {
color: #333;
background: #efefef;
font: 0.75em sans-serif;
margin-top: 5em;
margin-bottom: 0;
padding: 0.5em 2em;
}
/* === Classes =================================== */
table.header-table {
color: white;
font-size: small;
}
.type-note {
font-size: small;
color: #DEDEDE;
}
.xxsection-bar {
background: #eee;
color: #333;
padding: 3px;
}
.section-bar {
color: #333;
border-bottom: 1px solid #999;
margin-left: -20px;
}
.section-title {
background: #79a;
color: #eee;
padding: 3px;
margin-top: 2em;
margin-left: -30px;
border: 1px solid #999;
}
.top-aligned-row { vertical-align: top }
.bottom-aligned-row { vertical-align: bottom }
/* --- Context section classes ----------------------- */
.context-row { }
.context-item-name { font-family: monospace; font-weight: bold; color: black; }
.context-item-value { font-size: small; color: #448; }
.context-item-desc { color: #333; padding-left: 2em; }
/* --- Method classes -------------------------- */
.method-detail {
background: #efefef;
padding: 0;
margin-top: 0.5em;
margin-bottom: 1em;
border: 1px dotted #ccc;
}
.method-heading {
color: black;
background: #ccc;
border-bottom: 1px solid #666;
padding: 0.2em 0.5em 0 0.5em;
}
.method-signature { color: black; background: inherit; }
.method-name { font-weight: bold; }
.method-args { font-style: italic; }
.method-description { padding: 0 0.5em 0 0.5em; }
/* --- Source code sections -------------------- */
a.source-toggle { font-size: 90%; }
div.method-source-code {
background: #262626;
color: #ffdead;
margin: 1em;
padding: 0.5em;
border: 1px dashed #999;
overflow: hidden;
}
div.method-source-code pre { color: #ffdead; overflow: hidden; }
/* --- Ruby keyword styles --------------------- */
.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
.ruby-constant { color: #7fffd4; background: transparent; }
.ruby-keyword { color: #00ffff; background: transparent; }
.ruby-ivar { color: #eedd82; background: transparent; }
.ruby-operator { color: #00ffee; background: transparent; }
.ruby-identifier { color: #ffdead; background: transparent; }
.ruby-node { color: #ffa07a; background: transparent; }
.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
.ruby-regexp { color: #ffa07a; background: transparent; }
.ruby-value { color: #7fffd4; background: transparent; }
RDOCCSS
def self.run(options)
new(options[:gemdir], options[:port], options[:daemon]).run
end
def initialize(gemdir, port, daemon)
Socket.do_not_reverse_lookup = true
@gemdir = gemdir
@port = port
@daemon = daemon
logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
@server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
@spec_dir = File.join @gemdir, "specifications"
@source_index = Gem::SourceIndex.from_gems_in @spec_dir
end
def quick(req, res)
res['content-type'] = 'text/plain'
res['date'] = File.stat(@spec_dir).mtime
case req.request_uri.request_uri
when '/quick/index' then
res.body << @source_index.map { |name,_| name }.join("\n")
when '/quick/index.rz' then
index = @source_index.map { |name,_| name }.join("\n")
res.body << Zlib::Deflate.deflate(index)
when %r|^/quick/(.*)-([0-9.]+)\.gemspec(\.marshal)?\.rz$| then
specs = @source_index.search $1, $2
if specs.empty? then
res.status = 404
elsif specs.length > 1 then
res.status = 500
elsif $3 # marshal quickindex instead of YAML
res.body << Zlib::Deflate.deflate(Marshal.dump(specs.first))
else # deprecated YAML format
res.body << Zlib::Deflate.deflate(specs.first.to_yaml)
end
else
res.status = 404
end
end
def run
@server.listen nil, @port
say "Starting gem server on http://localhost:#{@port}/"
WEBrick::Daemon.start if @daemon
@server.mount_proc("/yaml") do |req, res|
res['content-type'] = 'text/plain'
res['date'] = File.stat(@spec_dir).mtime
if req.request_method == 'HEAD' then
res['content-length'] = @source_index.to_yaml.length
else
res.body << @source_index.to_yaml
end
end
@server.mount_proc("/Marshal") do |req, res|
res['content-type'] = 'text/plain'
res['date'] = File.stat(@spec_dir).mtime
if req.request_method == 'HEAD' then
res['content-length'] = Marshal.dump(@source_index).length
else
res.body << Marshal.dump(@source_index)
end
end
@server.mount_proc("/quick/", &method(:quick))
@server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
res['content-type'] = 'text/css'
res['date'] = File.stat(@spec_dir).mtime
res.body << RDOC_CSS
end
@server.mount_proc("/") do |req, res|
specs = []
total_file_count = 0
@source_index.each do |path, spec|
total_file_count += spec.files.size
deps = spec.dependencies.collect { |dep|
{ "name" => dep.name,
"version" => dep.version_requirements.to_s, }
}
deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
deps.last["is_last"] = true unless deps.empty?
# executables
executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
executables = nil if executables.empty?
executables.last["is_last"] = true if executables
specs << {
"authors" => spec.authors.sort.join(", "),
"date" => spec.date.to_s,
"dependencies" => deps,
"doc_path" => ('/doc_root/' + spec.full_name + '/rdoc/index.html'),
"executables" => executables,
"only_one_executable" => (executables && executables.size==1),
"full_name" => spec.full_name,
"has_deps" => !deps.empty?,
"homepage" => spec.homepage,
"name" => spec.name,
"rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
"summary" => spec.summary,
"version" => spec.version.to_s,
}
end
specs << {
"authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
"dependencies" => [],
"doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
"executables" => [{"executable" => 'gem', "is_last" => true}],
"only_one_executable" => true,
"full_name" => "rubygems-#{Gem::RubyGemsVersion}",
"has_deps" => false,
"homepage" => "http://rubygems.org/",
"name" => 'rubygems',
"rdoc_installed" => true,
"summary" => "RubyGems itself",
"version" => Gem::RubyGemsVersion,
}
specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
specs.last["is_last"] = true
# tag all specs with first_name_entry
last_spec = nil
specs.each do |spec|
is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
spec["first_name_entry"] = is_first
last_spec = spec
end
# create page from template
template = 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/" }
paths.each do |mount_point, mount_dir|
@server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
File.join(@gemdir, mount_dir), true)
end
trap("INT") { @server.shutdown; exit! }
trap("TERM") { @server.shutdown; exit! }
@server.start
end
end

View file

@ -0,0 +1,446 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'forwardable'
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
extend Forwardable
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)
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_delegators :@gems, :size, :length
# 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 short name pattern and optional version
#
# gem_name::
# [String] a partial for the (short) name of the gem, or
# [Regex] a pattern to match against the short name
# version_requirement::
# [String | default=Gem::Requirement.default] version to
# find
# return::
# [Array] list of Gem::Specification objects in sorted (version)
# order. Empty if not found.
#
def search(gem_pattern, platform_only_or_version_req = false)
version_requirement = nil
only_platform = false
case gem_pattern
when Regexp then
version_requirement = platform_only_or_version_req ||
Gem::Requirement.default
when Gem::Dependency then
only_platform = platform_only_or_version_req
version_requirement = gem_pattern.version_requirements
gem_pattern = gem_pattern.name.empty? ? // : /^#{gem_pattern.name}$/
else
version_requirement = platform_only_or_version_req ||
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

View file

@ -0,0 +1,232 @@
require 'fileutils'
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:
# * The system cache is prefered if it is writable or can be created.
# * The user cache is used otherwise
#
# Once a cache is selected, it will be used for all operations.
# SourceInfoCache will not switch between cache files dynamically.
#
# Cache data is a Hash mapping a source URI to a SourceInfoCacheEntry.
#
#--
# To keep things straight, this is how the cache objects all fit together:
#
# Gem::SourceInfoCache
# @cache_data = {
# source_uri => Gem::SourceInfoCacheEntry
# @size => source index size
# @source_index => Gem::SourceIndex
# ...
# }
#
class Gem::SourceInfoCache
include Gem::UserInteraction
@cache = nil
@system_cache_file = nil
@user_cache_file = nil
def self.cache
return @cache if @cache
@cache = new
@cache.refresh if Gem.configuration.update_sources
@cache
end
def self.cache_data
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
cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
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
end.flatten.compact
end
# Searches all source indexes for +pattern+. If +only_platform+ is true,
# 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|
next unless Gem.sources.include? source_uri
sic_entry.source_index.search(pattern, only_platform).each do |spec|
results << [spec, source_uri]
end
end
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 ||= File.join(Gem.dir, "source_cache")
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'] || File.join(Gem.user_home, ".gem", "source_cache")
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
return nil
end
end
if File.writable?(dir)
File.open(fn, "wb") { |f| f << Marshal.dump({}) }
return fn
end
nil
end
end

View file

@ -0,0 +1,46 @@
require 'rubygems'
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
rescue Gem::RemoteSourceException
yaml_uri = URI.join source_uri.to_s, 'yaml'
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
def ==(other) # :nodoc:
self.class === other and
@size == other.size and
@source_index == other.source_index
end
end

View file

@ -0,0 +1,905 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'time'
require 'rubygems'
require 'rubygems/version'
require 'rubygems/platform'
# :stopdoc:
# Time::today has been deprecated in 0.9.5 and will be removed.
def Time.today
t = Time.now
t - ((t.to_i + t.gmt_offset) % 86400)
end unless defined? Time.today
# :startdoc:
module Gem
# == Gem::Specification
#
# The Specification class contains the metadata for a Gem. Typically
# defined in a .gemspec file or a Rakefile, and looks like this:
#
# spec = Gem::Specification.new do |s|
# s.name = 'rfoo'
# s.version = '1.0'
# s.summary = 'Example gem specification'
# ...
# end
#
# There are many <em>gemspec attributes</em>, and the best place to learn
# about them in the "Gemspec Reference" linked from the RubyGems wiki.
#
class Specification
# Allows deinstallation of gems with legacy platforms.
attr_accessor :original_platform # :nodoc:
# ------------------------- Specification version contstants.
# The the version number of a specification that does not specify one
# (i.e. RubyGems 0.7 or earlier).
NONEXISTENT_SPECIFICATION_VERSION = -1
# The specification version applied to any new Specification instances
# created. This should be bumped whenever something in the spec format
# changes.
CURRENT_SPECIFICATION_VERSION = 2
# An informal list of changes to the specification. The highest-valued
# key should be equal to the CURRENT_SPECIFICATION_VERSION.
SPECIFICATION_VERSION_HISTORY = {
-1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'],
1 => [
'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"',
'"test_file=x" is a shortcut for "test_files=[x]"'
],
2 => [
'Added "required_rubygems_version"',
'Now forward-compatible with future versions',
],
}
# :stopdoc:
MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16 }
now = Time.at(Time.now.to_i)
TODAY = now - ((now.to_i + now.gmt_offset) % 86400)
# :startdoc:
# ------------------------- Class variables.
# List of Specification instances.
@@list = []
# Optional block used to gather newly defined instances.
@@gather = nil
# List of attribute names: [:name, :version, ...]
@@required_attributes = []
# List of _all_ attributes and default values: [[:name, nil], [:bindir, 'bin'], ...]
@@attributes = []
@@nil_attributes = []
@@non_nil_attributes = [:@original_platform]
# List of array attributes
@@array_attributes = []
# Map of attribute names to default values.
@@default_value = {}
# ------------------------- Convenience class methods.
def self.attribute_names
@@attributes.map { |name, default| name }
end
def self.attribute_defaults
@@attributes.dup
end
def self.default_value(name)
@@default_value[name]
end
def self.required_attributes
@@required_attributes.dup
end
def self.required_attribute?(name)
@@required_attributes.include? name.to_sym
end
def self.array_attributes
@@array_attributes.dup
end
# ------------------------- Infrastructure class methods.
# A list of Specification instances that have been defined in this Ruby instance.
def self.list
@@list
end
# Used to specify the name and default value of a specification
# attribute. The side effects are:
# * the name and default value are added to the @@attributes list
# and @@default_value map
# * a standard _writer_ method (<tt>attribute=</tt>) is created
# * a non-standard _reader method (<tt>attribute</tt>) is created
#
# The reader method behaves like this:
# def attribute
# @attribute ||= (copy of default value)
# end
#
# This allows lazy initialization of attributes to their default
# values.
#
def self.attribute(name, default=nil)
ivar_name = "@#{name}".intern
if default.nil? then
@@nil_attributes << ivar_name
else
@@non_nil_attributes << [ivar_name, default]
end
@@attributes << [name, default]
@@default_value[name] = default
attr_accessor(name)
end
# Same as :attribute, but ensures that values assigned to the
# attribute are array values by applying :to_a to the value.
def self.array_attribute(name)
@@non_nil_attributes << ["@#{name}".intern, []]
@@array_attributes << name
@@attributes << [name, []]
@@default_value[name] = []
code = %{
def #{name}
@#{name} ||= []
end
def #{name}=(value)
@#{name} = Array(value)
end
}
module_eval code, __FILE__, __LINE__ - 9
end
# Same as attribute above, but also records this attribute as mandatory.
def self.required_attribute(*args)
@@required_attributes << args.first
attribute(*args)
end
# Sometimes we don't want the world to use a setter method for a particular attribute.
# +read_only+ makes it private so we can still use it internally.
def self.read_only(*names)
names.each do |name|
private "#{name}="
end
end
# Shortcut for creating several attributes at once (each with a default value of
# +nil+).
def self.attributes(*args)
args.each do |arg|
attribute(arg, nil)
end
end
# Some attributes require special behaviour when they are accessed. This allows for
# that.
def self.overwrite_accessor(name, &block)
remove_method name
define_method(name, &block)
end
# Defines a _singular_ version of an existing _plural_ attribute
# (i.e. one whose value is expected to be an array). This means
# just creating a helper method that takes a single value and
# appends it to the array. These are created for convenience, so
# that in a spec, one can write
#
# s.require_path = 'mylib'
#
# instead of
#
# s.require_paths = ['mylib']
#
# That above convenience is available courtesy of
#
# attribute_alias_singular :require_path, :require_paths
#
def self.attribute_alias_singular(singular, plural)
define_method("#{singular}=") { |val|
send("#{plural}=", [val])
}
define_method("#{singular}") {
val = send("#{plural}")
val.nil? ? nil : val.first
}
end
# Dump only crucial instance variables.
#
# MAINTAIN ORDER!
def _dump(limit) # :nodoc:
Marshal.dump [
@rubygems_version,
@specification_version,
@name,
@version,
(Time === @date ? @date : Time.parse(@date.to_s)),
@summary,
@required_ruby_version,
@required_rubygems_version,
@new_platform,
@dependencies,
@rubyforge_project,
@email,
@authors,
@description,
@homepage,
@has_rdoc
]
end
# Load custom marshal format, re-initializing defaults as needed
def self._load(str)
array = Marshal.load str
spec = Gem::Specification.new
spec.instance_variable_set :@specification_version, array[1]
current_version = CURRENT_SPECIFICATION_VERSION
field_count = MARSHAL_FIELDS[spec.specification_version]
if field_count.nil? or array.size < field_count then
raise TypeError, "invalid Gem::Specification format #{array.inspect}"
end
spec.instance_variable_set :@rubygems_version, array[0]
# spec version
spec.instance_variable_set :@name, array[2]
spec.instance_variable_set :@version, array[3]
spec.instance_variable_set :@date, array[4]
spec.instance_variable_set :@summary, array[5]
spec.instance_variable_set :@required_ruby_version, array[6]
spec.instance_variable_set :@required_rubygems_version, array[7]
spec.instance_variable_set :@new_platform, array[8]
spec.instance_variable_set :@original_platform, array[8]
spec.instance_variable_set :@platform, array[8].to_s
spec.instance_variable_set :@dependencies, array[9]
spec.instance_variable_set :@rubyforge_project, array[10]
spec.instance_variable_set :@email, array[11]
spec.instance_variable_set :@authors, array[12]
spec.instance_variable_set :@description, array[13]
spec.instance_variable_set :@homepage, array[14]
spec.instance_variable_set :@has_rdoc, array[15]
spec.instance_variable_set :@loaded, false
spec
end
def warn_deprecated(old, new)
# How (if at all) to implement this? We only want to warn when
# a gem is being built, I should think.
end
# REQUIRED gemspec attributes ------------------------------------
required_attribute :rubygems_version, RubyGemsVersion
required_attribute :specification_version, CURRENT_SPECIFICATION_VERSION
required_attribute :name
required_attribute :version
required_attribute :date, TODAY
required_attribute :summary
required_attribute :require_paths, ['lib']
# OPTIONAL gemspec attributes ------------------------------------
attributes :email, :homepage, :rubyforge_project, :description
attributes :autorequire, :default_executable
attribute :bindir, 'bin'
attribute :has_rdoc, false
attribute :required_ruby_version, Gem::Requirement.default
attribute :required_rubygems_version, Gem::Requirement.default
attribute :platform, Gem::Platform::RUBY
attribute :signing_key, nil
attribute :cert_chain, []
attribute :post_install_message, nil
array_attribute :authors
array_attribute :files
array_attribute :test_files
array_attribute :rdoc_options
array_attribute :extra_rdoc_files
array_attribute :executables
# Array of extensions to build. See Gem::Installer#build_extensions for
# valid values.
array_attribute :extensions
array_attribute :requirements
array_attribute :dependencies
read_only :dependencies
# ALIASED gemspec attributes -------------------------------------
attribute_alias_singular :executable, :executables
attribute_alias_singular :author, :authors
attribute_alias_singular :require_path, :require_paths
attribute_alias_singular :test_file, :test_files
# DEPRECATED gemspec attributes ----------------------------------
def test_suite_file
warn_deprecated(:test_suite_file, :test_files)
test_files.first
end
def test_suite_file=(val)
warn_deprecated(:test_suite_file, :test_files)
@test_files = [] unless defined? @test_files
@test_files << val
end
# true when this gemspec has been loaded from a specifications directory.
# This attribute is not persisted.
attr_writer :loaded
# Path this gemspec was loaded from. This attribute is not persisted.
attr_accessor :loaded_from
# Special accessor behaviours (overwriting default) --------------
overwrite_accessor :version= do |version|
@version = Version.create(version)
end
overwrite_accessor :platform do
@new_platform
end
overwrite_accessor :platform= do |platform|
@original_platform = platform if @original_platform.nil?
case platform
when Gem::Platform::CURRENT then
@new_platform = Gem::Platform.local
when Gem::Platform then
@new_platform = platform
# legacy constants
when nil, Gem::Platform::RUBY then
@new_platform = Gem::Platform::RUBY
when Gem::Platform::WIN32 then
@new_platform = Gem::Platform::MSWIN32
when Gem::Platform::LINUX_586 then
@new_platform = Gem::Platform::X86_LINUX
when Gem::Platform::DARWIN then
@new_platform = Gem::Platform::PPC_DARWIN
else
@new_platform = platform
end
@platform = @new_platform.to_s
@new_platform
end
overwrite_accessor :required_ruby_version= do |value|
@required_ruby_version = Gem::Requirement.create(value)
end
overwrite_accessor :required_rubygems_version= do |value|
@required_rubygems_version = Gem::Requirement.create(value)
end
overwrite_accessor :date= do |date|
# We want to end up with a Time object with one-day resolution.
# This is the cleanest, most-readable, faster-than-using-Date
# way to do it.
case date
when String then
@date = Time.parse date
when Time then
@date = Time.parse date.strftime("%Y-%m-%d")
when Date then
@date = Time.parse date.to_s
else
@date = TODAY
end
end
overwrite_accessor :date do
self.date = nil if @date.nil? # HACK Sets the default value for date
@date
end
overwrite_accessor :summary= do |str|
@summary = if str then
str.strip.
gsub(/(\w-)\n[ \t]*(\w)/, '\1\2').
gsub(/\n[ \t]*/, " ")
end
end
overwrite_accessor :description= do |str|
@description = if str then
str.strip.
gsub(/(\w-)\n[ \t]*(\w)/, '\1\2').
gsub(/\n[ \t]*/, " ")
end
end
overwrite_accessor :default_executable do
begin
if defined? @default_executable and @default_executable
result = @default_executable
elsif @executables and @executables.size == 1
result = Array(@executables).first
else
result = nil
end
result
rescue
nil
end
end
def add_bindir(executables)
if not defined? @executables || @executables.nil?
return nil
end
if defined? @bindir and @bindir then
Array(@executables).map {|e| File.join(@bindir, e) }
else
@executables
end
rescue
return nil
end
overwrite_accessor :files do
result = []
result.push(*@files) if defined?(@files)
result.push(*@test_files) if defined?(@test_files)
result.push(*(add_bindir(@executables)))
result.push(*@extra_rdoc_files) if defined?(@extra_rdoc_files)
result.push(*@extensions) if defined?(@extensions)
result.uniq.compact
end
# Files in the Gem under one of the require_paths
def lib_files
@files.select do |file|
require_paths.any? do |path|
file.index(path) == 0
end
end
end
overwrite_accessor :test_files do
# Handle the possibility that we have @test_suite_file but not
# @test_files. This will happen when an old gem is loaded via
# YAML.
if defined? @test_suite_file then
@test_files = [@test_suite_file].flatten
@test_suite_file = nil
end
if defined? @test_files and @test_files then
@test_files
else
@test_files = []
end
end
# Predicates -----------------------------------------------------
def loaded?; @loaded ? true : false ; end
def has_rdoc?; has_rdoc ? true : false ; end
def has_unit_tests?; not test_files.empty?; end
alias has_test_suite? has_unit_tests? # (deprecated)
# Constructors ---------------------------------------------------
# Specification constructor. Assigns the default values to the
# attributes, adds this spec to the list of loaded specs (see
# Specification.list), and yields itself for further initialization.
#
def initialize
@new_platform = nil
assign_defaults
@loaded = false
@@list << self
yield self if block_given?
@@gather.call(self) if @@gather
end
# Each attribute has a default value (possibly nil). Here, we
# initialize all attributes to their default value. This is
# done through the accessor methods, so special behaviours will
# be honored. Furthermore, we take a _copy_ of the default so
# each specification instance has its own empty arrays, etc.
def assign_defaults
@@nil_attributes.each do |name|
instance_variable_set name, nil
end
@@non_nil_attributes.each do |name, default|
value = case default
when Time, Numeric, Symbol, true, false, nil then default
else default.dup
end
instance_variable_set name, value
end
# HACK
instance_variable_set :@new_platform, Gem::Platform::RUBY
end
# Special loader for YAML files. When a Specification object is
# loaded from a YAML file, it bypasses the normal Ruby object
# initialization routine (#initialize). This method makes up for
# that and deals with gems of different ages.
#
# 'input' can be anything that YAML.load() accepts: String or IO.
#
def self.from_yaml(input)
input = normalize_yaml_input input
spec = YAML.load input
if spec && spec.class == FalseClass then
raise Gem::EndOfYAMLException
end
unless Gem::Specification === spec then
raise Gem::Exception, "YAML data doesn't evaluate to gem specification"
end
unless (spec.instance_variables.include? '@specification_version' or
spec.instance_variables.include? :@specification_version) and
spec.instance_variable_get :@specification_version
spec.instance_variable_set :@specification_version,
NONEXISTENT_SPECIFICATION_VERSION
end
spec
end
def self.load(filename)
gemspec = nil
fail "NESTED Specification.load calls not allowed!" if @@gather
@@gather = proc { |gs| gemspec = gs }
data = File.read(filename)
eval(data)
gemspec
ensure
@@gather = nil
end
# Make sure the yaml specification is properly formatted with dashes.
def self.normalize_yaml_input(input)
result = input.respond_to?(:read) ? input.read : input
result = "--- " + result unless result =~ /^--- /
result
end
# Instance methods -----------------------------------------------
# Sets the rubygems_version to Gem::RubyGemsVersion.
#
def mark_version
@rubygems_version = RubyGemsVersion
end
# Ignore unknown attributes if the
def method_missing(sym, *a, &b) # :nodoc:
if @specification_version > CURRENT_SPECIFICATION_VERSION and
sym.to_s =~ /=$/ then
warn "ignoring #{sym} loading #{full_name}" if $DEBUG
else
super
end
end
# Adds a dependency to this Gem. For example,
#
# spec.add_dependency('jabber4r', '> 0.1', '<= 0.5')
#
# gem:: [String or Gem::Dependency] The Gem name/dependency.
# requirements:: [default=">= 0"] The version requirements.
#
def add_dependency(gem, *requirements)
requirements = if requirements.empty? then
Gem::Requirement.default
else
requirements.flatten
end
unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
gem = Dependency.new(gem, requirements)
end
dependencies << gem
end
# Returns the full name (name-version) of this Gem. Platform information
# is included (name-version-platform) if it is specified (and not the
# default Ruby platform).
#
def full_name
if platform == Gem::Platform::RUBY or platform.nil? then
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{platform}"
end
end
# The full path to the gem (install path + full name).
#
# return:: [String] the full gem path
#
def full_gem_path
path = File.join installation_path, 'gems', full_name
return path if File.directory? path
File.join installation_path, 'gems',
"#{name}-#{version}-#{@original_platform}"
end
# The default (generated) file name of the gem.
def file_name
full_name + ".gem"
end
# The root directory that the gem was installed into.
#
# return:: [String] the installation path
#
def installation_path
(File.dirname(@loaded_from).split(File::SEPARATOR)[0..-2]).
join(File::SEPARATOR)
end
# Checks if this Specification meets the requirement of the supplied
# dependency.
#
# dependency:: [Gem::Dependency] the dependency to check
# return:: [Boolean] true if dependency is met, otherwise false
#
def satisfies_requirement?(dependency)
return @name == dependency.name &&
dependency.version_requirements.satisfied_by?(@version)
end
# Comparison methods ---------------------------------------------
def sort_obj
[@name, @version.to_ints, @new_platform == Gem::Platform::RUBY ? -1 : 1]
end
def <=>(other) # :nodoc:
sort_obj <=> other.sort_obj
end
# Tests specs for equality (across all attributes).
def ==(other) # :nodoc:
self.class === other && same_attributes?(other)
end
alias eql? == # :nodoc:
def same_attributes?(other)
@@attributes.each do |name, default|
return false unless self.send(name) == other.send(name)
end
true
end
private :same_attributes?
def hash # :nodoc:
@@attributes.inject(0) { |hash_code, (name, default_value)|
n = self.send(name).hash
hash_code + n
}
end
# Export methods (YAML and Ruby code) ----------------------------
# Returns an array of attribute names to be used when generating a
# YAML representation of this object. If an attribute still has
# its default value, it is omitted.
def to_yaml_properties
mark_version
@@attributes.map { |name, default| "@#{name}" }
end
def yaml_initialize(tag, vals)
vals.each do |ivar, val|
instance_variable_set "@#{ivar}", val
end
@original_platform = @platform # for backwards compatibility
self.platform = Gem::Platform.new @platform
end
# Returns a Ruby code representation of this specification, such that it
# can be eval'ed and reconstruct the same specification later. Attributes
# that still have their default values are omitted.
def to_ruby
mark_version
result = []
result << "Gem::Specification.new do |s|"
result << " s.name = #{ruby_code name}"
result << " s.version = #{ruby_code version}"
result << ""
result << " s.specification_version = #{specification_version} if s.respond_to? :specification_version="
result << ""
result << " s.required_rubygems_version = #{ruby_code required_rubygems_version} if s.respond_to? :required_rubygems_version="
handled = [
:dependencies,
:name,
:required_rubygems_version,
:specification_version,
:version,
]
attributes = @@attributes.sort_by { |name,| name.to_s }
attributes.each do |name, default|
next if handled.include? name
current_value = self.send(name)
if current_value != default or self.class.required_attribute? name then
result << " s.#{name} = #{ruby_code current_value}"
end
end
result << "" unless dependencies.empty?
dependencies.each do |dep|
version_reqs_param = dep.requirements_list.inspect
result << " s.add_dependency(%q<#{dep.name}>, #{version_reqs_param})"
end
result << "end"
result << ""
result.join "\n"
end
# Validation and normalization methods ---------------------------
# Checks that the specification contains all required fields, and
# does a very basic sanity check.
#
# Raises InvalidSpecificationException if the spec does not pass
# the checks..
def validate
normalize
if rubygems_version != RubyGemsVersion then
raise Gem::InvalidSpecificationException,
"expected RubyGems version #{RubyGemsVersion}, was #{rubygems_version}"
end
@@required_attributes.each do |symbol|
unless self.send symbol then
raise Gem::InvalidSpecificationException,
"missing value for attribute #{symbol}"
end
end
if require_paths.empty? then
raise Gem::InvalidSpecificationException,
"specification must have at least one require_path"
end
case platform
when Gem::Platform, Platform::RUBY then # ok
else
raise Gem::InvalidSpecificationException,
"invalid platform #{platform.inspect}, see Gem::Platform"
end
true
end
# Normalize the list of files so that:
# * All file lists have redundancies removed.
# * Files referenced in the extra_rdoc_files are included in the
# package file list.
#
# Also, the summary and description are converted to a normal
# format.
def normalize
if defined? @extra_rdoc_files and @extra_rdoc_files then
@extra_rdoc_files.uniq!
@files ||= []
@files.concat(@extra_rdoc_files)
end
@files.uniq! if @files
end
# Dependency methods ---------------------------------------------
# Return a list of all gems that have a dependency on this
# gemspec. The list is structured with entries that conform to:
#
# [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]]
#
# return:: [Array] [[dependent_gem, dependency, [list_of_satisfiers]]]
#
def dependent_gems
out = []
Gem.source_index.each do |name,gem|
gem.dependencies.each do |dep|
if self.satisfies_requirement?(dep) then
sats = []
find_all_satisfiers(dep) do |sat|
sats << sat
end
out << [gem, dep, sats]
end
end
end
out
end
def to_s
"#<Gem::Specification name=#{@name} version=#{@version}>"
end
private
def find_all_satisfiers(dep)
Gem.source_index.each do |name,gem|
if(gem.satisfies_requirement?(dep)) then
yield gem
end
end
end
# Return a string containing a Ruby code representation of the
# given object.
def ruby_code(obj)
case obj
when String then '%q{' + obj + '}'
when Array then obj.inspect
when Gem::Version then obj.to_s.inspect
when Date then '%q{' + obj.strftime('%Y-%m-%d') + '}'
when Time then '%q{' + obj.strftime('%Y-%m-%d') + '}'
when Numeric then obj.inspect
when true, false, nil then obj.inspect
when Gem::Platform then "Gem::Platform.new(#{obj.to_a.inspect})"
when Gem::Requirement then "Gem::Requirement.new(#{obj.to_s.inspect})"
else raise Exception, "ruby_code case not handled: #{obj.class}"
end
end
end
end

25
lib/rubygems/timer.rb Executable file
View file

@ -0,0 +1,25 @@
#
# This file defines a $log variable for logging, and a time() method for recording timing
# information.
#
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
$log = Object.new
def $log.debug(str)
STDERR.puts str
end
def time(msg, width=25)
t = Time.now
return_value = yield
elapsed = Time.now.to_f - t.to_f
elapsed = sprintf("%3.3f", elapsed)
$log.debug "#{msg.ljust(width)}: #{elapsed}s"
return_value
end

183
lib/rubygems/uninstaller.rb Normal file
View file

@ -0,0 +1,183 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'fileutils'
require 'rubygems'
require 'rubygems/dependency_list'
require 'rubygems/doc_manager'
require 'rubygems/user_interaction'
##
# An Uninstaller.
#
class Gem::Uninstaller
include Gem::UserInteraction
##
# Constructs an Uninstaller instance
#
# gem:: [String] The Gem name to uninstall
#
def initialize(gem, options)
@gem = gem
@version = options[:version] || Gem::Requirement.default
@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)
if list.empty? then
raise Gem::InstallError, "Unknown gem #{@gem}-#{@version}"
elsif list.size > 1 && @force_all
remove_all(list.dup)
remove_executables(list.last)
elsif list.size > 1
say
gem_names = list.collect {|gem| gem.full_name} + ["All versions"]
gem_name, index =
choose_from_list("Select gem to uninstall:", gem_names)
if index == list.size
remove_all(list.dup)
remove_executables(list.last)
elsif index >= 0 && index < list.size
to_remove = list[index]
remove(to_remove, list)
remove_executables(to_remove)
else
say "Error: must enter a number [1-#{list.size+1}]"
end
else
remove(list[0], list.dup)
remove_executables(list.last)
end
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)
raise Gem::FilePermissionError.new(Gem.bindir) unless
File.writable?(Gem.bindir)
list = Gem.source_index.search(gemspec.name).delete_if { |spec|
spec.version == gemspec.version
}
executables = gemspec.executables.clone
list.each do |spec|
spec.executables.each do |exe_name|
executables.delete(exe_name)
end
end
return if executables.size == 0
answer = @force_executables || ask_yes_no(
"Remove executables and scripts for\n" +
"'#{gemspec.executables.join(", ")}' in addition to the gem?",
true) # " # appease ruby-mode - don't ask
unless answer
say "Executables and scripts will remain installed."
return
else
gemspec.executables.each do |exe_name|
say "Removing #{exe_name}"
File.unlink File.join(Gem.bindir, exe_name) rescue nil
File.unlink File.join(Gem.bindir, exe_name + ".bat") rescue nil
end
end
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 ok_to_remove? spec then
raise Gem::DependencyRemovalException,
"Uninstallation aborted due to dependent gem(s)"
end
raise Gem::FilePermissionError, spec.installation_path unless
File.writable?(spec.installation_path)
FileUtils.rm_rf spec.full_gem_path
original_platform_name = [
spec.name, spec.version, spec.original_platform].join '-'
spec_dir = File.join spec.installation_path, 'specifications'
gemspec = File.join spec_dir, "#{spec.full_name}.gemspec"
unless File.exist? gemspec then
gemspec = File.join spec_dir, "#{original_platform_name}.gemspec"
end
FileUtils.rm_rf gemspec
cache_dir = File.join spec.installation_path, 'cache'
gem = File.join cache_dir, "#{spec.full_name}.gem"
unless File.exist? gemspec then
gem = File.join cache_dir, "#{original_platform_name}.gem"
end
FileUtils.rm_rf gem
Gem::DocManager.new(spec).uninstall_doc
say "Successfully uninstalled #{spec.full_name}"
list.delete spec
end
def ok_to_remove?(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
def ask_if_ok(spec)
msg = ['']
msg << 'You have requested to uninstall the gem:'
msg << "\t#{spec.full_name}"
spec.dependent_gems.each do |gem,dep,satlist|
msg <<
("#{gem.name}-#{gem.version} depends on " +
"[#{dep.name} (#{dep.version_requirements})]")
end
msg << 'If you remove this gems, one or more dependencies will not be met.'
msg << 'Continue with Uninstall?'
return ask_yes_no(msg.join("\n"), true)
end
end

View file

@ -0,0 +1,291 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
module Gem
####################################################################
# Module that defines the default UserInteraction. Any class
# including this module will have access to the +ui+ method that
# returns the default UI.
module DefaultUserInteraction
# Return the default UI.
def ui
DefaultUserInteraction.ui
end
# Set the default UI. If the default UI is never explicity set, a
# simple console based UserInteraction will be used automatically.
def ui=(new_ui)
DefaultUserInteraction.ui = new_ui
end
def use_ui(new_ui, &block)
DefaultUserInteraction.use_ui(new_ui, &block)
end
# The default UI is a class variable of the singleton class for
# this module.
@ui = nil
class << self
def ui
@ui ||= Gem::ConsoleUI.new
end
def ui=(new_ui)
@ui = new_ui
end
def use_ui(new_ui)
old_ui = @ui
@ui = new_ui
yield
ensure
@ui = old_ui
end
end
end
####################################################################
# Make the default UI accessable without the "ui." prefix. Classes
# including this module may use the interaction methods on the
# default UI directly. Classes may also reference the +ui+ and
# <tt>ui=</tt> methods.
#
# Example:
#
# class X
# include Gem::UserInteraction
#
# def get_answer
# n = ask("What is the meaning of life?")
# end
# end
module UserInteraction
include DefaultUserInteraction
[
:choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning,
:alert_error, :terminate_interaction!, :terminate_interaction
].each do |methname|
class_eval %{
def #{methname}(*args)
ui.#{methname}(*args)
end
}
end
end
####################################################################
# StreamUI implements a simple stream based user interface.
class StreamUI
attr_reader :ins, :outs, :errs
def initialize(in_stream, out_stream, err_stream=STDERR)
@ins = in_stream
@outs = out_stream
@errs = err_stream
end
# Choose from a list of options. +question+ is a prompt displayed
# above the list. +list+ is a list of option strings. Returns
# the pair [option_name, option_index].
def choose_from_list(question, list)
@outs.puts question
list.each_with_index do |item, index|
@outs.puts " #{index+1}. #{item}"
end
@outs.print "> "
@outs.flush
result = @ins.gets
return nil, nil unless result
result = result.strip.to_i - 1
return list[result], result
end
# Ask a question. Returns a true for yes, false for no. If not
# connected to a tty, raises an exception if default is nil,
# otherwise returns default.
def ask_yes_no(question, default=nil)
if not @ins.tty? then
if default.nil? then
raise(
Gem::OperationNotSupportedError,
"Not connected to a tty and no default specified")
else
return default
end
end
qstr = case default
when nil
'yn'
when true
'Yn'
else
'yN'
end
result = nil
while result.nil?
result = ask("#{question} [#{qstr}]")
result = case result
when /^[Yy].*/
true
when /^[Nn].*/
false
when /^$/
default
else
nil
end
end
return result
end
# Ask a question. Returns an answer if connected to a tty, nil
# otherwise.
def ask(question)
return nil if not @ins.tty?
@outs.print(question + " ")
@outs.flush
result = @ins.gets
result.chomp! if result
result
end
# Display a statement.
def say(statement="")
@outs.puts statement
end
# Display an informational alert.
def alert(statement, question=nil)
@outs.puts "INFO: #{statement}"
return ask(question) if question
end
# Display a warning in a location expected to get error messages.
def alert_warning(statement, question=nil)
@errs.puts "WARNING: #{statement}"
ask(question) if question
end
# Display an error message in a location expected to get error
# messages.
def alert_error(statement, question=nil)
@errs.puts "ERROR: #{statement}"
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
def progress_reporter(*args)
case Gem.configuration.verbose
when nil, false
SilentProgressReporter.new(@outs, *args)
when true
SimpleProgressReporter.new(@outs, *args)
else
VerboseProgressReporter.new(@outs, *args)
end
end
class SilentProgressReporter
attr_reader :count
def initialize(out_stream, size, initial_message, terminal_message = nil)
end
def updated(message)
end
def done
end
end
class SimpleProgressReporter
include DefaultUserInteraction
attr_reader :count
def initialize(out_stream, size, initial_message,
terminal_message = "complete")
@out = out_stream
@total = size
@count = 0
@terminal_message = terminal_message
@out.puts initial_message
end
def updated(message)
@count += 1
@out.print "."
@out.flush
end
def done
@out.puts "\n#{@terminal_message}"
end
end
class VerboseProgressReporter
include DefaultUserInteraction
attr_reader :count
def initialize(out_stream, size, initial_message,
terminal_message = 'complete')
@out = out_stream
@total = size
@count = 0
@terminal_message = terminal_message
@out.puts initial_message
end
def updated(message)
@count += 1
@out.puts "#{@count}/#{@total}: #{message}"
end
def done
@out.puts @terminal_message
end
end
end
####################################################################
# Subclass of StreamUI that instantiates the user interaction using
# standard in, out and error.
class ConsoleUI < StreamUI
def initialize
super(STDIN, STDOUT, STDERR)
end
end
####################################################################
# SilentUI is a UI choice that is absolutely silent.
class SilentUI
def method_missing(sym, *args, &block)
self
end
end
end

185
lib/rubygems/validator.rb Executable file
View file

@ -0,0 +1,185 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'find'
require 'rubygems/digest/md5'
require 'rubygems/format'
require 'rubygems/installer'
module Gem
##
# Validator performs various gem file and gem database validation
class Validator
include UserInteraction
##
# Given a gem file's contents, validates against its own MD5 checksum
# gem_data:: [String] Contents of the gem file
def verify_gem(gem_data)
raise VerificationError, 'empty gem file' if gem_data.size == 0
unless gem_data =~ /MD5SUM/ then
return # Don't worry about it...this sucks. Need to fix MD5 stuff for
# new format
# FIXME
end
sum_data = gem_data.gsub(/MD5SUM = "([a-z0-9]+)"/,
"MD5SUM = \"#{"F" * 32}\"")
unless Gem::MD5.hexdigest(sum_data) == $1.to_s then
raise VerificationError, 'invalid checksum for gem file'
end
end
##
# Given the path to a gem file, validates against its own MD5 checksum
#
# gem_path:: [String] Path to gem file
def verify_gem_file(gem_path)
File.open gem_path, 'rb' do |file|
gem_data = file.read
verify_gem gem_data
end
rescue Errno::ENOENT
raise Gem::VerificationError.new("missing gem file #{gem_path}")
end
private
def find_files_for_gem(gem_directory)
installed_files = []
Find.find(gem_directory) {|file_name|
fn = file_name.slice((gem_directory.size)..(file_name.size-1)).sub(/^\//, "")
if(!(fn =~ /CVS/ || File.directory?(fn) || fn == "")) then
installed_files << fn
end
}
installed_files
end
public
ErrorData = Struct.new(:path, :problem)
##
# Checks the gem directory for the following potential
# inconsistencies/problems:
# * Checksum gem itself
# * For each file in each gem, check consistency of installed versions
# * Check for files that aren't part of the gem but are in the gems directory
# * 1 cache - 1 spec - 1 directory.
#
# returns a hash of ErrorData objects, keyed on the problem gem's name.
def alien
errors = {}
Gem::SourceIndex.from_installed_gems.each do |gem_name, gem_spec|
errors[gem_name] ||= []
gem_path = File.join(Gem.dir, "cache", gem_spec.full_name) + ".gem"
spec_path = File.join(Gem.dir, "specifications", gem_spec.full_name) + ".gemspec"
gem_directory = File.join(Gem.dir, "gems", gem_spec.full_name)
installed_files = find_files_for_gem(gem_directory)
if(!File.exist?(spec_path)) then
errors[gem_name] << ErrorData.new(spec_path, "Spec file doesn't exist for installed gem")
end
begin
verify_gem_file(gem_path)
File.open(gem_path, 'rb') do |file|
format = Gem::Format.from_file_by_path(gem_path)
format.file_entries.each do |entry, data|
# Found this file. Delete it from list
installed_files.delete remove_leading_dot_dir(entry['path'])
next unless data # HACK `gem check -a mkrf`
File.open(File.join(gem_directory, entry['path']), 'rb') do |f|
unless Gem::MD5.hexdigest(f.read).to_s ==
Gem::MD5.hexdigest(data).to_s then
errors[gem_name] << ErrorData.new(entry['path'], "installed file doesn't match original from gem")
end
end
end
end
rescue VerificationError => e
errors[gem_name] << ErrorData.new(gem_path, e.message)
end
# Clean out directories that weren't explicitly included in the gemspec
# FIXME: This still allows arbitrary incorrect directories.
installed_files.delete_if {|potential_directory|
File.directory?(File.join(gem_directory, potential_directory))
}
if(installed_files.size > 0) then
errors[gem_name] << ErrorData.new(gem_path, "Unmanaged files in gem: #{installed_files.inspect}")
end
end
errors
end
class TestRunner
def initialize(suite, ui)
@suite = suite
@ui = ui
end
def self.run(suite, ui)
require 'test/unit/ui/testrunnermediator'
return new(suite, ui).start
end
def start
@mediator = Test::Unit::UI::TestRunnerMediator.new(@suite)
@mediator.add_listener(Test::Unit::TestResult::FAULT, &method(:add_fault))
return @mediator.run_suite
end
def add_fault(fault)
if Gem.configuration.verbose then
@ui.say fault.long_display
end
end
end
autoload :TestRunner, 'test/unit/ui/testrunnerutilities'
##
# Runs unit tests for a given gem specification
def unit_test(gem_spec)
start_dir = Dir.pwd
Dir.chdir(gem_spec.full_gem_path)
$: << File.join(Gem.dir, "gems", gem_spec.full_name)
# XXX: why do we need this gem_spec when we've already got 'spec'?
test_files = gem_spec.test_files
if test_files.empty?
say "There are no unit tests to run for #{gem_spec.name}-#{gem_spec.version}"
return
end
gem gem_spec.name, "= #{gem_spec.version.version}"
test_files.each do |f| require f end
suite = Test::Unit::TestSuite.new("#{gem_spec.name}-#{gem_spec.version}")
ObjectSpace.each_object(Class) do |klass|
suite << klass.suite if (klass < Test::Unit::TestCase)
end
result = TestRunner.run(suite, ui())
unless result.passed?
alert_error(result.to_s)
#unless ask_yes_no(result.to_s + "...keep Gem?", true) then
#Gem::Uninstaller.new(gem_spec.name, gem_spec.version.version).uninstall
#end
end
result
ensure
Dir.chdir(start_dir)
end
def remove_leading_dot_dir(path)
path.sub(/^\.\//, "")
end
end
end

158
lib/rubygems/version.rb Normal file
View file

@ -0,0 +1,158 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
##
# The Version class processes string versions into comparable values
class Gem::Version
include Comparable
attr_reader :ints
attr_reader :version
##
# Checks if version string is valid format
#
# str:: [String] the version string
# return:: [Boolean] true if the string format is correct, otherwise false
#
def self.correct?(version)
case version
when Integer, /\A\s*(\d+(\.\d+)*)*\s*\z/ then true
else false
end
end
##
# Factory method to create a Version object. Input may be a Version or a
# String. Intended to simplify client code.
#
# ver1 = Version.create('1.3.17') # -> (Version object)
# ver2 = Version.create(ver1) # -> (ver1)
# ver3 = Version.create(nil) # -> nil
#
def self.create(input)
if input.respond_to? :version then
input
elsif input.nil? then
nil
else
new input
end
end
##
# Constructs a version from the supplied string
#
# version:: [String] The version string. Format is digit.digit...
#
def initialize(version)
raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version)
self.version = version
end
def inspect # :nodoc:
"#<#{self.class} #{@version.inspect}>"
end
# Dump only the raw version string, not the complete object
def marshal_dump
[@version]
end
# Load custom marshal format
def marshal_load(array)
self.version = array[0]
end
# Strip ignored trailing zeros.
def normalize
@ints = @version.to_s.scan(/\d+/).map { |s| s.to_i }
return if @ints.length == 1
@ints.pop while @ints.last == 0
@ints = [0] if @ints.empty?
end
##
# Returns the text representation of the version
#
# return:: [String] version as string
#
def to_s
@version
end
##
# Convert version to integer array
#
# return:: [Array] list of integers
#
def to_ints
normalize unless @ints
@ints
end
def to_yaml_properties
['@version']
end
def version=(version)
@version = version.to_s.strip
normalize
end
def yaml_initialize(tag, values)
self.version = values['version']
end
##
# Compares two versions
#
# other:: [Version or .ints] other version to compare to
# return:: [Fixnum] -1, 0, 1
#
def <=>(other)
return 1 unless other
@ints <=> other.ints
end
def hash
to_ints.inject { |hash_code, n| hash_code + n }
end
# Return a new version object where the next to the last revision
# number is one greater. (e.g. 5.3.1 => 5.4)
def bump
ints = @ints.dup
ints.pop if ints.size > 1
ints[-1] += 1
self.class.new(ints.join("."))
end
#:stopdoc:
require 'rubygems/requirement'
# Gem::Requirement's original definition is nested in Version.
# Although an inappropriate place, current gems specs reference the nested
# class name explicitly. To remain compatible with old software loading
# gemspecs, we leave a copy of original definition in Version, but define an
# alias Gem::Requirement for use everywhere else.
Requirement = ::Gem::Requirement
# :startdoc:
end

View file

@ -0,0 +1,49 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
# Mixin methods for --version and --platform Gem::Command options.
module Gem::VersionOption
# Add the --platform option to the option parser.
def add_platform_option(task = command, *wrap)
OptionParser.accept Gem::Platform do |value|
if value == Gem::Platform::RUBY then
value
else
Gem::Platform.new value
end
end
add_option('--platform PLATFORM', Gem::Platform,
"Specify the platform of gem to #{task}", *wrap) do
|value, options|
unless options[:added_platform] then
Gem.platforms.clear
Gem.platforms << Gem::Platform::RUBY
options[:added_platform] = true
end
Gem.platforms << value unless Gem.platforms.include? value
end
end
# Add the --version option to the option parser.
def add_version_option(task = command, *wrap)
OptionParser.accept Gem::Requirement do |value|
Gem::Requirement.new value
end
add_option('-v', '--version VERSION', Gem::Requirement,
"Specify version of gem to #{task}", *wrap) do
|value, options|
options[:version] = value
end
end
end

10
lib/ubygems.rb Normal file
View file

@ -0,0 +1,10 @@
# This file allows for the running of rubygems with a nice
# command line look-and-feel: ruby -rubygems foo.rb
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'

View file

@ -0,0 +1,8 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
Gem.use_paths("test/mock/gems")

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAz0tTOtsJuHDKAEXrQx0f6DUEzBEUTSLR1fk0iEHsY9rDCQxm
sw5Bf2UnVhdD03B4/XzIK+pat2CMQc37/vLIBuVgS7g/fzatGiM0m5rAHtycr0XU
8Ek6zjx4iSv70OLjybY+/utHCEc838awGDMCFR21jYxgATPVwqAIyasvwbKh/Vhw
uErFPqT9G8BKTHsaX+H+ADIRH001OmWkjB6EyjF05114kNMa0+2C7daV9hoBL3md
hCt6zOGcapl/9LkGxhcNEUB/So16V1ZQldg9macGyWktyNTSfctlF+f8okAmicG3
XIwaW8UTmjFCmvDs/h1R/uKpe2IOHz87n29d2QIDAQABAoIBAQCR6n/nyg+JmTtX
/d+hGns/RTLfQpZ7xarXZ9gmoeD4WSE42VXhbIOGXXnXDAFecKl6Jb/xycGZm4if
OZPM3rEWyZeDNWrc7WvkHiwF7GSYVMqmRg2iJqoSSla+mAtl+pBFiNfHMW6K0Tp0
erOyFRW+L2+A9/MMZaRun6AP9URkn0jz2kwmMFf+6szmzVn6fPFzZDRI+hEeaDmi
LBzSrfrddrIBX+xGEoBj6RmfnKBCSUVSSxOauYjd4mVjVYxvMH4SV1hXDUS5GPl5
MbCiBb7bpNIg/8ljMoRrQiqk0XwwS7MaCqPtMhUtpSmC/zSjAfmoN7AOc/Xh69cQ
OCMNZH9BAoGBAPBlsuuU6fg0gVTKDdR12jHx03uRRt8/nPxHnpJkZCIh9XKh1LtY
bkumi9HZpp3mzDiaGg/rwfCwNckKx8NLhICLgkric6ClrKftxTu6C8tBAb5YDi6u
74KYnV8lMY/unzBtIloPgM3uluS292POmrWZpKwhvHLD71MewzMor5HFAoGBANy/
mwsBs8i3Gzk8Twjq8effhPpE7kpxhC7bhwmjX3q41EjQWDT8M6xb1P9dRSsCIebi
kqP1yhl27dJpA8r5WqE/z89xhBvObAGRv41eXxOI0LaH2k5lJQrUeSC+51dy+BEB
T3GXD4C5ezZHQ8Wz/oL73uikrfhD+AqOZT2YbMEFAoGBAJvWEWpOGm3f+4bvhI+Z
5lxCG4oa3wqRvj58XvsfQRovUWGCLtlTtgwsZq8enLf3iaOXohV4Czzvva4Z4u1i
4v5BcbEBo1scixRBOn5BWKvl9C9j/a2dkX3jWQD4p2xaj69gz8f6DNFyPTb+tNhq
cjgO5YUASZ1MDrSfWIKteULRAoGAZkZv8x2KyofrmQ0UITGZerDYz4t4TA1kDMGx
QwnqhtVzpXjCJWpkFotFmDsCfPaz9mErR8PtKvcrIL1/AF+fWe5Sve3+I1P0PpXk
hf8fVdGhwbAXuRKrouTmagGI9b9Sp65PvHUcvasyJufFwqeuV8mScX87CzeSiHGI
/ozMdnECgYEAq4+losrhe0DEmiC9zVPvwRXjbSixDsSJxHfOcqIsZqhUgBiZ4TJD
SrkuukrMZib6BAD+PtCJS1TBbJyyvL3QecizhHSIh3ZnT0HnaRPatLEYmU65+3kE
kTqL4ik92bJnnWowy677sydl1lzBJDVa9ZlTs7BFSd8y/0DZaUxGg2I=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMREwDwYDVQQDDAhydWJ5
Z2VtczEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxEzARBgoJkiaJk/IsZAEZFgNj
b20wHhcNMDcwODAyMDMyNTQyWhcNMDgwODAxMDMyNTQyWjBBMREwDwYDVQQDDAhy
dWJ5Z2VtczEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxEzARBgoJkiaJk/IsZAEZ
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPS1M62wm4cMoA
RetDHR/oNQTMERRNItHV+TSIQexj2sMJDGazDkF/ZSdWF0PTcHj9fMgr6lq3YIxB
zfv+8sgG5WBLuD9/Nq0aIzSbmsAe3JyvRdTwSTrOPHiJK/vQ4uPJtj7+60cIRzzf
xrAYMwIVHbWNjGABM9XCoAjJqy/BsqH9WHC4SsU+pP0bwEpMexpf4f4AMhEfTTU6
ZaSMHoTKMXTnXXiQ0xrT7YLt1pX2GgEveZ2EK3rM4ZxqmX/0uQbGFw0RQH9KjXpX
VlCV2D2ZpwbJaS3I1NJ9y2UX5/yiQCaJwbdcjBpbxROaMUKa8Oz+HVH+4ql7Yg4f
Pzufb13ZAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
BBRYTAoj4cn8CWZMHFnHGQgoO5jyFTANBgkqhkiG9w0BAQUFAAOCAQEATRrJC05l
dOmx67Sy3bU+AVXkOr7B9nn2Myqo9uSIAncPoElN6aHr/Q8wOOjtok4r0JcHPe1e
eotDCZUE1Jkl13Tpv26rOfOOUHtGlyAIAtpsUGOraaJkSut4WKLr1/KckyAAEtgP
c13A0s0mEiWFRuYxIdEi54561pTT2qQBE/DUPGoYD5rUg9XYAlSovMMwG99Oca7L
cI6vCymr1bzzddExoywBNOy0fbBT62I3ICBGbH5yOVVKVmlxeo2Zp10FCj0kDrnq
OuMJSDr5I2XPYqoC+W4YSbwn55o2jGIUX1lOq2Hvj4tFgSxlnJZn0tUhBfR3gSOn
IFnrqu8PlZsLFw==
-----END CERTIFICATE-----

View file

@ -0,0 +1,7 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
fail LoadError, "no such file to load -- openssl"

View file

@ -0,0 +1,95 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require 'rubygems'
require 'test/insure_session'
require 'rubygems/format'
require 'rubygems/command_manager'
class FunctionalTest < Test::Unit::TestCase
def setup
@gem_path = File.expand_path("bin/gem")
lib_path = File.expand_path("lib")
@ruby_options = "-I#{lib_path} -I."
@verbose = false
end
def test_gem_help_options
gem_nossl 'help options'
assert_match(/Usage:/, @out, @err)
assert_status
end
def test_gem_help_commands
gem_nossl 'help commands'
assert_match(/gem install/, @out)
assert_status
end
def test_gem_no_args_shows_help
gem_nossl
assert_match(/Usage:/, @out)
assert_status 1
end
# This test is disabled because of the insanely long time it takes
# to time out.
def xtest_bogus_source_hoses_up_remote_install_but_gem_command_gives_decent_error_message
@ruby_options << " -rtest/bogussources"
gem_nossl "install asdf --remote"
assert_match(/error/im, @err)
assert_status 1
end
def test_all_command_helps
mgr = Gem::CommandManager.new
mgr.command_names.each do |cmdname|
gem_nossl "help #{cmdname}"
assert_match(/Usage: gem #{cmdname}/, @out,
"should see help for #{cmdname}")
end
end
# :section: Help Methods
# Run a gem command without the SSL library.
def gem_nossl(options="")
old_options = @ruby_options.dup
@ruby_options << " -Itest/fake_certlib"
gem(options)
ensure
@ruby_options = old_options
end
# Run a gem command with the SSL library.
def gem_withssl(options="")
gem(options)
end
# Run a gem command for the functional test.
def gem(options="")
shell = Session::Shell.new
options = options + " --config-file missing_file" if options !~ /--config-file/
command = "#{Gem.ruby} #{@ruby_options} #{@gem_path} #{options}"
puts "\n\nCOMMAND: [#{command}]" if @verbose
@out, @err = shell.execute command
@status = shell.exit_status
puts "STATUS: [#{@status}]" if @verbose
puts "OUTPUT: [#{@out}]" if @verbose
puts "ERROR: [#{@err}]" if @verbose
puts "PWD: [#{Dir.pwd}]" if @verbose
shell.close
end
private
def assert_status(expected_status=0)
assert_equal expected_status, @status
end
end

View file

@ -0,0 +1,295 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
at_exit { $SAFE = 1 }
require 'fileutils'
require 'test/unit/testcase'
require 'tmpdir'
require 'uri'
require 'rubygems/gem_open_uri'
require 'rubygems/source_info_cache'
require File.join(File.expand_path(File.dirname(__FILE__)), 'mockgemui')
module Gem
def self.source_index=(si)
@@source_index = si
end
end
class FakeFetcher
attr_reader :data
attr_accessor :uri
attr_accessor :paths
def initialize
@data = {}
@paths = []
@uri = nil
end
def fetch_path(path)
path = path.to_s
@paths << path
raise ArgumentError, 'need full URI' unless path =~ %r'^http://'
data = @data[path]
raise OpenURI::HTTPError.new("no data for #{path}", nil) if data.nil?
data.respond_to?(:call) ? data.call : data
end
def fetch_size(path)
path = path.to_s
@paths << path
raise ArgumentError, 'need full URI' unless path =~ %r'^http://'
data = @data[path]
raise OpenURI::HTTPError.new("no data for #{path}", nil) if data.nil?
data.respond_to?(:call) ? data.call : data.length
end
end
class RubyGemTestCase < Test::Unit::TestCase
include Gem::DefaultUserInteraction
undef_method :default_test
def setup
super
@ui = MockGemUi.new
tmpdir = nil
Dir.chdir Dir.tmpdir do tmpdir = Dir.pwd end # HACK OSX /private/tmp
@tempdir = File.join tmpdir, "test_rubygems_#{$$}"
@tempdir.untaint
@gemhome = File.join @tempdir, "gemhome"
@gemcache = File.join(@gemhome, "source_cache")
@usrcache = File.join(@gemhome, ".gem", "user_cache")
FileUtils.mkdir_p @gemhome
ENV['GEMCACHE'] = @usrcache
Gem.use_paths(@gemhome)
Gem.loaded_specs.clear
Gem.configuration.verbose = true
Gem.configuration.update_sources = true
@gem_repo = "http://gems.example.com"
Gem.sources.replace [@gem_repo]
@orig_arch = Config::CONFIG['arch']
if win_platform?
util_set_arch 'i386-mswin32'
else
util_set_arch 'i686-darwin8.10.1'
end
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
end
def teardown
Config::CONFIG['arch'] = @orig_arch
if defined? Gem::RemoteFetcher then
Gem::RemoteFetcher.instance_variable_set :@fetcher, nil
end
FileUtils.rm_rf @tempdir
ENV.delete 'GEMCACHE'
ENV.delete 'GEM_HOME'
ENV.delete 'GEM_PATH'
Gem.clear_paths
Gem::SourceInfoCache.instance_variable_set :@cache, nil
end
def install_gem gem
require 'rubygems/installer'
use_ui MockGemUi.new do
Dir.chdir @tempdir do
Gem::Builder.new(gem).build
end
end
gem = File.join(@tempdir, "#{gem.full_name}.gem").untaint
Gem::Installer.new(gem).install
end
def prep_cache_files(lc)
[ [lc.system_cache_file, 'sys'],
[lc.user_cache_file, 'usr'],
].each do |fn, data|
FileUtils.mkdir_p File.dirname(fn).untaint
open(fn.dup.untaint, "wb") { |f| f.write(Marshal.dump({'key' => data})) }
end
end
def read_cache(fn)
open(fn.dup.untaint) { |f| Marshal.load f.read }
end
def write_file(path)
path = File.join(@gemhome, path)
dir = File.dirname path
FileUtils.mkdir_p dir
File.open(path, "w") { |io|
yield(io)
}
path
end
def quick_gem(gemname, version='0.0.2')
require 'rubygems/specification'
spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = gemname
s.version = version
s.author = 'A User'
s.email = 'example@example.com'
s.homepage = 'http://example.com'
s.has_rdoc = true
s.summary = "this is a summary"
s.description = "This is a test description"
yield(s) if block_given?
end
path = File.join "specifications", "#{spec.full_name}.gemspec"
written_path = write_file path do |io|
io.write(spec.to_ruby)
end
spec.loaded_from = written_path
return spec
end
def util_build_gem(spec)
dir = File.join(@gemhome, 'gems', spec.full_name)
FileUtils.mkdir_p dir
Dir.chdir dir do
spec.files.each do |file|
next if File.exist? file
FileUtils.mkdir_p File.dirname(file)
File.open file, 'w' do |fp| fp.puts "# #{file}" end
end
use_ui MockGemUi.new do
Gem::Builder.new(spec).build
end
FileUtils.mv "#{spec.full_name}.gem", File.join(@gemhome, 'cache')
end
end
def util_make_gems
spec = proc do |s|
s.files = %w[lib/code.rb]
s.require_paths = %w[lib]
end
@a0_0_1 = quick_gem('a', '0.0.1', &spec)
@a0_0_2 = quick_gem('a', '0.0.2', &spec)
@b0_0_2 = quick_gem('b', '0.0.2', &spec)
@c1_2 = quick_gem('c', '1.2', &spec)
write_file File.join(*%w[gems a-0.0.1 lib code.rb]) do end
write_file File.join(*%w[gems a-0.0.2 lib code.rb]) do end
write_file File.join(*%w[gems b-0.0.2 lib code.rb]) do end
write_file File.join(*%w[gems c-1.2 lib code.rb]) do end
[@a0_0_1, @a0_0_2, @b0_0_2, @c1_2].each { |spec| util_build_gem spec }
Gem.source_index = nil
end
##
# Set the platform to +cpu+ and +os+
def util_set_arch(arch)
Config::CONFIG['arch'] = arch
platform = Gem::Platform.new arch
Gem.instance_variable_set :@platforms, nil
Gem::Platform.instance_variable_set :@local, nil
platform
end
def util_setup_fake_fetcher
require 'zlib'
require 'socket'
require 'rubygems/remote_fetcher'
@uri = URI.parse @gem_repo
@fetcher = FakeFetcher.new
@fetcher.uri = @uri
@gem1 = quick_gem 'gem_one' do |gem|
gem.files = %w[Rakefile lib/gem_one.rb]
end
@gem2 = quick_gem 'gem_two' do |gem|
gem.files = %w[Rakefile lib/gem_two.rb]
end
@gem3 = quick_gem 'gem_three' do |gem| # missing gem
gem.files = %w[Rakefile lib/gem_three.rb]
end
# this gem has a higher version and longer name than the gem we want
@gem4 = quick_gem 'gem_one_evil', '666' do |gem|
gem.files = %w[Rakefile lib/gem_one.rb]
end
@all_gems = [@gem1, @gem2, @gem3, @gem4].sort
@all_gem_names = @all_gems.map { |gem| gem.full_name }
gem_names = [@gem1.full_name, @gem2.full_name, @gem4.full_name]
@gem_names = gem_names.sort.join("\n")
@source_index = Gem::SourceIndex.new @gem1.full_name => @gem1,
@gem2.full_name => @gem2,
@gem4.full_name => @gem4
Gem::RemoteFetcher.instance_variable_set :@fetcher, @fetcher
end
def util_setup_source_info_cache(*specs)
require 'rubygems/source_info_cache_entry'
specs = Hash[*specs.map { |spec| [spec.full_name, spec] }.flatten]
si = Gem::SourceIndex.new specs
sice = Gem::SourceInfoCacheEntry.new si, 0
sic = Gem::SourceInfoCache.new
sic.set_cache_data( { @gem_repo => sice } )
Gem::SourceInfoCache.instance_variable_set :@cache, sic
si
end
def util_zip(data)
Zlib::Deflate.deflate data
end
def self.win_platform?
Gem.win_platform?
end
def win_platform?
Gem.win_platform?
end
end

View file

@ -0,0 +1,51 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'rubygems'
def install_session
path_to_gem = File.join("redist", "session.gem")
begin
Gem::Installer.new(path_to_gem).install
rescue Errno::EACCES => ex
puts
puts "*****************************************************************"
puts "Unable to install Gem 'Session'."
puts "Reason: #{ex.message}"
puts "Try running:"
puts
puts " gem -Li #{path_to_gem}"
puts
puts "with the appropriate admin privileges."
puts "*****************************************************************"
puts
exit
end
gem 'session'
end
begin
require 'session'
rescue LoadError => e
puts
puts "Required Gem 'Session' missing."
puts "We can attempt to install from the RubyGems Distribution,"
puts "but installation may require admin privileges on your system."
puts
print "Install now from RubyGems distribution? [Yn]"
answer = gets
if(answer =~ /^y/i || answer =~ /^[^a-zA-Z0-9]$/) then
install_session
puts
puts "Retry running the functional tests."
exit(0)
else
puts "Test cancelled...quitting"
exit(1)
end
end

View file

@ -0,0 +1,51 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'stringio'
require 'rubygems/user_interaction'
class MockGemUi < Gem::StreamUI
class TermError < RuntimeError; end
def initialize(input="")
super(StringIO.new(input), StringIO.new, StringIO.new)
@terminated = false
@banged = false
end
def input
@ins.string
end
def output
@outs.string
end
def error
@errs.string
end
def banged?
@banged
end
def terminated?
@terminated
end
def terminate_interaction!(status=1)
@terminated = true
@banged = true
fail TermError
end
def terminate_interaction(status=0)
@terminated = true
fail TermError
end
end

View file

@ -0,0 +1,72 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
SIMPLE_GEM = <<-GEMDATA
MD5SUM = "e3701f9db765a2358aef94c40ded71c8"
if $0 == __FILE__
require 'optparse'
options = {}
ARGV.options do |opts|
opts.on_tail("--help", "show this message") {puts opts; exit}
opts.on('--dir=DIRNAME', "Installation directory for the Gem") {|options[:directory]|}
opts.on('--force', "Force Gem to intall, bypassing dependency checks") {|options[:force]|}
opts.on('--gen-rdoc', "Generate RDoc documentation for the Gem") {|options[:gen_rdoc]|}
opts.parse!
end
require 'rubygems'
@directory = options[:directory] || Gem.dir
@force = options[:force]
gem = Gem::Installer.new(__FILE__).install(@force, @directory)
if options[:gen_rdoc]
Gem::DocManager.new(gem).generate_rdoc
end
end
__END__
--- !ruby/object:Gem::Specification
rubygems_version: "1.0"
name: testing
version: !ruby/object:Gem::Version
version: 1.2.3
date: 2004-03-18 22:01:52.859121 -05:00
platform:
summary: This exercise the gem testing stuff.
require_paths:
- lib
files:
- lib/foo.rb
- lib/test
- lib/test.rb
- lib/test/wow.rb
autorequire: test
test_suite_file: foo
requirements:
- a computer processor
---
-
size: 109
mode: 420
path: lib/foo.rb
-
size: 0
mode: 420
path: lib/test.rb
-
size: 15
mode: 420
path: lib/test/wow.rb
---
eJwVjDEKgDAUQ/eeIpsKguhY3ARPoHMp9quF0mL7e39/h5DwQpLpqz4TOqbC
U42eO6WuYEvBntIhECuaaX1KqXXLmy2kAEc32szExK+PjyBAlpTZyK0N/Twu
g1CKTjX9BGAj1w==
---
eJwDAAAAAAE=
---
eJwrKC0pVlAvzy9XyE3MU+cCACwiBP4=
GEMDATA

View file

@ -0,0 +1,26 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rbconfig'
require 'rubygems'
class TestConfig < RubyGemTestCase
def test_gem_original_datadir
datadir = Config::CONFIG['datadir']
assert_equal "#{datadir}/xyz", Config.gem_original_datadir('xyz')
end
def test_datadir
datadir = Config::CONFIG['datadir']
assert_equal "#{datadir}/xyz", Config.datadir('xyz')
end
end

367
test/rubygems/test_gem.rb Normal file
View file

@ -0,0 +1,367 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems'
require 'rubygems/gem_openssl'
require 'pathname'
class TestGem < RubyGemTestCase
def setup
super
@additional = %w[a b].map { |d| File.join @tempdir, d }
@default_dir_re = %r|/ruby/gems/[0-9.]+|
end
def test_self_all_load_paths
util_make_gems
expected = [
File.join(@tempdir, *%w[gemhome gems a-0.0.1 lib]),
File.join(@tempdir, *%w[gemhome gems a-0.0.2 lib]),
File.join(@tempdir, *%w[gemhome gems b-0.0.2 lib]),
File.join(@tempdir, *%w[gemhome gems c-1.2 lib]),
]
assert_equal expected, Gem.all_load_paths.sort
end
def test_self_bindir
assert_equal File.join(@gemhome, 'bin'), Gem.bindir
assert_equal File.join(@gemhome, 'bin'), Gem.bindir(Gem.dir)
assert_equal File.join(@gemhome, 'bin'), Gem.bindir(Pathname.new(Gem.dir))
end
def test_self_bindir_default_dir
default = Gem.default_dir
assert_equal Config::CONFIG['bindir'], Gem.bindir(default)
assert_equal Config::CONFIG['bindir'], Gem.bindir(Pathname.new(default))
end
def test_self_clear_paths
Gem.dir
Gem.path
searcher = Gem.searcher
source_index = Gem.source_index
Gem.clear_paths
assert_equal nil, Gem.instance_variable_get(:@gem_home)
assert_equal nil, Gem.instance_variable_get(:@gem_path)
assert_not_equal searcher, Gem.searcher
assert_not_equal source_index, Gem.source_index
end
def test_self_configuration
expected = Gem::ConfigFile.new []
Gem.configuration = nil
assert_equal expected, Gem.configuration
end
def test_self_datadir
foo = nil
Dir.chdir @tempdir do
FileUtils.mkdir_p 'data'
File.open File.join('data', 'foo.txt'), 'w' do |fp|
fp.puts 'blah'
end
foo = quick_gem 'foo' do |s| s.files = %w[data/foo.txt] end
install_gem foo
end
gem 'foo'
expected = File.join @gemhome, 'gems', foo.full_name, 'data', 'foo'
assert_equal expected, Gem.datadir('foo')
end
def test_self_datadir_nonexistent_package
assert_nil Gem.datadir('xyzzy')
end
def test_self_default_dir
assert_match @default_dir_re, Gem.default_dir
end
def test_self_default_sources
assert_equal %w[http://gems.rubyforge.org], Gem.default_sources
end
def test_self_dir
assert_equal @gemhome, Gem.dir
Gem::DIRECTORIES.each do |filename|
assert File.directory?(File.join(Gem.dir, filename)),
"expected #{filename} to exist"
end
end
def test_self_ensure_gem_directories
FileUtils.rm_r @gemhome
Gem.use_paths @gemhome
Gem.ensure_gem_subdirectories @gemhome
assert File.directory?(File.join(@gemhome, "cache"))
end
def test_self_ensure_gem_directories_missing_parents
gemdir = File.join @tempdir, 'a/b/c/gemdir'
FileUtils.rm_rf File.join(@tempdir, 'a') rescue nil
assert !File.exist?(File.join(@tempdir, 'a')),
"manually remove #{File.join @tempdir, 'a'}, tests are broken"
Gem.use_paths gemdir
Gem.ensure_gem_subdirectories gemdir
assert File.directory?("#{gemdir}/cache")
end
unless win_platform? then # only for FS that support write protection
def test_self_ensure_gem_directories_write_protected
gemdir = File.join @tempdir, "egd"
FileUtils.rm_r gemdir rescue nil
assert !File.exist?(gemdir), "manually remove #{gemdir}, tests are broken"
FileUtils.mkdir_p gemdir
FileUtils.chmod 0400, gemdir
Gem.use_paths gemdir
Gem.ensure_gem_subdirectories gemdir
assert !File.exist?("#{gemdir}/cache")
ensure
FileUtils.chmod 0600, gemdir
end
def test_self_ensure_gem_directories_write_protected_parents
parent = File.join(@tempdir, "egd")
gemdir = "#{parent}/a/b/c"
FileUtils.rm_r parent rescue nil
assert !File.exist?(parent), "manually remove #{parent}, tests are broken"
FileUtils.mkdir_p parent
FileUtils.chmod 0400, parent
Gem.use_paths(gemdir)
Gem.ensure_gem_subdirectories gemdir
assert !File.exist?("#{gemdir}/cache")
ensure
FileUtils.chmod 0600, parent
end
end
def test_ensure_ssl_available
orig_Gem_ssl_available = Gem.ssl_available?
Gem.ssl_available = true
assert_nothing_raised do Gem.ensure_ssl_available end
Gem.ssl_available = false
e = assert_raise Gem::Exception do Gem.ensure_ssl_available end
assert_equal 'SSL is not installed on this system', e.message
ensure
Gem.ssl_available = orig_Gem_ssl_available
end
def test_self_latest_load_paths
util_make_gems
expected = [
File.join(@tempdir, *%w[gemhome gems a-0.0.2 lib]),
File.join(@tempdir, *%w[gemhome gems b-0.0.2 lib]),
File.join(@tempdir, *%w[gemhome gems c-1.2 lib]),
]
assert_equal expected, Gem.latest_load_paths.sort
end
def test_self_loaded_specs
foo = quick_gem 'foo'
install_gem foo
Gem.source_index = nil
Gem.activate 'foo', false
assert_equal true, Gem.loaded_specs.keys.include?('foo')
end
def test_self_path
assert_equal [Gem.dir], Gem.path
end
def test_self_path_ENV_PATH
Gem.clear_paths
util_ensure_gem_dirs
ENV['GEM_PATH'] = @additional.join(File::PATH_SEPARATOR)
assert_equal @additional, Gem.path[0,2]
assert_equal 3, Gem.path.size
assert_match Gem.dir, Gem.path.last
end
def test_self_path_duplicate
Gem.clear_paths
util_ensure_gem_dirs
dirs = @additional + [@gemhome] + [File.join(@tempdir, 'a')]
ENV['GEM_HOME'] = @gemhome
ENV['GEM_PATH'] = dirs.join File::PATH_SEPARATOR
assert_equal @gemhome, Gem.dir
assert_equal @additional + [Gem.dir], Gem.path
end
def test_self_path_overlap
Gem.clear_paths
util_ensure_gem_dirs
ENV['GEM_HOME'] = @gemhome
ENV['GEM_PATH'] = @additional.join(File::PATH_SEPARATOR)
assert_equal @gemhome, Gem.dir
assert_equal @additional + [Gem.dir], Gem.path
end
def test_self_platforms
assert_equal [Gem::Platform::RUBY, Gem::Platform.local], Gem.platforms
end
def test_self_prefix
file_name = File.expand_path __FILE__
assert_equal File.dirname(File.dirname(file_name)), Gem.prefix
end
def test_self_required_location
util_make_gems
assert_equal File.join(@tempdir, *%w[gemhome gems c-1.2 lib code.rb]),
Gem.required_location("c", "code.rb")
assert_equal File.join(@tempdir, *%w[gemhome gems a-0.0.1 lib code.rb]),
Gem.required_location("a", "code.rb", "<0.0.2")
assert_equal File.join(@tempdir, *%w[gemhome gems a-0.0.2 lib code.rb]),
Gem.required_location("a", "code.rb", "=0.0.2")
end
def test_self_searcher
assert_kind_of Gem::GemPathSearcher, Gem.searcher
end
def test_self_source_index
assert_kind_of Gem::SourceIndex, Gem.source_index
end
def test_self_sources
assert_equal %w[http://gems.example.com], Gem.sources
end
def test_ssl_available_eh
orig_Gem_ssl_available = Gem.ssl_available?
Gem.ssl_available = true
assert_equal true, Gem.ssl_available?
Gem.ssl_available = false
assert_equal false, Gem.ssl_available?
ensure
Gem.ssl_available = orig_Gem_ssl_available
end
def test_self_use_paths
util_ensure_gem_dirs
Gem.use_paths @gemhome, @additional
assert_equal @gemhome, Gem.dir
assert_equal @additional + [Gem.dir], Gem.path
end
def test_self_user_home
if ENV['HOME'] then
assert_equal ENV['HOME'], Gem.user_home
else
assert true, 'count this test'
end
end
def test_require_gem_autorequire
name = "AutorequireArray"
files = %w(a.rb b.rb)
gem = quick_gem(name) do |s|
s.files = files.map { |f| File.join("lib", f) }
s.autorequire = files
end
fullname = gem.full_name
write_file "gems/#{fullname}/lib/a.rb" do |io|
io.puts "$LOADED_A = true"
end
write_file "gems/#{fullname}/lib/b.rb" do |io|
io.puts "$LOADED_B = true"
end
Gem.source_index = nil
old_loaded = $".dup
old_verbose = $VERBOSE
$VERBOSE = nil
require_gem name
$VERBOSE = old_verbose
new_loaded = $".dup
if RUBY_VERSION > "1.9" then
files = files.map do |file|
File.join @gemhome, 'gems', gem.full_name, 'lib', file
end
end
assert_equal files, (new_loaded - old_loaded)
assert defined?($LOADED_A)
assert defined?($LOADED_B)
end
def test_require_gem_autorequire_string
name = "AutorequireString"
file = "c.rb"
gem = quick_gem(name) do |s|
s.files = File.join("lib", file)
s.autorequire = file
end
fullname = gem.full_name
write_file("gems/#{fullname}/lib/c.rb") do |io|
io.puts "$LOADED_C = true"
end
old_loaded = $".dup
old_verbose = $VERBOSE
$VERBOSE = nil
require_gem name
$VERBOSE = old_verbose
new_loaded = $".dup
if RUBY_VERSION > "1.9" then
file = File.join @gemhome, 'gems', gem.full_name, 'lib', file
end
assert_equal(Array(file), (new_loaded - old_loaded))
assert(defined? $LOADED_C)
end
def util_ensure_gem_dirs
Gem.ensure_gem_subdirectories @gemhome
@additional.each do |dir|
Gem.ensure_gem_subdirectories @gemhome
end
end
end

View file

@ -0,0 +1,34 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/builder'
class TestGemBuilder < RubyGemTestCase
def test_build
builder = Gem::Builder.new quick_gem('a')
use_ui @ui do
Dir.chdir @tempdir do
builder.build
end
end
assert_match %r|Successfully built RubyGem\n Name: a|, @ui.output
end
def test_build_validates
builder = Gem::Builder.new Gem::Specification.new
assert_raises Gem::InvalidSpecificationException do
builder.build
end
end
end

View file

@ -0,0 +1,196 @@
#!/usr/bin/env ruby
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/command'
class Gem::Command
public :parser
end
class TestGemCommand < RubyGemTestCase
def setup
super
@xopt = nil
Gem::Command.common_options.clear
Gem::Command.common_options << [
['-x', '--exe', 'Execute'], lambda do |*a|
@xopt = true
end
]
@cmd_name = 'doit'
@cmd = Gem::Command.new @cmd_name, 'summary'
end
def test_self_add_specific_extra_args
added_args = %w[--all]
@cmd.add_option '--all' do |v,o| end
Gem::Command.add_specific_extra_args @cmd_name, added_args
assert_equal added_args, Gem::Command.specific_extra_args(@cmd_name)
h = @cmd.add_extra_args []
assert_equal added_args, h
end
def test_self_add_specific_extra_args_unknown
added_args = %w[--definitely_not_there]
Gem::Command.add_specific_extra_args @cmd_name, added_args
assert_equal added_args, Gem::Command.specific_extra_args(@cmd_name)
h = @cmd.add_extra_args []
assert_equal [], h
end
def test_add_option_overlapping_common_and_local_options
@cmd.add_option('-x', '--zip', 'BAD!') do end
@cmd.add_option('-z', '--exe', 'BAD!') do end
@cmd.add_option('-x', '--exe', 'BAD!') do end
assert_match %r|-x, --zip|, @cmd.parser.to_s
assert_match %r|-z, --exe|, @cmd.parser.to_s
assert_no_match %r|-x, --exe|, @cmd.parser.to_s
end
def test_basic_accessors
assert_equal "doit", @cmd.command
assert_equal "gem doit", @cmd.program_name
assert_equal "summary", @cmd.summary
end
def test_common_option_in_class
assert Array === Gem::Command.common_options
end
def test_defaults
@cmd.add_option('-h', '--help [COMMAND]', 'Get help on COMMAND') do |value, options|
options[:help] = value
end
@cmd.defaults = { :help => true }
@cmd.when_invoked do |options|
assert options[:help], "Help options should default true"
end
use_ui @ui do
@cmd.invoke
end
assert_match %r|Usage: gem doit|, @ui.output
end
def test_invoke
done = false
@cmd.when_invoked { done = true }
use_ui @ui do
@cmd.invoke
end
assert done
end
def test_invode_with_bad_options
use_ui @ui do
@cmd.when_invoked do true end
ex = assert_raise(OptionParser::InvalidOption) do
@cmd.invoke('-zzz')
end
assert_match(/invalid option:/, ex.message)
end
end
def test_invoke_with_common_options
@cmd.when_invoked do true end
use_ui @ui do
@cmd.invoke "-x"
end
assert @xopt, "Should have done xopt"
end
# Returning false from the command handler invokes the usage output.
def test_invoke_with_help
done = false
use_ui @ui do
@cmd.add_option('-h', '--help [COMMAND]', 'Get help on COMMAND') do |value, options|
options[:help] = true
done = true
end
@cmd.invoke('--help')
assert done
end
assert_match(/Usage/, @ui.output)
assert_match(/gem doit/, @ui.output)
assert_match(/\[options\]/, @ui.output)
assert_match(/-h/, @ui.output)
assert_match(/--help \[COMMAND\]/, @ui.output)
assert_match(/Get help on COMMAND/, @ui.output)
assert_match(/-x/, @ui.output)
assert_match(/--exe/, @ui.output)
assert_match(/Execute/, @ui.output)
assert_match(/Common Options:/, @ui.output)
end
def test_invoke_with_options
@cmd.add_option('-h', '--help [COMMAND]', 'Get help on COMMAND') do |value, options|
options[:help] = true
end
@cmd.when_invoked do |opts|
assert opts[:help]
end
use_ui @ui do
@cmd.invoke '-h'
end
assert_match %r|Usage: gem doit|, @ui.output
end
def test_option_recognition
@cmd.add_option('-h', '--help [COMMAND]', 'Get help on COMMAND') do |value, options|
options[:help] = true
end
@cmd.add_option('-f', '--file FILE', 'File option') do |value, options|
options[:help] = true
end
assert @cmd.handles?(['-x'])
assert @cmd.handles?(['-h'])
assert @cmd.handles?(['-h', 'command'])
assert @cmd.handles?(['--help', 'command'])
assert @cmd.handles?(['-f', 'filename'])
assert @cmd.handles?(['--file=filename'])
assert ! @cmd.handles?(['-z'])
assert ! @cmd.handles?(['-f'])
assert ! @cmd.handles?(['--toothpaste'])
args = ['-h', 'command']
@cmd.handles?(args)
assert_equal ['-h', 'command'], args
end
end

View file

@ -0,0 +1,211 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/command_manager'
class InterruptCommand < Gem::Command
def initialize
super('interrupt', 'Raises an Interrupt Exception', {})
end
def execute
raise Interrupt, "Interrupt exception"
end
end
class TestGemCommandManager < RubyGemTestCase
def setup
super
@command_manager = Gem::CommandManager.new
end
def test_run_interrupt
use_ui @ui do
@command_manager.register_command :interrupt
assert_raises MockGemUi::TermError do
@command_manager.run 'interrupt'
end
assert_equal '', ui.output
assert_equal "ERROR: Interrupted\n", ui.error
end
end
def test_process_args_bad_arg
use_ui @ui do
assert_raises(MockGemUi::TermError) {
@command_manager.process_args("--bad-arg")
}
end
assert_match(/invalid option: --bad-arg/i, @ui.error)
end
def test_process_args_install
#capture all install options
use_ui @ui do
check_options = nil
@command_manager['install'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("install")
assert_equal false, check_options[:test]
assert_equal true, check_options[:generate_rdoc]
assert_equal false, check_options[:force]
assert_equal :both, check_options[:domain]
assert_equal true, check_options[:wrappers]
assert_equal Gem::Requirement.default, check_options[:version]
assert_equal Gem.dir, check_options[:install_dir]
#check settings
check_options = nil
@command_manager.process_args(
"install --force --test --local --rdoc --install-dir . --version 3.0 --no-wrapper")
assert_equal true, check_options[:test]
assert_equal true, check_options[:generate_rdoc]
assert_equal true, check_options[:force]
assert_equal :local, check_options[:domain]
assert_equal false, check_options[:wrappers]
assert_equal Gem::Requirement.new('3.0'), check_options[:version]
assert_equal Dir.pwd, check_options[:install_dir]
#check remote domain
check_options = nil
@command_manager.process_args("install --remote")
assert_equal :remote, check_options[:domain]
#check both domain
check_options = nil
@command_manager.process_args("install --both")
assert_equal :both, check_options[:domain]
#check both domain
check_options = nil
@command_manager.process_args("install --both")
assert_equal :both, check_options[:domain]
end
end
def test_process_args_uninstall
#capture all uninstall options
check_options = nil
@command_manager['uninstall'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("uninstall")
assert_equal Gem::Requirement.default, check_options[:version]
#check settings
check_options = nil
@command_manager.process_args("uninstall foobar --version 3.0")
assert_equal "foobar", check_options[:args].first
assert_equal Gem::Requirement.new('3.0'), check_options[:version]
end
def test_process_args_check
#capture all check options
check_options = nil
@command_manager['check'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("check")
assert_equal false, check_options[:verify]
assert_equal false, check_options[:alien]
#check settings
check_options = nil
@command_manager.process_args("check --verify foobar --alien")
assert_equal "foobar", check_options[:verify]
assert_equal true, check_options[:alien]
end
def test_process_args_build
#capture all build options
check_options = nil
@command_manager['build'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("build")
#NOTE: Currently no defaults
#check settings
check_options = nil
@command_manager.process_args("build foobar.rb")
assert_equal 'foobar.rb', check_options[:args].first
end
def test_process_args_query
#capture all query options
check_options = nil
@command_manager['query'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("query")
assert_equal(/.*/, check_options[:name])
assert_equal :local, check_options[:domain]
assert_equal false, check_options[:details]
#check settings
check_options = nil
@command_manager.process_args("query --name foobar --local --details")
assert_equal(/foobar/i, check_options[:name])
assert_equal :local, check_options[:domain]
assert_equal true, check_options[:details]
#remote domain
check_options = nil
@command_manager.process_args("query --remote")
assert_equal :remote, check_options[:domain]
#both (local/remote) domains
check_options = nil
@command_manager.process_args("query --both")
assert_equal :both, check_options[:domain]
end
def test_process_args_update
#capture all update options
check_options = nil
@command_manager['update'].when_invoked do |options|
check_options = options
true
end
#check defaults
@command_manager.process_args("update")
assert_equal true, check_options[:generate_rdoc]
#check settings
check_options = nil
@command_manager.process_args("update --force --test --rdoc --install-dir .")
assert_equal true, check_options[:test]
assert_equal true, check_options[:generate_rdoc]
assert_equal true, check_options[:force]
assert_equal Dir.pwd, check_options[:install_dir]
end
end

View file

@ -0,0 +1,75 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/commands/build_command'
require 'rubygems/format'
class TestGemCommandsBuildCommand < RubyGemTestCase
def setup
super
@cmd = Gem::Commands::BuildCommand.new
end
def test_execute
gem = quick_gem 'some_gem'
gemspec_file = File.join(@tempdir, "#{gem.full_name}.gemspec")
File.open gemspec_file, 'w' do |gs|
gs.write gem.to_ruby
end
util_test_build_gem gem, gemspec_file
end
def test_execute_yaml
gem = quick_gem 'some_gem'
gemspec_file = File.join(@tempdir, "#{gem.full_name}.gemspec")
File.open gemspec_file, 'w' do |gs|
gs.write gem.to_yaml
end
util_test_build_gem gem, gemspec_file
end
def test_execute_bad_gem
@cmd.options[:args] = %w[some_gem]
use_ui @ui do
@cmd.execute
end
assert_equal '', @ui.output
assert_equal "ERROR: Gemspec file not found: some_gem\n", @ui.error
end
def util_test_build_gem(gem, gemspec_file)
@cmd.options[:args] = [gemspec_file]
use_ui @ui do
Dir.chdir @tempdir do
@cmd.execute
end
end
output = @ui.output.split "\n"
assert_equal " Successfully built RubyGem", output.shift
assert_equal " Name: some_gem", output.shift
assert_equal " Version: 0.0.2", output.shift
assert_equal " File: some_gem-0.0.2.gem", output.shift
assert_equal [], output
assert_equal '', @ui.error
gem_file = File.join @tempdir, "#{gem.full_name}.gem"
assert File.exist?(gem_file)
spec = Gem::Format.from_file_by_path(gem_file).spec
assert_equal "some_gem", spec.name
assert_equal "this is a summary", spec.summary
end
end

View file

@ -0,0 +1,122 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/commands/cert_command'
class TestGemCommandsCertCommand < RubyGemTestCase
def setup
super
@orig_security_trust_dir = Gem::Security::OPT[:trust_dir]
Gem::Security::OPT[:trust_dir] = @tempdir
@cmd = Gem::Commands::CertCommand.new
root = File.expand_path(File.dirname(__FILE__))
FileUtils.cp File.join(root, 'data', 'gem-private_key.pem'), @tempdir
FileUtils.cp File.join(root, 'data', 'gem-public_cert.pem'), @tempdir
@cert_file_name = File.join @tempdir, 'gem-public_cert.pem'
@pkey_file_name = File.join @tempdir, 'gem-private_key.pem'
end
def teardown
Gem::Security::OPT[:trust_dir] = @orig_security_trust_dir
super
end
def test_execute_add
use_ui @ui do
@cmd.send :handle_options, %W[--add #{@cert_file_name}]
end
assert_equal "Added '/CN=rubygems/DC=example/DC=com'\n", @ui.output
assert_equal '', @ui.error
end
def test_execute_build
FileUtils.rm @cert_file_name
FileUtils.rm @pkey_file_name
use_ui @ui do
Dir.chdir @tempdir do
@cmd.send :handle_options, %W[--build nobody@example.com]
end
end
output = @ui.output.split "\n"
assert_equal 'Public Cert: gem-public_cert.pem', output.shift
assert_equal 'Private Key: gem-private_key.pem', output.shift
assert_equal 'Don\'t forget to move the key file to somewhere private...',
output.shift
assert_equal [], output
assert_equal '', @ui.error
assert File.exist?(File.join(@tempdir, 'gem-private_key.pem'))
assert File.exist?(File.join(@tempdir, 'gem-public_cert.pem'))
end
def test_execute_certificate
use_ui @ui do
@cmd.send :handle_options, %W[--certificate #{@cert_file_name}]
end
assert_equal '', @ui.output
assert_equal '', @ui.error
assert_equal File.read(@cert_file_name),
Gem::Security::OPT[:issuer_cert].to_s
end
def test_execute_list
use_ui @ui do
@cmd.send :handle_options, %W[--list]
end
assert_equal "/CN=rubygems/DC=example/DC=com\n", @ui.output
assert_equal '', @ui.error
end
def test_execute_private_key
use_ui @ui do
@cmd.send :handle_options, %W[--private-key #{@pkey_file_name}]
end
assert_equal '', @ui.output
assert_equal '', @ui.error
assert_equal File.read(@pkey_file_name),
Gem::Security::OPT[:issuer_key].to_s
end
def test_execute_remove
use_ui @ui do
@cmd.send :handle_options, %W[--remove rubygems]
end
assert_equal "Removed '/CN=rubygems/DC=example/DC=com'\n", @ui.output
assert_equal '', @ui.error
assert !File.exist?(@cert_file_name)
end
def test_execute_sign
use_ui @ui do
@cmd.send :handle_options, %W[
-K #{@pkey_file_name} -C #{@cert_file_name} --sign #{@cert_file_name}
]
end
assert_equal '', @ui.output
assert_equal '', @ui.error
# HACK this test sucks
end
end

View file

@ -0,0 +1,25 @@
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/commands/check_command'
class TestGemCommandsCheckCommand < RubyGemTestCase
def setup
super
@cmd = Gem::Commands::CheckCommand.new
end
def test_initialize
assert_equal "check", @cmd.command
assert_equal "gem check", @cmd.program_name
assert_match(/Check/, @cmd.summary)
end
end

View file

@ -0,0 +1,92 @@
require 'test/unit'
require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
require 'rubygems/commands/contents_command'
class TestGemCommandsContentsCommand < RubyGemTestCase
def setup
super
@cmd = Gem::Commands::ContentsCommand.new
end
def test_execute
@cmd.options[:args] = %w[foo]
quick_gem 'foo' do |gem|
gem.files = %w[lib/foo.rb Rakefile]
end
use_ui @ui do
@cmd.execute
end
assert_match %r|lib/foo\.rb|, @ui.output
assert_match %r|Rakefile|, @ui.output
assert_equal "", @ui.error
end
def test_execute_bad_gem
@cmd.options[:args] = %w[foo]
assert_raise MockGemUi::TermError do
use_ui @ui do
@cmd.execute
end
end
assert_match %r|Unable to find gem 'foo' in default gem paths|, @ui.output
assert_match %r|Directories searched:|, @ui.output
assert_equal "", @ui.error
end
def test_execute_exact_match
@cmd.options[:args] = %w[foo]
quick_gem 'foo' do |gem|
gem.files = %w[lib/foo.rb Rakefile]
end
quick_gem 'foo_bar' do |gem|
gem.files = %w[lib/foo_bar.rb Rakefile]
end
use_ui @ui do
@cmd.execute
end
assert_match %r|lib/foo\.rb|, @ui.output
assert_match %r|Rakefile|, @ui.output
assert_equal "", @ui.error
end
def test_execute_lib_only
@cmd.options[:args] = %w[foo]
@cmd.options[:lib_only] = true
quick_gem 'foo' do |gem|
gem.files = %w[lib/foo.rb Rakefile]
end
use_ui @ui do
@cmd.execute
end
assert_match %r|lib/foo\.rb|, @ui.output
assert_no_match %r|Rakefile|, @ui.output
assert_equal "", @ui.error
end
def test_handle_options
assert_equal false, @cmd.options[:lib_only]
assert_equal [], @cmd.options[:specdirs]
assert_equal nil, @cmd.options[:version]
@cmd.send :handle_options, %w[-l -s foo --version 0.0.2]
assert_equal true, @cmd.options[:lib_only]
assert_equal %w[foo], @cmd.options[:specdirs]
assert_equal Gem::Requirement.new('0.0.2'), @cmd.options[:version]
end
end

Some files were not shown because too many files have changed in this diff Show more