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:
parent
7a4aad7535
commit
fbf59bdbea
144 changed files with 21330 additions and 0 deletions
12
ChangeLog
12
ChangeLog
|
@ -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
24
lib/rbconfig/datadir.rb
Normal 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
561
lib/rubygems.rb
Normal 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
81
lib/rubygems/builder.rb
Normal 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
406
lib/rubygems/command.rb
Normal 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
|
144
lib/rubygems/command_manager.rb
Normal file
144
lib/rubygems/command_manager.rb
Normal 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
|
53
lib/rubygems/commands/build_command.rb
Normal file
53
lib/rubygems/commands/build_command.rb
Normal 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
|
86
lib/rubygems/commands/cert_command.rb
Normal file
86
lib/rubygems/commands/cert_command.rb
Normal 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
|
||||
|
74
lib/rubygems/commands/check_command.rb
Normal file
74
lib/rubygems/commands/check_command.rb
Normal 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
|
93
lib/rubygems/commands/cleanup_command.rb
Normal file
93
lib/rubygems/commands/cleanup_command.rb
Normal 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
|
74
lib/rubygems/commands/contents_command.rb
Normal file
74
lib/rubygems/commands/contents_command.rb
Normal 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
|
||||
|
150
lib/rubygems/commands/dependency_command.rb
Normal file
150
lib/rubygems/commands/dependency_command.rb
Normal 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
|
||||
|
80
lib/rubygems/commands/environment_command.rb
Normal file
80
lib/rubygems/commands/environment_command.rb
Normal 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
|
||||
|
62
lib/rubygems/commands/fetch_command.rb
Normal file
62
lib/rubygems/commands/fetch_command.rb
Normal 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
|
||||
|
57
lib/rubygems/commands/generate_index_command.rb
Normal file
57
lib/rubygems/commands/generate_index_command.rb
Normal 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
|
||||
|
172
lib/rubygems/commands/help_command.rb
Normal file
172
lib/rubygems/commands/help_command.rb
Normal 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
|
||||
|
125
lib/rubygems/commands/install_command.rb
Normal file
125
lib/rubygems/commands/install_command.rb
Normal 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
|
||||
|
35
lib/rubygems/commands/list_command.rb
Normal file
35
lib/rubygems/commands/list_command.rb
Normal 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
|
101
lib/rubygems/commands/lock_command.rb
Normal file
101
lib/rubygems/commands/lock_command.rb
Normal 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
|
||||
|
105
lib/rubygems/commands/mirror_command.rb
Normal file
105
lib/rubygems/commands/mirror_command.rb
Normal 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
|
||||
|
30
lib/rubygems/commands/outdated_command.rb
Normal file
30
lib/rubygems/commands/outdated_command.rb
Normal 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
|
||||
|
133
lib/rubygems/commands/pristine_command.rb
Normal file
133
lib/rubygems/commands/pristine_command.rb
Normal 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
|
||||
|
118
lib/rubygems/commands/query_command.rb
Normal file
118
lib/rubygems/commands/query_command.rb
Normal 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
|
||||
|
78
lib/rubygems/commands/rdoc_command.rb
Normal file
78
lib/rubygems/commands/rdoc_command.rb
Normal 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
|
37
lib/rubygems/commands/search_command.rb
Normal file
37
lib/rubygems/commands/search_command.rb
Normal 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
|
48
lib/rubygems/commands/server_command.rb
Normal file
48
lib/rubygems/commands/server_command.rb
Normal 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
|
||||
|
115
lib/rubygems/commands/sources_command.rb
Normal file
115
lib/rubygems/commands/sources_command.rb
Normal 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
|
||||
|
72
lib/rubygems/commands/specification_command.rb
Normal file
72
lib/rubygems/commands/specification_command.rb
Normal 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
|
||||
|
56
lib/rubygems/commands/uninstall_command.rb
Normal file
56
lib/rubygems/commands/uninstall_command.rb
Normal 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
|
76
lib/rubygems/commands/unpack_command.rb
Normal file
76
lib/rubygems/commands/unpack_command.rb
Normal 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
|
||||
|
149
lib/rubygems/commands/update_command.rb
Normal file
149
lib/rubygems/commands/update_command.rb
Normal 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
|
86
lib/rubygems/commands/which_command.rb
Normal file
86
lib/rubygems/commands/which_command.rb
Normal 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
224
lib/rubygems/config_file.rb
Normal 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
38
lib/rubygems/custom_require.rb
Executable 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
|
||||
|
65
lib/rubygems/dependency.rb
Normal file
65
lib/rubygems/dependency.rb
Normal 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
|
||||
|
219
lib/rubygems/dependency_installer.rb
Normal file
219
lib/rubygems/dependency_installer.rb
Normal 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
|
||||
|
165
lib/rubygems/dependency_list.rb
Normal file
165
lib/rubygems/dependency_list.rb
Normal 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
|
||||
|
40
lib/rubygems/digest/digest_adapter.rb
Executable file
40
lib/rubygems/digest/digest_adapter.rb
Executable 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
23
lib/rubygems/digest/md5.rb
Executable 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
17
lib/rubygems/digest/sha1.rb
Executable 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
17
lib/rubygems/digest/sha2.rb
Executable 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
161
lib/rubygems/doc_manager.rb
Normal 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
|
63
lib/rubygems/exceptions.rb
Normal file
63
lib/rubygems/exceptions.rb
Normal 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
18
lib/rubygems/ext.rb
Normal 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'
|
||||
|
56
lib/rubygems/ext/builder.rb
Normal file
56
lib/rubygems/ext/builder.rb
Normal 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
|
||||
|
24
lib/rubygems/ext/configure_builder.rb
Normal file
24
lib/rubygems/ext/configure_builder.rb
Normal 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
|
||||
|
23
lib/rubygems/ext/ext_conf_builder.rb
Normal file
23
lib/rubygems/ext/ext_conf_builder.rb
Normal 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
|
||||
|
27
lib/rubygems/ext/rake_builder.rb
Normal file
27
lib/rubygems/ext/rake_builder.rb
Normal 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
81
lib/rubygems/format.rb
Normal 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
|
7
lib/rubygems/gem_open_uri.rb
Normal file
7
lib/rubygems/gem_open_uri.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
if RUBY_VERSION < "1.9"
|
||||
require 'rubygems/open-uri'
|
||||
else
|
||||
require 'open-uri'
|
||||
end
|
83
lib/rubygems/gem_openssl.rb
Normal file
83
lib/rubygems/gem_openssl.rb
Normal 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
|
||||
|
84
lib/rubygems/gem_path_searcher.rb
Normal file
84
lib/rubygems/gem_path_searcher.rb
Normal 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
|
||||
|
58
lib/rubygems/gem_runner.rb
Normal file
58
lib/rubygems/gem_runner.rb
Normal 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
171
lib/rubygems/indexer.rb
Normal 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'
|
||||
|
80
lib/rubygems/indexer/abstract_index_builder.rb
Normal file
80
lib/rubygems/indexer/abstract_index_builder.rb
Normal 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
|
||||
|
8
lib/rubygems/indexer/marshal_index_builder.rb
Normal file
8
lib/rubygems/indexer/marshal_index_builder.rb
Normal 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
|
44
lib/rubygems/indexer/master_index_builder.rb
Normal file
44
lib/rubygems/indexer/master_index_builder.rb
Normal 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
|
48
lib/rubygems/indexer/quick_index_builder.rb
Normal file
48
lib/rubygems/indexer/quick_index_builder.rb
Normal 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
|
||||
|
87
lib/rubygems/install_update_options.rb
Normal file
87
lib/rubygems/install_update_options.rb
Normal 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
421
lib/rubygems/installer.rb
Normal 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
|
||||
|
106
lib/rubygems/local_remote_options.rb
Normal file
106
lib/rubygems/local_remote_options.rb
Normal 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
148
lib/rubygems/old_format.rb
Normal 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
773
lib/rubygems/open-uri.rb
Normal 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
851
lib/rubygems/package.rb
Normal 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
187
lib/rubygems/platform.rb
Normal 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
|
||||
|
164
lib/rubygems/remote_fetcher.rb
Normal file
164
lib/rubygems/remote_fetcher.rb
Normal 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
|
||||
|
195
lib/rubygems/remote_installer.rb
Normal file
195
lib/rubygems/remote_installer.rb
Normal 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
157
lib/rubygems/requirement.rb
Normal 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
|
||||
|
6
lib/rubygems/rubygems_version.rb
Normal file
6
lib/rubygems/rubygems_version.rb
Normal 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
785
lib/rubygems/security.rb
Normal 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
504
lib/rubygems/server.rb
Normal 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
|
||||
|
446
lib/rubygems/source_index.rb
Normal file
446
lib/rubygems/source_index.rb
Normal 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
|
||||
|
232
lib/rubygems/source_info_cache.rb
Normal file
232
lib/rubygems/source_info_cache.rb
Normal 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
|
||||
|
46
lib/rubygems/source_info_cache_entry.rb
Normal file
46
lib/rubygems/source_info_cache_entry.rb
Normal 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
|
||||
|
905
lib/rubygems/specification.rb
Normal file
905
lib/rubygems/specification.rb
Normal 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
25
lib/rubygems/timer.rb
Executable 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
183
lib/rubygems/uninstaller.rb
Normal 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
|
||||
|
291
lib/rubygems/user_interaction.rb
Normal file
291
lib/rubygems/user_interaction.rb
Normal 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
185
lib/rubygems/validator.rb
Executable 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
158
lib/rubygems/version.rb
Normal 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
|
||||
|
49
lib/rubygems/version_option.rb
Normal file
49
lib/rubygems/version_option.rb
Normal 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
10
lib/ubygems.rb
Normal 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'
|
8
test/rubygems/bogussources.rb
Normal file
8
test/rubygems/bogussources.rb
Normal 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")
|
27
test/rubygems/data/gem-private_key.pem
Normal file
27
test/rubygems/data/gem-private_key.pem
Normal 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-----
|
20
test/rubygems/data/gem-public_cert.pem
Normal file
20
test/rubygems/data/gem-public_cert.pem
Normal 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-----
|
7
test/rubygems/fake_certlib/openssl.rb
Normal file
7
test/rubygems/fake_certlib/openssl.rb
Normal 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"
|
95
test/rubygems/functional.rb
Normal file
95
test/rubygems/functional.rb
Normal 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
|
295
test/rubygems/gemutilities.rb
Normal file
295
test/rubygems/gemutilities.rb
Normal 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
|
||||
|
51
test/rubygems/insure_session.rb
Normal file
51
test/rubygems/insure_session.rb
Normal 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
|
51
test/rubygems/mockgemui.rb
Normal file
51
test/rubygems/mockgemui.rb
Normal 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
|
72
test/rubygems/simple_gem.rb
Normal file
72
test/rubygems/simple_gem.rb
Normal 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
|
26
test/rubygems/test_config.rb
Normal file
26
test/rubygems/test_config.rb
Normal 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
367
test/rubygems/test_gem.rb
Normal 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
|
||||
|
34
test/rubygems/test_gem_builder.rb
Normal file
34
test/rubygems/test_gem_builder.rb
Normal 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
|
||||
|
196
test/rubygems/test_gem_command.rb
Normal file
196
test/rubygems/test_gem_command.rb
Normal 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
|
||||
|
211
test/rubygems/test_gem_command_manager.rb
Normal file
211
test/rubygems/test_gem_command_manager.rb
Normal 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
|
||||
|
75
test/rubygems/test_gem_commands_build_command.rb
Normal file
75
test/rubygems/test_gem_commands_build_command.rb
Normal 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
|
||||
|
122
test/rubygems/test_gem_commands_cert_command.rb
Normal file
122
test/rubygems/test_gem_commands_cert_command.rb
Normal 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
|
||||
|
25
test/rubygems/test_gem_commands_check_command.rb
Normal file
25
test/rubygems/test_gem_commands_check_command.rb
Normal 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
|
92
test/rubygems/test_gem_commands_contents_command.rb
Normal file
92
test/rubygems/test_gem_commands_contents_command.rb
Normal 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
Loading…
Add table
Add a link
Reference in a new issue