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

* lib/rubygems: Import RubyGems 2.1

* test/rubygems:  Ditto.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@41873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2013-07-09 23:21:36 +00:00
parent cd9f9e4719
commit 47f0248b08
113 changed files with 4964 additions and 2610 deletions

View file

@ -1,3 +1,8 @@
Wed Jul 10 08:21:15 2013 Eric Hodel <drbrain@segment7.net>
* lib/rubygems: Import RubyGems 2.1
* test/rubygems: Ditto.
Wed Jul 10 07:34:34 2013 Eric Hodel <drbrain@segment7.net> Wed Jul 10 07:34:34 2013 Eric Hodel <drbrain@segment7.net>
* lib/rubygems/ext/ext_conf_builder.rb: Remove siteconf file after * lib/rubygems/ext/ext_conf_builder.rb: Remove siteconf file after

View file

@ -8,7 +8,7 @@
require 'rbconfig' require 'rbconfig'
module Gem module Gem
VERSION = '2.0.4' VERSION = '2.1.0'
end end
# Must be first since it unloads the prelude from 1.9.2 # Must be first since it unloads the prelude from 1.9.2
@ -143,6 +143,14 @@ module Gem
specifications specifications
] ]
##
# Subdirectories in a gem repository for default gems
REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES = %w[
gems
specifications/default
]
@@win_platform = nil @@win_platform = nil
@configuration = nil @configuration = nil
@ -379,6 +387,10 @@ module Gem
paths.path paths.path
end end
def self.spec_cache_dir
paths.spec_cache_dir
end
## ##
# Quietly ensure the Gem directory +dir+ contains all the proper # Quietly ensure the Gem directory +dir+ contains all the proper
# subdirectories. If we can't create a directory due to a permission # subdirectories. If we can't create a directory due to a permission
@ -389,6 +401,23 @@ module Gem
# World-writable directories will never be created. # World-writable directories will never be created.
def self.ensure_gem_subdirectories dir = Gem.dir, mode = nil def self.ensure_gem_subdirectories dir = Gem.dir, mode = nil
ensure_subdirectories(dir, mode, REPOSITORY_SUBDIRECTORIES)
end
##
# Quietly ensure the Gem directory +dir+ contains all the proper
# subdirectories for handling default gems. If we can't create a
# directory due to a permission problem, then we will silently continue.
#
# If +mode+ is given, missing directories are created with this mode.
#
# World-writable directories will never be created.
def self.ensure_default_gem_subdirectories dir = Gem.dir, mode = nil
ensure_subdirectories(dir, mode, REPOSITORY_DEFAULT_GEM_SUBDIRECTORIES)
end
def self.ensure_subdirectories dir, mode, subdirs # :nodoc:
old_umask = File.umask old_umask = File.umask
File.umask old_umask | 002 File.umask old_umask | 002
@ -398,7 +427,7 @@ module Gem
options[:mode] = mode if mode options[:mode] = mode if mode
REPOSITORY_SUBDIRECTORIES.each do |name| subdirs.each do |name|
subdir = File.join dir, name subdir = File.join dir, name
next if File.exist? subdir next if File.exist? subdir
FileUtils.mkdir_p subdir, options rescue nil FileUtils.mkdir_p subdir, options rescue nil
@ -971,10 +1000,33 @@ module Gem
attr_reader :loaded_specs attr_reader :loaded_specs
## ##
# Register a Gem::Specification for default gem # Register a Gem::Specification for default gem.
#
# Two formats for the specification are supported:
#
# * MRI 2.0 style, where spec.files contains unprefixed require names.
# The spec's filenames will be registered as-is.
# * New style, where spec.files contains files prefixed with paths
# from spec.require_paths. The prefixes are stripped before
# registering the spec's filenames. Unprefixed files are omitted.
#
def register_default_spec(spec) def register_default_spec(spec)
new_format, prefix_pattern = nil
spec.files.each do |file| spec.files.each do |file|
if new_format == nil
new_format = spec.require_paths.any? {|path| file.start_with? path}
prefix_group = spec.require_paths.map {|f| f + "/"}.join("|")
prefix_pattern = /^(#{prefix_group})/
end
if new_format
file = file.sub(prefix_pattern, "")
next unless $~
end
@path_to_default_spec_map[file] = spec @path_to_default_spec_map[file] = spec
end end
end end

View file

@ -1,4 +1,7 @@
class Gem::AvailableSet class Gem::AvailableSet
include Enumerable
Tuple = Struct.new(:spec, :source) Tuple = Struct.new(:spec, :source)
def initialize def initialize
@ -36,6 +39,28 @@ class Gem::AvailableSet
self self
end end
##
# Yields each Tuple in this AvailableSet
def each
return enum_for __method__ unless block_given?
@set.each do |tuple|
yield tuple
end
end
##
# Yields the Gem::Specification for each Tuple in this AvailableSet
def each_spec
return enum_for __method__ unless block_given?
each do |tuple|
yield tuple.spec
end
end
def empty? def empty?
@set.empty? @set.empty?
end end
@ -66,6 +91,49 @@ class Gem::AvailableSet
f.source f.source
end end
##
# Converts this AvailableSet into a RequestSet that can be used to install
# gems.
#
# If +development+ is :none then no development dependencies are installed.
# Other options are :shallow for only direct development dependencies of the
# gems in this set or :all for all development dependencies.
def to_request_set development = :none
request_set = Gem::RequestSet.new
request_set.development = :all == development
each_spec do |spec|
request_set.always_install << spec
request_set.gem spec.name, spec.version
request_set.import spec.development_dependencies if
:shallow == development
end
request_set
end
##
#
# Used by the DependencyResolver, the protocol to use a AvailableSet as a
# search Set.
def find_all(req)
dep = req.dependency
match = @set.find_all do |t|
dep.matches_spec? t.spec
end
match.map do |t|
Gem::DependencyResolver::InstalledSpecification.new(self, t.spec, t.source)
end
end
def prefetch(reqs)
end
def pick_best! def pick_best!
return self if empty? return self if empty?

View file

@ -0,0 +1,139 @@
module Gem
# BasicSpecification is an abstract class which implements some common code used by
# both Specification and StubSpecification.
class BasicSpecification
def self.default_specifications_dir
File.join(Gem.default_dir, "specifications", "default")
end
##
# Name of the gem
def name
raise NotImplementedError
end
##
# Version of the gem
def version
raise NotImplementedError
end
##
# Platform of the gem
def platform
raise NotImplementedError
end
##
# Require paths of the gem
def require_paths
raise NotImplementedError
end
##
# True when the gem has been activated
def activated?
raise NotImplementedError
end
##
# Return a Gem::Specification from this gem
def to_spec
raise NotImplementedError
end
##
# The filename of the gem specification
attr_reader :filename
##
# Set the filename of the Specification was loaded from. +path+ is converted
# to a String.
def filename= path
@filename = path && path.to_s
@full_gem_path = nil
@gems_dir = nil
@base_dir = nil
end
##
# Return true if this spec can require +file+.
def contains_requirable_file? file
root = full_gem_path
suffixes = Gem.suffixes
require_paths.any? do |lib|
base = "#{root}/#{lib}/#{file}"
suffixes.any? { |suf| File.file? "#{base}#{suf}" }
end
end
##
# The full path to the gem (install path + full name).
def full_gem_path
# TODO: This is a heavily used method by gems, so we'll need
# to aleast just alias it to #gem_dir rather than remove it.
@full_gem_path ||= find_full_gem_path
end
# :nodoc:
def find_full_gem_path
# TODO: also, shouldn't it default to full_name if it hasn't been written?
path = File.expand_path File.join(gems_dir, full_name)
path.untaint
path if File.directory? path
end
private :find_full_gem_path
##
# Returns the full path to the gems directory containing this spec's
# gem directory. eg: /usr/local/lib/ruby/1.8/gems
def gems_dir
# TODO: this logic seems terribly broken, but tests fail if just base_dir
@gems_dir ||= File.join(filename && base_dir || Gem.dir, "gems")
end
##
# Returns the full path to the base gem directory.
#
# eg: /usr/local/lib/ruby/gems/1.8
def base_dir
return Gem.dir unless filename
@base_dir ||= if default_gem? then
File.dirname File.dirname File.dirname filename
else
File.dirname File.dirname filename
end
end
def default_gem?
filename &&
File.dirname(filename) == self.class.default_specifications_dir
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}".untaint
else
"#{name}-#{version}-#{platform}".untaint
end
end
end
end

View file

@ -1,6 +1,11 @@
require 'rubygems/command' require 'rubygems/command'
require 'rubygems/security' require 'rubygems/security'
require 'openssl' begin
require 'openssl'
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
end
class Gem::Commands::CertCommand < Gem::Command class Gem::Commands::CertCommand < Gem::Command
@ -21,7 +26,8 @@ class Gem::Commands::CertCommand < Gem::Command
OptionParser.accept OpenSSL::PKey::RSA do |key_file| OptionParser.accept OpenSSL::PKey::RSA do |key_file|
begin begin
key = OpenSSL::PKey::RSA.new File.read key_file passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
rescue Errno::ENOENT rescue Errno::ENOENT
raise OptionParser::InvalidArgument, "#{key_file}: does not exist" raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
rescue OpenSSL::PKey::RSAError rescue OpenSSL::PKey::RSAError
@ -115,16 +121,31 @@ class Gem::Commands::CertCommand < Gem::Command
end end
def build name def build name
key = options[:key] || Gem::Security.create_key if options[:key]
key = options[:key]
else
passphrase = ask_for_password 'Passphrase for your Private Key:'
say "\n"
cert = Gem::Security.create_cert_email name, key passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
say "\n"
key_path = Gem::Security.write key, "gem-private_key.pem" raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
key = Gem::Security.create_key
key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
end
cert = Gem::Security.create_cert_email name, key
cert_path = Gem::Security.write cert, "gem-public_cert.pem" cert_path = Gem::Security.write cert, "gem-public_cert.pem"
say "Certificate: #{cert_path}" say "Certificate: #{cert_path}"
say "Private Key: #{key_path}"
say "Don't forget to move the key file to somewhere private!" if key_path
say "Private Key: #{key_path}"
say "Don't forget to move the key file to somewhere private!"
end
end end
def certificates_matching filter def certificates_matching filter
@ -198,7 +219,8 @@ For further reading on signing gems see `ri Gem::Security`.
def load_default_key def load_default_key
key_file = File.join Gem.default_key_path key_file = File.join Gem.default_key_path
key = File.read key_file key = File.read key_file
options[:key] = OpenSSL::PKey::RSA.new key passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
options[:key] = OpenSSL::PKey::RSA.new key, passphrase
rescue Errno::ENOENT rescue Errno::ENOENT
alert_error \ alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist" "--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
@ -225,5 +247,5 @@ For further reading on signing gems see `ri Gem::Security`.
Gem::Security.write cert, cert_file, permissions Gem::Security.write cert, cert_file, permissions
end end
end end if defined?(OpenSSL::SSL)

View file

@ -9,7 +9,8 @@ class Gem::Commands::CleanupCommand < Gem::Command
'Clean up old versions of installed gems in the local repository', 'Clean up old versions of installed gems in the local repository',
:force => false, :install_dir => Gem.dir :force => false, :install_dir => Gem.dir
add_option('-d', '--dryrun', "") do |value, options| add_option('-n', '-d', '--dryrun',
'Do not uninstall gems') do |value, options|
options[:dryrun] = true options[:dryrun] = true
end end
@ -162,4 +163,3 @@ are not removed.
end end
end end

View file

@ -99,6 +99,8 @@ lib/rubygems/defaults/operating_system.rb
out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n"
out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n"
out << " - RUBYGEMS PLATFORMS:\n" out << " - RUBYGEMS PLATFORMS:\n"
Gem.platforms.each do |platform| Gem.platforms.each do |platform|
out << " - #{platform}\n" out << " - #{platform}\n"
@ -107,11 +109,9 @@ lib/rubygems/defaults/operating_system.rb
out << " - GEM PATHS:\n" out << " - GEM PATHS:\n"
out << " - #{Gem.dir}\n" out << " - #{Gem.dir}\n"
path = Gem.path.dup gem_path = Gem.path.dup
path.delete Gem.dir gem_path.delete Gem.dir
path.each do |p| add_path out, gem_path
out << " - #{p}\n"
end
out << " - GEM CONFIGURATION:\n" out << " - GEM CONFIGURATION:\n"
Gem.configuration.each do |name, value| Gem.configuration.each do |name, value|
@ -124,6 +124,11 @@ lib/rubygems/defaults/operating_system.rb
out << " - #{s}\n" out << " - #{s}\n"
end end
out << " - SHELL PATH:\n"
shell_path = ENV['PATH'].split(File::PATH_SEPARATOR)
add_path out, shell_path
else else
raise Gem::CommandLineError, "Unknown environment option [#{arg}]" raise Gem::CommandLineError, "Unknown environment option [#{arg}]"
end end
@ -131,5 +136,11 @@ lib/rubygems/defaults/operating_system.rb
true true
end end
def add_path out, path
path.each do |component|
out << " - #{component}\n"
end
end
end end

View file

@ -46,6 +46,10 @@ Some examples of 'gem' usage.
* Update all gems on your system: * Update all gems on your system:
gem update gem update
* Update your local version of RubyGems
gem update --system
EOF EOF
PLATFORMS = <<-'EOF' PLATFORMS = <<-'EOF'
@ -55,8 +59,9 @@ your current platform by running `gem environment`.
RubyGems matches platforms as follows: RubyGems matches platforms as follows:
* The CPU must match exactly, unless one of the platforms has * The CPU must match exactly unless one of the platforms has
"universal" as the CPU. "universal" as the CPU or the local CPU starts with "arm" and the gem's
CPU is exactly "arm" (for gems that support generic ARM architecture).
* The OS must match exactly. * The OS must match exactly.
* The versions must match exactly unless one of the versions is nil. * The versions must match exactly unless one of the versions is nil.
@ -66,11 +71,20 @@ you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin
platforms, the version is the compiler version, not the OS version. (Ruby platforms, the version is the compiler version, not the OS version. (Ruby
compiled with VC6 uses "60" as the compiler version, VC8 uses "80".) compiled with VC6 uses "60" as the compiler version, VC8 uses "80".)
For the ARM architecture, gems with a platform of "arm-linux" should run on a
reasonable set of ARM CPUs and not depend on instructions present on a limited
subset of the architecture. For example, the binary should run on platforms
armv5, armv6hf, armv6l, armv7, etc. If you use the "arm-linux" platform
please test your gem on a variety of ARM hardware before release to ensure it
functions correctly.
Example platforms: Example platforms:
x86-freebsd # Any FreeBSD version on an x86 CPU x86-freebsd # Any FreeBSD version on an x86 CPU
universal-darwin-8 # Darwin 8 only gems that run on any CPU universal-darwin-8 # Darwin 8 only gems that run on any CPU
x86-mswin32-80 # Windows gems compiled with VC8 x86-mswin32-80 # Windows gems compiled with VC8
armv7-linux # Gem complied for an ARMv7 CPU running linux
arm-linux # Gem compiled for any ARM CPU running linux
When building platform gems, set the platform in the gem specification to 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 Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
@ -119,7 +133,7 @@ platform.
if command then if command then
command.summary command.summary
else else
"[No command found for #{cmd_name}, bug?]" "[No command found for #{cmd_name}]"
end end
summary = wrap(summary, summary_width).split "\n" summary = wrap(summary, summary_width).split "\n"

View file

@ -4,8 +4,6 @@ require 'rubygems/dependency_installer'
require 'rubygems/local_remote_options' require 'rubygems/local_remote_options'
require 'rubygems/validator' require 'rubygems/validator'
require 'rubygems/version_option' require 'rubygems/version_option'
require 'rubygems/install_message' # must come before rdoc for messaging
require 'rubygems/rdoc'
## ##
# Gem installer command line tool # Gem installer command line tool
@ -39,6 +37,12 @@ class Gem::Commands::InstallCommand < Gem::Command
'install the listed gems') do |v,o| 'install the listed gems') do |v,o|
o[:gemdeps] = v o[:gemdeps] = v
end end
add_option(:"Install/Update", '--default',
'Add the gem\'s full specification to',
'specifications/default and extract only its bin') do |v,o|
o[:install_as_default] = v
end
@installed_specs = nil @installed_specs = nil
end end
@ -153,7 +157,14 @@ to write the specification by hand. For example:
alert_error "Can't use --version w/ multiple gems. Use name:ver instead." alert_error "Can't use --version w/ multiple gems. Use name:ver instead."
terminate_interaction 1 terminate_interaction 1
end end
# load post-install hooks appropriate to options
if options[:install_as_default]
require 'rubygems/install_default_message'
else
require 'rubygems/install_message'
end
require 'rubygems/rdoc'
get_all_gem_names_and_versions.each do |gem_name, gem_version| get_all_gem_names_and_versions.each do |gem_name, gem_version|
gem_version ||= options[:version] gem_version ||= options[:version]

View file

@ -31,9 +31,15 @@ class Gem::Commands::OwnerCommand < Gem::Command
add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options| add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options|
options[:remove] << value options[:remove] << value
end end
add_option '-h', '--host HOST', 'Use another gemcutter-compatible host' do |value, options|
options[:host] = value
end
end end
def execute def execute
@host = options[:host]
sign_in sign_in
name = get_one_gem_name name = get_one_gem_name

View file

@ -30,6 +30,12 @@ class Gem::Commands::PristineCommand < Gem::Command
options[:only_executables] = value options[:only_executables] = value
end end
add_option('-E', '--[no-]env-shebang',
'Rewrite executables with a shebang',
'of /usr/bin/env') do |value, options|
options[:env_shebang] = value
end
add_version_option('restore to', 'pristine condition') add_version_option('restore to', 'pristine condition')
end end
@ -104,16 +110,21 @@ with extensions.
Gem::RemoteFetcher.fetcher.download_to_cache dep Gem::RemoteFetcher.fetcher.download_to_cache dep
end end
# TODO use installer options env_shebang =
install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install'] if options.include? :env_shebang then
installer_env_shebang = install_defaults.to_s['--env-shebang'] options[:env_shebang]
else
install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install']
install_defaults.to_s['--env-shebang']
end
installer = Gem::Installer.new(gem, installer = Gem::Installer.new(gem,
:wrappers => true, :wrappers => true,
:force => true, :force => true,
:install_dir => spec.base_dir, :install_dir => spec.base_dir,
:env_shebang => installer_env_shebang, :env_shebang => env_shebang,
:build_args => spec.build_args) :build_args => spec.build_args)
if options[:only_executables] then if options[:only_executables] then
installer.generate_bin installer.generate_bin
else else

View file

@ -48,7 +48,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:update]) options[:update])
if options[:clear_all] then if options[:clear_all] then
path = File.join Gem.user_home, '.gem', 'specs' path = Gem.spec_cache_dir
FileUtils.rm_rf path FileUtils.rm_rf path
unless File.exist? path then unless File.exist? path then

View file

@ -67,6 +67,12 @@ class Gem::Commands::UninstallCommand < Gem::Command
options[:force] = value options[:force] = value
end end
add_option('--[no-]abort-on-dependent',
'Prevent uninstalling gems that are',
'depended on by other gems.') do |value, options|
options[:abort_on_dependent] = value
end
add_version_option add_version_option
add_platform_option add_platform_option
end end

View file

@ -140,6 +140,11 @@ class Gem::ConfigFile
attr_reader :ssl_ca_cert attr_reader :ssl_ca_cert
##
# Path name of directory or file of openssl client certificate, used for remote https connection with client authentication
attr_reader :ssl_client_cert
## ##
# Create the config file object. +args+ is the list of arguments # Create the config file object. +args+ is the list of arguments
# from the command line. # from the command line.
@ -210,6 +215,7 @@ class Gem::ConfigFile
@ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
@ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
@ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert
@api_keys = nil @api_keys = nil
@rubygems_api_key = nil @rubygems_api_key = nil
@ -246,6 +252,10 @@ Your gem push credentials file located at:
has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required. has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required.
To fix this error run:
\tchmod 0600 #{credentials_path}
You should reset your credentials at: You should reset your credentials at:
\thttps://rubygems.org/profile/edit \thttps://rubygems.org/profile/edit
@ -309,6 +319,9 @@ if you believe they were disclosed to a third party.
@rubygems_api_key = api_key @rubygems_api_key = api_key
end end
YAMLErrors = [ArgumentError]
YAMLErrors << Psych::SyntaxError if defined?(Psych::SyntaxError)
def load_file(filename) def load_file(filename)
Gem.load_yaml Gem.load_yaml
@ -321,8 +334,8 @@ if you believe they were disclosed to a third party.
return {} return {}
end end
return content return content
rescue ArgumentError rescue *YAMLErrors => e
warn "Failed to load #{filename}" warn "Failed to load #{filename}, #{e.to_s}"
rescue Errno::EACCES rescue Errno::EACCES
warn "Failed to load #{filename} due to permissions problem." warn "Failed to load #{filename} due to permissions problem."
end end

View file

@ -57,7 +57,7 @@ module Kernel
#-- #--
# TODO request access to the C implementation of this to speed up RubyGems # TODO request access to the C implementation of this to speed up RubyGems
spec = Gem::Specification.find { |s| spec = Gem::Specification.stubs.find { |s|
s.activated? and s.contains_requirable_file? path s.activated? and s.contains_requirable_file? path
} }

View file

@ -14,6 +14,14 @@ module Gem
%w[https://rubygems.org/] %w[https://rubygems.org/]
end end
##
# Default spec directory path to be used if an alternate value is not
# specified in the environment
def self.default_spec_cache_dir
File.join Gem.user_home, '.gem', 'specs'
end
## ##
# Default home directory path to be used if an alternate value is not # Default home directory path to be used if an alternate value is not
# specified in the environment # specified in the environment

View file

@ -203,6 +203,8 @@ class Gem::Dependency
requirement.satisfied_by? version requirement.satisfied_by? version
end end
alias === =~
# DOC: this method needs either documented or :nodoc'd # DOC: this method needs either documented or :nodoc'd
def match? obj, version=nil def match? obj, version=nil
@ -250,10 +252,10 @@ class Gem::Dependency
# DOC: this method needs either documented or :nodoc'd # DOC: this method needs either documented or :nodoc'd
def matching_specs platform_only = false def matching_specs platform_only = false
matches = Gem::Specification.find_all { |spec| matches = Gem::Specification.stubs.find_all { |spec|
self.name === spec.name and # TODO: == instead of === self.name === spec.name and # TODO: == instead of ===
requirement.satisfied_by? spec.version requirement.satisfied_by? spec.version
} }.map(&:to_spec)
if platform_only if platform_only
matches.reject! { |spec| matches.reject! { |spec|

View file

@ -1,11 +1,12 @@
require 'rubygems' require 'rubygems'
require 'rubygems/dependency_list' require 'rubygems/dependency_list'
require 'rubygems/dependency_resolver'
require 'rubygems/package' require 'rubygems/package'
require 'rubygems/installer' require 'rubygems/installer'
require 'rubygems/spec_fetcher' require 'rubygems/spec_fetcher'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
require 'rubygems/source_local' require 'rubygems/source/local'
require 'rubygems/source_specific_file' require 'rubygems/source/specific_file'
require 'rubygems/available_set' require 'rubygems/available_set'
## ##
@ -15,15 +16,7 @@ class Gem::DependencyInstaller
include Gem::UserInteraction include Gem::UserInteraction
attr_reader :gems_to_install DEFAULT_OPTIONS = { # :nodoc:
attr_reader :installed_gems
##
# Documentation types. For use by the Gem.done_installing hook
attr_reader :document
DEFAULT_OPTIONS = {
:env_shebang => false, :env_shebang => false,
:document => %w[ri], :document => %w[ri],
:domain => :both, # HACK dup :domain => :both, # HACK dup
@ -35,8 +28,30 @@ class Gem::DependencyInstaller
:wrappers => true, :wrappers => true,
:build_args => nil, :build_args => nil,
:build_docs_in_background => false, :build_docs_in_background => false,
:install_as_default => false
}.freeze }.freeze
##
# Documentation types. For use by the Gem.done_installing hook
attr_reader :document
##
# Errors from SpecFetcher while searching for remote specifications
attr_reader :errors
##
#--
# TODO remove, no longer used
attr_reader :gems_to_install # :nodoc:
##
# List of gems installed by #install in alphabetic order
attr_reader :installed_gems
## ##
# Creates a new installer instance. # Creates a new installer instance.
# #
@ -56,7 +71,8 @@ class Gem::DependencyInstaller
# :wrappers:: See Gem::Installer::new # :wrappers:: See Gem::Installer::new
# :build_args:: See Gem::Installer::new # :build_args:: See Gem::Installer::new
def initialize(options = {}) def initialize options = {}
@only_install_dir = !!options[:install_dir]
@install_dir = options[:install_dir] || Gem.dir @install_dir = options[:install_dir] || Gem.dir
if options[:install_dir] then if options[:install_dir] then
@ -82,6 +98,7 @@ class Gem::DependencyInstaller
@wrappers = options[:wrappers] @wrappers = options[:wrappers]
@build_args = options[:build_args] @build_args = options[:build_args]
@build_docs_in_background = options[:build_docs_in_background] @build_docs_in_background = options[:build_docs_in_background]
@install_as_default = options[:install_as_default]
# Indicates that we should not try to update any deps unless # Indicates that we should not try to update any deps unless
# we absolutely must. # we absolutely must.
@ -93,128 +110,14 @@ class Gem::DependencyInstaller
@cache_dir = options[:cache_dir] || @install_dir @cache_dir = options[:cache_dir] || @install_dir
# Set with any errors that SpecFetcher finds while search through
# gemspecs for a dep
@errors = nil @errors = nil
end end
attr_reader :errors
## ##
# Creates an AvailableSet to install from based on +dep_or_name+ and #--
# +version+ # TODO remove, no longer used
def available_set_for dep_or_name, version # :nodoc: def add_found_dependencies to_do, dependency_list # :nodoc:
if String === dep_or_name then
find_spec_by_name_and_version dep_or_name, version, @prerelease
else
dep = dep_or_name.dup
dep.prerelease = @prerelease
@available = find_gems_with_sources dep
end
@available.pick_best!
end
##
# Indicated, based on the requested domain, if local
# gems should be considered.
def consider_local?
@domain == :both or @domain == :local
end
##
# Indicated, based on the requested domain, if remote
# gems should be considered.
def consider_remote?
@domain == :both or @domain == :remote
end
##
# Returns a list of pairs of gemspecs and source_uris that match
# Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
# sources. Gems are sorted with newer gems preferred over older gems, and
# local gems preferred over remote gems.
def find_gems_with_sources(dep)
set = Gem::AvailableSet.new
if consider_local?
sl = Gem::Source::Local.new
if spec = sl.find_gem(dep.name)
if dep.matches_spec? spec
set.add spec, sl
end
end
end
if consider_remote?
begin
found, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
if @errors
@errors += errors
else
@errors = errors
end
set << found
rescue Gem::RemoteFetcher::FetchError => e
# FIX if there is a problem talking to the network, we either need to always tell
# the user (no really_verbose) or fail hard, not silently tell them that we just
# couldn't find their requested gem.
if Gem.configuration.really_verbose then
say "Error fetching remote data:\t\t#{e.message}"
say "Falling back to local-only install"
end
@domain = :local
end
end
set
end
##
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
def gather_dependencies
specs = @available.all_specs
# these gems were listed by the user, always install them
keep_names = specs.map { |spec| spec.full_name }
if @dev_shallow
@toplevel_specs = keep_names
end
dependency_list = Gem::DependencyList.new @development
dependency_list.add(*specs)
to_do = specs.dup
add_found_dependencies to_do, dependency_list unless @ignore_dependencies
# REFACTOR maybe abstract away using Gem::Specification.include? so
# that this isn't dependent only on the currently installed gems
dependency_list.specs.reject! { |spec|
not keep_names.include?(spec.full_name) and
Gem::Specification.include?(spec)
}
unless dependency_list.ok? or @ignore_dependencies or @force then
reason = dependency_list.why_not_ok?.map { |k,v|
"#{k} requires #{v.join(", ")}"
}.join("; ")
raise Gem::DependencyError, "Unable to resolve dependencies: #{reason}"
end
@gems_to_install = dependency_list.dependency_order.reverse
end
def add_found_dependencies to_do, dependency_list
seen = {} seen = {}
dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name } dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name }
@ -262,15 +165,92 @@ class Gem::DependencyInstaller
dependency_list.remove_specs_unsatisfied_by dependencies dependency_list.remove_specs_unsatisfied_by dependencies
end end
##
# Creates an AvailableSet to install from based on +dep_or_name+ and
# +version+
def available_set_for dep_or_name, version # :nodoc:
if String === dep_or_name then
find_spec_by_name_and_version dep_or_name, version, @prerelease
else
dep = dep_or_name.dup
dep.prerelease = @prerelease
@available = find_gems_with_sources dep
end
@available.pick_best!
end
##
# Indicated, based on the requested domain, if local
# gems should be considered.
def consider_local?
@domain == :both or @domain == :local
end
##
# Indicated, based on the requested domain, if remote
# gems should be considered.
def consider_remote?
@domain == :both or @domain == :remote
end
##
# Returns a list of pairs of gemspecs and source_uris that match
# Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
# sources. Gems are sorted with newer gems preferred over older gems, and
# local gems preferred over remote gems.
def find_gems_with_sources dep # :nodoc:
set = Gem::AvailableSet.new
if consider_local?
sl = Gem::Source::Local.new
if spec = sl.find_gem(dep.name)
if dep.matches_spec? spec
set.add spec, sl
end
end
end
if consider_remote?
begin
found, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
if @errors
@errors += errors
else
@errors = errors
end
set << found
rescue Gem::RemoteFetcher::FetchError => e
# FIX if there is a problem talking to the network, we either need to always tell
# the user (no really_verbose) or fail hard, not silently tell them that we just
# couldn't find their requested gem.
if Gem.configuration.really_verbose then
say "Error fetching remote data:\t\t#{e.message}"
say "Falling back to local-only install"
end
@domain = :local
end
end
set
end
## ##
# Finds a spec and the source_uri it came from for gem +gem_name+ and # Finds a spec and the source_uri it came from for gem +gem_name+ and
# +version+. Returns an Array of specs and sources required for # +version+. Returns an Array of specs and sources required for
# installation of the gem. # installation of the gem.
def find_spec_by_name_and_version(gem_name, def find_spec_by_name_and_version gem_name,
version = Gem::Requirement.default, version = Gem::Requirement.default,
prerelease = false) prerelease = false
set = Gem::AvailableSet.new set = Gem::AvailableSet.new
@ -303,6 +283,59 @@ class Gem::DependencyInstaller
@available = set @available = set
end end
##
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
#--
# TODO remove, no longer used
def gather_dependencies # :nodoc:
specs = @available.all_specs
# these gems were listed by the user, always install them
keep_names = specs.map { |spec| spec.full_name }
if @dev_shallow
@toplevel_specs = keep_names
end
dependency_list = Gem::DependencyList.new @development
dependency_list.add(*specs)
to_do = specs.dup
add_found_dependencies to_do, dependency_list unless @ignore_dependencies
# REFACTOR maybe abstract away using Gem::Specification.include? so
# that this isn't dependent only on the currently installed gems
dependency_list.specs.reject! { |spec|
not keep_names.include?(spec.full_name) and
Gem::Specification.include?(spec)
}
unless dependency_list.ok? or @ignore_dependencies or @force then
reason = dependency_list.why_not_ok?.map { |k,v|
"#{k} requires #{v.join(", ")}"
}.join("; ")
raise Gem::DependencyError, "Unable to resolve dependencies: #{reason}"
end
@gems_to_install = dependency_list.dependency_order.reverse
end
def in_background what # :nodoc:
fork_happened = false
if @build_docs_in_background and Process.respond_to?(:fork)
begin
Process.fork do
yield
end
fork_happened = true
say "#{what} in a background process."
rescue NotImplementedError
end
end
yield unless fork_happened
end
## ##
# Installs the gem +dep_or_name+ and all its dependencies. Returns an Array # Installs the gem +dep_or_name+ and all its dependencies. Returns an Array
# of installed gem specifications. # of installed gem specifications.
@ -318,61 +351,30 @@ class Gem::DependencyInstaller
# separately. # separately.
def install dep_or_name, version = Gem::Requirement.default def install dep_or_name, version = Gem::Requirement.default
available_set_for dep_or_name, version request_set = resolve_dependencies dep_or_name, version
@installed_gems = [] @installed_gems = []
gather_dependencies options = {
:bin_dir => @bin_dir,
:build_args => @build_args,
:env_shebang => @env_shebang,
:force => @force,
:format_executable => @format_executable,
:ignore_dependencies => @ignore_dependencies,
:security_policy => @security_policy,
:user_install => @user_install,
:wrappers => @wrappers,
:install_as_default => @install_as_default
}
options[:install_dir] = @install_dir if @only_install_dir
# REFACTOR is the last gem always the one that the user requested? request_set.install options do |_, installer|
# This code assumes that but is that actually validated by the code? @installed_gems << installer.spec if installer
last = @gems_to_install.size - 1
@gems_to_install.each_with_index do |spec, index|
# REFACTOR more current spec set hardcoding, should be abstracted?
next if Gem::Specification.include?(spec) and index != last
# TODO: make this sorta_verbose so other users can benefit from it
say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
source = @available.source_for spec
begin
# REFACTOR make the fetcher to use configurable
local_gem_path = source.download spec, @cache_dir
rescue Gem::RemoteFetcher::FetchError
# TODO I doubt all fetch errors are recoverable, we should at least
# report the errors probably.
next if @force
raise
end
if @development
if @dev_shallow
is_dev = @toplevel_specs.include? spec.full_name
else
is_dev = true
end
end
inst = Gem::Installer.new local_gem_path,
:bin_dir => @bin_dir,
:development => is_dev,
:env_shebang => @env_shebang,
:force => @force,
:format_executable => @format_executable,
:ignore_dependencies => @ignore_dependencies,
:install_dir => @install_dir,
:security_policy => @security_policy,
:user_install => @user_install,
:wrappers => @wrappers,
:build_args => @build_args
spec = inst.install
@installed_gems << spec
end end
@installed_gems.sort!
# Since this is currently only called for docs, we can be lazy and just say # Since this is currently only called for docs, we can be lazy and just say
# it's documentation. Ideally the hook adder could decide whether to be in # it's documentation. Ideally the hook adder could decide whether to be in
# the background or not, and what to call it. # the background or not, and what to call it.
@ -385,18 +387,34 @@ class Gem::DependencyInstaller
@installed_gems @installed_gems
end end
def in_background what def install_development_deps # :nodoc:
fork_happened = false if @development and @dev_shallow then
if @build_docs_in_background and Process.respond_to?(:fork) :shallow
begin elsif @development then
Process.fork do :all
yield else
end :none
fork_happened = true
say "#{what} in a background process."
rescue NotImplementedError
end
end end
yield unless fork_happened
end end
def resolve_dependencies dep_or_name, version # :nodoc:
as = available_set_for dep_or_name, version
request_set = as.to_request_set install_development_deps
request_set.soft_missing = @force
installer_set = Gem::DependencyResolver::InstallerSet.new @domain
installer_set.always_install.concat request_set.always_install
installer_set.ignore_installed = @only_install_dir
if @ignore_dependencies then
installer_set.ignore_dependencies = true
request_set.soft_missing = true
end
request_set.resolve Gem::DependencyResolver.compose_sets(as, installer_set)
request_set
end
end end

View file

@ -1,575 +1,240 @@
require 'rubygems' require 'rubygems'
require 'rubygems/dependency' require 'rubygems/dependency'
require 'rubygems/exceptions' require 'rubygems/exceptions'
require 'rubygems/util/list'
require 'uri' require 'uri'
require 'net/http' require 'net/http'
module Gem ##
# Given a set of Gem::Dependency objects as +needed+ and a way to query the
# set of available specs via +set+, calculates a set of ActivationRequest
# objects which indicate all the specs that should be activated to meet the
# all the requirements.
# Raised when a DependencyConflict reaches the toplevel. class Gem::DependencyResolver
# Indicates which dependencies were incompatible.
#
class DependencyResolutionError < Gem::Exception
def initialize(conflict)
@conflict = conflict
a, b = conflicting_dependencies
super "unable to resolve conflicting dependencies '#{a}' and '#{b}'" ##
end # Contains all the conflicts encountered while doing resolution
attr_reader :conflict attr_reader :conflicts
def conflicting_dependencies attr_accessor :development
@conflict.conflicting_dependencies
end attr_reader :missing
##
# When a missing dependency, don't stop. Just go on and record what was
# missing.
attr_accessor :soft_missing
def self.compose_sets *sets
Gem::DependencyResolver::ComposedSet.new(*sets)
end end
# Raised when a dependency requests a gem for which there is ##
# no spec. # Provide a DependencyResolver that queries only against the already
# # installed gems.
class UnsatisfiableDepedencyError < Gem::Exception
def initialize(dep)
super "unable to find any gem matching dependency '#{dep}'"
@dependency = dep def self.for_current_gems needed
end new needed, Gem::DependencyResolver::CurrentSet.new
attr_reader :dependency
end end
# Raised when dependencies conflict and create the inability to ##
# find a valid possible spec for a request. # Create DependencyResolver object which will resolve the tree starting
# with +needed+ Depedency objects.
# #
class ImpossibleDependenciesError < Gem::Exception # +set+ is an object that provides where to look for specifications to
def initialize(request, conflicts) # satisify the Dependencies. This defaults to IndexSet, which will query
s = conflicts.size == 1 ? "" : "s" # rubygems.org.
super "detected #{conflicts.size} conflict#{s} with dependency '#{request.dependency}'"
@request = request
@conflicts = conflicts
end
def dependency def initialize needed, set = nil
@request.dependency @set = set || Gem::DependencyResolver::IndexSet.new
end @needed = needed
attr_reader :conflicts @conflicts = nil
@development = false
@missing = []
@soft_missing = false
end end
# Given a set of Gem::Dependency objects as +needed+ and a way def requests s, act, reqs=nil
# to query the set of available specs via +set+, calculates s.dependencies.reverse_each do |d|
# a set of ActivationRequest objects which indicate all the specs next if d.type == :development and not @development
# that should be activated to meet the all the requirements. reqs = Gem::List.new Gem::DependencyResolver::DependencyRequest.new(d, act), reqs
#
class DependencyResolver
# Represents a specification retrieved via the rubygems.org
# API. This is used to avoid having to load the full
# Specification object when all we need is the name, version,
# and dependencies.
#
class APISpecification
attr_reader :set # :nodoc:
def initialize(set, api_data)
@set = set
@name = api_data[:name]
@version = Gem::Version.new api_data[:number]
@dependencies = api_data[:dependencies].map do |name, ver|
Gem::Dependency.new name, ver.split(/\s*,\s*/)
end
end
attr_reader :name, :version, :dependencies
def == other # :nodoc:
self.class === other and
@set == other.set and
@name == other.name and
@version == other.version and
@dependencies == other.dependencies
end
def full_name
"#{@name}-#{@version}"
end
end end
# The global rubygems pool, available via the rubygems.org API. @set.prefetch reqs
# Returns instances of APISpecification.
#
class APISet
def initialize
@data = Hash.new { |h,k| h[k] = [] }
@dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
end
# Return data for all versions of the gem +name+. reqs
# end
def versions(name)
if @data.key?(name)
return @data[name]
end
uri = @dep_uri + "?gems=#{name}" ##
str = Gem::RemoteFetcher.fetcher.fetch_path uri # Proceed with resolution! Returns an array of ActivationRequest objects.
Marshal.load(str).each do |ver| def resolve
@data[ver[:name]] << ver @conflicts = []
end
@data[name] needed = nil
end
# Return an array of APISpecification objects matching @needed.reverse_each do |n|
# DependencyRequest +req+. needed = Gem::List.new(Gem::DependencyResolver::DependencyRequest.new(n, nil), needed)
#
def find_all(req)
res = []
versions(req.name).each do |ver|
if req.dependency.match? req.name, ver[:number]
res << APISpecification.new(self, ver)
end
end
res
end
# A hint run by the resolver to allow the Set to fetch
# data for DependencyRequests +reqs+.
#
def prefetch(reqs)
names = reqs.map { |r| r.dependency.name }
needed = names.find_all { |d| !@data.key?(d) }
return if needed.empty?
uri = @dep_uri + "?gems=#{needed.sort.join ','}"
str = Gem::RemoteFetcher.fetcher.fetch_path uri
Marshal.load(str).each do |ver|
@data[ver[:name]] << ver
end
end
end end
# Represents a possible Specification object returned res = resolve_for needed, nil
# from IndexSet. Used to delay needed to download full
# Specification objects when only the +name+ and +version+
# are needed.
#
class IndexSpecification
def initialize(set, name, version, source, plat)
@set = set
@name = name
@version = version
@source = source
@platform = plat
@spec = nil raise Gem::DependencyResolutionError, res if
end res.kind_of? Gem::DependencyResolver::DependencyConflict
attr_reader :name, :version, :source res.to_a
end
def full_name ##
"#{@name}-#{@version}" # The meat of the algorithm. Given +needed+ DependencyRequest objects and
end # +specs+ being a list to ActivationRequest, calculate a new list of
# ActivationRequest objects.
def spec def resolve_for needed, specs
@spec ||= @set.load_spec(@name, @version, @source) while needed
end dep = needed.value
needed = needed.tail
def dependencies # If there is already a spec activated for the requested name...
spec.dependencies if specs && existing = specs.find { |s| dep.name == s.name }
end
end
# The global rubygems pool represented via the traditional # then we're done since this new dep matches the
# source index. # existing spec.
# next if dep.matches_spec? existing
class IndexSet
def initialize
@f = Gem::SpecFetcher.fetcher
@all = Hash.new { |h,k| h[k] = [] } # There is a conflict! We return the conflict
# object which will be seen by the caller and be
# handled at the right level.
list, _ = @f.available_specs(:released) # If the existing activation indicates that there
list.each do |uri, specs| # are other possibles for it, then issue the conflict
specs.each do |n| # on the dep for the activation itself. Otherwise, issue
@all[n.name] << [uri, n] # it on the requester's request itself.
end #
end if existing.others_possible?
conflict =
@specs = {} Gem::DependencyResolver::DependencyConflict.new dep, existing
end
# Return an array of IndexSpecification objects matching
# DependencyRequest +req+.
#
def find_all(req)
res = []
name = req.dependency.name
@all[name].each do |uri, n|
if req.dependency.match? n
res << IndexSpecification.new(self, n.name, n.version,
uri, n.platform)
end
end
res
end
# No prefetching needed since we load the whole index in
# initially.
#
def prefetch(gems)
end
# Called from IndexSpecification to get a true Specification
# object.
#
def load_spec(name, ver, source)
key = "#{name}-#{ver}"
@specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver))
end
end
# A set which represents the installed gems. Respects
# all the normal settings that control where to look
# for installed gems.
#
class CurrentSet
def find_all(req)
req.dependency.matching_specs
end
def prefetch(gems)
end
end
# Create DependencyResolver object which will resolve
# the tree starting with +needed+ Depedency objects.
#
# +set+ is an object that provides where to look for
# specifications to satisify the Dependencies. This
# defaults to IndexSet, which will query rubygems.org.
#
def initialize(needed, set=IndexSet.new)
@set = set || IndexSet.new # Allow nil to mean IndexSet
@needed = needed
@conflicts = nil
end
# Provide a DependencyResolver that queries only against
# the already installed gems.
#
def self.for_current_gems(needed)
new needed, CurrentSet.new
end
# Contains all the conflicts encountered while doing resolution
#
attr_reader :conflicts
# Proceed with resolution! Returns an array of ActivationRequest
# objects.
#
def resolve
@conflicts = []
needed = @needed.map { |n| DependencyRequest.new(n, nil) }
res = resolve_for needed, []
if res.kind_of? DependencyConflict
raise DependencyResolutionError.new(res)
end
res
end
# Used internally to indicate that a dependency conflicted
# with a spec that would be activated.
#
class DependencyConflict
def initialize(dependency, activated, failed_dep=dependency)
@dependency = dependency
@activated = activated
@failed_dep = failed_dep
end
attr_reader :dependency, :activated
# Return the Specification that listed the dependency
#
def requester
@failed_dep.requester
end
def for_spec?(spec)
@dependency.name == spec.name
end
# Return the 2 dependency objects that conflicted
#
def conflicting_dependencies
[@failed_dep.dependency, @activated.request.dependency]
end
end
# Used Internally. Wraps a Depedency object to also track
# which spec contained the Dependency.
#
class DependencyRequest
def initialize(dep, act)
@dependency = dep
@requester = act
end
attr_reader :dependency, :requester
def name
@dependency.name
end
def matches_spec?(spec)
@dependency.matches_spec? spec
end
def to_s
@dependency.to_s
end
def ==(other)
case other
when Dependency
@dependency == other
when DependencyRequest
@dependency == other.dependency && @requester == other.requester
else else
false depreq = existing.request.requester.request
conflict =
Gem::DependencyResolver::DependencyConflict.new depreq, existing, dep
end end
end @conflicts << conflict
end
# Specifies a Specification object that should be activated. return conflict
# Also contains a dependency that was used to introduce this
# activation.
#
class ActivationRequest
def initialize(spec, req, others_possible=true)
@spec = spec
@request = req
@others_possible = others_possible
end end
attr_reader :spec, :request # Get a list of all specs that satisfy dep
possible = @set.find_all dep
# Indicate if this activation is one of a set of possible case possible.size
# requests for the same Dependency request. when 0
# @missing << dep
def others_possible?
@others_possible
end
# Return the ActivationRequest that contained the dependency unless @soft_missing
# that we were activated for.
#
def parent
@request.requester
end
def name
@spec.name
end
def full_name
@spec.full_name
end
def version
@spec.version
end
def full_spec
Gem::Specification === @spec ? @spec : @spec.spec
end
def download(path)
if @spec.respond_to? :source
source = @spec.source
else
source = Gem.sources.first
end
Gem.ensure_gem_subdirectories path
source.download full_spec, path
end
def ==(other)
case other
when Gem::Specification
@spec == other
when ActivationRequest
@spec == other.spec && @request == other.request
else
false
end
end
##
# Indicates if the requested gem has already been installed.
def installed?
this_spec = full_spec
Gem::Specification.any? do |s|
s == this_spec
end
end
end
def requests(s, act)
reqs = []
s.dependencies.each do |d|
next unless d.type == :runtime
reqs << DependencyRequest.new(d, act)
end
@set.prefetch(reqs)
reqs
end
# The meat of the algorithm. Given +needed+ DependencyRequest objects
# and +specs+ being a list to ActivationRequest, calculate a new list
# of ActivationRequest objects.
#
def resolve_for(needed, specs)
until needed.empty?
dep = needed.shift
# If there is already a spec activated for the requested name...
if existing = specs.find { |s| dep.name == s.name }
# then we're done since this new dep matches the
# existing spec.
next if dep.matches_spec? existing
# There is a conflict! We return the conflict
# object which will be seen by the caller and be
# handled at the right level.
# If the existing activation indicates that there
# are other possibles for it, then issue the conflict
# on the dep for the activation itself. Otherwise, issue
# it on the requester's request itself.
#
if existing.others_possible?
conflict = DependencyConflict.new(dep, existing)
else
depreq = existing.request.requester.request
conflict = DependencyConflict.new(depreq, existing, dep)
end
@conflicts << conflict
return conflict
end
# Get a list of all specs that satisfy dep
possible = @set.find_all(dep)
case possible.size
when 0
# If there are none, then our work here is done. # If there are none, then our work here is done.
raise UnsatisfiableDepedencyError.new(dep) raise Gem::UnsatisfiableDependencyError, dep
when 1
# If there is one, then we just add it to specs
# and process the specs dependencies by adding
# them to needed.
spec = possible.first
act = ActivationRequest.new(spec, dep, false)
specs << act
# Put the deps for at the beginning of needed
# rather than the end to match the depth first
# searching done by the multiple case code below.
#
# This keeps the error messages consistent.
needed = requests(spec, act) + needed
else
# There are multiple specs for this dep. This is
# the case that this class is built to handle.
# Sort them so that we try the highest versions
# first.
possible = possible.sort_by { |s| s.version }
# We track the conflicts seen so that we can report them
# to help the user figure out how to fix the situation.
conflicts = []
# To figure out which to pick, we keep resolving
# given each one being activated and if there isn't
# a conflict, we know we've found a full set.
#
# We use an until loop rather than #reverse_each
# to keep the stack short since we're using a recursive
# algorithm.
#
until possible.empty?
s = possible.pop
# Recursively call #resolve_for with this spec
# and add it's dependencies into the picture...
act = ActivationRequest.new(s, dep)
try = requests(s, act) + needed
res = resolve_for(try, specs + [act])
# While trying to resolve these dependencies, there may
# be a conflict!
if res.kind_of? DependencyConflict
# The conflict might be created not by this invocation
# but rather one up the stack, so if we can't attempt
# to resolve this conflict (conflict isn't with the spec +s+)
# then just return it so the caller can try to sort it out.
return res unless res.for_spec? s
# Otherwise, this is a conflict that we can attempt to fix
conflicts << [s, res]
# Optimization:
#
# Because the conflict indicates the dependency that trigger
# it, we can prune possible based on this new information.
#
# This cuts down on the number of iterations needed.
possible.delete_if { |x| !res.dependency.matches_spec? x }
else
# No conflict, return the specs
return res
end
end
# We tried all possibles and nothing worked, so we let the user
# know and include as much information about the problem since
# the user is going to have to take action to fix this.
raise ImpossibleDependenciesError.new(dep, conflicts)
end end
end when 1
# If there is one, then we just add it to specs
# and process the specs dependencies by adding
# them to needed.
specs spec = possible.first
act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false
specs = Gem::List.prepend specs, act
# Put the deps for at the beginning of needed
# rather than the end to match the depth first
# searching done by the multiple case code below.
#
# This keeps the error messages consistent.
needed = requests(spec, act, needed)
else
# There are multiple specs for this dep. This is
# the case that this class is built to handle.
# Sort them so that we try the highest versions
# first.
possible = possible.sort_by { |s| [s.source, s.version] }
# We track the conflicts seen so that we can report them
# to help the user figure out how to fix the situation.
conflicts = []
# To figure out which to pick, we keep resolving
# given each one being activated and if there isn't
# a conflict, we know we've found a full set.
#
# We use an until loop rather than #reverse_each
# to keep the stack short since we're using a recursive
# algorithm.
#
until possible.empty?
s = possible.pop
# Recursively call #resolve_for with this spec
# and add it's dependencies into the picture...
act = Gem::DependencyResolver::ActivationRequest.new s, dep
try = requests(s, act, needed)
res = resolve_for try, Gem::List.prepend(specs, act)
# While trying to resolve these dependencies, there may
# be a conflict!
if res.kind_of? Gem::DependencyResolver::DependencyConflict
# The conflict might be created not by this invocation
# but rather one up the stack, so if we can't attempt
# to resolve this conflict (conflict isn't with the spec +s+)
# then just return it so the caller can try to sort it out.
return res unless res.for_spec? s
# Otherwise, this is a conflict that we can attempt to fix
conflicts << [s, res]
# Optimization:
#
# Because the conflict indicates the dependency that trigger
# it, we can prune possible based on this new information.
#
# This cuts down on the number of iterations needed.
possible.delete_if { |x| !res.dependency.matches_spec? x }
else
# No conflict, return the specs
return res
end
end
# We tried all possibles and nothing worked, so we let the user
# know and include as much information about the problem since
# the user is going to have to take action to fix this.
raise Gem::ImpossibleDependenciesError.new(dep, conflicts)
end
end end
specs
end end
end end
require 'rubygems/dependency_resolver/api_set'
require 'rubygems/dependency_resolver/api_specification'
require 'rubygems/dependency_resolver/activation_request'
require 'rubygems/dependency_resolver/composed_set'
require 'rubygems/dependency_resolver/current_set'
require 'rubygems/dependency_resolver/dependency_conflict'
require 'rubygems/dependency_resolver/dependency_request'
require 'rubygems/dependency_resolver/index_set'
require 'rubygems/dependency_resolver/index_specification'
require 'rubygems/dependency_resolver/installed_specification'
require 'rubygems/dependency_resolver/installer_set'

View file

@ -0,0 +1,109 @@
##
# Specifies a Specification object that should be activated.
# Also contains a dependency that was used to introduce this
# activation.
class Gem::DependencyResolver::ActivationRequest
attr_reader :request
attr_reader :spec
def initialize spec, req, others_possible = true
@spec = spec
@request = req
@others_possible = others_possible
end
def == other
case other
when Gem::Specification
@spec == other
when Gem::DependencyResolver::ActivationRequest
@spec == other.spec && @request == other.request
else
false
end
end
def download path
if @spec.respond_to? :source
source = @spec.source
else
source = Gem.sources.first
end
Gem.ensure_gem_subdirectories path
source.download full_spec, path
end
def full_name
@spec.full_name
end
def full_spec
Gem::Specification === @spec ? @spec : @spec.spec
end
def inspect # :nodoc:
others_possible = nil
others_possible = ' (others possible)' if @others_possible
'#<%s for %p from %s%s>' % [
self.class, @spec, @request, others_possible
]
end
##
# Indicates if the requested gem has already been installed.
def installed?
this_spec = full_spec
Gem::Specification.any? do |s|
s == this_spec
end
end
def name
@spec.name
end
##
# Indicate if this activation is one of a set of possible
# requests for the same Dependency request.
def others_possible?
@others_possible
end
##
# Return the ActivationRequest that contained the dependency
# that we were activated for.
def parent
@request.requester
end
def pretty_print q # :nodoc:
q.group 2, '[Activation request', ']' do
q.breakable
q.pp @spec
q.breakable
q.text ' for '
q.pp @request
q.breakable
q.text ' (other possible)' if @others_possible
end
end
def version
@spec.version
end
end

View file

@ -0,0 +1,65 @@
##
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
class Gem::DependencyResolver::APISet
def initialize
@data = Hash.new { |h,k| h[k] = [] }
@dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
end
##
# Return an array of APISpecification objects matching
# DependencyRequest +req+.
def find_all req
res = []
versions(req.name).each do |ver|
if req.dependency.match? req.name, ver[:number]
res << Gem::DependencyResolver::APISpecification.new(self, ver)
end
end
res
end
##
# A hint run by the resolver to allow the Set to fetch
# data for DependencyRequests +reqs+.
def prefetch reqs
names = reqs.map { |r| r.dependency.name }
needed = names.find_all { |d| !@data.key?(d) }
return if needed.empty?
uri = @dep_uri + "?gems=#{needed.sort.join ','}"
str = Gem::RemoteFetcher.fetcher.fetch_path uri
Marshal.load(str).each do |ver|
@data[ver[:name]] << ver
end
end
##
# Return data for all versions of the gem +name+.
def versions name
if @data.key?(name)
return @data[name]
end
uri = @dep_uri + "?gems=#{name}"
str = Gem::RemoteFetcher.fetcher.fetch_path uri
Marshal.load(str).each do |ver|
@data[ver[:name]] << ver
end
@data[name]
end
end

View file

@ -0,0 +1,36 @@
##
# Represents a specification retrieved via the rubygems.org
# API. This is used to avoid having to load the full
# Specification object when all we need is the name, version,
# and dependencies.
class Gem::DependencyResolver::APISpecification
attr_reader :dependencies
attr_reader :name
attr_reader :set # :nodoc:
attr_reader :version
def initialize(set, api_data)
@set = set
@name = api_data[:name]
@version = Gem::Version.new api_data[:number]
@dependencies = api_data[:dependencies].map do |name, ver|
Gem::Dependency.new name, ver.split(/\s*,\s*/)
end
end
def == other # :nodoc:
self.class === other and
@set == other.set and
@name == other.name and
@version == other.version and
@dependencies == other.dependencies
end
def full_name
"#{@name}-#{@version}"
end
end

View file

@ -0,0 +1,18 @@
class Gem::DependencyResolver::ComposedSet
def initialize *sets
@sets = sets
end
def find_all req
res = []
@sets.each { |s| res += s.find_all(req) }
res
end
def prefetch reqs
@sets.each { |s| s.prefetch(reqs) }
end
end

View file

@ -0,0 +1,16 @@
##
# A set which represents the installed gems. Respects
# all the normal settings that control where to look
# for installed gems.
class Gem::DependencyResolver::CurrentSet
def find_all req
req.dependency.matching_specs
end
def prefetch gems
end
end

View file

@ -0,0 +1,85 @@
##
# Used internally to indicate that a dependency conflicted
# with a spec that would be activated.
class Gem::DependencyResolver::DependencyConflict
attr_reader :activated
attr_reader :dependency
def initialize(dependency, activated, failed_dep=dependency)
@dependency = dependency
@activated = activated
@failed_dep = failed_dep
end
##
# Return the 2 dependency objects that conflicted
def conflicting_dependencies
[@failed_dep.dependency, @activated.request.dependency]
end
##
# Explanation of the conflict used by exceptions to print useful messages
def explanation
activated = @activated.spec.full_name
requirement = @failed_dep.dependency.requirement
" Activated %s instead of (%s) via:\n %s\n" % [
activated, requirement, request_path.join(', ')
]
end
def for_spec?(spec)
@dependency.name == spec.name
end
def pretty_print q # :nodoc:
q.group 2, '[Dependency conflict: ', ']' do
q.breakable
q.text 'activated '
q.pp @activated
q.breakable
q.text ' dependency '
q.pp @dependency
q.breakable
if @dependency == @failed_dep then
q.text ' failed'
else
q.text ' failed dependency '
q.pp @failed_dep
end
end
end
##
# Path of specifications that requested this dependency
def request_path
current = requester
path = []
while current do
path << current.spec.full_name
current = current.request.requester
end
path
end
##
# Return the Specification that listed the dependency
def requester
@failed_dep.requester
end
end

View file

@ -0,0 +1,51 @@
##
# Used Internally. Wraps a Dependency object to also track which spec
# contained the Dependency.
class Gem::DependencyResolver::DependencyRequest
attr_reader :dependency
attr_reader :requester
def initialize(dep, act)
@dependency = dep
@requester = act
end
def ==(other)
case other
when Gem::Dependency
@dependency == other
when Gem::DependencyResolver::DependencyRequest
@dependency == other.dependency && @requester == other.requester
else
false
end
end
def matches_spec?(spec)
@dependency.matches_spec? spec
end
def name
@dependency.name
end
def pretty_print q # :nodoc:
q.group 2, '[Dependency request ', ']' do
q.breakable
q.text @dependency.to_s
q.breakable
q.text ' requested by '
q.pp @requester
end
end
def to_s # :nodoc:
@dependency.to_s
end
end

View file

@ -0,0 +1,59 @@
##
# The global rubygems pool represented via the traditional
# source index.
class Gem::DependencyResolver::IndexSet
def initialize
@f = Gem::SpecFetcher.fetcher
@all = Hash.new { |h,k| h[k] = [] }
list, = @f.available_specs :released
list.each do |uri, specs|
specs.each do |n|
@all[n.name] << [uri, n]
end
end
@specs = {}
end
##
# Return an array of IndexSpecification objects matching
# DependencyRequest +req+.
def find_all req
res = []
name = req.dependency.name
@all[name].each do |uri, n|
if req.dependency.match? n
res << Gem::DependencyResolver::IndexSpecification.new(
self, n.name, n.version, uri, n.platform)
end
end
res
end
##
# Called from IndexSpecification to get a true Specification
# object.
def load_spec name, ver, source
key = "#{name}-#{ver}"
@specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver))
end
##
# No prefetching needed since we load the whole index in
# initially.
def prefetch gems
end
end

View file

@ -0,0 +1,53 @@
##
# Represents a possible Specification object returned
# from IndexSet. Used to delay needed to download full
# Specification objects when only the +name+ and +version+
# are needed.
class Gem::DependencyResolver::IndexSpecification
attr_reader :name
attr_reader :source
attr_reader :version
def initialize set, name, version, source, plat
@set = set
@name = name
@version = version
@source = source
@platform = plat
@spec = nil
end
def dependencies
spec.dependencies
end
def full_name
"#{@name}-#{@version}"
end
def inspect # :nodoc:
'#<%s %s source %s>' % [self.class, full_name, @source]
end
def pretty_print q # :nodoc:
q.group 2, '[Index specification', ']' do
q.breakable
q.text full_name
q.breakable
q.text ' source '
q.pp @source
end
end
def spec
@spec ||= @set.load_spec(@name, @version, @source)
end
end

View file

@ -0,0 +1,38 @@
class Gem::DependencyResolver::InstalledSpecification
attr_reader :spec
def initialize set, spec, source=nil
@set = set
@source = source
@spec = spec
end
def == other # :nodoc:
self.class === other and
@set == other.set and
@spec == other.spec
end
def dependencies
@spec.dependencies
end
def full_name
"#{@spec.name}-#{@spec.version}"
end
def name
@spec.name
end
def source
@source ||= Gem::Source::Installed.new
end
def version
@spec.version
end
end

View file

@ -0,0 +1,130 @@
class Gem::DependencyResolver::InstallerSet
##
# List of Gem::Specification objects that must always be installed.
attr_reader :always_install
##
# Only install gems in the always_install list
attr_accessor :ignore_dependencies
##
# Do not look in the installed set when finding specifications. This is
# used by the --install-dir option to `gem install`
attr_accessor :ignore_installed
def initialize domain
@domain = domain
@f = Gem::SpecFetcher.fetcher
@all = Hash.new { |h,k| h[k] = [] }
@always_install = []
@ignore_dependencies = false
@ignore_installed = false
@loaded_remote_specs = []
@specs = {}
end
##
# Should local gems should be considered?
def consider_local?
@domain == :both or @domain == :local
end
##
# Should remote gems should be considered?
def consider_remote?
@domain == :both or @domain == :remote
end
##
# Returns an array of IndexSpecification objects matching DependencyRequest
# +req+.
def find_all req
res = []
dep = req.dependency
return res if @ignore_dependencies and
@always_install.none? { |spec| dep.matches_spec? spec }
name = dep.name
dep.matching_specs.each do |gemspec|
next if @always_install.include? gemspec
res << Gem::DependencyResolver::InstalledSpecification.new(self, gemspec)
end unless @ignore_installed
if consider_local? then
local_source = Gem::Source::Local.new
if spec = local_source.find_gem(name, dep.requirement) then
res << Gem::DependencyResolver::IndexSpecification.new(
self, spec.name, spec.version, local_source, spec.platform)
end
end
if consider_remote? then
load_remote_specs dep
@all[name].each do |remote_source, n|
if dep.match? n then
res << Gem::DependencyResolver::IndexSpecification.new(
self, n.name, n.version, remote_source, n.platform)
end
end
end
res
end
def inspect # :nodoc:
'#<%s domain: %s specs: %p>' % [ self.class, @domain, @specs.keys ]
end
##
# Loads remote prerelease specs if +dep+ is a prerelease dependency
def load_remote_specs dep
types = [:released]
types << :prerelease if dep.prerelease?
types.each do |type|
next if @loaded_remote_specs.include? type
@loaded_remote_specs << type
list, = @f.available_specs type
list.each do |uri, specs|
specs.each do |n|
@all[n.name] << [uri, n]
end
end
end
end
##
# Called from IndexSpecification to get a true Specification
# object.
def load_spec name, ver, source
key = "#{name}-#{ver}"
@specs[key] ||= source.fetch_spec Gem::NameTuple.new name, ver
end
##
# No prefetching needed since we load the whole index in initially.
def prefetch(reqs)
end
end

View file

@ -16,6 +16,28 @@ class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end class Gem::DependencyRemovalException < Gem::Exception; end
##
# Raised by Gem::DependencyResolver when a Gem::DependencyConflict reaches the
# toplevel. Indicates which dependencies were incompatible through #conflict
# and #conflicting_dependencies
class Gem::DependencyResolutionError < Gem::Exception
attr_reader :conflict
def initialize conflict
@conflict = conflict
a, b = conflicting_dependencies
super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
end
def conflicting_dependencies
@conflict.conflicting_dependencies
end
end
## ##
# Raised when attempting to uninstall a gem that isn't in GEM_HOME. # Raised when attempting to uninstall a gem that isn't in GEM_HOME.
@ -65,6 +87,42 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException
attr_reader :name, :version, :errors attr_reader :name, :version, :errors
end end
##
# Raised by Gem::DependencyResolver when dependencies conflict and create the
# inability to find a valid possible spec for a request.
class Gem::ImpossibleDependenciesError < Gem::Exception
attr_reader :conflicts
attr_reader :request
def initialize request, conflicts
@request = request
@conflicts = conflicts
super build_message
end
def build_message # :nodoc:
requester = @request.requester
requester = requester ? requester.spec.full_name : 'The user'
dependency = @request.dependency
message = "#{requester} requires #{dependency} but it conflicted:\n"
@conflicts.each do |_, conflict|
message << conflict.explanation
end
message
end
def dependency
@request.dependency
end
end
class Gem::InstallError < Gem::Exception; end class Gem::InstallError < Gem::Exception; end
## ##
@ -107,3 +165,26 @@ class Gem::SystemExitException < SystemExit
end end
##
# Raised by DependencyResolver when a dependency requests a gem for which
# there is no spec.
class Gem::UnsatisfiableDependencyError < Gem::Exception
attr_reader :dependency
def initialize dep
requester = dep.requester ? dep.requester.request : '(unknown)'
super "Unable to resolve dependency: #{requester} requires #{dep}"
@dependency = dep
end
end
##
# Backwards compatible typo'd exception class for early RubyGems 2.0.x
Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc:

View file

@ -18,7 +18,7 @@ class Gem::Ext::Builder
# try to find make program from Ruby configure arguments first # try to find make program from Ruby configure arguments first
RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/ RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
make_program = $1 || ENV['MAKE'] || ENV['make'] make_program = ENV['MAKE'] || ENV['make'] || $1
unless make_program then unless make_program then
make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end end

View file

@ -33,17 +33,11 @@ class Gem::GemRunner
## ##
# Run the gem command with the following arguments. # Run the gem command with the following arguments.
def run(args) def run args
if args.include?('--') build_args = extract_build_args args
# We need to preserve the original ARGV to use for passing gem options
# to source gems. If there is a -- in the line, strip all options after
# it...its for the source building process.
# TODO use slice!
build_args = args[args.index("--") + 1...args.length]
args = args[0...args.index("--")]
end
do_configuration args do_configuration args
cmd = @command_manager_class.instance cmd = @command_manager_class.instance
cmd.command_names.each do |command_name| cmd.command_names.each do |command_name|
@ -60,6 +54,20 @@ class Gem::GemRunner
cmd.run Gem.configuration.args, build_args cmd.run Gem.configuration.args, build_args
end end
##
# Separates the build arguments (those following <code>--</code>) from the
# other arguments in the list.
def extract_build_args args # :nodoc:
return [] unless offset = args.index('--')
build_args = args.slice!(offset...args.length)
build_args.shift
build_args
end
private private
def do_configuration(args) def do_configuration(args)

View file

@ -1,11 +1,17 @@
require 'rubygems/remote_fetcher' require 'rubygems/remote_fetcher'
##
# Utility methods for using the RubyGems API.
module Gem::GemcutterUtilities module Gem::GemcutterUtilities
# TODO: move to Gem::Command # TODO: move to Gem::Command
OptionParser.accept Symbol do |value| OptionParser.accept Symbol do |value|
value.to_sym value.to_sym
end end
attr_writer :host
## ##
# Add the --key option # Add the --key option
@ -17,6 +23,9 @@ module Gem::GemcutterUtilities
end end
end end
##
# The API key from the command options or from the user's configuration.
def api_key def api_key
if options[:key] then if options[:key] then
verify_api_key options[:key] verify_api_key options[:key]
@ -27,6 +36,47 @@ module Gem::GemcutterUtilities
end end
end end
##
# The host to connect to either from the RUBYGEMS_HOST environment variable
# or from the user's configuration
def host
configured_host = Gem.host unless
Gem.configuration.disable_default_gem_server
@host ||=
begin
env_rubygems_host = ENV['RUBYGEMS_HOST']
env_rubygems_host = nil if
env_rubygems_host and env_rubygems_host.empty?
env_rubygems_host|| configured_host
end
end
##
# Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+.
def rubygems_api_request(method, path, host = nil, &block)
require 'net/http'
self.host = host if host
unless self.host
alert_error "You must specify a gem server"
terminate_interaction 1 # TODO: question this
end
uri = URI.parse "#{self.host}/#{path}"
request_method = Net::HTTP.const_get method.to_s.capitalize
Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
end
##
# Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API
# key.
def sign_in sign_in_host = self.host def sign_in sign_in_host = self.host
return if Gem.configuration.rubygems_api_key return if Gem.configuration.rubygems_api_key
@ -55,53 +105,9 @@ module Gem::GemcutterUtilities
end end
end end
attr_writer :host ##
def host # Retrieves the pre-configured API key +key+ or terminates interaction with
configured_host = Gem.host unless # an error.
Gem.configuration.disable_default_gem_server
@host ||=
begin
env_rubygems_host = ENV['RUBYGEMS_HOST']
env_rubygems_host = nil if
env_rubygems_host and env_rubygems_host.empty?
env_rubygems_host|| configured_host
end
end
def rubygems_api_request(method, path, host = nil, &block)
require 'net/http'
self.host = host if host
unless self.host
alert_error "You must specify a gem server"
terminate_interaction 1 # TODO: question this
end
uri = URI.parse "#{self.host}/#{path}"
request_method = Net::HTTP.const_get method.to_s.capitalize
Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
end
def with_response resp, error_prefix = nil
case resp
when Net::HTTPSuccess then
if block_given? then
yield resp
else
say resp.body
end
else
message = resp.body
message = "#{error_prefix}: #{message}" if error_prefix
say message
terminate_interaction 1 # TODO: question this
end
end
def verify_api_key(key) def verify_api_key(key)
if Gem.configuration.api_keys.key? key then if Gem.configuration.api_keys.key? key then
@ -112,4 +118,29 @@ module Gem::GemcutterUtilities
end end
end end
##
# If +response+ is an HTTP Success (2XX) response, yields the response if a
# block was given or shows the response body to the user.
#
# If the response was not successful, shows an error to the user including
# the +error_prefix+ and the response body.
def with_response response, error_prefix = nil
case response
when Net::HTTPSuccess then
if block_given? then
yield response
else
say response.body
end
else
message = response.body
message = "#{error_prefix}: #{message}" if error_prefix
say message
terminate_interaction 1 # TODO: question this
end
end
end end

View file

@ -0,0 +1,12 @@
require 'rubygems'
require 'rubygems/user_interaction'
##
# A post-install hook that displays "Successfully installed
# some_gem-1.0 as a default gem"
Gem.post_install do |installer|
ui = Gem::DefaultUserInteraction.ui
ui.say "Successfully installed #{installer.spec.full_name} as a default gem"
end

View file

@ -26,6 +26,9 @@ module Gem::InstallUpdateOptions
OptionParser.accept Gem::Security::Policy do |value| OptionParser.accept Gem::Security::Policy do |value|
require 'rubygems/security' require 'rubygems/security'
raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless
defined?(Gem::Security::HighSecurity)
value = Gem::Security::Policies[value] value = Gem::Security::Policies[value]
valid = Gem::Security::Policies.keys.sort valid = Gem::Security::Policies.keys.sort
message = "#{value} (#{valid.join ', '} are valid)" message = "#{value} (#{valid.join ', '} are valid)"

View file

@ -212,16 +212,21 @@ class Gem::Installer
FileUtils.rm_rf gem_dir FileUtils.rm_rf gem_dir
FileUtils.mkdir_p gem_dir FileUtils.mkdir_p gem_dir
extract_files if @options[:install_as_default]
extract_bin
build_extensions write_default_spec
write_build_info_file else
run_post_build_hooks extract_files
generate_bin build_extensions
write_spec write_build_info_file
write_cache_file run_post_build_hooks
generate_bin
write_spec
write_cache_file
end
say spec.post_install_message unless spec.post_install_message.nil? say spec.post_install_message unless spec.post_install_message.nil?
@ -326,6 +331,14 @@ class Gem::Installer
File.join gem_home, "specifications", "#{spec.full_name}.gemspec" File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
end end
##
# The location of of the default spec file for default gems.
#
def default_spec_file
File.join gem_home, "specifications/default", "#{spec.full_name}.gemspec"
end
## ##
# Writes the .gemspec specification (in Ruby) to the gem home's # Writes the .gemspec specification (in Ruby) to the gem home's
# specifications directory. # specifications directory.
@ -336,6 +349,16 @@ class Gem::Installer
file.fsync rescue nil # for filesystems without fsync(2) file.fsync rescue nil # for filesystems without fsync(2)
end end
end end
##
# Writes the full .gemspec specification (in Ruby) to the gem home's
# specifications/default directory.
def write_default_spec
File.open(default_spec_file, "w") do |file|
file.puts spec.to_ruby
end
end
## ##
# Creates windows .bat files for easy running of commands # Creates windows .bat files for easy running of commands
@ -538,13 +561,13 @@ class Gem::Installer
:bin_dir => nil, :bin_dir => nil,
:env_shebang => false, :env_shebang => false,
:force => false, :force => false,
:install_dir => Gem.dir,
:only_install_dir => false :only_install_dir => false
}.merge options }.merge options
@env_shebang = options[:env_shebang] @env_shebang = options[:env_shebang]
@force = options[:force] @force = options[:force]
@gem_home = options[:install_dir] @install_dir = options[:install_dir]
@gem_home = options[:install_dir] || Gem.dir
@ignore_dependencies = options[:ignore_dependencies] @ignore_dependencies = options[:ignore_dependencies]
@format_executable = options[:format_executable] @format_executable = options[:format_executable]
@security_policy = options[:security_policy] @security_policy = options[:security_policy]
@ -715,6 +738,15 @@ EOF
def extract_files def extract_files
@package.extract_files gem_dir @package.extract_files gem_dir
end end
##
# Extracts only the bin/ files from the gem into the gem directory.
# This is used by default gems to allow a gem-aware stub to function
# without the full gem installed.
def extract_bin
@package.extract_files gem_dir, "bin/*"
end
## ##
# Prefix and suffix the program filename the same as ruby. # Prefix and suffix the program filename the same as ruby.
@ -756,7 +788,11 @@ EOF
ensure_loadable_spec ensure_loadable_spec
Gem.ensure_gem_subdirectories gem_home if options[:install_as_default]
Gem.ensure_default_gem_subdirectories gem_home
else
Gem.ensure_gem_subdirectories gem_home
end
return true if @force return true if @force

View file

@ -42,6 +42,20 @@ class Gem::NameTuple
new nil, Gem::Version.new(0), nil new nil, Gem::Version.new(0), nil
end end
##
# Returns the full name (name-version) of this Gem. Platform information is
# included if it is not the default Ruby platform. This mimics the behavior
# of Gem::Specification#full_name.
def full_name
case @platform
when nil, 'ruby', ''
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{@platform}"
end
end
## ##
# Indicate if this NameTuple matches the current platform. # Indicate if this NameTuple matches the current platform.
@ -59,12 +73,7 @@ class Gem::NameTuple
# Return the name that the gemspec file would be # Return the name that the gemspec file would be
def spec_name def spec_name
case @platform "#{full_name}.gemspec"
when nil, 'ruby', ''
"#{@name}-#{@version}.gemspec"
else
"#{@name}-#{@version}-#{@platform}.gemspec"
end
end end
## ##
@ -74,10 +83,12 @@ class Gem::NameTuple
[@name, @version, @platform] [@name, @version, @platform]
end end
def to_s def inspect # :nodoc:
"#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>" "#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>"
end end
alias to_s inspect # :nodoc:
def <=> other def <=> other
to_a <=> other.to_a to_a <=> other.to_a
end end

View file

@ -280,11 +280,16 @@ EOM
algorithms = if @checksums then algorithms = if @checksums then
@checksums.keys @checksums.keys
else else
[Gem::Security::DIGEST_NAME] [Gem::Security::DIGEST_NAME].compact
end end
algorithms.each do |algorithm| algorithms.each do |algorithm|
digester = OpenSSL::Digest.new algorithm digester =
if defined?(OpenSSL::Digest) then
OpenSSL::Digest.new algorithm
else
Digest.const_get(algorithm).new
end
digester << entry.read(16384) until entry.eof? digester << entry.read(16384) until entry.eof?
@ -298,8 +303,11 @@ EOM
## ##
# Extracts the files in this package into +destination_dir+ # Extracts the files in this package into +destination_dir+
#
# If +pattern+ is specified, only entries matching that glob will be
# extracted.
def extract_files destination_dir def extract_files destination_dir, pattern = "*"
verify unless @spec verify unless @spec
FileUtils.mkdir_p destination_dir FileUtils.mkdir_p destination_dir
@ -310,7 +318,7 @@ EOM
reader.each do |entry| reader.each do |entry|
next unless entry.full_name == 'data.tar.gz' next unless entry.full_name == 'data.tar.gz'
extract_tar_gz entry, destination_dir extract_tar_gz entry, destination_dir, pattern
return # ignore further entries return # ignore further entries
end end
@ -324,10 +332,15 @@ EOM
# If an entry in the archive contains a relative path above # If an entry in the archive contains a relative path above
# +destination_dir+ or an absolute path is encountered an exception is # +destination_dir+ or an absolute path is encountered an exception is
# raised. # raised.
#
# If +pattern+ is specified, only entries matching that glob will be
# extracted.
def extract_tar_gz io, destination_dir # :nodoc: def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc:
open_tar_gz io do |tar| open_tar_gz io do |tar|
tar.each do |entry| tar.each do |entry|
next unless File.fnmatch pattern, entry.full_name
destination = install_location entry.full_name, destination_dir destination = install_location entry.full_name, destination_dir
FileUtils.rm_rf destination FileUtils.rm_rf destination
@ -428,12 +441,13 @@ EOM
# certificate and key are not present only checksum generation is set up. # certificate and key are not present only checksum generation is set up.
def setup_signer def setup_signer
passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
if @spec.signing_key then if @spec.signing_key then
@signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain, passphrase
@spec.signing_key = nil @spec.signing_key = nil
@spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s } @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s }
else else
@signer = Gem::Security::Signer.new nil, nil @signer = Gem::Security::Signer.new nil, nil, passphrase
@spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if
@signer.cert_chain @signer.cert_chain
end end
@ -509,28 +523,39 @@ EOM
end end
end end
##
# Verifies +entry+ in a .gem file.
def verify_entry entry
file_name = entry.full_name
@files << file_name
case file_name
when /\.sig$/ then
@signatures[$`] = entry.read if @security_policy
return
else
digest entry
end
case file_name
when /^metadata(.gz)?$/ then
load_spec entry
when 'data.tar.gz' then
verify_gz entry
end
rescue => e
message = "package is corrupt, exception while verifying: " +
"#{e.message} (#{e.class})"
raise Gem::Package::FormatError.new message, @gem
end
## ##
# Verifies the files of the +gem+ # Verifies the files of the +gem+
def verify_files gem def verify_files gem
gem.each do |entry| gem.each do |entry|
file_name = entry.full_name verify_entry entry
@files << file_name
case file_name
when /\.sig$/ then
@signatures[$`] = entry.read if @security_policy
next
else
digest entry
end
case file_name
when /^metadata(.gz)?$/ then
load_spec entry
when 'data.tar.gz' then
verify_gz entry
end
end end
unless @spec then unless @spec then

View file

@ -71,7 +71,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
SP(Z(to_oct(sum, 6))) SP(Z(to_oct(sum, 6)))
end end
def header(type, fname, dname, length, mode, checksum = nil) def header(type, fname, dname, length, mode, mtime, checksum = nil)
checksum ||= " " * 8 checksum ||= " " * 8
arr = [ # struct tarfile_entry_posix arr = [ # struct tarfile_entry_posix
@ -80,7 +80,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
Z(to_oct(0, 7)), # char uid[8]; ditto Z(to_oct(0, 7)), # char uid[8]; ditto
Z(to_oct(0, 7)), # char gid[8]; ditto Z(to_oct(0, 7)), # char gid[8]; ditto
Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null
Z(to_oct(0, 11)), # char mtime[12]; 0 padded, octal, null Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null
checksum, # char checksum[8]; 0 padded, octal, null, space checksum, # char checksum[8]; 0 padded, octal, null, space
type, # char typeflag[1]; file: "0" dir: "5" type, # char typeflag[1]; file: "0" dir: "5"
"\0" * 100, # char linkname[100]; ASCII + (Z unless filled) "\0" * 100, # char linkname[100]; ASCII + (Z unless filled)
@ -105,16 +105,16 @@ class Gem::Package::TarTestCase < Gem::TestCase
ret ret
end end
def tar_dir_header(name, prefix, mode) def tar_dir_header(name, prefix, mode, mtime)
h = header("5", name, prefix, 0, mode) h = header("5", name, prefix, 0, mode, mtime)
checksum = calc_checksum(h) checksum = calc_checksum(h)
header("5", name, prefix, 0, mode, checksum) header("5", name, prefix, 0, mode, mtime, checksum)
end end
def tar_file_header(fname, dname, mode, length) def tar_file_header(fname, dname, mode, length, mtime)
h = header("0", fname, dname, length, mode) h = header("0", fname, dname, length, mode, mtime)
checksum = calc_checksum(h) checksum = calc_checksum(h)
header("0", fname, dname, length, mode, checksum) header("0", fname, dname, length, mode, mtime, checksum)
end end
def to_oct(n, pad_size) def to_oct(n, pad_size)
@ -130,7 +130,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
end end
def util_dir_entry def util_dir_entry
util_entry tar_dir_header("foo", "bar", 0) util_entry tar_dir_header("foo", "bar", 0, Time.now)
end end
end end

View file

@ -4,6 +4,8 @@
# See LICENSE.txt for additional licensing information. # See LICENSE.txt for additional licensing information.
#++ #++
require 'digest'
## ##
# Allows writing of tar files # Allows writing of tar files
@ -121,7 +123,8 @@ class Gem::Package::TarWriter
@io.pos = init_pos @io.pos = init_pos
header = Gem::Package::TarHeader.new :name => name, :mode => mode, header = Gem::Package::TarHeader.new :name => name, :mode => mode,
:size => size, :prefix => prefix :size => size, :prefix => prefix,
:mtime => Time.now
@io.write header @io.write header
@io.pos = final_pos @io.pos = final_pos
@ -140,7 +143,15 @@ class Gem::Package::TarWriter
def add_file_digest name, mode, digest_algorithms # :yields: io def add_file_digest name, mode, digest_algorithms # :yields: io
digests = digest_algorithms.map do |digest_algorithm| digests = digest_algorithms.map do |digest_algorithm|
digest = digest_algorithm.new digest = digest_algorithm.new
[digest.name, digest] digest_name =
if digest.respond_to? :name then
digest.name
else
/::([^:]+)$/ =~ digest_algorithm.name
$1
end
[digest_name, digest]
end end
digests = Hash[*digests.flatten] digests = Hash[*digests.flatten]
@ -165,22 +176,32 @@ class Gem::Package::TarWriter
def add_file_signed name, mode, signer def add_file_signed name, mode, signer
digest_algorithms = [ digest_algorithms = [
signer.digest_algorithm, signer.digest_algorithm,
OpenSSL::Digest::SHA512, Digest::SHA512,
].uniq ].compact.uniq
digests = add_file_digest name, mode, digest_algorithms do |io| digests = add_file_digest name, mode, digest_algorithms do |io|
yield io yield io
end end
signature_digest = digests.values.find do |digest| signature_digest = digests.values.compact.find do |digest|
digest.name == signer.digest_name digest_name =
if digest.respond_to? :name then
digest.name
else
/::([^:]+)$/ =~ digest.class.name
$1
end
digest_name == signer.digest_name
end end
signature = signer.sign signature_digest.digest if signer.key then
signature = signer.sign signature_digest.digest
add_file_simple "#{name}.sig", 0444, signature.length do |io| add_file_simple "#{name}.sig", 0444, signature.length do |io|
io.write signature io.write signature
end if signature end
end
digests digests
end end
@ -195,7 +216,8 @@ class Gem::Package::TarWriter
name, prefix = split_name name name, prefix = split_name name
header = Gem::Package::TarHeader.new(:name => name, :mode => mode, header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
:size => size, :prefix => prefix).to_s :size => size, :prefix => prefix,
:mtime => Time.now).to_s
@io.write header @io.write header
os = BoundedStream.new @io, size os = BoundedStream.new @io, size
@ -256,7 +278,8 @@ class Gem::Package::TarWriter
header = Gem::Package::TarHeader.new :name => name, :mode => mode, header = Gem::Package::TarHeader.new :name => name, :mode => mode,
:typeflag => "5", :size => 0, :typeflag => "5", :size => 0,
:prefix => prefix :prefix => prefix,
:mtime => Time.now
@io.write header @io.write header

View file

@ -12,6 +12,10 @@ class Gem::PathSupport
# Array of paths to search for Gems. # Array of paths to search for Gems.
attr_reader :path attr_reader :path
##
# Directory with spec cache
attr_reader :spec_cache_dir # :nodoc:
## ##
# #
# Constructor. Takes a single argument which is to be treated like a # Constructor. Takes a single argument which is to be treated like a
@ -28,6 +32,10 @@ class Gem::PathSupport
end end
self.path = env["GEM_PATH"] || ENV["GEM_PATH"] self.path = env["GEM_PATH"] || ENV["GEM_PATH"]
@spec_cache_dir =
env["GEM_SPEC_CACHE"] || ENV["GEM_SPEC_CACHE"] ||
Gem.default_spec_cache_dir
end end
private private

View file

@ -2,6 +2,8 @@ require "rubygems/deprecate"
## ##
# Available list of platforms for targeting Gem installations. # Available list of platforms for targeting Gem installations.
#
# See `gem help platform` for information on platform matching.
class Gem::Platform class Gem::Platform
@ -129,12 +131,16 @@ class Gem::Platform
# Does +other+ match this platform? Two platforms match if they have the # Does +other+ match this platform? Two platforms match if they have the
# same CPU, or either has a CPU of 'universal', they have the same OS, and # same CPU, or either has a CPU of 'universal', they have the same OS, and
# they have the same version, or either has no version. # they have the same version, or either has no version.
#
# Additionally, the platform will match if the local CPU is 'arm' and the
# other CPU starts with "arm" (for generic ARM family support).
def ===(other) def ===(other)
return nil unless Gem::Platform === other return nil unless Gem::Platform === other
# cpu # cpu
(@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu) and (@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu or
(@cpu == 'arm' and other.cpu =~ /\Aarm/)) and
# os # os
@os == other.os and @os == other.os and

View file

@ -1,6 +1,7 @@
require 'rubygems' require 'rubygems'
require 'rubygems/request'
require 'rubygems/uri_formatter'
require 'rubygems/user_interaction' require 'rubygems/user_interaction'
require 'uri'
require 'resolv' require 'resolv'
## ##
@ -71,17 +72,7 @@ class Gem::RemoteFetcher
Socket.do_not_reverse_lookup = true Socket.do_not_reverse_lookup = true
@connections = {} @proxy = proxy
@requests = Hash.new 0
@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
@user_agent = user_agent
@env_no_proxy = get_no_proxy_from_env
@dns = dns @dns = dns
end end
@ -200,7 +191,7 @@ class Gem::RemoteFetcher
source_uri.path source_uri.path
end end
source_path = unescape source_path source_path = Gem::UriFormatter.new(source_path).unescape
begin begin
FileUtils.cp source_path, local_gem_path unless FileUtils.cp source_path, local_gem_path unless
@ -319,125 +310,6 @@ class Gem::RemoteFetcher
response['content-length'].to_i response['content-length'].to_i
end end
def escape(str)
return unless str
@uri_parser ||= uri_escaper
@uri_parser.escape str
end
def unescape(str)
return unless str
@uri_parser ||= uri_escaper
@uri_parser.unescape str
end
def uri_escaper
URI::Parser.new
rescue NameError
URI
end
##
# Returns list of no_proxy entries (if any) from the environment
def get_no_proxy_from_env
env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
return [] if env_no_proxy.nil? or env_no_proxy.empty?
env_no_proxy.split(/\s*,\s*/)
end
##
# Returns an HTTP proxy URI if one is set in the environment variables.
def get_proxy_from_env
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
return nil if env_proxy.nil? or env_proxy.empty?
uri = URI.parse(normalize_uri(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):/i) ? uri : "http://#{uri}"
end
##
# Creates or an HTTP connection based on +uri+, or retrieves an existing
# connection, using a proxy if needed.
def connection_for(uri)
net_http_args = [uri.host, uri.port]
if @proxy_uri and not no_proxy?(uri.host) then
net_http_args += [
@proxy_uri.host,
@proxy_uri.port,
@proxy_uri.user,
@proxy_uri.password
]
end
connection_id = [Thread.current.object_id, *net_http_args].join ':'
@connections[connection_id] ||= Net::HTTP.new(*net_http_args)
connection = @connections[connection_id]
if https?(uri) and not connection.started? then
configure_connection_for_https(connection)
end
connection.start unless connection.started?
connection
rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN,
Errno::EHOSTDOWN => e
raise FetchError.new(e.message, uri)
end
def configure_connection_for_https(connection)
require 'net/https'
connection.use_ssl = true
connection.verify_mode =
Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
store = OpenSSL::X509::Store.new
if Gem.configuration.ssl_ca_cert
if File.directory? Gem.configuration.ssl_ca_cert
store.add_path Gem.configuration.ssl_ca_cert
else
store.add_file Gem.configuration.ssl_ca_cert
end
else
store.set_default_paths
add_rubygems_trusted_certs(store)
end
connection.cert_store = store
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
raise Gem::Exception.new(
'Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources')
end
def add_rubygems_trusted_certs(store)
pattern = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__))
Dir.glob(pattern).each do |ssl_cert_file|
store.add_file ssl_cert_file
end
end
def correct_for_windows_path(path) def correct_for_windows_path(path)
if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':' if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
path = path[1..-1] path = path[1..-1]
@ -446,136 +318,13 @@ class Gem::RemoteFetcher
end end
end end
def no_proxy? host
host = host.downcase
@env_no_proxy.each do |pattern|
pattern = pattern.downcase
return true if host[-pattern.length, pattern.length ] == pattern
end
return false
end
## ##
# Performs a Net::HTTP request of type +request_class+ on +uri+ returning # Performs a Net::HTTP request of type +request_class+ on +uri+ returning
# a Net::HTTP response object. request maintains a table of persistent # a Net::HTTP response object. request maintains a table of persistent
# connections to reduce connect overhead. # connections to reduce connect overhead.
def request(uri, request_class, last_modified = nil) def request(uri, request_class, last_modified = nil)
request = request_class.new uri.request_uri Gem::Request.new(uri, request_class, last_modified, @proxy).fetch
unless uri.nil? || uri.user.nil? || uri.user.empty? then
request.basic_auth uri.user, uri.password
end
request.add_field 'User-Agent', @user_agent
request.add_field 'Connection', 'keep-alive'
request.add_field 'Keep-Alive', '30'
if last_modified then
last_modified = last_modified.utc
request.add_field 'If-Modified-Since', last_modified.rfc2822
end
yield request if block_given?
connection = connection_for uri
retried = false
bad_response = false
begin
@requests[connection.object_id] += 1
say "#{request.method} #{uri}" if
Gem.configuration.really_verbose
file_name = File.basename(uri.path)
# perform download progress reporter only for gems
if request.response_body_permitted? && file_name =~ /\.gem$/
reporter = ui.download_reporter
response = connection.request(request) do |incomplete_response|
if Net::HTTPOK === incomplete_response
reporter.fetch(file_name, incomplete_response.content_length)
downloaded = 0
data = ''
incomplete_response.read_body do |segment|
data << segment
downloaded += segment.length
reporter.update(downloaded)
end
reporter.done
if incomplete_response.respond_to? :body=
incomplete_response.body = data
else
incomplete_response.instance_variable_set(:@body, data)
end
end
end
else
response = connection.request request
end
say "#{response.code} #{response.message}" if
Gem.configuration.really_verbose
rescue Net::HTTPBadResponse
say "bad response" if Gem.configuration.really_verbose
reset connection
raise FetchError.new('too many bad responses', uri) if bad_response
bad_response = true
retry
# HACK work around EOFError bug in Net::HTTP
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
# to install gems.
rescue EOFError, Timeout::Error,
Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
requests = @requests[connection.object_id]
say "connection reset after #{requests} requests, retrying" if
Gem.configuration.really_verbose
raise FetchError.new('too many connection resets', uri) if retried
reset connection
retried = true
retry
end
response
end
##
# Resets HTTP connection +connection+.
def reset(connection)
@requests.delete connection.object_id
connection.finish
connection.start
end
def user_agent
ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}"
ruby_version = RUBY_VERSION
ruby_version += 'dev' if RUBY_PATCHLEVEL == -1
ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}"
if RUBY_PATCHLEVEL >= 0 then
ua << " patchlevel #{RUBY_PATCHLEVEL}"
elsif defined?(RUBY_REVISION) then
ua << " revision #{RUBY_REVISION}"
end
ua << ")"
ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby'
ua
end end
def https?(uri) def https?(uri)

262
lib/rubygems/request.rb Normal file
View file

@ -0,0 +1,262 @@
require 'net/http'
require 'time'
require 'rubygems/user_interaction'
class Gem::Request
include Gem::UserInteraction
attr_reader :proxy_uri
def initialize(uri, request_class, last_modified, proxy)
@uri = uri
@request_class = request_class
@last_modified = last_modified
@requests = Hash.new 0
@connections = {}
@user_agent = user_agent
@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
@env_no_proxy = get_no_proxy_from_env
end
def add_rubygems_trusted_certs(store)
pattern = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__))
Dir.glob(pattern).each do |ssl_cert_file|
store.add_file ssl_cert_file
end
end
def configure_connection_for_https(connection)
require 'net/https'
connection.use_ssl = true
connection.verify_mode =
Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
store = OpenSSL::X509::Store.new
if Gem.configuration.ssl_client_cert then
pem = File.read Gem.configuration.ssl_client_cert
connection.cert = OpenSSL::X509::Certificate.new pem
connection.key = OpenSSL::PKey::RSA.new pem
end
if Gem.configuration.ssl_ca_cert
if File.directory? Gem.configuration.ssl_ca_cert
store.add_path Gem.configuration.ssl_ca_cert
else
store.add_file Gem.configuration.ssl_ca_cert
end
else
store.set_default_paths
add_rubygems_trusted_certs(store)
end
connection.cert_store = store
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
raise Gem::Exception.new(
'Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources')
end
##
# Creates or an HTTP connection based on +uri+, or retrieves an existing
# connection, using a proxy if needed.
def connection_for(uri)
net_http_args = [uri.host, uri.port]
if @proxy_uri and not no_proxy?(uri.host) then
net_http_args += [
@proxy_uri.host,
@proxy_uri.port,
@proxy_uri.user,
@proxy_uri.password
]
end
connection_id = [Thread.current.object_id, *net_http_args].join ':'
@connections[connection_id] ||= Net::HTTP.new(*net_http_args)
connection = @connections[connection_id]
if https?(uri) and not connection.started? then
configure_connection_for_https(connection)
end
connection.start unless connection.started?
connection
rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN,
Errno::EHOSTDOWN => e
raise Gem::RemoteFetcher::FetchError.new(e.message, uri)
end
def fetch
request = @request_class.new @uri.request_uri
unless @uri.nil? || @uri.user.nil? || @uri.user.empty? then
request.basic_auth @uri.user, @uri.password
end
request.add_field 'User-Agent', @user_agent
request.add_field 'Connection', 'keep-alive'
request.add_field 'Keep-Alive', '30'
if @last_modified then
@last_modified = @last_modified.utc
request.add_field 'If-Modified-Since', @last_modified.rfc2822
end
yield request if block_given?
connection = connection_for @uri
retried = false
bad_response = false
begin
@requests[connection.object_id] += 1
say "#{request.method} #{@uri}" if
Gem.configuration.really_verbose
file_name = File.basename(@uri.path)
# perform download progress reporter only for gems
if request.response_body_permitted? && file_name =~ /\.gem$/
reporter = ui.download_reporter
response = connection.request(request) do |incomplete_response|
if Net::HTTPOK === incomplete_response
reporter.fetch(file_name, incomplete_response.content_length)
downloaded = 0
data = ''
incomplete_response.read_body do |segment|
data << segment
downloaded += segment.length
reporter.update(downloaded)
end
reporter.done
if incomplete_response.respond_to? :body=
incomplete_response.body = data
else
incomplete_response.instance_variable_set(:@body, data)
end
end
end
else
response = connection.request request
end
say "#{response.code} #{response.message}" if
Gem.configuration.really_verbose
rescue Net::HTTPBadResponse
say "bad response" if Gem.configuration.really_verbose
reset connection
raise Gem::RemoteFetcher::FetchError.new('too many bad responses', @uri) if bad_response
bad_response = true
retry
# HACK work around EOFError bug in Net::HTTP
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
# to install gems.
rescue EOFError, Timeout::Error,
Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
requests = @requests[connection.object_id]
say "connection reset after #{requests} requests, retrying" if
Gem.configuration.really_verbose
raise Gem::RemoteFetcher::FetchError.new('too many connection resets', @uri) if retried
reset connection
retried = true
retry
end
response
end
##
# Returns list of no_proxy entries (if any) from the environment
def get_no_proxy_from_env
env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
return [] if env_no_proxy.nil? or env_no_proxy.empty?
env_no_proxy.split(/\s*,\s*/)
end
##
# Returns an HTTP proxy URI if one is set in the environment variables.
def get_proxy_from_env
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
return nil if env_proxy.nil? or env_proxy.empty?
uri = URI(Gem::UriFormatter.new(env_proxy).normalize)
if uri and uri.user.nil? and uri.password.nil? then
# Probably we have http_proxy_* variables?
uri.user = Gem::UriFormatter.new(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']).escape
uri.password = Gem::UriFormatter.new(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']).escape
end
uri
end
def https?(uri)
uri.scheme.downcase == 'https'
end
def no_proxy? host
host = host.downcase
@env_no_proxy.each do |pattern|
pattern = pattern.downcase
return true if host[-pattern.length, pattern.length ] == pattern
end
return false
end
##
# Resets HTTP connection +connection+.
def reset(connection)
@requests.delete connection.object_id
connection.finish
connection.start
end
def user_agent
ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}"
ruby_version = RUBY_VERSION
ruby_version += 'dev' if RUBY_PATCHLEVEL == -1
ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}"
if RUBY_PATCHLEVEL >= 0 then
ua << " patchlevel #{RUBY_PATCHLEVEL}"
elsif defined?(RUBY_REVISION) then
ua << " revision #{RUBY_REVISION}"
end
ua << ")"
ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby'
ua
end
end

View file

@ -5,178 +5,176 @@ require 'rubygems/dependency_list'
require 'rubygems/installer' require 'rubygems/installer'
require 'tsort' require 'tsort'
module Gem class Gem::RequestSet
class RequestSet
include TSort include TSort
def initialize(*deps) ##
@dependencies = deps # Array of gems to install even if already installed
yield self if block_given? attr_reader :always_install
attr_reader :dependencies
attr_accessor :development
##
# Treat missing dependencies as silent errors
attr_accessor :soft_missing
def initialize *deps
@dependencies = deps
@always_install = []
@development = false
@soft_missing = false
yield self if block_given?
end
##
# Declare that a gem of name +name+ with +reqs+ requirements is needed.
def gem name, *reqs
@dependencies << Gem::Dependency.new(name, reqs)
end
##
# Add +deps+ Gem::Dependency objects to the set.
def import deps
@dependencies += deps
end
def install options, &block
if dir = options[:install_dir]
return install_into dir, false, options, &block
end end
attr_reader :dependencies cache_dir = options[:cache_dir] || Gem.dir
# Declare that a gem of name +name+ with +reqs+ requirements specs = []
# is needed.
# sorted_requests.each do |req|
def gem(name, *reqs) if req.installed? and
@dependencies << Gem::Dependency.new(name, reqs) @always_install.none? { |spec| spec == req.spec.spec } then
yield req, nil if block_given?
next
end
path = req.download cache_dir
inst = Gem::Installer.new path, options
yield req, inst if block_given?
specs << inst.install
end end
# Add +deps+ Gem::Depedency objects to the set. specs
# end
def import(deps)
@dependencies += deps def install_into dir, force = true, options = {}
existing = force ? [] : specs_in(dir)
existing.delete_if { |s| @always_install.include? s }
dir = File.expand_path dir
installed = []
sorted_requests.each do |req|
if existing.find { |s| s.full_name == req.spec.full_name }
yield req, nil if block_given?
next
end
path = req.download(dir)
unless path then # already installed
yield req, nil if block_given?
next
end
options[:install_dir] = dir
options[:only_install_dir] = true
inst = Gem::Installer.new path, options
yield req, inst if block_given?
inst.install
installed << req
end end
# Resolve the requested dependencies and return an Array of installed
# Specification objects to be activated. end
#
def resolve(set=nil) ##
r = Gem::DependencyResolver.new(@dependencies, set) # Load a dependency management file.
@requests = r.resolve
def load_gemdeps path
gf = Gem::RequestSet::GemDepedencyAPI.new self, path
gf.load
end
##
# Resolve the requested dependencies and return an Array of Specification
# objects to be activated.
def resolve set = nil
resolver = Gem::DependencyResolver.new @dependencies, set
resolver.development = @development
resolver.soft_missing = @soft_missing
@requests = resolver.resolve
end
##
# Resolve the requested dependencies against the gems available via Gem.path
# and return an Array of Specification objects to be activated.
def resolve_current
resolve Gem::DependencyResolver::CurrentSet.new
end
def sorted_requests
@sorted ||= strongly_connected_components.flatten
end
def specs
@specs ||= @requests.map { |r| r.full_spec }
end
def specs_in dir
Dir["#{dir}/specifications/*.gemspec"].map do |g|
Gem::Specification.load g
end end
end
# Resolve the requested dependencies against the gems def tsort_each_node &block # :nodoc:
# available via Gem.path and return an Array of Specification @requests.each(&block)
# objects to be activated. end
#
def resolve_current
resolve DependencyResolver::CurrentSet.new
end
# Load a dependency management file. def tsort_each_child node # :nodoc:
# node.spec.dependencies.each do |dep|
def load_gemdeps(path) next if dep.type == :development and not @development
gf = GemDepedencyAPI.new(self, path)
gf.load
end
def specs match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
@specs ||= @requests.map { |r| r.full_spec } if match
end begin
yield match
def tsort_each_node(&block) rescue TSort::Cyclic
@requests.each(&block) end
end else
unless @soft_missing
def tsort_each_child(node)
node.spec.dependencies.each do |dep|
next if dep.type == :development
match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
if match
begin
yield match
rescue TSort::Cyclic
end
else
raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}" raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}"
end end
end end
end end
def sorted_requests
@sorted ||= strongly_connected_components.flatten
end
def specs_in(dir)
Dir["#{dir}/specifications/*.gemspec"].map do |g|
Gem::Specification.load g
end
end
def install_into(dir, force=true, &b)
existing = force ? [] : specs_in(dir)
dir = File.expand_path dir
installed = []
sorted_requests.each do |req|
if existing.find { |s| s.full_name == req.spec.full_name }
b.call req, nil if b
next
end
path = req.download(dir)
inst = Gem::Installer.new path, :install_dir => dir,
:only_install_dir => true
b.call req, inst if b
inst.install
installed << req
end
installed
end
def install(options, &b)
if dir = options[:install_dir]
return install_into(dir, false, &b)
end
cache_dir = options[:cache_dir] || Gem.dir
specs = []
sorted_requests.each do |req|
if req.installed?
b.call req, nil if b
next
end
path = req.download cache_dir
inst = Gem::Installer.new path, options
b.call req, inst if b
specs << inst.install
end
specs
end
# A semi-compatible DSL for Bundler's Gemfile format
#
class GemDepedencyAPI
def initialize(set, path)
@set = set
@path = path
end
def load
instance_eval File.read(@path).untaint, @path, 1
end
# DSL
def source(url)
end
def gem(name, *reqs)
# Ignore the opts for now.
reqs.pop if reqs.last.kind_of?(Hash)
@set.gem name, *reqs
end
def platform(what)
if what == :ruby
yield
end
end
alias_method :platforms, :platform
def group(*what)
end
end
end end
end end
require 'rubygems/request_set/gem_dependency_api'

View file

@ -0,0 +1,39 @@
##
# A semi-compatible DSL for Bundler's Gemfile format
class Gem::RequestSet::GemDepedencyAPI
def initialize set, path
@set = set
@path = path
end
def load
instance_eval File.read(@path).untaint, @path, 1
end
# :category: Bundler Gemfile DSL
def gem name, *reqs
# Ignore the opts for now.
reqs.pop if reqs.last.kind_of?(Hash)
@set.gem name, *reqs
end
def group *what
end
def platform what
if what == :ruby
yield
end
end
alias :platforms :platform
def source url
end
end

View file

@ -12,20 +12,6 @@ begin
rescue LoadError => e rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') || raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/ e.message =~ / -- openssl$/
module OpenSSL # :nodoc:
class Digest # :nodoc:
class SHA1 # :nodoc:
def name
'SHA1'
end
end
end
module PKey # :nodoc:
class RSA # :nodoc:
end
end
end
end end
## ##
@ -352,23 +338,38 @@ module Gem::Security
## ##
# Digest algorithm used to sign gems # Digest algorithm used to sign gems
DIGEST_ALGORITHM = OpenSSL::Digest::SHA1 DIGEST_ALGORITHM =
if defined?(OpenSSL::Digest) then
OpenSSL::Digest::SHA1
end
## ##
# Used internally to select the signing digest from all computed digests # Used internally to select the signing digest from all computed digests
DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc: DIGEST_NAME = # :nodoc:
if DIGEST_ALGORITHM then
DIGEST_ALGORITHM.new.name
end
## ##
# Algorithm for creating the key pair used to sign gems # Algorithm for creating the key pair used to sign gems
KEY_ALGORITHM = OpenSSL::PKey::RSA KEY_ALGORITHM =
if defined?(OpenSSL::PKey) then
OpenSSL::PKey::RSA
end
## ##
# Length of keys created by KEY_ALGORITHM # Length of keys created by KEY_ALGORITHM
KEY_LENGTH = 2048 KEY_LENGTH = 2048
##
# Cipher used to encrypt the key pair used to sign gems.
# Must be in the list returned by OpenSSL::Cipher.ciphers
KEY_CIPHER = OpenSSL::Cipher.new('aes256') if defined?(OpenSSL::Cipher)
## ##
# One year in seconds # One year in seconds
@ -563,13 +564,18 @@ module Gem::Security
## ##
# Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
# +permissions+. # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be
# passed to +to_pem+.
def self.write pemmable, path, permissions = 0600 def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER
path = File.expand_path path path = File.expand_path path
open path, 'wb', permissions do |io| open path, 'wb', permissions do |io|
io.write pemmable.to_pem if passphrase and cipher
io.write pemmable.to_pem cipher, passphrase
else
io.write pemmable.to_pem
end
end end
path path
@ -579,8 +585,11 @@ module Gem::Security
end end
require 'rubygems/security/policy' if defined?(OpenSSL::SSL) then
require 'rubygems/security/policies' require 'rubygems/security/policy'
require 'rubygems/security/signer' require 'rubygems/security/policies'
require 'rubygems/security/trust_dir' require 'rubygems/security/trust_dir'
end
require 'rubygems/security/signer'

View file

@ -1,3 +1,5 @@
require 'rubygems/user_interaction'
## ##
# A Gem::Security::Policy object encapsulates the settings for verifying # A Gem::Security::Policy object encapsulates the settings for verifying
# signed gem files. This is the base class. You can either declare an # signed gem files. This is the base class. You can either declare an
@ -6,6 +8,8 @@
class Gem::Security::Policy class Gem::Security::Policy
include Gem::UserInteraction
attr_reader :name attr_reader :name
attr_accessor :only_signed attr_accessor :only_signed
@ -175,6 +179,19 @@ class Gem::Security::Policy
true true
end end
##
# Extracts the email or subject from +certificate+
def subject certificate # :nodoc:
certificate.extensions.each do |extension|
next unless extension.oid == 'subjectAltName'
return extension.value
end
certificate.subject.to_s
end
def inspect # :nodoc: def inspect # :nodoc:
("[Policy: %s - data: %p signer: %p chain: %p root: %p " + ("[Policy: %s - data: %p signer: %p chain: %p root: %p " +
"signed-only: %p trusted-only: %p]") % [ "signed-only: %p trusted-only: %p]") % [
@ -184,16 +201,21 @@ class Gem::Security::Policy
end end
## ##
# Verifies the certificate +chain+ is valid, the +digests+ match the # For +full_name+, verifies the certificate +chain+ is valid, the +digests+
# signatures +signatures+ created by the signer depending on the +policy+ # match the signatures +signatures+ created by the signer depending on the
# settings. # +policy+ settings.
# #
# If +key+ is given it is used to validate the signing certificate. # If +key+ is given it is used to validate the signing certificate.
def verify chain, key = nil, digests = {}, signatures = {} def verify chain, key = nil, digests = {}, signatures = {},
if @only_signed and signatures.empty? then full_name = '(unknown)'
raise Gem::Security::Exception, if signatures.empty? then
"unsigned gems are not allowed by the #{name} policy" if @only_signed then
raise Gem::Security::Exception,
"unsigned gems are not allowed by the #{name} policy"
else
alert_warning "#{full_name} is not signed"
end
end end
opt = @opt opt = @opt
@ -222,7 +244,11 @@ class Gem::Security::Policy
check_root chain, time if @verify_root check_root chain, time if @verify_root
check_trust chain, digester, trust_dir if @only_trusted if @only_trusted then
check_trust chain, digester, trust_dir
else
alert_warning "#{subject signer} is not trusted for #{full_name}"
end
signatures.each do |file, _| signatures.each do |file, _|
digest = signer_digests[file] digest = signer_digests[file]
@ -252,7 +278,7 @@ class Gem::Security::Policy
OpenSSL::X509::Certificate.new cert_pem OpenSSL::X509::Certificate.new cert_pem
end end
verify chain, nil, digests, signatures verify chain, nil, digests, signatures, spec.full_name
true true
end end

View file

@ -29,7 +29,7 @@ class Gem::Security::Signer
# +chain+ containing X509 certificates, encoding certificates or paths to # +chain+ containing X509 certificates, encoding certificates or paths to
# certificates. # certificates.
def initialize key, cert_chain def initialize key, cert_chain, passphrase = nil
@cert_chain = cert_chain @cert_chain = cert_chain
@key = key @key = key
@ -46,7 +46,7 @@ class Gem::Security::Signer
@digest_algorithm = Gem::Security::DIGEST_ALGORITHM @digest_algorithm = Gem::Security::DIGEST_ALGORITHM
@digest_name = Gem::Security::DIGEST_NAME @digest_name = Gem::Security::DIGEST_NAME
@key = OpenSSL::PKey::RSA.new File.read @key if @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if
@key and not OpenSSL::PKey::RSA === @key @key and not OpenSSL::PKey::RSA === @key
if @cert_chain then if @cert_chain then

View file

@ -25,14 +25,21 @@ class Gem::Source
end end
def <=>(other) def <=>(other)
if !@uri case other
return 0 unless other.uri when Gem::Source::Installed, Gem::Source::Local then
return -1 -1
when Gem::Source then
if !@uri
return 0 unless other.uri
return -1
end
return 1 if !other.uri
@uri.to_s <=> other.uri.to_s
else
nil
end end
return 1 if !other.uri
@uri.to_s <=> other.uri.to_s
end end
include Comparable include Comparable
@ -58,8 +65,7 @@ class Gem::Source
def cache_dir(uri) def cache_dir(uri)
# Correct for windows paths # Correct for windows paths
escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/') escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
root = File.join Gem.user_home, '.gem', 'specs' File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
File.join root, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
end end
def update_cache? def update_cache?
@ -141,4 +147,14 @@ class Gem::Source
fetcher = Gem::RemoteFetcher.fetcher fetcher = Gem::RemoteFetcher.fetcher
fetcher.download spec, @uri.to_s, dir fetcher.download spec, @uri.to_s, dir
end end
def pretty_print q # :nodoc:
q.group 2, '[Remote:', ']' do
q.breakable
q.text @uri.to_s
end
end
end end
require 'rubygems/source/installed'

View file

@ -0,0 +1,28 @@
class Gem::Source::Installed < Gem::Source
def initialize
end
##
# Installed sources sort before all other sources
def <=> other
case other
when Gem::Source::Installed then
0
when Gem::Source then
1
else
nil
end
end
##
# We don't need to download an installed gem
def download spec, path
nil
end
end

View file

@ -0,0 +1,122 @@
require 'rubygems/source'
class Gem::Source::Local < Gem::Source
def initialize
@uri = nil
end
##
# Local sorts before Gem::Source and after Gem::Source::Installed
def <=> other
case other
when Gem::Source::Installed then
-1
when Gem::Source::Local then
0
when Gem::Source then
1
else
nil
end
end
def inspect # :nodoc:
"#<%s specs: %p>" % [self.class, @specs.keys]
end
def load_specs(type)
names = []
@specs = {}
Dir["*.gem"].each do |file|
begin
pkg = Gem::Package.new(file)
rescue SystemCallError, Gem::Package::FormatError
# ignore
else
tup = pkg.spec.name_tuple
@specs[tup] = [File.expand_path(file), pkg]
case type
when :released
unless pkg.spec.version.prerelease?
names << pkg.spec.name_tuple
end
when :prerelease
if pkg.spec.version.prerelease?
names << pkg.spec.name_tuple
end
when :latest
tup = pkg.spec.name_tuple
cur = names.find { |x| x.name == tup.name }
if !cur
names << tup
elsif cur.version < tup.version
names.delete cur
names << tup
end
else
names << pkg.spec.name_tuple
end
end
end
names
end
def find_gem(gem_name, version=Gem::Requirement.default,
prerelease=false)
load_specs :complete
found = []
@specs.each do |n, data|
if n.name == gem_name
s = data[1].spec
if version.satisfied_by?(s.version)
if prerelease
found << s
elsif !s.version.prerelease?
found << s
end
end
end
end
found.sort_by { |s| s.version }.last
end
def fetch_spec(name)
load_specs :complete
if data = @specs[name]
data.last.spec
else
raise Gem::Exception, "Unable to find spec for '#{name}'"
end
end
def download(spec, cache_dir=nil)
load_specs :complete
@specs.each do |name, data|
return data[0] if data[1].spec == spec
end
raise Gem::Exception, "Unable to find file for '#{spec.full_name}'"
end
def pretty_print q # :nodoc:
q.group 2, '[Local gems:', ']' do
q.breakable
q.seplist @specs.keys do |v|
q.text v.full_name
end
end
end
end

View file

@ -0,0 +1,28 @@
class Gem::Source::SpecificFile < Gem::Source
def initialize(file)
@uri = nil
@path = ::File.expand_path(file)
@package = Gem::Package.new @path
@spec = @package.spec
@name = @spec.name_tuple
end
attr_reader :spec
def load_specs(*a)
[@name]
end
def fetch_spec(name)
return @spec if name == @name
raise Gem::Exception, "Unable to find '#{name}'"
@spec
end
def download(spec, dir=nil)
return @path if spec == @spec
raise Gem::Exception, "Unable to download '#{spec.full_name}'"
end
end

View file

@ -1,92 +1,5 @@
require 'rubygems/source' require 'rubygems/source'
require 'rubygems/source_local'
class Gem::Source::Local < Gem::Source # TODO warn upon require, this file is deprecated.
def initialize
@uri = nil
end
def load_specs(type)
names = []
@specs = {}
Dir["*.gem"].each do |file|
begin
pkg = Gem::Package.new(file)
rescue SystemCallError, Gem::Package::FormatError
# ignore
else
tup = pkg.spec.name_tuple
@specs[tup] = [File.expand_path(file), pkg]
case type
when :released
unless pkg.spec.version.prerelease?
names << pkg.spec.name_tuple
end
when :prerelease
if pkg.spec.version.prerelease?
names << pkg.spec.name_tuple
end
when :latest
tup = pkg.spec.name_tuple
cur = names.find { |x| x.name == tup.name }
if !cur
names << tup
elsif cur.version < tup.version
names.delete cur
names << tup
end
else
names << pkg.spec.name_tuple
end
end
end
names
end
def find_gem(gem_name, version=Gem::Requirement.default,
prerelease=false)
load_specs :complete
found = []
@specs.each do |n, data|
if n.name == gem_name
s = data[1].spec
if version.satisfied_by?(s.version)
if prerelease
found << s
elsif !s.version.prerelease?
found << s
end
end
end
end
found.sort_by { |s| s.version }.last
end
def fetch_spec(name)
load_specs :complete
if data = @specs[name]
data.last.spec
else
raise Gem::Exception, "Unable to find spec for '#{name}'"
end
end
def download(spec, cache_dir=nil)
load_specs :complete
@specs.each do |name, data|
return data[0] if data[1].spec == spec
end
raise Gem::Exception, "Unable to find file for '#{spec.full_name}'"
end
end

View file

@ -1,28 +1,4 @@
class Gem::Source::SpecificFile < Gem::Source require 'rubygems/source/specific_file'
def initialize(file)
@uri = nil
@path = ::File.expand_path(file)
@package = Gem::Package.new @path # TODO warn upon require, this file is deprecated.
@spec = @package.spec
@name = @spec.name_tuple
end
attr_reader :spec
def load_specs(*a)
[@name]
end
def fetch_spec(name)
return @spec if name == @name
raise Gem::Exception, "Unable to find '#{name}'"
@spec
end
def download(spec, dir=nil)
return @path if spec == @spec
raise Gem::Exception, "Unable to download '#{spec.full_name}'"
end
end

View file

@ -38,7 +38,6 @@ class Gem::SpecFetcher
end end
def initialize def initialize
@dir = File.join Gem.user_home, '.gem', 'specs'
@update_cache = File.stat(Gem.user_home).uid == Process.uid @update_cache = File.stat(Gem.user_home).uid == Process.uid
@specs = {} @specs = {}

View file

@ -5,10 +5,13 @@
# See LICENSE.txt for permissions. # See LICENSE.txt for permissions.
#++ #++
require 'rubygems/version' require 'rubygems/version'
require 'rubygems/requirement' require 'rubygems/requirement'
require 'rubygems/platform' require 'rubygems/platform'
require 'rubygems/deprecate' require 'rubygems/deprecate'
require 'rubygems/basic_specification'
require 'rubygems/stub_specification'
# :stopdoc: # :stopdoc:
# date.rb can't be loaded for `make install` due to miniruby # date.rb can't be loaded for `make install` due to miniruby
@ -45,7 +48,7 @@ class Date; end
# #
# s.metadata = { "bugtracker" => "http://somewhere.com/blah" } # s.metadata = { "bugtracker" => "http://somewhere.com/blah" }
class Gem::Specification class Gem::Specification < Gem::BasicSpecification
# REFACTOR: Consider breaking out this version stuff into a separate # REFACTOR: Consider breaking out this version stuff into a separate
# module. There's enough special stuff around it that it may justify # module. There's enough special stuff around it that it may justify
@ -107,6 +110,10 @@ class Gem::Specification
today = Time.now.utc today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day) TODAY = Time.utc(today.year, today.month, today.day)
LOAD_CACHE = {}
private_constant :LOAD_CACHE if defined? private_constant
# :startdoc: # :startdoc:
## ##
@ -156,6 +163,17 @@ class Gem::Specification
:version => nil, :version => nil,
} }
Dupable = { }
@@default_value.each do |k,v|
case v
when Time, Numeric, Symbol, true, false, nil
Dupable[k] = false
else
Dupable[k] = true
end
end
@@attributes = @@default_value.keys.sort_by { |s| s.to_s } @@attributes = @@default_value.keys.sort_by { |s| s.to_s }
@@array_attributes = @@default_value.reject { |k,v| v != [] }.keys @@array_attributes = @@default_value.reject { |k,v| v != [] }.keys
@@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition { |k| @@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition { |k|
@ -583,11 +601,6 @@ class Gem::Specification
attr_writer :default_executable attr_writer :default_executable
##
# Path this gemspec was loaded from. This attribute is not persisted.
attr_reader :loaded_from
## ##
# Allows deinstallation of gems with legacy platforms. # Allows deinstallation of gems with legacy platforms.
@ -615,44 +628,9 @@ class Gem::Specification
attr_accessor :specification_version attr_accessor :specification_version
class << self
def default_specifications_dir
File.join(Gem.default_dir, "specifications", "default")
end
def each_spec(search_dirs) # :nodoc:
search_dirs.each { |dir|
Dir[File.join(dir, "*.gemspec")].each { |path|
spec = Gem::Specification.load path.untaint
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
yield(spec) if spec
}
}
end
def each_default(&block) # :nodoc:
each_spec([default_specifications_dir],
&block)
end
def each_normal(&block) # :nodoc:
each_spec(dirs, &block)
end
end
def self._all # :nodoc: def self._all # :nodoc:
unless defined?(@@all) && @@all then unless defined?(@@all) && @@all then
@@all = stubs.map(&:to_spec)
specs = {}
each_default do |spec|
specs[spec.full_name] ||= spec
end
each_normal do |spec|
specs[spec.full_name] ||= spec
end
@@all = specs.values
# After a reset, make sure already loaded specs # After a reset, make sure already loaded specs
# are still marked as activated. # are still marked as activated.
@ -660,13 +638,58 @@ class Gem::Specification
Gem.loaded_specs.each_value{|s| specs[s] = true} Gem.loaded_specs.each_value{|s| specs[s] = true}
@@all.each{|s| s.activated = true if specs[s]} @@all.each{|s| s.activated = true if specs[s]}
_resort! _resort!(@@all)
end end
@@all @@all
end end
def self._resort! # :nodoc: def self._clear_load_cache # :nodoc:
@@all.sort! { |a, b| LOAD_CACHE.clear
end
# :nodoc:
def self.each_gemspec(dirs)
dirs.each do |dir|
Dir[File.join(dir, "*.gemspec")].each do |path|
yield path.untaint
end
end
end
# :nodoc:
def self.each_stub(dirs)
each_gemspec(dirs) do |path|
stub = Gem::StubSpecification.new(path)
yield stub if stub.valid?
end
end
# :nodoc:
def self.each_spec(dirs)
each_gemspec(dirs) do |path|
spec = self.load path
yield spec if spec
end
end
##
# Returns a Gem::StubSpecification for every installed gem
def self.stubs
@@stubs ||= begin
stubs = {}
each_stub([default_specifications_dir] + dirs) do |stub|
stubs[stub.full_name] ||= stub
end
stubs = stubs.values
_resort!(stubs)
stubs
end
end
def self._resort!(specs) # :nodoc:
specs.sort! { |a, b|
names = a.name <=> b.name names = a.name <=> b.name
next names if names.nonzero? next names if names.nonzero?
b.version <=> a.version b.version <=> a.version
@ -677,7 +700,9 @@ class Gem::Specification
# Loads the default specifications. It should be called only once. # Loads the default specifications. It should be called only once.
def self.load_defaults def self.load_defaults
each_default do |spec| each_spec([default_specifications_dir]) do |spec|
# #load returns nil if the spec is bad, so we just ignore
# it at this stage
Gem.register_default_spec(spec) Gem.register_default_spec(spec)
end end
end end
@ -700,7 +725,9 @@ class Gem::Specification
return if _all.include? spec return if _all.include? spec
_all << spec _all << spec
_resort! stubs << spec
_resort!(_all)
_resort!(stubs)
end end
## ##
@ -843,9 +870,10 @@ class Gem::Specification
# amongst the specs that are not activated. # amongst the specs that are not activated.
def self.find_inactive_by_path path def self.find_inactive_by_path path
self.find { |spec| stub = stubs.find { |s|
spec.contains_requirable_file? path unless spec.activated? s.contains_requirable_file? path unless s.activated?
} }
stub && stub.to_spec
end end
## ##
@ -937,6 +965,9 @@ class Gem::Specification
file = file.dup.untaint file = file.dup.untaint
return unless File.file?(file) return unless File.file?(file)
spec = LOAD_CACHE[file]
return spec if spec
code = if defined? Encoding code = if defined? Encoding
File.read file, :mode => 'r:UTF-8:-' File.read file, :mode => 'r:UTF-8:-'
else else
@ -950,6 +981,7 @@ class Gem::Specification
if Gem::Specification === spec if Gem::Specification === spec
spec.loaded_from = file.to_s spec.loaded_from = file.to_s
LOAD_CACHE[file] = spec
return spec return spec
end end
@ -1013,6 +1045,7 @@ class Gem::Specification
raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless
_all.include? spec _all.include? spec
_all.delete spec _all.delete spec
stubs.delete_if { |s| s.full_name == spec.full_name }
end end
## ##
@ -1037,6 +1070,8 @@ class Gem::Specification
@@dirs = nil @@dirs = nil
Gem.pre_reset_hooks.each { |hook| hook.call } Gem.pre_reset_hooks.each { |hook| hook.call }
@@all = nil @@all = nil
@@stubs = nil
_clear_load_cache
unresolved = unresolved_deps unresolved = unresolved_deps
unless unresolved.empty? then unless unresolved.empty? then
w = "W" + "ARN" w = "W" + "ARN"
@ -1280,20 +1315,6 @@ class Gem::Specification
@authors ||= [] @authors ||= []
end end
##
# Returns the full path to the base gem directory.
#
# eg: /usr/local/lib/ruby/gems/1.8
def base_dir
return Gem.dir unless loaded_from
@base_dir ||= if default_gem? then
File.dirname File.dirname File.dirname loaded_from
else
File.dirname File.dirname loaded_from
end
end
## ##
# Returns the full path to installed gem's bin directory. # Returns the full path to installed gem's bin directory.
# #
@ -1367,19 +1388,6 @@ class Gem::Specification
conflicts conflicts
end end
##
# Return true if this spec can require +file+.
def contains_requirable_file? file
root = full_gem_path
suffixes = Gem.suffixes
require_paths.any? do |lib|
base = "#{root}/#{lib}/#{file}"
suffixes.any? { |suf| File.file? "#{base}#{suf}" }
end
end
## ##
# The date this gem was created. Lazily defaults to TODAY. # The date this gem was created. Lazily defaults to TODAY.
@ -1623,35 +1631,14 @@ class Gem::Specification
spec spec
end end
## # :nodoc:
# The full path to the gem (install path + full name). def find_full_gem_path
super || File.expand_path(File.join(gems_dir, original_name))
def full_gem_path
# TODO: This is a heavily used method by gems, so we'll need
# to aleast just alias it to #gem_dir rather than remove it.
# TODO: also, shouldn't it default to full_name if it hasn't been written?
return @full_gem_path if defined?(@full_gem_path) && @full_gem_path
@full_gem_path = File.expand_path File.join(gems_dir, full_name)
@full_gem_path.untaint
return @full_gem_path if File.directory? @full_gem_path
@full_gem_path = File.expand_path File.join(gems_dir, original_name)
end end
private :find_full_gem_path
##
# 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 def full_name
@full_name ||= if platform == Gem::Platform::RUBY or platform.nil? then @full_name ||= super
"#{@name}-#{@version}".untaint
else
"#{@name}-#{@version}-#{platform}".untaint
end
end end
## ##
@ -1662,15 +1649,6 @@ class Gem::Specification
@gem_dir ||= File.expand_path File.join(gems_dir, full_name) @gem_dir ||= File.expand_path File.join(gems_dir, full_name)
end end
##
# Returns the full path to the gems directory containing this spec's
# gem directory. eg: /usr/local/lib/ruby/1.8/gems
def gems_dir
# TODO: this logic seems terribly broken, but tests fail if just base_dir
@gems_dir ||= File.join(loaded_from && base_dir || Gem.dir, "gems")
end
## ##
# Deprecated and ignored, defaults to true. # Deprecated and ignored, defaults to true.
# #
@ -1703,9 +1681,7 @@ class Gem::Specification
# :startdoc: # :startdoc:
def hash # :nodoc: def hash # :nodoc:
@@attributes.inject(0) { |hash_code, (name, _)| name.hash ^ version.hash
hash_code ^ self.send(name).hash
}
end end
def init_with coder # :nodoc: def init_with coder # :nodoc:
@ -1720,7 +1696,7 @@ class Gem::Specification
def initialize name = nil, version = nil def initialize name = nil, version = nil
@loaded = false @loaded = false
@activated = false @activated = false
@loaded_from = nil self.loaded_from = nil
@original_platform = nil @original_platform = nil
@@nil_attributes.each do |key| @@nil_attributes.each do |key|
@ -1729,11 +1705,7 @@ class Gem::Specification
@@non_nil_attributes.each do |key| @@non_nil_attributes.each do |key|
default = default_value(key) default = default_value(key)
value = case default value = Dupable[key] ? default.dup : default
when Time, Numeric, Symbol, true, false, nil then default
else default.dup
end
instance_variable_set "@#{key}", value instance_variable_set "@#{key}", value
end end
@ -1828,27 +1800,30 @@ class Gem::Specification
@licenses ||= [] @licenses ||= []
end end
## def filename= path
# Set the location a Specification was loaded from. +obj+ is converted super
# to a String.
def loaded_from= path
@loaded_from = path.to_s
# reset everything @loaded_from depends upon
@base_dir = nil
@bin_dir = nil @bin_dir = nil
@cache_dir = nil @cache_dir = nil
@cache_file = nil @cache_file = nil
@doc_dir = nil @doc_dir = nil
@full_gem_path = nil
@gem_dir = nil @gem_dir = nil
@gems_dir = nil
@ri_dir = nil @ri_dir = nil
@spec_dir = nil @spec_dir = nil
@spec_file = nil @spec_file = nil
end end
##
# Path this gemspec was loaded from. This attribute is not persisted.
alias loaded_from filename
##
# Set the location a Specification was loaded from. +obj+ is converted
# to a String.
alias loaded_from= filename=
## ##
# Sets the rubygems_version to the current RubyGems version. # Sets the rubygems_version to the current RubyGems version.
@ -1878,6 +1853,11 @@ class Gem::Specification
end end
end end
# Prevent ruby hitting spec.method_missing when [[spec]].flatten is called
def to_ary # :nodoc:
nil
end
## ##
# Normalize the list of files so that: # Normalize the list of files so that:
# * All file lists have redundancies removed. # * All file lists have redundancies removed.
@ -2093,6 +2073,13 @@ class Gem::Specification
[@name, @version, @new_platform == Gem::Platform::RUBY ? -1 : 1] [@name, @version, @new_platform == Gem::Platform::RUBY ? -1 : 1]
end end
##
# Used by Gem::DependencyResolver to order Gem::Specification objects
def source # :nodoc:
self
end
## ##
# Returns the full path to the directory containing this spec's # Returns the full path to the directory containing this spec's
# gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications # gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications
@ -2172,6 +2159,7 @@ class Gem::Specification
mark_version mark_version
result = [] result = []
result << "# -*- encoding: utf-8 -*-" result << "# -*- encoding: utf-8 -*-"
result << "#{Gem::StubSpecification::PREFIX}#{name} #{version} #{platform} #{require_paths.join("\0")}"
result << nil result << nil
result << "Gem::Specification.new do |s|" result << "Gem::Specification.new do |s|"
@ -2259,6 +2247,13 @@ class Gem::Specification
"#<Gem::Specification name=#{@name} version=#{@version}>" "#<Gem::Specification name=#{@name} version=#{@version}>"
end end
##
# Returns self
def to_spec
self
end
def to_yaml(opts = {}) # :nodoc: def to_yaml(opts = {}) # :nodoc:
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? then if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? then
# Because the user can switch the YAML engine behind our # Because the user can switch the YAML engine behind our
@ -2559,11 +2554,6 @@ class Gem::Specification
end end
end end
def default_gem?
loaded_from &&
File.dirname(loaded_from) == self.class.default_specifications_dir
end
extend Gem::Deprecate extend Gem::Deprecate
# TODO: # TODO:

View file

@ -0,0 +1,112 @@
module Gem
# Gem::StubSpecification reads the stub: line from the gemspec
# This prevents us having to eval the entire gemspec in order to
# find out certain information.
class StubSpecification < BasicSpecification
# :nodoc:
PREFIX = "# stub: "
# :nodoc:
class StubLine
attr_reader :parts
def initialize(data)
@parts = data[PREFIX.length..-1].split(" ")
end
def name
@parts[0]
end
def version
Gem::Version.new @parts[1]
end
def platform
Gem::Platform.new @parts[2]
end
def require_paths
@parts[3..-1].join(" ").split("\0")
end
end
def initialize(filename)
self.filename = filename
@data = nil
@spec = nil
end
##
# Name of the gem
def name
@name ||= data.name
end
##
# Version of the gem
def version
@version ||= data.version
end
##
# Platform of the gem
def platform
@platform ||= data.platform
end
##
# Require paths of the gem
def require_paths
@require_paths ||= data.require_paths
end
##
# The full Gem::Specification for this gem, loaded from evalling its gemspec
def to_spec
@spec ||= Gem::Specification.load(filename)
end
##
# True when this gem has been activated
def activated?
loaded = Gem.loaded_specs[name]
loaded && loaded.version == version
end
##
# Is this StubSpecification valid? i.e. have we found a stub line, OR does
# the filename contain a valid gemspec?
def valid?
data
end
private
##
# If the gemspec contains a stubline, returns a StubLine instance. Otherwise
# returns the full Gem::Specification.
def data
unless @data
File.open(filename, "r:UTF-8:-") do |file|
begin
file.readline # discard encoding line
stubline = file.readline.chomp
@data = StubLine.new(stubline) if stubline.start_with?(PREFIX)
rescue EOFError
end
end
end
@data ||= to_spec
end
end
end

View file

@ -78,6 +78,23 @@ end
class Gem::TestCase < MiniTest::Unit::TestCase class Gem::TestCase < MiniTest::Unit::TestCase
def assert_activate expected, *specs
specs.each do |spec|
case spec
when String then
Gem::Specification.find_by_name(spec).activate
when Gem::Specification then
spec.activate
else
flunk spec.inspect
end
end
loaded = Gem.loaded_specs.values.map(&:full_name)
assert_equal expected.sort, loaded.sort if expected
end
# TODO: move to minitest # TODO: move to minitest
def assert_path_exists path, msg = nil def assert_path_exists path, msg = nil
msg = message(msg) { "Expected path '#{path}' to exist" } msg = message(msg) { "Expected path '#{path}' to exist" }
@ -200,6 +217,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
@gemhome = File.join @tempdir, 'gemhome' @gemhome = File.join @tempdir, 'gemhome'
@userhome = File.join @tempdir, 'userhome' @userhome = File.join @tempdir, 'userhome'
ENV["GEM_SPEC_CACHE"] = File.join @tempdir, 'spec_cache'
@orig_ruby = if ENV['RUBY'] then @orig_ruby = if ENV['RUBY'] then
ruby = Gem.instance_variable_get :@ruby ruby = Gem.instance_variable_get :@ruby
@ -221,6 +239,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase
FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @gemhome
FileUtils.mkdir_p @userhome FileUtils.mkdir_p @userhome
@orig_gem_private_key_passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE
@default_dir = File.join @tempdir, 'default' @default_dir = File.join @tempdir, 'default'
@default_spec_dir = File.join @default_dir, "specifications", "default" @default_spec_dir = File.join @default_dir, "specifications", "default"
Gem.instance_variable_set :@default_dir, @default_dir Gem.instance_variable_set :@default_dir, @default_dir
@ -266,39 +287,6 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end end
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
# TODO: move to installer test cases
Gem.post_build_hooks.clear
Gem.post_install_hooks.clear
Gem.done_installing_hooks.clear
Gem.post_reset_hooks.clear
Gem.post_uninstall_hooks.clear
Gem.pre_install_hooks.clear
Gem.pre_reset_hooks.clear
Gem.pre_uninstall_hooks.clear
# TODO: move to installer test cases
Gem.post_build do |installer|
@post_build_hook_arg = installer
true
end
Gem.post_install do |installer|
@post_install_hook_arg = installer
end
Gem.post_uninstall do |uninstaller|
@post_uninstall_hook_arg = uninstaller
end
Gem.pre_install do |installer|
@pre_install_hook_arg = installer
true
end
Gem.pre_uninstall do |uninstaller|
@pre_uninstall_hook_arg = uninstaller
end
end end
## ##
@ -332,6 +320,47 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end end
Gem.instance_variable_set :@default_dir, nil Gem.instance_variable_set :@default_dir, nil
ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = @orig_gem_private_key_passphrase
Gem::Specification._clear_load_cache
end
def common_installer_setup
common_installer_teardown
Gem.post_build do |installer|
@post_build_hook_arg = installer
true
end
Gem.post_install do |installer|
@post_install_hook_arg = installer
end
Gem.post_uninstall do |uninstaller|
@post_uninstall_hook_arg = uninstaller
end
Gem.pre_install do |installer|
@pre_install_hook_arg = installer
true
end
Gem.pre_uninstall do |uninstaller|
@pre_uninstall_hook_arg = uninstaller
end
end
def common_installer_teardown
Gem.post_build_hooks.clear
Gem.post_install_hooks.clear
Gem.done_installing_hooks.clear
Gem.post_reset_hooks.clear
Gem.post_uninstall_hooks.clear
Gem.pre_install_hooks.clear
Gem.pre_reset_hooks.clear
Gem.pre_uninstall_hooks.clear
end end
## ##
@ -560,6 +589,21 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end end
end end
def loaded_spec_names
Gem.loaded_specs.values.map(&:full_name).sort
end
def unresolved_names
Gem::Specification.unresolved_deps.values.map(&:to_s).sort
end
def save_loaded_features
old_loaded_features = $LOADED_FEATURES.dup
yield
ensure
$LOADED_FEATURES.replace old_loaded_features
end
## ##
# Create a new spec (or gem if passed an array of files) and set it # Create a new spec (or gem if passed an array of files) and set it
# up properly. Use this instead of util_spec and util_gem. # up properly. Use this instead of util_spec and util_gem.
@ -1005,6 +1049,24 @@ Also, a list:
Gem::Dependency.new name, *requirements Gem::Dependency.new name, *requirements
end end
##
# Constructs a Gem::DependencyResolver::DependencyRequest from a
# Gem::Dependency +dep+, a +from_name+ and +from_version+ requesting the
# dependency and a +parent+ DependencyRequest
def dependency_request dep, from_name, from_version, parent = nil
remote = Gem::Source.new @uri
parent ||= Gem::DependencyResolver::DependencyRequest.new \
dep, nil
spec = Gem::DependencyResolver::IndexSpecification.new \
nil, from_name, from_version, remote, Gem::Platform::RUBY
activation = Gem::DependencyResolver::ActivationRequest.new spec, parent
Gem::DependencyResolver::DependencyRequest.new dep, activation
end
## ##
# Constructs a new Gem::Requirement. # Constructs a new Gem::Requirement.
@ -1074,18 +1136,18 @@ Also, a list:
end end
## ##
# Loads an RSA private key named +key_name+ in <tt>test/rubygems/</tt> # Loads an RSA private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt>
def self.load_key key_name def self.load_key key_name, passphrase = nil
key_file = key_path key_name key_file = key_path key_name
key = File.read key_file key = File.read key_file
OpenSSL::PKey::RSA.new key OpenSSL::PKey::RSA.new key, passphrase
end end
## ##
# Returns the path tot he key named +key_name+ from <tt>test/rubygems</tt> # Returns the path to the key named +key_name+ from <tt>test/rubygems</tt>
def self.key_path key_name def self.key_path key_name
File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__ File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__
@ -1094,17 +1156,24 @@ Also, a list:
# :stopdoc: # :stopdoc:
# only available in RubyGems tests # only available in RubyGems tests
begin PRIVATE_KEY_PASSPHRASE = 'Foo bar'
PRIVATE_KEY = load_key 'private'
PRIVATE_KEY_PATH = key_path 'private'
PUBLIC_KEY = PRIVATE_KEY.public_key
PUBLIC_CERT = load_cert 'public' begin
PUBLIC_CERT_PATH = cert_path 'public' PRIVATE_KEY = load_key 'private'
PRIVATE_KEY_PATH = key_path 'private'
# ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY = load_key 'encrypted_private', PRIVATE_KEY_PASSPHRASE
ENCRYPTED_PRIVATE_KEY_PATH = key_path 'encrypted_private'
PUBLIC_KEY = PRIVATE_KEY.public_key
PUBLIC_CERT = load_cert 'public'
PUBLIC_CERT_PATH = cert_path 'public'
rescue Errno::ENOENT rescue Errno::ENOENT
PRIVATE_KEY = nil PRIVATE_KEY = nil
PUBLIC_KEY = nil PUBLIC_KEY = nil
PUBLIC_CERT = nil PUBLIC_CERT = nil
end end if defined?(OpenSSL::SSL)
end end

View file

@ -43,14 +43,15 @@ class Gem::Uninstaller
def initialize(gem, options = {}) def initialize(gem, options = {})
# TODO document the valid options # TODO document the valid options
@gem = gem @gem = gem
@version = options[:version] || Gem::Requirement.default @version = options[:version] || Gem::Requirement.default
@gem_home = File.expand_path(options[:install_dir] || Gem.dir) @gem_home = File.expand_path(options[:install_dir] || Gem.dir)
@force_executables = options[:executables] @force_executables = options[:executables]
@force_all = options[:all] @force_all = options[:all]
@force_ignore = options[:ignore] @force_ignore = options[:ignore]
@bin_dir = options[:bin_dir] @bin_dir = options[:bin_dir]
@format_executable = options[:format_executable] @format_executable = options[:format_executable]
@abort_on_dependent = options[:abort_on_dependent]
# Indicate if development dependencies should be checked when # Indicate if development dependencies should be checked when
# uninstalling. (default: false) # uninstalling. (default: false)
@ -143,7 +144,7 @@ class Gem::Uninstaller
@spec = spec @spec = spec
unless dependencies_ok? spec unless dependencies_ok? spec
unless ask_if_ok(spec) if abort_on_dependent? || !ask_if_ok(spec)
raise Gem::DependencyRemovalException, raise Gem::DependencyRemovalException,
"Uninstallation aborted due to dependent gem(s)" "Uninstallation aborted due to dependent gem(s)"
end end
@ -290,6 +291,10 @@ class Gem::Uninstaller
deplist.ok_to_remove?(spec.full_name, @check_dev) deplist.ok_to_remove?(spec.full_name, @check_dev)
end end
def abort_on_dependent?
@abort_on_dependent
end
def ask_if_ok(spec) def ask_if_ok(spec)
msg = [''] msg = ['']
msg << 'You have requested to uninstall the gem:' msg << 'You have requested to uninstall the gem:'

View file

@ -0,0 +1,39 @@
require 'uri'
class Gem::UriFormatter
attr_reader :uri
def initialize uri
@uri = uri
end
def escape
return unless @uri
escaper.escape @uri
end
##
# Normalize the URI by adding "http://" if it is missing.
def normalize
(@uri =~ /^(https?|ftp|file):/i) ? @uri : "http://#{@uri}"
end
def unescape
return unless @uri
escaper.unescape @uri
end
private
def escaper
@uri_parser ||=
begin
URI::Parser.new
rescue NameError
URI
end
end
end

44
lib/rubygems/util/list.rb Normal file
View file

@ -0,0 +1,44 @@
module Gem
List = Struct.new(:value, :tail)
class List
def each
n = self
while n
yield n.value
n = n.tail
end
end
def to_a
ary = []
n = self
while n
ary.unshift n.value
n = n.tail
end
ary
end
def find
n = self
while n
v = n.value
return v if yield(v)
n = n.tail
end
nil
end
def prepend(value)
List.new value, self
end
def self.prepend(list, value)
return List.new(value) unless list
List.new value, list
end
end
end

View file

@ -147,13 +147,16 @@ class Gem::Version
# FIX: These are only used once, in .correct?. Do they deserve to be # FIX: These are only used once, in .correct?. Do they deserve to be
# constants? # constants?
VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc: VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc: ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
## ##
# A string representation of this Version. # A string representation of this Version.
attr_reader :version def version
@version.dup
end
alias to_s version alias to_s version
## ##
@ -183,6 +186,12 @@ class Gem::Version
end end
end end
@@all = {}
def self.new version
@@all[version] ||= super
end
## ##
# Constructs a Version from the +version+ string. A version string is a # Constructs a Version from the +version+ string. A version string is a
# series of digits or ASCII letters separated by dots. # series of digits or ASCII letters separated by dots.
@ -191,7 +200,8 @@ class Gem::Version
raise ArgumentError, "Malformed version number string #{version}" unless raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version) self.class.correct?(version)
@version = version.to_s.dup.strip @version = version.to_s.strip.gsub("-",".pre.")
@segments = nil
end end
## ##

View file

@ -42,6 +42,7 @@ module Gem::VersionOption
add_option("--[no-]prerelease", add_option("--[no-]prerelease",
"Allow prerelease versions of a gem", *wrap) do |value, options| "Allow prerelease versions of a gem", *wrap) do |value, options|
options[:prerelease] = value options[:prerelease] = value
options[:explicit_prerelease] = true
end end
end end
@ -50,14 +51,19 @@ module Gem::VersionOption
def add_version_option(task = command, *wrap) def add_version_option(task = command, *wrap)
OptionParser.accept Gem::Requirement do |value| OptionParser.accept Gem::Requirement do |value|
Gem::Requirement.new value Gem::Requirement.new(*value.split(/\s*,\s*/))
end end
add_option('-v', '--version VERSION', Gem::Requirement, add_option('-v', '--version VERSION', Gem::Requirement,
"Specify version of gem to #{task}", *wrap) do "Specify version of gem to #{task}", *wrap) do
|value, options| |value, options|
options[:version] = value options[:version] = value
options[:prerelease] = true if value.prerelease?
explicit_prerelease_set = !options[:explicit_prerelease].nil?
options[:explicit_prerelease] = false unless explicit_prerelease_set
options[:prerelease] = value.prerelease? unless
options[:explicit_prerelease]
end end
end end

View file

@ -43,3 +43,26 @@ ySjIblqVQkPuzebv3Ror6ZnVDukn96Mg7kP4u6zgxOeqlJGRe1M949SS9Vudjl8X
SF4aZUUB9pQGhsqQJVqaz2OlhGOp9D0q54xko/rekjAIcuDjl1mdX4F2WRrzpUmZ SF4aZUUB9pQGhsqQJVqaz2OlhGOp9D0q54xko/rekjAIcuDjl1mdX4F2WRrzpUmZ
uY/bPeOBYiVsOYVe uY/bPeOBYiVsOYVe
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIJANz6ehBcVuuiMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTMwNTAxMTQ0NTQxWhcNMjMwMzEwMTQ0NTQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAzlpZwhEYoALOEKU4lmMw5l3YI/gadzDOoELtdcidvVvovKK8IIOTDwbA
3XcjwV0UPGEPOK4Uk1aD0EKkOQVg8ivSre2a3FFGffs2kXck+doJMzAA+pf8tvFk
QsETVOurOp74GN+er2xbbRSDVxQKq6d+QTe1E60btyXQS5M1Nt5SvLn8dazZJgvv
3yzJQ1IOQl+xeEO0WVVhPIx5Mx3VtjjcDyl8aewPkYkzia6UOrAyQZnl5sIzWGOb
kYKCNeKjTPepzlbMx0dN6jBupPYGNB+4FYY9GezInjGbRP5np5382wd3EWwsVzic
Nau8kXHTL2r7GzNvoy0p//iPCqx9FQIDAQABo4GnMIGkMB0GA1UdDgQWBBS7B027
H/ZIkW3ngm1SrR0X/aTCwDB1BgNVHSMEbjBsgBS7B027H/ZIkW3ngm1SrR0X/aTC
wKFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJANz6ehBcVuuiMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAC0glUrUiylTfuOWlwkQvi74oiYC5CzW
Jfusg6o/Gg1XEuJhaHiYMsK/do16gSc6Za3934rHQbYu3mesyFkCWF9kD4J6/hEO
OQL8xmmgN7wS6GXy6oIODpny0MgnFrV4gd1aEx69NIfL/wXaM8Gw2sj1TnuGLs8+
HFmWLRRH3WSR7ZLnqYzPVJwhHu8vtZBL9HZk1J6xyq00Nwi2Cz5WdiHamgaza3TS
OgBdWwDeSClwhrTJni4d30dbq+eNMByIZ7QNGBQivpFzDxeNV/2UBrTU0CilKG5Q
j7ZwknfKeA4xUTd8TMK3vKab5JJCfjbXOTHZQsYUcEEGSjOMS8/YVQs=
-----END CERTIFICATE-----

49
test/rubygems/client.pem Normal file
View file

@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDgTCCAmmgAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUx
EzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZDAeFw0xMzA1MDExNTAxMzFaFw0yMzAzMTAxNTAxMzFaMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw
+lcrpdWcloQCgAlxcM3GjvBxZ3yjzi6SgXKRBSi54i0J1LXxznWKcJ5P/O1+j+7i
LjHK+OWqsa0+EbKTwSu+0tx20h0z++YJF9GWEoCwT5aH1kor/0+EQLgYnxBaF8GC
2xAbkRkWmbSu2aLDIey3lg7lqAazYqdS2wH0UjSDjFKDLxz9LwpfFm0yGL3DgwLW
+dobYkgt1A6F/8Pz6D2FjwYKcM8JE6w7KJSJDUvXcv2E18wmhZ/qF/MtFAF4coB1
f5ALnz8YqY6eyDF5aY/VfaHZvXdirLlMH6/miie9GBVMnJWF0ah5ssbsMvcpmnDJ
qkiYju2e1oLFEE7zztU/AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgEN
BB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTcOELj
hSUdiLrdRF3CFZDZkWaGzDAfBgNVHSMEGDAWgBS7B027H/ZIkW3ngm1SrR0X/aTC
wDANBgkqhkiG9w0BAQUFAAOCAQEAlQMzHlnT6L1qqA4hL6tABPbiMsVwXyKCcfNB
zBn82Wkxgbg7Mp31fbR6/qvGeXOtaX6IdPdgtVf8nh1NURk0MmFBP+gfnwfNBD+m
Q1cldDt9kY2LGIrPii40xbugF1/xqEYcZMgXU08aEvQ2IHX46J8wZoqMa2KhrU8/
mzY0F+UEFOGWtKDgUzz3dyBPsdzVrX+SXULwH0lqZX8Nsw5LyfrlVt3xQvS5Ogm4
kYlt8kqhF8lUS3WTbuADrIs3NaDPRWSs1iLRRFgosgUtHN7tkrkrVaHeBo0KbAJG
mMqtxSY0XZI9WBxffP9UtoY3EiTWNVWLtuCN3OSvryP6NDe4BA==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsPpXK6XVnJaEAoAJcXDNxo7wcWd8o84ukoFykQUoueItCdS1
8c51inCeT/ztfo/u4i4xyvjlqrGtPhGyk8ErvtLcdtIdM/vmCRfRlhKAsE+Wh9ZK
K/9PhEC4GJ8QWhfBgtsQG5EZFpm0rtmiwyHst5YO5agGs2KnUtsB9FI0g4xSgy8c
/S8KXxZtMhi9w4MC1vnaG2JILdQOhf/D8+g9hY8GCnDPCROsOyiUiQ1L13L9hNfM
JoWf6hfzLRQBeHKAdX+QC58/GKmOnsgxeWmP1X2h2b13Yqy5TB+v5oonvRgVTJyV
hdGoebLG7DL3KZpwyapImI7tntaCxRBO887VPwIDAQABAoIBAFOpdG3gzlNg3/Ti
nBQxdEVqKwYhGs3A2UlOwl8F5lPBNPNRx9UQeYZBaMV9VrQezJnFpqpB8Sg5KCGQ
ci/hAJIL0kalW0LI0Nz5ko10H7u5U/rQ9W1JG0j041JYV32Pf14husKdXBPQA5co
sQW30tSSrmYogUpp15mWiJz8A3EvqiCTlQv5JwwMFGnjVl8+HNfuLghK/vqY/Eb9
YmwTKxPFejqN7E0Mud2ylNiuPTSLwBy8UvV9uxOlDc6lMyZjVRO0woiEzrjw5dKF
yf5tUkICRcPkekcx+XtpGrCMlRLl770bZBZX+YNmbYXVWhFp09cNR+U0KZqPNcDp
jg73vXECgYEA3huOKzfHGt3qUdMlEHd1FvQhW9fYIrmUSnuVYQJOnY8lFfKfmrOH
gpwOIHDNiVHYlhAJaNocCLYx4hWHgZXarY7NKxmlY2+Vp8mcCIf2Cw3Kr/sFklUJ
KpiRxqEPGR7U4C/E31kkH/C+w7m9Zh3ndhltU2Pki9/Eq0lk8YClMMkCgYEAy/vU
jxzviIk8bll5uCIuXJyCfao7ywaZABbL6a20kdVGKrHj57O/OJ2WZVwBihhB7OS+
QsKC/J8LrUJkobOFtQvQ8O23uep5rB6kqCkXsXCG4SCl2L5xZySBp/qhiqbuMwvp
EAWPSIA6UNoR0J2rDYVmq6jtY526wQf5ivE8IccCgYEAphfzJAyNH2FOZixArmS2
shiUjasG3UjsRRrP5YClK5wtPpF2m2if8KMkyUux2HvVPLr3XmqkxjsBaLFy6QwY
QOvmL9H45Tg/sP7KaXLLIw8IQLu2OezPcwQvF1u//6gXxyLR1bhClIQjFBjlMuUv
/xgasl6kPZlz6Cd1jkgGwEkCgYAI1IT2EQWZfn9cM4leXDRvk+LeN8FQ35897r6z
Be78JSRdcsfv3ssXU1MQXjQ+2x/3dkt6LltnPidOP8KFcXUHSlSoKVI7vRe5SLZO
BUFeUAW2tygWwt+73Eu0jtfxXZqQISLcq7DxLYPYvifpRPoDotO3+J8WIdzUwFig
GCNHPwKBgHqXOyRef7ykVUCptMf61/BvNU8NP1f9PkKQBMYQZC39UwqEQ675QBUh
hSG9t/kyc44zUVmBeKIlWHVyLQ83Dv+ennz/D9t7tstet0VMKvALNdiVT0sjFKN7
1VINygCeFkqrlTXlOwFcRSo1gHn3/JIrhSgRuYKHSf0GZOcN6d9l
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,1AC78C928C296A1D7C70D525B0F1051C
QL7dLRBmNpbSYsq+4niIdtP9LpJYQxG9tXaTKjQfgkYtLbDzhQMhxpKcJwCTtZUK
kJWxt7AOq8JwvvH69kp8fEULR5IThSPyFTjnLxtg1ZpMJZfHyfjAtveBO+Z4pCWA
Z6xrLI7RoFEVuSEgAkNYlb2JY4Z26nfCakvciEpHOkeYEYsneBQkr7Zf/IcKuKwd
wjOMzuLwvF3+cYaxcoHViRsuwyI6YrToJvPtin0xJlJczWalVSQciwjuDDGfjzow
J2o1O5UZc+VnEItpIbLWriRQPGP5ezOiTUCCxN+v/lignaeykfk+apAiKliKl2w6
eyxfBAIt8yE3RyhE3mX+AZN8sX+mfduEXCcAziZLSTYm3Lfq90eKGs+cUMFmwz1N
NvFVfIHpiRSzKlrJlvd38SRbSbQfvS2OEo+e0f4ZW7cKCXayczwF0gQQY9VZ23bn
Sk1CGuA2ugn+cd9T/yrSTtgz1EDpZxp7HYE242DiJb7wUY30nAqgYZ//ug6HGBJA
OYQldiinj6lWr0i/jEdKknUKIZTQQ+aH0c+hvbsagQRoVFZUCG6RFbKtWHRxL/a0
teMT1SFeab6pulh3/VfdLzdBKVvHaY3bpujAmOg4lq0O2MQWMGvIPdso9iTBoAJm
TrLR/YO0RfvnfC0uM2YHXcLlhgsBUiGQUNnk6EZ5qK2aEiZuaCecpsCYEt2uhO9W
HF7CpAh3T1OUY33HEw/4KdvMG+5uwK+4D1JatKHsU0Umpp2+2C9T6W/iSLXndg0L
Xr8NFu9ziXdEe4tZy/9VDo4QOnqFhSBXxkimGrdnUrbTxH4nwUzmv4VRnbAXTEJM
XkVat7zZ1dvUf+iJXiRxjo6BbwXtL6+ZmL1aYbnbN8HrQdhuFN/QD/OzhYj7f9Yn
sTSQUleAK1+sppcTs6tiEdxWBgnKUeQNCXEBXG4twy7rd2ymamvunBTaoywebcaG
RTnK8eyOkoDeVEFZx+EI2TrG2PaA0Zuq+7IYqID+6/asa4K/3J/ChXqjIAgqUcML
56DlF5DCTvaRRUwftARaOqJZ+VxoW62i30nP/oD35xh++Esf8YgxhPeg1Gjzozx7
ZC1GZ5f44EvDJyFlXUUNtNy3dC3cSdUUM6oYvDLrPI3wVEw3QgLUJ+Tc8lA5Gx7M
wW2i/Y6JqlVUabvkaKe4d+w8eo219Bnfo7D199TppbEXOob6AaC2CJranActTfrm
fFrWQKJrdWz1mWZT3efoBpxVAds8fYk2hNaXL6LQepOAF6ObbS4hHcRHbI7HIdVB
6GNUfVWlrISZ6thj84way/niR1ikXUFipN5gCRERc0+brXK4OCnksyLqYgvMI74Z
5lW8HfuX4FNp/Gd5uU+tbYnNy6nIqa8oZScLp0Kjg9tPKjjrDbZS2LJ8kxf7q9lb
YbxhzMy+uKwdmxIB4fKjWZTgPX4MwjA8FAaMncyvA64rxGnfyLExmOOZWSXqZQ8z
y+xoqA239Wob98mJn+oluneMKwSAM3ActGTmp5X5jHVk++yEcJN9uGYAa3UohKlm
/wgpQ79yfBywju2rZR0hQXN0ExBdE/UnJucJMv/iB5fxlkJlkNJPwFgq8iMbzQLu
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDgTCCAmmgAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQVUx
EzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZDAeFw0xMzA1MDExNTAxMzFaFw0yMzAzMTAxNTAxMzFaMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eXXXdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw
+lcrpdWcloQCgAlxcM3GjvBxZ3yjzi6SgXKRBSi54i0J1LXxznWKcJ5P/O1+j+7i
LjHK+OWqsa0+EbKTwSu+0tx20h0z++YJF9GWEoCwT5aH1kor/0+EQLgYnxBaF8GC
2xAbkRkWmbSu2aLDIey3lg7lqAazYqdS2wH0UjSDjFKDLxz9LwpfFm0yGL3DgwLW
+dobYkgt1A6F/8Pz6D2FjwYKcM8JE6w7KJSJDUvXcv2E18wmhZ/qF/MtFAF4coB1
f5ALnz8YqY6eyDF5aY/VfaHZvXdirLlMH6/miie9GBVMnJWF0ah5ssbsMvcpmnDJ
qkiYju2e1oLFEE7zztU/AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgEN
BB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTcOELj
hSUdiLrdRF3CFZDZkWaGzDAfBgNVHSMEGDAWgBS7B027H/ZIkW3ngm1SrR0X/aTC
wDANBgkqhkiG9w0BAQUFAAOCAQEAlQMzHlnT6L1qqA4hL6tABPbiMsVwXyKCcfNB
zBn82Wkxgbg7Mp31fbR6/qvGeXOtaX6IdPdgtVf8nh1NURk0MmFBP+gfnwfNBD+m
Q1cldDt9kY2LGIrPii40xbugF1/xqEYcZMgXU08aEvQ2IHX46J8wZoqMa2KhrU8/
mzY0F+UEFOGWtKDgUzz3dyBPsdzVrX+SXULwH0lqZX8Nsw5LyfrlVt3xQvS5Ogm4
kYlt8kqhF8lUS3WTbuADrIs3NaDPRWSs1iLRRFgosgUtHN7tkrkrVaHeBo0KbAJG
mMqtxSY0XZI9WBxffP9UtoY3EiTWNVWLtuCN3OSvryP6NDe4BA==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsPpXK6XVnJaEAoAJcXDNxo7wcWd8o84ukoFykQUoueItCdS1
8c51inCeT/ztfo/u4i4xyvjlqrGtPhGyk8ErvtLcdtIdM/vmCRfRlhKAsE+Wh9ZK
K/9PhEC4GJ8QWhfBgtsQG5EZFpm0rtmiwyHst5YO5agGs2KnUtsB9FI0g4xSgy8c
/S8KXxZtMhi9w4MC1vnaG2JILdQOhf/D8+g9hY8GCnDPCROsOyiUiQ1L13L9hNfM
JoWf6hfzLRQBeHKAdX+QC58/GKmOnsgxeWmP1X2h2b13Yqy5TB+v5oonvRgVTJyV
hdGoebLG7DL3KZpwyapImI7tntaCxRBO887VPwIDAQABAoIBAFOpdG3gzlNg3/Ti
nBQxdEVqKwYhGs3A2UlOwl8F5lPBNPNRx9UQeYZBaMV9VrQezJnFpqpB8Sg5KCGQ
ci/hAJIL0kalW0LI0Nz5ko10H7u5U/rQ9W1JG0j041JYV32Pf14husKdXBPQA5co
sQW30tSSrmYogUpp15mWiJz8A3EvqiCTlQv5JwwMFGnjVl8+HNfuLghK/vqY/Eb9
YmwTKxPFejqN7E0Mud2ylNiuPTSLwBy8UvV9uxOlDc6lMyZjVRO0woiEzrjw5dKF
yf5tUkICRcPkekcx+XtpGrCMlRLl770bZBZX+YNmbYXVWhFp09cNR+U0KZqPNcDp
jg73vXECgYEA3huOKzfHGt3qUdMlEHd1FvQhW9fYIrmUSnuVYQJOnY8lFfKfmrOH
gpwOIHDNiVHYlhAJaNocCLYx4hWHgZXarY7NKxmlY2+Vp8mcCIf2Cw3Kr/sFklUJ
KpiRxqEPGR7U4C/E31kkH/C+w7m9Zh3ndhltU2Pki9/Eq0lk8YClMMkCgYEAy/vU
jxzviIk8bll5uCIuXJyCfao7ywaZABbL6a20kdVGKrHj57O/OJ2WZVwBihhB7OS+
QsKC/J8LrUJkobOFtQvQ8O23uep5rB6kqCkXsXCG4SCl2L5xZySBp/qhiqbuMwvp
EAWPSIA6UNoR0J2rDYVmq6jtY526wQf5ivE8IccCgYEAphfzJAyNH2FOZixArmS2
shiUjasG3UjsRRrP5YClK5wtPpF2m2if8KMkyUux2HvVPLr3XmqkxjsBaLFy6QwY
QOvmL9H45Tg/sP7KaXLLIw8IQLu2OezPcwQvF1u//6gXxyLR1bhClIQjFBjlMuUv
/xgasl6kPZlz6Cd1jkgGwEkCgYAI1IT2EQWZfn9cM4leXDRvk+LeN8FQ35897r6z
Be78JSRdcsfv3ssXU1MQXjQ+2x/3dkt6LltnPidOP8KFcXUHSlSoKVI7vRe5SLZO
BUFeUAW2tygWwt+73Eu0jtfxXZqQISLcq7DxLYPYvifpRPoDotO3+J8WIdzUwFig
GCNHPwKBgHqXOyRef7ykVUCptMf61/BvNU8NP1f9PkKQBMYQZC39UwqEQ675QBUh
hSG9t/kyc44zUVmBeKIlWHVyLQ83Dv+ennz/D9t7tstet0VMKvALNdiVT0sjFKN7
1VINygCeFkqrlTXlOwFcRSo1gHn3/JIrhSgRuYKHSf0GZOcN6d9l
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,9 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = "bar"
s.version = "0.0.2"
s.platform = "ruby"
s.require_paths = ["lib"]
s.summary = "A very bar gem"
end

Binary file not shown.

View file

@ -15,6 +15,7 @@ class TestGem < Gem::TestCase
def setup def setup
super super
common_installer_setup
ENV.delete 'RUBYGEMS_GEMDEPS' ENV.delete 'RUBYGEMS_GEMDEPS'
@additional = %w[a b].map { |d| File.join @tempdir, d } @additional = %w[a b].map { |d| File.join @tempdir, d }
@ -22,119 +23,6 @@ class TestGem < Gem::TestCase
util_remove_interrupt_command util_remove_interrupt_command
end end
def assert_activate expected, *specs
specs.each do |spec|
case spec
when String then
Gem::Specification.find_by_name(spec).activate
when Gem::Specification then
spec.activate
else
flunk spec.inspect
end
end
loaded = Gem.loaded_specs.values.map(&:full_name)
assert_equal expected.sort, loaded.sort if expected
end
def test_self_activate
foo = util_spec 'foo', '1'
assert_activate %w[foo-1], foo
end
def loaded_spec_names
Gem.loaded_specs.values.map(&:full_name).sort
end
def unresolved_names
Gem::Specification.unresolved_deps.values.map(&:to_s).sort
end
# TODO: move these to specification
def test_self_activate_via_require
a1 = new_spec "a", "1", "b" => "= 1"
b1 = new_spec "b", "1", nil, "lib/b/c.rb"
b2 = new_spec "b", "2", nil, "lib/b/c.rb"
install_specs a1, b1, b2
a1.activate
save_loaded_features do
require "b/c"
end
assert_equal %w(a-1 b-1), loaded_spec_names
end
# TODO: move these to specification
def test_self_activate_deep_unambiguous
a1 = new_spec "a", "1", "b" => "= 1"
b1 = new_spec "b", "1", "c" => "= 1"
b2 = new_spec "b", "2", "c" => "= 2"
c1 = new_spec "c", "1"
c2 = new_spec "c", "2"
install_specs a1, b1, b2, c1, c2
a1.activate
assert_equal %w(a-1 b-1 c-1), loaded_spec_names
end
def save_loaded_features
old_loaded_features = $LOADED_FEATURES.dup
yield
ensure
$LOADED_FEATURES.replace old_loaded_features
end
# TODO: move these to specification
def test_self_activate_ambiguous_direct
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0"
b1 = new_spec("b", "1", { "c" => ">= 1" }, "lib/d.rb")
b2 = new_spec("b", "2", { "c" => ">= 2" }, "lib/d.rb")
c1 = new_spec "c", "1"
c2 = new_spec "c", "2"
Gem::Specification.reset
install_specs a1, b1, b2, c1, c2
a1.activate
assert_equal %w(a-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
require "d"
assert_equal %w(a-1 b-2 c-2), loaded_spec_names
assert_equal [], unresolved_names
end
end
# TODO: move these to specification
def test_self_activate_ambiguous_indirect
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0"
b1 = new_spec "b", "1", "c" => ">= 1"
b2 = new_spec "b", "2", "c" => ">= 2"
c1 = new_spec "c", "1", nil, "lib/d.rb"
c2 = new_spec "c", "2", nil, "lib/d.rb"
install_specs a1, b1, b2, c1, c2
a1.activate
assert_equal %w(a-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
require "d"
assert_equal %w(a-1 b-2 c-2), loaded_spec_names
assert_equal [], unresolved_names
end
end
def test_self_finish_resolve def test_self_finish_resolve
save_loaded_features do save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0" a1 = new_spec "a", "1", "b" => "> 0"
@ -157,36 +45,6 @@ class TestGem < Gem::TestCase
end end
end end
def test_self_activate_via_require_wtf
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0", "d" => "> 0" # this
b1 = new_spec "b", "1", { "c" => ">= 1" }, "lib/b.rb"
b2 = new_spec "b", "2", { "c" => ">= 2" }, "lib/b.rb" # this
c1 = new_spec "c", "1"
c2 = new_spec "c", "2" # this
d1 = new_spec "d", "1", { "c" => "< 2" }, "lib/d.rb"
d2 = new_spec "d", "2", { "c" => "< 2" }, "lib/d.rb" # this
install_specs a1, b1, b2, c1, c2, d1, d2
a1.activate
assert_equal %w(a-1), loaded_spec_names
assert_equal ["b (> 0)", "d (> 0)"], unresolved_names
require "b"
e = assert_raises Gem::LoadError do
require "d"
end
assert_equal "unable to find a version of 'd' to activate", e.message
assert_equal %w(a-1 b-2 c-2), loaded_spec_names
assert_equal ["d (> 0)"], unresolved_names
end
end
def test_self_finish_resolve_wtf def test_self_finish_resolve_wtf
save_loaded_features do save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0", "d" => "> 0" # this a1 = new_spec "a", "1", "b" => "> 0", "d" => "> 0" # this
@ -211,94 +69,6 @@ class TestGem < Gem::TestCase
end end
end end
# TODO: move these to specification
def test_self_activate_ambiguous_unrelated
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0"
b1 = new_spec "b", "1", "c" => ">= 1"
b2 = new_spec "b", "2", "c" => ">= 2"
c1 = new_spec "c", "1"
c2 = new_spec "c", "2"
d1 = new_spec "d", "1", nil, "lib/d.rb"
install_specs a1, b1, b2, c1, c2, d1
a1.activate
assert_equal %w(a-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
require "d"
assert_equal %w(a-1 d-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
end
end
# TODO: move these to specification
def test_self_activate_ambiguous_indirect_conflict
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0"
a2 = new_spec "a", "2", "b" => "> 0"
b1 = new_spec "b", "1", "c" => ">= 1"
b2 = new_spec "b", "2", "c" => ">= 2"
c1 = new_spec "c", "1", nil, "lib/d.rb"
c2 = new_spec("c", "2", { "a" => "1" }, "lib/d.rb") # conflicts with a-2
install_specs a1, a2, b1, b2, c1, c2
a2.activate
assert_equal %w(a-2), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
require "d"
assert_equal %w(a-2 b-1 c-1), loaded_spec_names
assert_equal [], unresolved_names
end
end
# TODO: move these to specification
def test_require_already_activated
save_loaded_features do
a1 = new_spec "a", "1", nil, "lib/d.rb"
install_specs a1 # , a2, b1, b2, c1, c2
a1.activate
assert_equal %w(a-1), loaded_spec_names
assert_equal [], unresolved_names
assert require "d"
assert_equal %w(a-1), loaded_spec_names
assert_equal [], unresolved_names
end
end
# TODO: move these to specification
def test_require_already_activated_indirect_conflict
save_loaded_features do
a1 = new_spec "a", "1", "b" => "> 0"
a2 = new_spec "a", "2", "b" => "> 0"
b1 = new_spec "b", "1", "c" => ">= 1"
b2 = new_spec "b", "2", "c" => ">= 2"
c1 = new_spec "c", "1", nil, "lib/d.rb"
c2 = new_spec("c", "2", { "a" => "1" }, "lib/d.rb") # conflicts with a-2
install_specs a1, a2, b1, b2, c1, c2
a1.activate
c1.activate
assert_equal %w(a-1 c-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
assert require "d"
assert_equal %w(a-1 c-1), loaded_spec_names
assert_equal ["b (> 0)"], unresolved_names
end
end
def test_require_missing def test_require_missing
save_loaded_features do save_loaded_features do
assert_raises ::LoadError do assert_raises ::LoadError do
@ -321,221 +91,6 @@ class TestGem < Gem::TestCase
end end
end end
# TODO: move these to specification
def test_self_activate_loaded
foo = util_spec 'foo', '1'
assert foo.activate
refute foo.activate
end
##
# [A] depends on
# [B] >= 1.0 (satisfied by 2.0)
# [C] depends on nothing
def test_self_activate_unrelated
a = util_spec 'a', '1.0', 'b' => '>= 1.0'
util_spec 'b', '1.0'
c = util_spec 'c', '1.0'
assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b"
end
##
# [A] depends on
# [B] >= 1.0 (satisfied by 2.0)
# [C] = 1.0 depends on
# [B] ~> 1.0
#
# and should resolve using b-1.0
# TODO: move these to specification
def test_self_activate_over
a = util_spec 'a', '1.0', 'b' => '>= 1.0', 'c' => '= 1.0'
util_spec 'b', '1.0'
util_spec 'b', '1.1'
util_spec 'b', '2.0'
util_spec 'c', '1.0', 'b' => '~> 1.0'
a.activate
assert_equal %w[a-1.0 c-1.0], loaded_spec_names
assert_equal ["b (>= 1.0, ~> 1.0)"], unresolved_names
end
##
# [A] depends on
# [B] ~> 1.0 (satisfied by 1.1)
# [C] = 1.0 depends on
# [B] = 1.0
#
# and should resolve using b-1.0
#
# TODO: this is not under, but over... under would require depth
# first resolve through a dependency that is later pruned.
def test_self_activate_under
a, _ = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0'
util_spec 'b', '1.0'
util_spec 'b', '1.1'
c, _ = util_spec 'c', '1.0', 'b' => '= 1.0'
assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b"
end
##
# [A1] depends on
# [B] > 0 (satisfied by 2.0)
# [B1] depends on
# [C] > 0 (satisfied by 1.0)
# [B2] depends on nothing!
# [C1] depends on nothing
def test_self_activate_dropped
a1, = util_spec 'a', '1', 'b' => nil
util_spec 'b', '1', 'c' => nil
util_spec 'b', '2'
util_spec 'c', '1'
assert_activate %w[b-2 a-1], a1, "b"
end
##
# [A] depends on
# [B] >= 1.0 (satisfied by 1.1) depends on
# [Z]
# [C] >= 1.0 depends on
# [B] = 1.0
#
# and should backtrack to resolve using b-1.0, pruning Z from the
# resolve.
def test_self_activate_raggi_the_edgecase_generator
a, _ = util_spec 'a', '1.0', 'b' => '>= 1.0', 'c' => '>= 1.0'
util_spec 'b', '1.0'
util_spec 'b', '1.1', 'z' => '>= 1.0'
c, _ = util_spec 'c', '1.0', 'b' => '= 1.0'
assert_activate %w[b-1.0 c-1.0 a-1.0], a, c, "b"
end
def test_self_activate_conflict
util_spec 'b', '1.0'
util_spec 'b', '2.0'
gem "b", "= 1.0"
assert_raises Gem::LoadError do
gem "b", "= 2.0"
end
end
##
# [A] depends on
# [C] = 1.0 depends on
# [B] = 2.0
# [B] ~> 1.0 (satisfied by 1.0)
def test_self_activate_checks_dependencies
a, _ = util_spec 'a', '1.0'
a.add_dependency 'c', '= 1.0'
a.add_dependency 'b', '~> 1.0'
util_spec 'b', '1.0'
util_spec 'b', '2.0'
c, _ = util_spec 'c', '1.0', 'b' => '= 2.0'
e = assert_raises Gem::LoadError do
assert_activate nil, a, c, "b"
end
expected = "can't satisfy 'b (~> 1.0)', already activated 'b-2.0'"
assert_equal expected, e.message
end
##
# [A] depends on
# [B] ~> 1.0 (satisfied by 1.0)
# [C] = 1.0 depends on
# [B] = 2.0
def test_self_activate_divergent
a, _ = util_spec 'a', '1.0', 'b' => '~> 1.0', 'c' => '= 1.0'
util_spec 'b', '1.0'
util_spec 'b', '2.0'
c, _ = util_spec 'c', '1.0', 'b' => '= 2.0'
e = assert_raises Gem::LoadError do
assert_activate nil, a, c, "b"
end
assert_match(/Unable to activate c-1.0,/, e.message)
assert_match(/because b-1.0 conflicts with b .= 2.0/, e.message)
end
##
# DOC
def test_self_activate_platform_alternate
@x1_m = util_spec 'x', '1' do |s|
s.platform = Gem::Platform.new %w[cpu my_platform 1]
end
@x1_o = util_spec 'x', '1' do |s|
s.platform = Gem::Platform.new %w[cpu other_platform 1]
end
@w1 = util_spec 'w', '1', 'x' => nil
util_set_arch 'cpu-my_platform1'
assert_activate %w[x-1-cpu-my_platform-1 w-1], @w1, @x1_m
end
##
# DOC
def test_self_activate_platform_bump
@y1 = util_spec 'y', '1'
@y1_1_p = util_spec 'y', '1.1' do |s|
s.platform = Gem::Platform.new %w[cpu my_platform 1]
end
@z1 = util_spec 'z', '1', 'y' => nil
assert_activate %w[y-1 z-1], @z1, @y1
end
##
# [C] depends on
# [A] = 1.a
# [B] = 1.0 depends on
# [A] >= 0 (satisfied by 1.a)
def test_self_activate_prerelease
@c1_pre = util_spec 'c', '1.a', "a" => "1.a", "b" => "1"
@a1_pre = util_spec 'a', '1.a'
@b1 = util_spec 'b', '1' do |s|
s.add_dependency 'a'
s.add_development_dependency 'aa'
end
assert_activate %w[a-1.a b-1 c-1.a], @c1_pre, @a1_pre, @b1
end
##
# DOC
def test_self_activate_old_required
e1, = util_spec 'e', '1', 'd' => '= 1'
@d1 = util_spec 'd', '1'
@d2 = util_spec 'd', '2'
assert_activate %w[d-1 e-1], e1, "d"
end
def test_self_bin_path_no_exec_name def test_self_bin_path_no_exec_name
e = assert_raises ArgumentError do e = assert_raises ArgumentError do
Gem.bin_path 'a' Gem.bin_path 'a'
@ -1557,6 +1112,34 @@ class TestGem < Gem::TestCase
assert_equal '["a-1", "b-1", "c-1"]', out.strip assert_equal '["a-1", "b-1", "c-1"]', out.strip
end end
def test_register_default_spec
Gem.clear_default_specs
old_style = Gem::Specification.new do |spec|
spec.files = ["foo.rb", "bar.rb"]
end
Gem.register_default_spec old_style
assert_equal old_style, Gem.find_unresolved_default_spec("foo.rb")
assert_equal old_style, Gem.find_unresolved_default_spec("bar.rb")
assert_equal nil, Gem.find_unresolved_default_spec("baz.rb")
Gem.clear_default_specs
new_style = Gem::Specification.new do |spec|
spec.files = ["lib/foo.rb", "ext/bar.rb", "bin/exec", "README"]
spec.require_paths = ["lib", "ext"]
end
Gem.register_default_spec new_style
assert_equal new_style, Gem.find_unresolved_default_spec("foo.rb")
assert_equal new_style, Gem.find_unresolved_default_spec("bar.rb")
assert_equal nil, Gem.find_unresolved_default_spec("exec")
assert_equal nil, Gem.find_unresolved_default_spec("README")
end
def with_plugin(path) def with_plugin(path)
test_plugin_path = File.expand_path("test/rubygems/plugin/#{path}", test_plugin_path = File.expand_path("test/rubygems/plugin/#{path}",
@ -1634,4 +1217,3 @@ class TestGem < Gem::TestCase
File.join Gem.dir, "cache" File.join Gem.dir, "cache"
end end
end end

View file

@ -2,8 +2,8 @@ require 'rubygems/test_case'
require 'rubygems/commands/cert_command' require 'rubygems/commands/cert_command'
require 'rubygems/fix_openssl_warnings' if RUBY_VERSION < "1.9" require 'rubygems/fix_openssl_warnings' if RUBY_VERSION < "1.9"
unless defined? OpenSSL then unless defined?(OpenSSL::SSL) then
warn "`gem cert` tests are being skipped, module OpenSSL not found" warn 'Skipping `gem cert` tests. openssl not found.'
end end
class TestGemCommandsCertCommand < Gem::TestCase class TestGemCommandsCertCommand < Gem::TestCase
@ -98,14 +98,22 @@ Added '/CN=alternate/DC=example'
end end
def test_execute_build def test_execute_build
passphrase = 'Foo bar'
@cmd.handle_options %W[--build nobody@example.com] @cmd.handle_options %W[--build nobody@example.com]
use_ui @ui do @build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase}"
use_ui @build_ui do
@cmd.execute @cmd.execute
end end
output = @ui.output.split "\n" output = @build_ui.output.split "\n"
assert_equal "Passphrase for your Private Key: ",
output.shift
assert_equal "Please repeat the passphrase for your Private Key: ",
output.shift
assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}",
output.shift output.shift
assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}", assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}",
@ -115,12 +123,43 @@ Added '/CN=alternate/DC=example'
output.shift output.shift
assert_empty output assert_empty output
assert_empty @ui.error assert_empty @build_ui.error
assert_path_exists File.join(@tempdir, 'gem-private_key.pem') assert_path_exists File.join(@tempdir, 'gem-private_key.pem')
assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') assert_path_exists File.join(@tempdir, 'gem-public_cert.pem')
end end
def test_execute_build_bad_passphrase_confirmation
passphrase = 'Foo bar'
passphrase_confirmation = 'Fu bar'
@cmd.handle_options %W[--build nobody@example.com]
@build_ui = Gem::MockGemUi.new "#{passphrase}\n#{passphrase_confirmation}"
use_ui @build_ui do
e = assert_raises Gem::CommandLineError do
@cmd.execute
end
output = @build_ui.output.split "\n"
assert_equal "Passphrase for your Private Key: ",
output.shift
assert_equal "Please repeat the passphrase for your Private Key: ",
output.shift
assert_empty output
assert_equal "Passphrase and passphrase confirmation don't match",
e.message
end
refute_path_exists File.join(@tempdir, 'gem-private_key.pem')
refute_path_exists File.join(@tempdir, 'gem-public_cert.pem')
end
def test_execute_build_key def test_execute_build_key
@cmd.handle_options %W[ @cmd.handle_options %W[
--build nobody@example.com --build nobody@example.com
@ -135,21 +174,32 @@ Added '/CN=alternate/DC=example'
assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}", assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}",
output.shift output.shift
assert_equal "Private Key: #{File.join @tempdir, 'gem-private_key.pem'}",
output.shift
assert_equal "Don't forget to move the key file to somewhere private!", assert_empty output
assert_empty @ui.error
assert_path_exists File.join(@tempdir, 'gem-public_cert.pem')
end
def test_execute_build_encrypted_key
@cmd.handle_options %W[
--build nobody@example.com
--private-key #{ENCRYPTED_PRIVATE_KEY_PATH}
]
use_ui @ui do
@cmd.execute
end
output = @ui.output.split "\n"
assert_equal "Certificate: #{File.join @tempdir, 'gem-public_cert.pem'}",
output.shift output.shift
assert_empty output assert_empty output
assert_empty @ui.error assert_empty @ui.error
assert_path_exists File.join(@tempdir, 'gem-public_cert.pem') assert_path_exists File.join(@tempdir, 'gem-public_cert.pem')
private_key_file = File.join @tempdir, 'gem-private_key.pem'
assert_path_exists private_key_file
assert_equal PRIVATE_KEY.to_pem, File.read(private_key_file)
end end
def test_execute_certificate def test_execute_certificate
@ -203,6 +253,17 @@ Added '/CN=alternate/DC=example'
assert_equal PRIVATE_KEY.to_pem, @cmd.options[:key].to_pem assert_equal PRIVATE_KEY.to_pem, @cmd.options[:key].to_pem
end end
def test_execute_encrypted_private_key
use_ui @ui do
@cmd.send :handle_options, %W[--private-key #{ENCRYPTED_PRIVATE_KEY_PATH}]
end
assert_equal '', @ui.output
assert_equal '', @ui.error
assert_equal ENCRYPTED_PRIVATE_KEY.to_pem, @cmd.options[:key].to_pem
end
def test_execute_remove def test_execute_remove
@trust_dir.trust_cert PUBLIC_CERT @trust_dir.trust_cert PUBLIC_CERT
@ -307,6 +368,35 @@ Removed '/CN=alternate/DC=example'
assert_equal mask, File.stat(path).mode unless win_platform? assert_equal mask, File.stat(path).mode unless win_platform?
end end
def test_execute_sign_encrypted_key
path = File.join @tempdir, 'cert.pem'
Gem::Security.write ALTERNATE_CERT, path, 0600
assert_equal '/CN=alternate/DC=example', ALTERNATE_CERT.issuer.to_s
@cmd.handle_options %W[
--private-key #{ENCRYPTED_PRIVATE_KEY_PATH}
--certificate #{PUBLIC_CERT_FILE}
--sign #{path}
]
use_ui @ui do
@cmd.execute
end
assert_equal '', @ui.output
assert_equal '', @ui.error
cert = OpenSSL::X509::Certificate.new File.read path
assert_equal '/CN=nobody/DC=example', cert.issuer.to_s
mask = 0100600 & (~File.umask)
assert_equal mask, File.stat(path).mode unless win_platform?
end
def test_execute_sign_default def test_execute_sign_default
FileUtils.mkdir_p File.join Gem.user_home, '.gem' FileUtils.mkdir_p File.join Gem.user_home, '.gem'
@ -339,6 +429,38 @@ Removed '/CN=alternate/DC=example'
assert_equal mask, File.stat(path).mode unless win_platform? assert_equal mask, File.stat(path).mode unless win_platform?
end end
def test_execute_sign_default_encrypted_key
FileUtils.mkdir_p File.join(Gem.user_home, '.gem')
private_key_path = File.join Gem.user_home, '.gem', 'gem-private_key.pem'
Gem::Security.write ENCRYPTED_PRIVATE_KEY, private_key_path, 0600, PRIVATE_KEY_PASSPHRASE
public_cert_path = File.join Gem.user_home, '.gem', 'gem-public_cert.pem'
Gem::Security.write PUBLIC_CERT, public_cert_path
path = File.join @tempdir, 'cert.pem'
Gem::Security.write ALTERNATE_CERT, path, 0600
assert_equal '/CN=alternate/DC=example', ALTERNATE_CERT.issuer.to_s
@cmd.handle_options %W[--sign #{path}]
use_ui @ui do
@cmd.execute
end
assert_equal '', @ui.output
assert_equal '', @ui.error
cert = OpenSSL::X509::Certificate.new File.read path
assert_equal '/CN=nobody/DC=example', cert.issuer.to_s
mask = 0100600 & (~File.umask)
assert_equal mask, File.stat(path).mode unless win_platform?
end
def test_execute_sign_no_cert def test_execute_sign_no_cert
FileUtils.mkdir_p File.join Gem.user_home, '.gem' FileUtils.mkdir_p File.join Gem.user_home, '.gem'
@ -509,6 +631,24 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
assert_equal [ALTERNATE_CERT_FILE, CHILD_CERT_FILE], @cmd.options[:sign] assert_equal [ALTERNATE_CERT_FILE, CHILD_CERT_FILE], @cmd.options[:sign]
end end
def test_handle_options_sign_encrypted_key
@cmd.handle_options %W[
--private-key #{ALTERNATE_KEY_FILE}
--private-key #{ENCRYPTED_PRIVATE_KEY_PATH}
--certificate #{ALTERNATE_CERT_FILE}
--certificate #{PUBLIC_CERT_FILE}
--sign #{ALTERNATE_CERT_FILE}
--sign #{CHILD_CERT_FILE}
]
assert_equal ENCRYPTED_PRIVATE_KEY.to_pem, @cmd.options[:key].to_pem
assert_equal PUBLIC_CERT.to_pem, @cmd.options[:issuer_cert].to_pem
assert_equal [ALTERNATE_CERT_FILE, CHILD_CERT_FILE], @cmd.options[:sign]
end
def test_handle_options_sign_nonexistent def test_handle_options_sign_nonexistent
nonexistent = File.join @tempdir, 'nonexistent' nonexistent = File.join @tempdir, 'nonexistent'
e = assert_raises OptionParser::InvalidArgument do e = assert_raises OptionParser::InvalidArgument do
@ -525,5 +665,5 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
e.message e.message
end end
end if defined? OpenSSL end if defined?(OpenSSL::SSL)

View file

@ -15,6 +15,21 @@ class TestGemCommandsCleanupCommand < Gem::TestCase
install_gem @a_2 install_gem @a_2
end end
def test_handle_options_d
@cmd.handle_options %w[-d]
assert @cmd.options[:dryrun]
end
def test_handle_options_dry_run
@cmd.handle_options %w[--dryrun]
assert @cmd.options[:dryrun]
end
def test_handle_options_n
@cmd.handle_options %w[-n]
assert @cmd.options[:dryrun]
end
def test_execute def test_execute
@cmd.options[:args] = %w[a] @cmd.options[:args] = %w[a]

View file

@ -140,10 +140,10 @@ lib/foo.rb
@cmd.execute @cmd.execute
end end
expected = %W[ expected = [
#{Gem::ConfigMap[:bindir]}/default_command File.join(Gem::ConfigMap[:bindir], 'default_command'),
#{Gem::ConfigMap[:rubylibdir]}/default/gem.rb File.join(Gem::ConfigMap[:rubylibdir], 'default/gem.rb'),
#{Gem::ConfigMap[:archdir]}/default_gem.so File.join(Gem::ConfigMap[:archdir], 'default_gem.so')
].sort.join "\n" ].sort.join "\n"
assert_equal expected, @ui.output.chomp assert_equal expected, @ui.output.chomp

View file

@ -11,6 +11,7 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase
def test_execute def test_execute
orig_sources = Gem.sources.dup orig_sources = Gem.sources.dup
orig_path, ENV['PATH'] = ENV['PATH'], '/usr/local/bin:/usr/bin:/bin'
Gem.sources.replace %w[http://gems.example.com] Gem.sources.replace %w[http://gems.example.com]
Gem.configuration['gemcutter_key'] = 'blah' Gem.configuration['gemcutter_key'] = 'blah'
@ -36,10 +37,17 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase
assert_match %r|"gemcutter_key" => "\*\*\*\*"|, @ui.output assert_match %r|"gemcutter_key" => "\*\*\*\*"|, @ui.output
assert_match %r|:verbose => |, @ui.output assert_match %r|:verbose => |, @ui.output
assert_match %r|REMOTE SOURCES:|, @ui.output assert_match %r|REMOTE SOURCES:|, @ui.output
assert_equal '', @ui.error
assert_match %r|- SHELL PATH:|, @ui.output
assert_match %r|- /usr/local/bin$|, @ui.output
assert_match %r|- /usr/bin$|, @ui.output
assert_match %r|- /bin$|, @ui.output
assert_empty @ui.error
ensure ensure
Gem.sources.replace orig_sources Gem.sources.replace orig_sources
ENV['PATH'] = orig_path
end end
def test_execute_gemdir def test_execute_gemdir

View file

@ -36,9 +36,12 @@ class TestGemCommandsHelpCommand < Gem::TestCase
mgr.command_names.each do |cmd| mgr.command_names.each do |cmd|
assert_match(/\s+#{cmd}\s+\S+/, out) assert_match(/\s+#{cmd}\s+\S+/, out)
end end
assert_equal '', err
refute_match 'No command found for ', out if defined?(OpenSSL::SSL) then
assert_empty err
refute_match 'No command found for ', out
end
end end
end end

View file

@ -5,6 +5,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase
def setup def setup
super super
common_installer_setup
@cmd = Gem::Commands::InstallCommand.new @cmd = Gem::Commands::InstallCommand.new
@cmd.options[:document] = [] @cmd.options[:document] = []
@ -167,8 +168,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase
# This is needed because we need to exercise the cache path # This is needed because we need to exercise the cache path
# within SpecFetcher # within SpecFetcher
path = File.join Gem.user_home, '.gem', 'specs', "not-there.nothing%80", path = File.join Gem.spec_cache_dir, "not-there.nothing%80", "latest_specs.4.8"
"latest_specs.4.8"
FileUtils.mkdir_p File.dirname(path) FileUtils.mkdir_p File.dirname(path)
@ -632,67 +632,6 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_equal x, e assert_equal x, e
end end
def test_execute_installs_dependencies
r, r_gem = util_gem 'r', '1', 'q' => '= 1'
q, q_gem = util_gem 'q', '1'
util_setup_fake_fetcher
util_setup_spec_fetcher r, q
Gem::Specification.reset
@fetcher.data["#{@gem_repo}gems/#{q.file_name}"] = read_binary(q_gem)
@fetcher.data["#{@gem_repo}gems/#{r.file_name}"] = read_binary(r_gem)
@cmd.options[:args] = ["r"]
e = nil
use_ui @ui do
e = assert_raises Gem::SystemExitException do
capture_io do
@cmd.execute
end
end
end
out = @ui.output.split "\n"
assert_equal "2 gems installed", out.shift
assert out.empty?, out.inspect
assert_equal %w[q-1 r-1], @cmd.installed_specs.map { |spec| spec.full_name }
assert_equal 0, e.exit_code
end
def test_execute_satisfy_deps_of_local_from_sources
r, r_gem = util_gem 'r', '1', 'q' => '= 1'
q, q_gem = util_gem 'q', '1'
util_setup_fake_fetcher
util_setup_spec_fetcher r, q
Gem::Specification.reset
@fetcher.data["#{@gem_repo}gems/#{q.file_name}"] = read_binary(q_gem)
@cmd.options[:args] = [r_gem]
use_ui @ui do
e = assert_raises Gem::SystemExitException do
capture_io do
@cmd.execute
end
end
assert_equal 0, e.exit_code
end
assert_equal %w[q-1 r-1], @cmd.installed_specs.map { |spec| spec.full_name }
out = @ui.output.split "\n"
assert_equal "2 gems installed", out.shift
assert out.empty?, out.inspect
end
def test_execute_uses_from_a_gemdeps def test_execute_uses_from_a_gemdeps
util_setup_fake_fetcher util_setup_fake_fetcher
util_setup_spec_fetcher util_setup_spec_fetcher
@ -950,4 +889,3 @@ ERROR: Possible alternatives: non_existent_with_hint
end end

View file

@ -6,6 +6,7 @@ class TestGemCommandsOwnerCommand < Gem::TestCase
def setup def setup
super super
ENV["RUBYGEMS_HOST"] = nil
@fetcher = Gem::FakeFetcher.new @fetcher = Gem::FakeFetcher.new
Gem::RemoteFetcher.fetcher = @fetcher Gem::RemoteFetcher.fetcher = @fetcher
Gem.configuration.rubygems_api_key = "ed244fbf2b1a52e012da8616c512fa47f9aa5250" Gem.configuration.rubygems_api_key = "ed244fbf2b1a52e012da8616c512fa47f9aa5250"
@ -34,6 +35,36 @@ EOF
assert_match %r{- user2@example.com}, @ui.output assert_match %r{- user2@example.com}, @ui.output
end end
def test_show_owners_setting_up_host_through_env_var
response = "- email: user1@example.com\n"
host = "http://rubygems.example"
ENV["RUBYGEMS_HOST"] = host
@fetcher.data["#{host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK']
use_ui @ui do
@cmd.show_owners("freewill")
end
assert_match %r{Owners for gem: freewill}, @ui.output
assert_match %r{- user1@example.com}, @ui.output
end
def test_show_owners_setting_up_host
response = "- email: user1@example.com\n"
host = "http://rubygems.example"
@cmd.host = host
@fetcher.data["#{host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK']
use_ui @ui do
@cmd.show_owners("freewill")
end
assert_match %r{Owners for gem: freewill}, @ui.output
assert_match %r{- user1@example.com}, @ui.output
end
def test_show_owners_denied def test_show_owners_denied
response = "You don't have permission to push to this gem" response = "You don't have permission to push to this gem"
@fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 403, 'Forbidden'] @fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 403, 'Forbidden']
@ -87,6 +118,24 @@ EOF
assert_match response, @ui.output assert_match response, @ui.output
end end
def test_add_owner_with_host_option_through_execute
host = "http://rubygems.example"
add_owner_response = "Owner added successfully."
show_owners_response = "- email: user1@example.com\n"
@fetcher.data["#{host}/api/v1/gems/freewill/owners"] = [add_owner_response, 200, 'OK']
@fetcher.data["#{host}/api/v1/gems/freewill/owners.yaml"] = [show_owners_response, 200, 'OK']
@cmd.handle_options %W[--host #{host} --add user-new1@example.com freewill]
use_ui @ui do
@cmd.execute
end
assert_match add_owner_response, @ui.output
assert_match %r{Owners for gem: freewill}, @ui.output
assert_match %r{- user1@example.com}, @ui.output
end
def test_add_owners_key def test_add_owners_key
response = "Owner added successfully." response = "Owner added successfully."
@fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [response, 200, 'OK'] @fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [response, 200, 'OK']

View file

@ -80,6 +80,32 @@ class TestGemCommandsPristineCommand < Gem::TestCase
assert_empty out, out.inspect assert_empty out, out.inspect
end end
def test_execute_env_shebang
a = quick_spec 'a' do |s|
s.executables = %w[foo]
s.files = %w[bin/foo]
end
write_file File.join(@tempdir, 'bin', 'foo') do |fp|
fp.puts "#!/usr/bin/ruby"
end
install_gem a
gem_exec = File.join @gemhome, 'bin', 'foo'
FileUtils.rm gem_exec
@cmd.handle_options %w[--all --env-shebang]
use_ui @ui do
@cmd.execute
end
assert_path_exists gem_exec
assert_match '/usr/bin/env', File.read(gem_exec)
end
def test_execute_no_extension def test_execute_no_extension
a = quick_spec 'a' do |s| s.extensions << 'ext/a/extconf.rb' end a = quick_spec 'a' do |s| s.extensions << 'ext/a/extconf.rb' end

View file

@ -194,7 +194,7 @@ beta-gems.example.com is not a URI
assert_equal expected, @ui.output assert_equal expected, @ui.output
assert_equal '', @ui.error assert_equal '', @ui.error
dir = File.join Gem.user_home, '.gem', 'specs' dir = Gem.spec_cache_dir
refute File.exist?(dir), 'cache dir removed' refute File.exist?(dir), 'cache dir removed'
end end

View file

@ -11,6 +11,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
def setup def setup
super super
common_installer_setup
@cmd = Gem::Commands::UpdateCommand.new @cmd = Gem::Commands::UpdateCommand.new
@ -253,7 +254,7 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
out = @ui.output.split "\n" out = @ui.output.split "\n"
assert_equal "Updating installed gems", out.shift assert_equal "Updating installed gems", out.shift
assert_equal "Updating #{@a2.name}", out.shift assert_equal "Updating #{@a2.name}", out.shift
assert_equal "Gems updated: #{@c2.name} #{@b2.name} #{@a2.name}", assert_equal "Gems updated: #{@a2.name} #{@b2.name} #{@c2.name}",
out.shift out.shift
assert_empty out assert_empty out

View file

@ -201,6 +201,10 @@ ERROR: Your gem push credentials file located at:
has file permissions of 0644 but 0600 is required. has file permissions of 0644 but 0600 is required.
To fix this error run:
\tchmod 0600 #{@cfg.credentials_path}
You should reset your credentials at: You should reset your credentials at:
\thttps://rubygems.org/profile/edit \thttps://rubygems.org/profile/edit
@ -428,6 +432,14 @@ if you believe they were disclosed to a third party.
assert_equal('/home/me/certs', @cfg.ssl_ca_cert) assert_equal('/home/me/certs', @cfg.ssl_ca_cert)
end end
def test_load_ssl_client_cert_from_config
File.open @temp_conf, 'w' do |fp|
fp.puts ":ssl_client_cert: /home/me/mine.pem"
end
util_config_file
assert_equal('/home/me/mine.pem', @cfg.ssl_client_cert)
end
def util_config_file(args = @cfg_args) def util_config_file(args = @cfg_args)
@cfg = Gem::ConfigFile.new args @cfg = Gem::ConfigFile.new args
end end

View file

@ -6,6 +6,7 @@ class TestGemDependencyInstaller < Gem::TestCase
def setup def setup
super super
common_installer_setup
@gems_dir = File.join @tempdir, 'gems' @gems_dir = File.join @tempdir, 'gems'
@cache_dir = File.join @gemhome, 'cache' @cache_dir = File.join @gemhome, 'cache'
@ -172,7 +173,8 @@ class TestGemDependencyInstaller < Gem::TestCase
FileUtils.mv @a1_gem, @tempdir FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir
FileUtils.mv e1_gem, @tempdir FileUtils.mv e1_gem, @tempdir
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
@ -180,40 +182,15 @@ class TestGemDependencyInstaller < Gem::TestCase
inst.install 'b' inst.install 'b'
end end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name },
'sanity check'
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new inst = Gem::DependencyInstaller.new
inst.install 'e' inst.install 'e'
end end
assert_equal %w[e-1 a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1 e-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_ignore_satified_deps
util_setup_gems
_, e1_gem = util_gem 'e', '1' do |s|
s.add_dependency 'b'
end
util_clear_gems
FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir
FileUtils.mv e1_gem, @tempdir
Dir.chdir @tempdir do
i = Gem::DependencyInstaller.new :ignore_dependencies => true
i.install 'b'
end
inst = nil
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new :minimal_deps => true
inst.install 'e'
end
assert_equal %w[e-1], inst.installed_gems.map { |s| s.full_name }
end end
def test_install_cache_dir def test_install_cache_dir
@ -246,15 +223,18 @@ class TestGemDependencyInstaller < Gem::TestCase
Gem::Specification.reset Gem::Specification.reset
FileUtils.mv @a1_gem, @tempdir FileUtils.mv @a1_gem, @tempdir
FileUtils.mv a2_gem, @tempdir # not in index FileUtils.mv a2_gem, @tempdir # not in index
FileUtils.mv @b1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new inst = Gem::DependencyInstaller.new
inst.install 'a', Gem::Requirement.create("= 2") inst.install 'a', req("= 2")
end end
assert_equal %w[a-2], inst.installed_gems.map { |s| s.full_name },
'sanity check'
FileUtils.rm File.join(@tempdir, a2.file_name) FileUtils.rm File.join(@tempdir, a2.file_name)
Dir.chdir @tempdir do Dir.chdir @tempdir do
@ -282,19 +262,18 @@ class TestGemDependencyInstaller < Gem::TestCase
Gem::Specification.reset Gem::Specification.reset
FileUtils.mv @a1_gem, @tempdir FileUtils.mv @a1_gem, @tempdir
FileUtils.mv a2_gem, @tempdir # not in index FileUtils.mv a2_gem, @tempdir # not in index
FileUtils.mv @b1_gem, @tempdir FileUtils.mv @b1_gem, @tempdir
FileUtils.mv a3_gem, @tempdir FileUtils.mv a3_gem, @tempdir
inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new Gem::DependencyInstaller.new.install 'a', req("= 2")
inst.install 'a', Gem::Requirement.create("= 2")
end end
FileUtils.rm File.join(@tempdir, a2.file_name) FileUtils.rm File.join(@tempdir, a2.file_name)
inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new inst = Gem::DependencyInstaller.new
inst.install 'b' inst.install 'b'
@ -488,6 +467,42 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name } assert_equal %w[a-1], inst.installed_gems.map { |s| s.full_name }
end end
def test_install_minimal_deps
util_setup_gems
_, e1_gem = util_gem 'e', '1' do |s|
s.add_dependency 'b'
end
_, b2_gem = util_gem 'b', '2' do |s|
s.add_dependency 'a'
end
util_clear_gems
FileUtils.mv @a1_gem, @tempdir
FileUtils.mv @b1_gem, @tempdir
FileUtils.mv b2_gem, @tempdir
FileUtils.mv e1_gem, @tempdir
inst = nil
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new :ignore_dependencies => true
inst.install 'b', req('= 1')
end
assert_equal %w[b-1], inst.installed_gems.map { |s| s.full_name },
'sanity check'
Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new :minimal_deps => true
inst.install 'e'
end
assert_equal %w[a-1 e-1], inst.installed_gems.map { |s| s.full_name }
end
def test_install_env_shebang def test_install_env_shebang
util_setup_gems util_setup_gems
@ -627,12 +642,12 @@ class TestGemDependencyInstaller < Gem::TestCase
inst = nil inst = nil
Dir.chdir @tempdir do Dir.chdir @tempdir do
e = assert_raises Gem::DependencyError do e = assert_raises Gem::UnsatisfiableDependencyError do
inst = Gem::DependencyInstaller.new :domain => :local inst = Gem::DependencyInstaller.new :domain => :local
inst.install 'b' inst.install 'b'
end end
expected = "Unable to resolve dependencies: b requires a (>= 0)" expected = "Unable to resolve dependency: b (= 1) requires a (>= 0)"
assert_equal expected, e.message assert_equal expected, e.message
end end
@ -910,12 +925,13 @@ class TestGemDependencyInstaller < Gem::TestCase
gems = set.sorted gems = set.sorted
assert_equal 2, gems.length assert_equal 2, gems.length
local = gems.first
remote, local = gems
assert_equal 'a-1', local.spec.full_name, 'local spec' assert_equal 'a-1', local.spec.full_name, 'local spec'
assert_equal File.join(@tempdir, @a1.file_name), assert_equal File.join(@tempdir, @a1.file_name),
local.source.download(local.spec), 'local path' local.source.download(local.spec), 'local path'
remote = gems.last
assert_equal 'a-1', remote.spec.full_name, 'remote spec' assert_equal 'a-1', remote.spec.full_name, 'remote spec'
assert_equal Gem::Source.new(@gem_repo), remote.source, 'remote path' assert_equal Gem::Source.new(@gem_repo), remote.source, 'remote path'

View file

@ -15,7 +15,9 @@ class TestGemDependencyResolver < Gem::TestCase
exp = expected.sort_by { |s| s.full_name } exp = expected.sort_by { |s| s.full_name }
act = actual.map { |a| a.spec }.sort_by { |s| s.full_name } act = actual.map { |a| a.spec }.sort_by { |s| s.full_name }
assert_equal exp, act msg = "Set of gems was not the same: #{exp.map { |x| x.full_name}.inspect} != #{act.map { |x| x.full_name}.inspect}"
assert_equal exp, act, msg
end end
def test_no_overlap_specificly def test_no_overlap_specificly
@ -177,7 +179,8 @@ class TestGemDependencyResolver < Gem::TestCase
r.resolve r.resolve
end end
assert_equal "unable to find any gem matching dependency 'a (>= 0)'", e.message assert_equal "Unable to resolve dependency: (unknown) requires a (>= 0)",
e.message
assert_equal "a (>= 0)", e.dependency.to_s assert_equal "a (>= 0)", e.dependency.to_s
end end
@ -215,7 +218,7 @@ class TestGemDependencyResolver < Gem::TestCase
r.resolve r.resolve
end end
assert_equal "detected 1 conflict with dependency 'c (>= 2)'", e.message assert_match "a-1 requires c (>= 2) but it conflicted", e.message
assert_equal "c (>= 2)", e.dependency.to_s assert_equal "c (>= 2)", e.dependency.to_s

View file

@ -0,0 +1,36 @@
require 'rubygems/test_case'
require 'rubygems/dependency_resolver'
class TestGemDependencyResolverDependencyConflict < Gem::TestCase
def test_explanation
root =
dependency_request dep('net-ssh', '>= 2.0.13'), 'rye', '0.9.8'
child =
dependency_request dep('net-ssh', '>= 2.6.5'), 'net-ssh', '2.2.2', root
conflict =
Gem::DependencyResolver::DependencyConflict.new child, child.requester
expected = <<-EXPECTED
Activated net-ssh-2.2.2 instead of (>= 2.6.5) via:
net-ssh-2.2.2, rye-0.9.8
EXPECTED
assert_equal expected, conflict.explanation
end
def test_request_path
root =
dependency_request dep('net-ssh', '>= 2.0.13'), 'rye', '0.9.8'
child =
dependency_request dep('net-ssh', '>= 2.6.5'), 'net-ssh', '2.2.2', root
conflict =
Gem::DependencyResolver::DependencyConflict.new child, nil
assert_equal %w[net-ssh-2.2.2 rye-0.9.8], conflict.request_path
end
end

View file

@ -7,6 +7,7 @@ class TestGemGemRunner < Gem::TestCase
super super
@orig_args = Gem::Command.build_args @orig_args = Gem::Command.build_args
@runner = Gem::GemRunner.new
end end
def teardown def teardown
@ -41,23 +42,26 @@ class TestGemGemRunner < Gem::TestCase
assert_equal %w[--commands], Gem::Command.extra_args assert_equal %w[--commands], Gem::Command.extra_args
end end
def test_build_args_are_handled def test_extract_build_args
Gem.clear_paths args = %w[]
assert_equal [], @runner.extract_build_args(args)
assert_equal %w[], args
cls = Class.new(Gem::Command) do args = %w[foo]
def execute assert_equal [], @runner.extract_build_args(args)
end assert_equal %w[foo], args
end
test_obj = cls.new :ba_test args = %w[--foo]
assert_equal [], @runner.extract_build_args(args)
assert_equal %w[--foo], args
cmds = Gem::CommandManager.new args = %w[--foo --]
cmds.register_command :ba_test, test_obj assert_equal [], @runner.extract_build_args(args)
assert_equal %w[--foo], args
runner = Gem::GemRunner.new :command_manager => cmds args = %w[--foo -- --bar]
runner.run(%W[ba_test -- --build_arg1 --build_arg2]) assert_equal %w[--bar], @runner.extract_build_args(args)
assert_equal %w[--foo], args
assert_equal %w[--build_arg1 --build_arg2], test_obj.options[:build_args]
end end
end end

View file

@ -0,0 +1,41 @@
require 'rubygems/test_case'
class TestGemImpossibleDependenciesError < Gem::TestCase
def test_message_conflict
request = dependency_request dep('net-ssh', '>= 2.0.13'), 'rye', '0.9.8'
conflicts = []
# These conflicts are lies as their dependencies does not have the correct
# requested-by entries, but they are suitable for testing the message.
# See #485 to construct a correct conflict.
net_ssh_2_2_2 =
dependency_request dep('net-ssh', '>= 2.6.5'), 'net-ssh', '2.2.2', request
net_ssh_2_6_5 =
dependency_request dep('net-ssh', '~> 2.2.2'), 'net-ssh', '2.6.5', request
conflict1 = Gem::DependencyResolver::DependencyConflict.new \
net_ssh_2_6_5, net_ssh_2_6_5.requester
conflict2 = Gem::DependencyResolver::DependencyConflict.new \
net_ssh_2_2_2, net_ssh_2_2_2.requester
conflicts << [net_ssh_2_6_5.requester.spec, conflict1]
conflicts << [net_ssh_2_2_2.requester.spec, conflict2]
error = Gem::ImpossibleDependenciesError.new request, conflicts
expected = <<-EXPECTED
rye-0.9.8 requires net-ssh (>= 2.0.13) but it conflicted:
Activated net-ssh-2.6.5 instead of (~> 2.2.2) via:
net-ssh-2.6.5, rye-0.9.8
Activated net-ssh-2.2.2 instead of (>= 2.6.5) via:
net-ssh-2.2.2, rye-0.9.8
EXPECTED
assert_equal expected, error.message
end
end

View file

@ -22,12 +22,13 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase
--rdoc --rdoc
--ri --ri
-E -E
-P HighSecurity
-f -f
-i /install_to -i /install_to
-w -w
] ]
args.concat %w[-P HighSecurity] if defined?(OpenSSL::SSL)
assert @cmd.handles?(args) assert @cmd.handles?(args)
end end
@ -100,6 +101,8 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase
end end
def test_security_policy def test_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@cmd.handle_options %w[-P HighSecurity] @cmd.handle_options %w[-P HighSecurity]
assert_equal Gem::Security::HighSecurity, @cmd.options[:security_policy] assert_equal Gem::Security::HighSecurity, @cmd.options[:security_policy]

View file

@ -4,6 +4,7 @@ class TestGemInstaller < Gem::InstallerTestCase
def setup def setup
super super
common_installer_setup
if __name__ =~ /^test_install(_|$)/ then if __name__ =~ /^test_install(_|$)/ then
FileUtils.rm_r @spec.gem_dir FileUtils.rm_r @spec.gem_dir
@ -14,6 +15,8 @@ class TestGemInstaller < Gem::InstallerTestCase
end end
def teardown def teardown
common_installer_teardown
super super
Gem.configuration = @config Gem.configuration = @config
@ -300,6 +303,8 @@ gem 'other', version
end end
def test_ensure_loadable_spec_security_policy def test_ensure_loadable_spec_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
_, a_gem = util_gem 'a', 2 do |s| _, a_gem = util_gem 'a', 2 do |s|
s.add_dependency 'garbage ~> 5' s.add_dependency 'garbage ~> 5'
end end
@ -1341,7 +1346,7 @@ gem 'other', version
assert File.exist?(File.join(dest, 'bin', 'executable')) assert File.exist?(File.join(dest, 'bin', 'executable'))
end end
def test_write_build_args def test_write_build_info_file
refute_path_exists @spec.build_info_file refute_path_exists @spec.build_info_file
@installer.build_args = %w[ @installer.build_args = %w[
@ -1357,7 +1362,7 @@ gem 'other', version
assert_equal expected, File.read(@spec.build_info_file) assert_equal expected, File.read(@spec.build_info_file)
end end
def test_write_build_args_empty def test_write_build_info_file_empty
refute_path_exists @spec.build_info_file refute_path_exists @spec.build_info_file
@installer.write_build_info_file @installer.write_build_info_file
@ -1429,6 +1434,30 @@ gem 'other', version
assert_match %r!/gemhome/gems/a-2$!, @installer.dir assert_match %r!/gemhome/gems/a-2$!, @installer.dir
end end
def test_default_gem
FileUtils.rm_f File.join(Gem.dir, 'specifications')
@installer.wrappers = true
@installer.options[:install_as_default] = true
@installer.gem_dir = util_gem_dir @spec
@installer.generate_bin
use_ui @ui do
@installer.install
end
assert File.directory? util_inst_bindir
installed_exec = File.join util_inst_bindir, 'executable'
assert File.exist? installed_exec
assert File.directory? File.join(Gem.dir, 'specifications')
assert File.directory? File.join(Gem.dir, 'specifications', 'default')
default_spec = eval File.read File.join(Gem.dir, 'specifications', 'default', 'a-2.gemspec')
assert_equal Gem::Version.new("2"), default_spec.version
assert_equal ['bin/executable'], default_spec.files
end
def old_ruby_required def old_ruby_required
spec = quick_spec 'old_ruby_required', '1' do |s| spec = quick_spec 'old_ruby_required', '1' do |s|
s.required_ruby_version = '= 1.4.6' s.required_ruby_version = '= 1.4.6'

View file

@ -2,6 +2,21 @@ require 'rubygems/test_case'
require 'rubygems/name_tuple' require 'rubygems/name_tuple'
class TestGemNameTuple < Gem::TestCase class TestGemNameTuple < Gem::TestCase
def test_full_name
n = Gem::NameTuple.new "a", Gem::Version.new(0), "ruby"
assert_equal "a-0", n.full_name
n = Gem::NameTuple.new "a", Gem::Version.new(0), nil
assert_equal "a-0", n.full_name
n = Gem::NameTuple.new "a", Gem::Version.new(0), ""
assert_equal "a-0", n.full_name
n = Gem::NameTuple.new "a", Gem::Version.new(0), "other"
assert_equal "a-0-other", n.full_name
end
def test_platform_normalization def test_platform_normalization
n = Gem::NameTuple.new "a", Gem::Version.new(0), "ruby" n = Gem::NameTuple.new "a", Gem::Version.new(0), "ruby"
assert_equal "ruby", n.platform assert_equal "ruby", n.platform
@ -12,4 +27,11 @@ class TestGemNameTuple < Gem::TestCase
n = Gem::NameTuple.new "a", Gem::Version.new(0), "" n = Gem::NameTuple.new "a", Gem::Version.new(0), ""
assert_equal "ruby", n.platform assert_equal "ruby", n.platform
end end
def test_spec_name
n = Gem::NameTuple.new "a", Gem::Version.new(0), "ruby"
assert_equal "a-0.gemspec", n.spec_name
end
end end

View file

@ -89,16 +89,19 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
expected = { expected = {
'SHA1' => {
'metadata.gz' => metadata_sha1,
'data.tar.gz' => data_digests['SHA1'].hexdigest,
},
'SHA512' => { 'SHA512' => {
'metadata.gz' => metadata_sha512, 'metadata.gz' => metadata_sha512,
'data.tar.gz' => data_digests['SHA512'].hexdigest, 'data.tar.gz' => data_digests['SHA512'].hexdigest,
} }
} }
if defined?(OpenSSL::Digest) then
expected['SHA1'] = {
'metadata.gz' => metadata_sha1,
'data.tar.gz' => data_digests['SHA1'].hexdigest,
}
end
assert_equal expected, YAML.load(checksums) assert_equal expected, YAML.load(checksums)
end end
@ -162,11 +165,56 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
def test_build_auto_signed def test_build_auto_signed
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
FileUtils.mkdir_p File.join(Gem.user_home, '.gem') FileUtils.mkdir_p File.join(Gem.user_home, '.gem')
private_key_path = File.join Gem.user_home, '.gem', 'gem-private_key.pem' private_key_path = File.join Gem.user_home, '.gem', 'gem-private_key.pem'
Gem::Security.write PRIVATE_KEY, private_key_path Gem::Security.write PRIVATE_KEY, private_key_path
public_cert_path = File.join Gem.user_home, '.gem', 'gem-public_cert.pem'
FileUtils.cp PUBLIC_CERT_PATH, public_cert_path
spec = Gem::Specification.new 'build', '1'
spec.summary = 'build'
spec.authors = 'build'
spec.files = ['lib/code.rb']
FileUtils.mkdir 'lib'
open 'lib/code.rb', 'w' do |io|
io.write '# lib/code.rb'
end
package = Gem::Package.new spec.file_name
package.spec = spec
package.build
assert_equal Gem::VERSION, spec.rubygems_version
assert_path_exists spec.file_name
reader = Gem::Package.new spec.file_name
assert reader.verify
assert_equal [PUBLIC_CERT.to_pem], reader.spec.cert_chain
assert_equal %w[metadata.gz metadata.gz.sig
data.tar.gz data.tar.gz.sig
checksums.yaml.gz checksums.yaml.gz.sig],
reader.files
assert_equal %w[lib/code.rb], reader.contents
end
def test_build_auto_signed_encrypted_key
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
FileUtils.mkdir_p File.join(Gem.user_home, '.gem')
private_key_path = File.join Gem.user_home, '.gem', 'gem-private_key.pem'
FileUtils.cp ENCRYPTED_PRIVATE_KEY_PATH, private_key_path
public_cert_path = File.join Gem.user_home, '.gem', 'gem-public_cert.pem' public_cert_path = File.join Gem.user_home, '.gem', 'gem-public_cert.pem'
Gem::Security.write PUBLIC_CERT, public_cert_path Gem::Security.write PUBLIC_CERT, public_cert_path
@ -216,6 +264,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
def test_build_signed def test_build_signed
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
spec = Gem::Specification.new 'build', '1' spec = Gem::Specification.new 'build', '1'
spec.summary = 'build' spec.summary = 'build'
spec.authors = 'build' spec.authors = 'build'
@ -250,6 +300,43 @@ class TestGemPackage < Gem::Package::TarTestCase
assert_equal %w[lib/code.rb], reader.contents assert_equal %w[lib/code.rb], reader.contents
end end
def test_build_signed_encryped_key
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
spec = Gem::Specification.new 'build', '1'
spec.summary = 'build'
spec.authors = 'build'
spec.files = ['lib/code.rb']
spec.cert_chain = [PUBLIC_CERT.to_pem]
spec.signing_key = ENCRYPTED_PRIVATE_KEY
FileUtils.mkdir 'lib'
open 'lib/code.rb', 'w' do |io|
io.write '# lib/code.rb'
end
package = Gem::Package.new spec.file_name
package.spec = spec
package.build
assert_equal Gem::VERSION, spec.rubygems_version
assert_path_exists spec.file_name
reader = Gem::Package.new spec.file_name
assert reader.verify
assert_equal spec, reader.spec
assert_equal %w[metadata.gz metadata.gz.sig
data.tar.gz data.tar.gz.sig
checksums.yaml.gz checksums.yaml.gz.sig],
reader.files
assert_equal %w[lib/code.rb], reader.contents
end
def test_contents def test_contents
package = Gem::Package.new @gem package = Gem::Package.new @gem
@ -446,7 +533,7 @@ class TestGemPackage < Gem::Package::TarTestCase
io.write metadata_gz io.write metadata_gz
end end
digest = OpenSSL::Digest::SHA1.new digest = Digest::SHA1.new
digest << metadata_gz digest << metadata_gz
checksums = { checksums = {
@ -478,7 +565,8 @@ class TestGemPackage < Gem::Package::TarTestCase
def test_verify_corrupt def test_verify_corrupt
Tempfile.open 'corrupt' do |io| Tempfile.open 'corrupt' do |io|
data = Gem.gzip 'a' * 10 data = Gem.gzip 'a' * 10
io.write tar_file_header('metadata.gz', "\000x", 0644, data.length) io.write \
tar_file_header('metadata.gz', "\000x", 0644, data.length, Time.now)
io.write data io.write data
io.rewind io.rewind
@ -517,6 +605,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
def test_verify_security_policy def test_verify_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
package = Gem::Package.new @gem package = Gem::Package.new @gem
package.security_policy = Gem::Security::HighSecurity package.security_policy = Gem::Security::HighSecurity
@ -532,6 +622,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
def test_verify_security_policy_low_security def test_verify_security_policy_low_security
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.cert_chain = [PUBLIC_CERT.to_pem]
@spec.signing_key = PRIVATE_KEY @spec.signing_key = PRIVATE_KEY
@ -550,6 +642,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end end
def test_verify_security_policy_checksum_missing def test_verify_security_policy_checksum_missing
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.cert_chain = [PUBLIC_CERT.to_pem]
@spec.signing_key = PRIVATE_KEY @spec.signing_key = PRIVATE_KEY
@ -605,6 +699,21 @@ class TestGemPackage < Gem::Package::TarTestCase
e.message e.message
end end
# end #verify tests
def test_verify_entry
entry = Object.new
def entry.full_name() raise ArgumentError, 'whatever' end
package = Gem::Package.new @gem
e = assert_raises Gem::Package::FormatError do
package.verify_entry entry
end
assert_equal "package is corrupt, exception while verifying: whatever (ArgumentError) in #{@gem}", e.message
end
def test_spec def test_spec
package = Gem::Package.new @gem package = Gem::Package.new @gem

View file

@ -21,6 +21,8 @@ class TestGemPackageOld < Gem::TestCase
end end
def test_contents_security_policy def test_contents_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@package.security_policy = Gem::Security::AlmostNoSecurity @package.security_policy = Gem::Security::AlmostNoSecurity
assert_raises Gem::Security::Exception do assert_raises Gem::Security::Exception do
@ -40,6 +42,8 @@ class TestGemPackageOld < Gem::TestCase
end end
def test_extract_files_security_policy def test_extract_files_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@package.security_policy = Gem::Security::AlmostNoSecurity @package.security_policy = Gem::Security::AlmostNoSecurity
assert_raises Gem::Security::Exception do assert_raises Gem::Security::Exception do
@ -52,6 +56,8 @@ class TestGemPackageOld < Gem::TestCase
end end
def test_spec_security_policy def test_spec_security_policy
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
@package.security_policy = Gem::Security::AlmostNoSecurity @package.security_policy = Gem::Security::AlmostNoSecurity
assert_raises Gem::Security::Exception do assert_raises Gem::Security::Exception do
@ -60,6 +66,8 @@ class TestGemPackageOld < Gem::TestCase
end end
def test_verify def test_verify
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
assert @package.verify assert @package.verify
@package.security_policy = Gem::Security::NoSecurity @package.security_policy = Gem::Security::NoSecurity

View file

@ -4,8 +4,8 @@ require 'rubygems/package'
class TestGemPackageTarReader < Gem::Package::TarTestCase class TestGemPackageTarReader < Gem::Package::TarTestCase
def test_each_entry def test_each_entry
tar = tar_dir_header "foo", "bar", 0 tar = tar_dir_header "foo", "bar", 0, Time.now
tar << tar_file_header("bar", "baz", 0, 0) tar << tar_file_header("bar", "baz", 0, 0, Time.now)
io = TempIO.new tar io = TempIO.new tar
@ -25,8 +25,9 @@ class TestGemPackageTarReader < Gem::Package::TarTestCase
def test_rewind def test_rewind
content = ('a'..'z').to_a.join(" ") content = ('a'..'z').to_a.join(" ")
str = tar_file_header("lib/foo", "", 010644, content.size) + content + str =
"\0" * (512 - content.size) tar_file_header("lib/foo", "", 010644, content.size, Time.now) +
content + "\0" * (512 - content.size)
str << "\0" * 1024 str << "\0" * 1024
Gem::Package::TarReader.new(TempIO.new(str)) do |tar_reader| Gem::Package::TarReader.new(TempIO.new(str)) do |tar_reader|
@ -43,8 +44,8 @@ class TestGemPackageTarReader < Gem::Package::TarTestCase
end end
def test_seek def test_seek
tar = tar_dir_header "foo", "bar", 0 tar = tar_dir_header "foo", "bar", 0, Time.now
tar << tar_file_header("bar", "baz", 0, 0) tar << tar_file_header("bar", "baz", 0, 0, Time.now)
io = TempIO.new tar io = TempIO.new tar
@ -60,8 +61,8 @@ class TestGemPackageTarReader < Gem::Package::TarTestCase
end end
def test_seek_missing def test_seek_missing
tar = tar_dir_header "foo", "bar", 0 tar = tar_dir_header "foo", "bar", 0, Time.now
tar << tar_file_header("bar", "baz", 0, 0) tar << tar_file_header("bar", "baz", 0, 0, Time.now)
io = TempIO.new tar io = TempIO.new tar

View file

@ -9,7 +9,7 @@ class TestGemPackageTarReaderEntry < Gem::Package::TarTestCase
@contents = ('a'..'z').to_a.join * 100 @contents = ('a'..'z').to_a.join * 100
@tar = '' @tar = ''
@tar << tar_file_header("lib/foo", "", 0, @contents.size) @tar << tar_file_header("lib/foo", "", 0, @contents.size, Time.now)
@tar << @contents @tar << @contents
@tar << "\0" * (512 - (@tar.size % 512)) @tar << "\0" * (512 - (@tar.size % 512))

View file

@ -1,5 +1,6 @@
require 'rubygems/package/tar_test_case' require 'rubygems/package/tar_test_case'
require 'rubygems/package/tar_writer' require 'rubygems/package/tar_writer'
require 'minitest/mock'
class TestGemPackageTarWriter < Gem::Package::TarTestCase class TestGemPackageTarWriter < Gem::Package::TarTestCase
@ -18,112 +19,130 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
end end
def test_add_file def test_add_file
@tar_writer.add_file 'x', 0644 do |f| f.write 'a' * 10 end Time.stub :now, Time.at(1458518157) do
@tar_writer.add_file 'x', 0644 do |f| f.write 'a' * 10 end
assert_headers_equal(tar_file_header('x', '', 0644, 10), assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512]) @io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos assert_equal 1024, @io.pos
end end
def test_add_file_digest def test_add_file_digest
digest_algorithms = OpenSSL::Digest::SHA1, OpenSSL::Digest::SHA512 digest_algorithms = Digest::SHA1, Digest::SHA512
digests = @tar_writer.add_file_digest 'x', 0644, digest_algorithms do |io| Time.stub :now, Time.at(1458518157) do
io.write 'a' * 10 digests = @tar_writer.add_file_digest 'x', 0644, digest_algorithms do |io|
end io.write 'a' * 10
end
assert_equal '3495ff69d34671d1e15b33a63c1379fdedd3a32a', assert_equal '3495ff69d34671d1e15b33a63c1379fdedd3a32a',
digests['SHA1'].hexdigest digests['SHA1'].hexdigest
assert_equal '4714870aff6c97ca09d135834fdb58a6389a50c1' \ assert_equal '4714870aff6c97ca09d135834fdb58a6389a50c1' \
'1fef8ec4afef466fb60a23ac6b7a9c92658f14df' \ '1fef8ec4afef466fb60a23ac6b7a9c92658f14df' \
'4993d6b40a4e4d8424196afc347e97640d68de61' \ '4993d6b40a4e4d8424196afc347e97640d68de61' \
'e1cf14b0', 'e1cf14b0',
digests['SHA512'].hexdigest digests['SHA512'].hexdigest
assert_headers_equal(tar_file_header('x', '', 0644, 10), assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512]) @io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos assert_equal 1024, @io.pos
end end
def test_add_file_digest_multiple def test_add_file_digest_multiple
digest_algorithms = [OpenSSL::Digest::SHA1, OpenSSL::Digest::SHA512] digest_algorithms = [Digest::SHA1, Digest::SHA512]
digests = @tar_writer.add_file_digest 'x', 0644, digest_algorithms do |io| Time.stub :now, Time.at(1458518157) do
io.write 'a' * 10 digests = @tar_writer.add_file_digest 'x', 0644, digest_algorithms do |io|
io.write 'a' * 10
end
assert_equal '3495ff69d34671d1e15b33a63c1379fdedd3a32a',
digests['SHA1'].hexdigest
assert_equal '4714870aff6c97ca09d135834fdb58a6389a50c1' \
'1fef8ec4afef466fb60a23ac6b7a9c92658f14df' \
'4993d6b40a4e4d8424196afc347e97640d68de61' \
'e1cf14b0',
digests['SHA512'].hexdigest
assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512])
end end
assert_equal '3495ff69d34671d1e15b33a63c1379fdedd3a32a',
digests['SHA1'].hexdigest
assert_equal '4714870aff6c97ca09d135834fdb58a6389a50c1' \
'1fef8ec4afef466fb60a23ac6b7a9c92658f14df' \
'4993d6b40a4e4d8424196afc347e97640d68de61' \
'e1cf14b0',
digests['SHA512'].hexdigest
assert_headers_equal(tar_file_header('x', '', 0644, 10),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos assert_equal 1024, @io.pos
end end
def test_add_file_signer def test_add_file_signer
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
signer = Gem::Security::Signer.new PRIVATE_KEY, [PUBLIC_CERT] signer = Gem::Security::Signer.new PRIVATE_KEY, [PUBLIC_CERT]
@tar_writer.add_file_signed 'x', 0644, signer do |io| Time.stub :now, Time.at(1458518157) do
io.write 'a' * 10 @tar_writer.add_file_signed 'x', 0644, signer do |io|
io.write 'a' * 10
end
assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
digest = signer.digest_algorithm.new
digest.update 'a' * 10
signature = signer.sign digest.digest
assert_headers_equal(tar_file_header('x.sig', '', 0444, signature.length,
Time.now),
@io.string[1024, 512])
assert_equal "#{signature}#{"\0" * (512 - signature.length)}",
@io.string[1536, 512]
assert_equal 2048, @io.pos
end end
assert_headers_equal(tar_file_header('x', '', 0644, 10),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
digest = signer.digest_algorithm.new
digest.update 'a' * 10
signature = signer.sign digest.digest
assert_headers_equal(tar_file_header('x.sig', '', 0444, signature.length),
@io.string[1024, 512])
assert_equal "#{signature}#{"\0" * (512 - signature.length)}",
@io.string[1536, 512]
assert_equal 2048, @io.pos
end end
def test_add_file_signer_empty def test_add_file_signer_empty
signer = Gem::Security::Signer.new nil, nil signer = Gem::Security::Signer.new nil, nil
@tar_writer.add_file_signed 'x', 0644, signer do |io| Time.stub :now, Time.at(1458518157) do
io.write 'a' * 10
end
assert_headers_equal(tar_file_header('x', '', 0644, 10), @tar_writer.add_file_signed 'x', 0644, signer do |io|
io.write 'a' * 10
end
assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512]) @io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
digest = signer.digest_algorithm.new
digest.update 'a' * 10
assert_equal 1024, @io.pos assert_equal 1024, @io.pos
end end
def test_add_file_simple def test_add_file_simple
@tar_writer.add_file_simple 'x', 0644, 10 do |io| io.write "a" * 10 end Time.stub :now, Time.at(1458518157) do
@tar_writer.add_file_simple 'x', 0644, 10 do |io| io.write "a" * 10 end
assert_headers_equal(tar_file_header('x', '', 0644, 10), assert_headers_equal(tar_file_header('x', '', 0644, 10, Time.now),
@io.string[0, 512]) @io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512] assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
assert_equal 1024, @io.pos assert_equal 1024, @io.pos
end end
def test_add_file_simple_padding def test_add_file_simple_padding
@tar_writer.add_file_simple 'x', 0, 100 Time.stub :now, Time.at(1458518157) do
@tar_writer.add_file_simple 'x', 0, 100
assert_headers_equal tar_file_header('x', '', 0, 100), assert_headers_equal tar_file_header('x', '', 0, 100, Time.now),
@io.string[0, 512] @io.string[0, 512]
end
assert_equal "\0" * 512, @io.string[512, 512] assert_equal "\0" * 512, @io.string[512, 512]
end end
@ -182,11 +201,14 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
end end
def test_mkdir def test_mkdir
@tar_writer.mkdir 'foo', 0644 Time.stub :now, Time.at(1458518157) do
@tar_writer.mkdir 'foo', 0644
assert_headers_equal tar_dir_header('foo', '', 0644), assert_headers_equal tar_dir_header('foo', '', 0644, Time.now),
@io.string[0, 512] @io.string[0, 512]
assert_equal 512, @io.pos
assert_equal 512, @io.pos
end
end end
def test_split_name def test_split_name

View file

@ -64,4 +64,21 @@ class TestGemPathSupport < Gem::TestCase
def util_path def util_path
ENV["GEM_PATH"].split(File::PATH_SEPARATOR) ENV["GEM_PATH"].split(File::PATH_SEPARATOR)
end end
def test_initialize_spec
ENV["GEM_SPEC_CACHE"] = nil
ps = Gem::PathSupport.new
assert_equal Gem.default_spec_cache_dir, ps.spec_cache_dir
ENV["GEM_SPEC_CACHE"] = 'bar'
ps = Gem::PathSupport.new
assert_equal ENV["GEM_SPEC_CACHE"], ps.spec_cache_dir
ENV["GEM_SPEC_CACHE"] = File.join @tempdir, 'spec_cache'
ps = Gem::PathSupport.new "GEM_SPEC_CACHE" => "foo"
assert_equal "foo", ps.spec_cache_dir
end
end end

View file

@ -186,6 +186,24 @@ class TestGemPlatform < Gem::TestCase
assert((x86_darwin8 === Gem::Platform.local), 'universal =~ x86') assert((x86_darwin8 === Gem::Platform.local), 'universal =~ x86')
end end
def test_equals3_cpu_arm
arm = Gem::Platform.new 'arm-linux'
armv5 = Gem::Platform.new 'armv5-linux'
armv7 = Gem::Platform.new 'armv7-linux'
util_set_arch 'armv5-linux'
assert((arm === Gem::Platform.local), 'arm === armv5')
assert((armv5 === Gem::Platform.local), 'armv5 === armv5')
refute((armv7 === Gem::Platform.local), 'armv7 === armv5')
refute((Gem::Platform.local === arm), 'armv5 === arm')
util_set_arch 'armv7-linux'
assert((arm === Gem::Platform.local), 'arm === armv7')
refute((armv5 === Gem::Platform.local), 'armv5 === armv7')
assert((armv7 === Gem::Platform.local), 'armv7 === armv7')
refute((Gem::Platform.local === arm), 'armv7 === arm')
end
def test_equals3_version def test_equals3_version
util_set_arch 'i686-darwin8' util_set_arch 'i686-darwin8'

View file

@ -1,7 +1,13 @@
require 'rubygems/test_case' require 'rubygems/test_case'
require 'ostruct'
require 'webrick' require 'webrick'
require 'webrick/https' begin
require 'webrick/https'
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
end
require 'rubygems/remote_fetcher' require 'rubygems/remote_fetcher'
require 'rubygems/package' require 'rubygems/package'
require 'minitest/mock' require 'minitest/mock'
@ -128,19 +134,7 @@ gems:
refute_nil fetcher refute_nil fetcher
assert_kind_of Gem::RemoteFetcher, fetcher assert_kind_of Gem::RemoteFetcher, fetcher
assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri).to_s assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy).to_s
end
def test_self_fetcher_with_proxy_URI
proxy_uri = URI.parse 'http://proxy.example.com'
Gem.configuration[:http_proxy] = proxy_uri
Gem::RemoteFetcher.fetcher = nil
fetcher = Gem::RemoteFetcher.fetcher
refute_nil fetcher
assert_kind_of Gem::RemoteFetcher, fetcher
assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri)
end end
def test_fetch_size_bad_uri def test_fetch_size_bad_uri
@ -155,7 +149,7 @@ gems:
def test_fetch_size_socket_error def test_fetch_size_socket_error
fetcher = Gem::RemoteFetcher.new nil fetcher = Gem::RemoteFetcher.new nil
def fetcher.connection_for(uri) def fetcher.request(uri, request_class, last_modified = nil)
raise SocketError, "tarded" raise SocketError, "tarded"
end end
@ -414,70 +408,6 @@ gems:
assert_equal @a2.file_name, File.basename(gem) assert_equal @a2.file_name, File.basename(gem)
end end
def test_explicit_proxy
use_ui @ui do
fetcher = Gem::RemoteFetcher.new @proxy_uri
assert_equal PROXY_DATA.size, fetcher.fetch_size(@server_uri)
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
end
def test_explicit_proxy_with_user_auth
use_ui @ui do
uri = URI.parse @proxy_uri
uri.user, uri.password = 'foo', 'bar'
fetcher = Gem::RemoteFetcher.new uri.to_s
proxy = fetcher.instance_variable_get("@proxy_uri")
assert_equal 'foo', proxy.user
assert_equal 'bar', proxy.password
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
use_ui @ui do
uri = URI.parse @proxy_uri
uri.user, uri.password = 'domain%5Cuser', 'bar'
fetcher = Gem::RemoteFetcher.new uri.to_s
proxy = fetcher.instance_variable_get("@proxy_uri")
assert_equal 'domain\user', fetcher.unescape(proxy.user)
assert_equal 'bar', proxy.password
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
use_ui @ui do
uri = URI.parse @proxy_uri
uri.user, uri.password = 'user', 'my%20pass'
fetcher = Gem::RemoteFetcher.new uri.to_s
proxy = fetcher.instance_variable_get("@proxy_uri")
assert_equal 'user', proxy.user
assert_equal 'my pass', fetcher.unescape(proxy.password)
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
end
def test_explicit_proxy_with_user_auth_in_env
use_ui @ui do
ENV['http_proxy'] = @proxy_uri
ENV['http_proxy_user'] = 'foo'
ENV['http_proxy_pass'] = 'bar'
fetcher = Gem::RemoteFetcher.new nil
proxy = fetcher.instance_variable_get("@proxy_uri")
assert_equal 'foo', proxy.user
assert_equal 'bar', proxy.password
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
use_ui @ui do
ENV['http_proxy'] = @proxy_uri
ENV['http_proxy_user'] = 'foo\user'
ENV['http_proxy_pass'] = 'my bar'
fetcher = Gem::RemoteFetcher.new nil
proxy = fetcher.instance_variable_get("@proxy_uri")
assert_equal 'foo\user', fetcher.unescape(proxy.user)
assert_equal 'my bar', fetcher.unescape(proxy.password)
assert_data_from_proxy fetcher.fetch_path(@server_uri)
end
end
def test_fetch_path_gzip def test_fetch_path_gzip
fetcher = Gem::RemoteFetcher.new nil fetcher = Gem::RemoteFetcher.new nil
@ -560,22 +490,6 @@ gems:
assert_equal nil, fetcher.fetch_path(URI.parse(@gem_repo), Time.at(0)) assert_equal nil, fetcher.fetch_path(URI.parse(@gem_repo), Time.at(0))
end end
def test_get_proxy_from_env_auto_normalizes
fetcher = Gem::RemoteFetcher.new(nil)
ENV['HTTP_PROXY'] = 'fakeurl:12345'
assert_equal('http://fakeurl:12345', fetcher.get_proxy_from_env.to_s)
end
def test_get_proxy_from_env_empty
ENV['HTTP_PROXY'] = ''
ENV.delete 'http_proxy'
fetcher = Gem::RemoteFetcher.new nil
assert_equal nil, fetcher.send(:get_proxy_from_env)
end
def test_implicit_no_proxy def test_implicit_no_proxy
use_ui @ui do use_ui @ui do
ENV['http_proxy'] = 'http://fakeurl:12345' ENV['http_proxy'] = 'http://fakeurl:12345'
@ -611,9 +525,7 @@ gems:
fetcher = Gem::RemoteFetcher.new nil fetcher = Gem::RemoteFetcher.new nil
url = 'http://gems.example.com/redirect' url = 'http://gems.example.com/redirect'
conn = Object.new def fetcher.request(uri, request_class, last_modified = nil)
def conn.started?() true end
def conn.request(req)
url = 'http://gems.example.com/redirect' url = 'http://gems.example.com/redirect'
unless defined? @requested then unless defined? @requested then
@requested = true @requested = true
@ -627,9 +539,6 @@ gems:
end end
end end
conn = { "#{Thread.current.object_id}:gems.example.com:80" => conn }
fetcher.instance_variable_set :@connections, conn
data = fetcher.fetch_http URI.parse(url) data = fetcher.fetch_http URI.parse(url)
assert_equal 'real_path', data assert_equal 'real_path', data
@ -639,18 +548,13 @@ gems:
fetcher = Gem::RemoteFetcher.new nil fetcher = Gem::RemoteFetcher.new nil
url = 'http://gems.example.com/redirect' url = 'http://gems.example.com/redirect'
conn = Object.new def fetcher.request(uri, request_class, last_modified = nil)
def conn.started?() true end
def conn.request(req)
url = 'http://gems.example.com/redirect' url = 'http://gems.example.com/redirect'
res = Net::HTTPMovedPermanently.new nil, 301, nil res = Net::HTTPMovedPermanently.new nil, 301, nil
res.add_field 'Location', url res.add_field 'Location', url
res res
end end
conn = { "#{Thread.current.object_id}:gems.example.com:80" => conn }
fetcher.instance_variable_set :@connections, conn
e = assert_raises Gem::RemoteFetcher::FetchError do e = assert_raises Gem::RemoteFetcher::FetchError do
fetcher.fetch_http URI.parse(url) fetcher.fetch_http URI.parse(url)
end end
@ -658,14 +562,6 @@ gems:
assert_equal "too many redirects (#{url})", e.message assert_equal "too many redirects (#{url})", e.message
end end
def test_normalize_uri
assert_equal 'FILE://example/', @fetcher.normalize_uri('FILE://example/')
assert_equal 'FTP://example/', @fetcher.normalize_uri('FTP://example/')
assert_equal 'HTTP://example/', @fetcher.normalize_uri('HTTP://example/')
assert_equal 'HTTPS://example/', @fetcher.normalize_uri('HTTPS://example/')
assert_equal 'http://example/', @fetcher.normalize_uri('example/')
end
def test_observe_no_proxy_env_single_host def test_observe_no_proxy_env_single_host
use_ui @ui do use_ui @ui do
ENV["http_proxy"] = @proxy_uri ENV["http_proxy"] = @proxy_uri
@ -684,117 +580,6 @@ gems:
end end
end end
def test_request
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
util_stub_connection_for :body => :junk, :code => 200
response = @fetcher.request uri, Net::HTTP::Get
assert_equal 200, response.code
assert_equal :junk, response.body
end
def test_request_head
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
util_stub_connection_for :body => '', :code => 200
response = @fetcher.request uri, Net::HTTP::Head
assert_equal 200, response.code
assert_equal '', response.body
end
def test_request_unmodified
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
conn = util_stub_connection_for :body => '', :code => 304
t = Time.now
response = @fetcher.request uri, Net::HTTP::Head, t
assert_equal 304, response.code
assert_equal '', response.body
assert_equal t.rfc2822, conn.payload['if-modified-since']
end
def test_user_agent
ua = @fetcher.user_agent
assert_match %r%^RubyGems/\S+ \S+ Ruby/\S+ \(.*?\)%, ua
assert_match %r%RubyGems/#{Regexp.escape Gem::VERSION}%, ua
assert_match %r% #{Regexp.escape Gem::Platform.local.to_s} %, ua
assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}%, ua
assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE} %, ua
end
def test_user_agent_engine
util_save_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, 'vroom'
ua = @fetcher.user_agent
assert_match %r%\) vroom%, ua
ensure
util_restore_version
end
def test_user_agent_engine_ruby
util_save_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, 'ruby'
ua = @fetcher.user_agent
assert_match %r%\)%, ua
ensure
util_restore_version
end
def test_user_agent_patchlevel
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, 5
ua = @fetcher.user_agent
assert_match %r% patchlevel 5\)%, ua
ensure
util_restore_version
end
def test_user_agent_revision
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, -1
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
Object.send :const_set, :RUBY_REVISION, 6
ua = @fetcher.user_agent
assert_match %r% revision 6\)%, ua
assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}dev%, ua
ensure
util_restore_version
end
def test_user_agent_revision_missing
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, -1
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
ua = @fetcher.user_agent
assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE}\)%, ua
ensure
util_restore_version
end
def test_yaml_error_on_size def test_yaml_error_on_size
use_ui @ui do use_ui @ui do
self.class.enable_yaml = false self.class.enable_yaml = false
@ -811,6 +596,42 @@ gems:
end end
end end
def test_ssl_client_cert_auth_connection
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
ssl_server = self.class.start_ssl_server({
:SSLVerifyClient =>
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT})
temp_ca_cert = File.join(DIR, 'ca_cert.pem')
temp_client_cert = File.join(DIR, 'client.pem')
with_configured_fetcher(
":ssl_ca_cert: #{temp_ca_cert}\n" +
":ssl_client_cert: #{temp_client_cert}\n") do |fetcher|
fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml")
end
end
def test_do_not_allow_invalid_client_cert_auth_connection
skip 'openssl is missing' unless defined?(OpenSSL::SSL)
ssl_server = self.class.start_ssl_server({
:SSLVerifyClient =>
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT})
temp_ca_cert = File.join(DIR, 'ca_cert.pem')
temp_client_cert = File.join(DIR, 'invalid_client.pem')
with_configured_fetcher(
":ssl_ca_cert: #{temp_ca_cert}\n" +
":ssl_client_cert: #{temp_client_cert}\n") do |fetcher|
assert_raises Gem::RemoteFetcher::FetchError do
fetcher.fetch_path("https://localhost:#{ssl_server.config[:Port]}/yaml")
end
end
end
def test_do_not_allow_insecure_ssl_connection_by_default def test_do_not_allow_insecure_ssl_connection_by_default
ssl_server = self.class.start_ssl_server ssl_server = self.class.start_ssl_server
with_configured_fetcher do |fetcher| with_configured_fetcher do |fetcher|
@ -850,18 +671,6 @@ gems:
Gem.configuration = nil Gem.configuration = nil
end end
def util_stub_connection_for hash
def @fetcher.connection= conn
@conn = conn
end
def @fetcher.connection_for uri
@conn
end
@fetcher.connection = Conn.new OpenStruct.new(hash)
end
def assert_error(exception_class=Exception) def assert_error(exception_class=Exception)
got_exception = false got_exception = false
@ -882,20 +691,6 @@ gems:
assert_match(/0\.4\.2/, data, "Data is not from proxy") assert_match(/0\.4\.2/, data, "Data is not from proxy")
end end
class Conn
attr_accessor :payload
def initialize(response)
@response = response
self.payload = nil
end
def request(req)
self.payload = req
@response
end
end
class NilLog < WEBrick::Log class NilLog < WEBrick::Log
def log(level, data) #Do nothing def log(level, data) #Do nothing
end end
@ -913,9 +708,11 @@ gems:
end end
DIR = File.expand_path(File.dirname(__FILE__)) DIR = File.expand_path(File.dirname(__FILE__))
DH_PARAM = OpenSSL::PKey::DH.new(128)
def start_ssl_server(config = {}) def start_ssl_server(config = {})
raise MiniTest::Skip, 'openssl not installed' unless
defined?(OpenSSL::SSL)
null_logger = NilLog.new null_logger = NilLog.new
server = WEBrick::HTTPServer.new({ server = WEBrick::HTTPServer.new({
:Port => 0, :Port => 0,
@ -934,7 +731,7 @@ gems:
server.mount_proc("/insecure_redirect") { |req, res| server.mount_proc("/insecure_redirect") { |req, res|
res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, req.query['to']) res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, req.query['to'])
} }
server.ssl_context.tmp_dh_callback = proc { DH_PARAM } server.ssl_context.tmp_dh_callback = proc { OpenSSL::PKey::DH.new 128 }
t = Thread.new do t = Thread.new do
begin begin
server.start server.start
@ -953,8 +750,6 @@ gems:
server server
end end
private private
def start_server(port, data) def start_server(port, data)
@ -1015,24 +810,5 @@ gems:
assert_equal "/home/skillet", @fetcher.correct_for_windows_path(path) assert_equal "/home/skillet", @fetcher.correct_for_windows_path(path)
end end
def util_save_version
@orig_RUBY_ENGINE = RUBY_ENGINE if defined? RUBY_ENGINE
@orig_RUBY_PATCHLEVEL = RUBY_PATCHLEVEL
@orig_RUBY_REVISION = RUBY_REVISION if defined? RUBY_REVISION
end
def util_restore_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, @orig_RUBY_ENGINE if
defined?(@orig_RUBY_ENGINE)
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, @orig_RUBY_PATCHLEVEL
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
Object.send :const_set, :RUBY_REVISION, @orig_RUBY_REVISION if
defined?(@orig_RUBY_REVISION)
end
end end

View file

@ -0,0 +1,239 @@
require 'rubygems/test_case'
require 'rubygems/request'
require 'ostruct'
class TestGemRequest < Gem::TestCase
def setup
@proxies = %w[http_proxy HTTP_PROXY http_proxy_user HTTP_PROXY_USER http_proxy_pass HTTP_PROXY_PASS no_proxy NO_PROXY]
@old_proxies = @proxies.map {|k| ENV[k] }
@proxies.each {|k| ENV[k] = nil }
super
@proxy_uri = "http://localhost:1234"
@request = Gem::Request.new nil, nil, nil, nil
end
def teardown
super
Gem.configuration[:http_proxy] = nil
@proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] }
end
def test_initialize_proxy
proxy_uri = 'http://proxy.example.com'
request = Gem::Request.new nil, nil, nil, proxy_uri
assert_equal proxy_uri, request.proxy_uri.to_s
end
def test_initialize_proxy_URI
proxy_uri = 'http://proxy.example.com'
request = Gem::Request.new nil, nil, nil, URI(proxy_uri)
assert_equal proxy_uri, request.proxy_uri.to_s
end
def test_initialize_proxy_ENV
ENV['http_proxy'] = @proxy_uri
ENV['http_proxy_user'] = 'foo'
ENV['http_proxy_pass'] = 'bar'
request = Gem::Request.new nil, nil, nil, nil
proxy = request.proxy_uri
assert_equal 'foo', proxy.user
assert_equal 'bar', proxy.password
end
def test_get_proxy_from_env_domain
ENV['http_proxy'] = @proxy_uri
ENV['http_proxy_user'] = 'foo\user'
ENV['http_proxy_pass'] = 'my bar'
proxy = @request.get_proxy_from_env
assert_equal 'foo\user', Gem::UriFormatter.new(proxy.user).unescape
assert_equal 'my bar', Gem::UriFormatter.new(proxy.password).unescape
end
def test_get_proxy_from_env_normalize
ENV['HTTP_PROXY'] = 'fakeurl:12345'
assert_equal 'http://fakeurl:12345', @request.get_proxy_from_env.to_s
end
def test_get_proxy_from_env_empty
ENV['HTTP_PROXY'] = ''
ENV.delete 'http_proxy'
assert_nil @request.get_proxy_from_env
end
def test_fetch
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
@request = Gem::Request.new(uri, Net::HTTP::Get, nil, nil)
util_stub_connection_for :body => :junk, :code => 200
response = @request.fetch
assert_equal 200, response.code
assert_equal :junk, response.body
end
def test_fetch_head
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
@request = Gem::Request.new(uri, Net::HTTP::Get, nil, nil)
util_stub_connection_for :body => '', :code => 200
response = @request.fetch
assert_equal 200, response.code
assert_equal '', response.body
end
def test_fetch_unmodified
uri = URI.parse "#{@gem_repo}/specs.#{Gem.marshal_version}"
t = Time.now
@request = Gem::Request.new(uri, Net::HTTP::Get, t, nil)
conn = util_stub_connection_for :body => '', :code => 304
response = @request.fetch
assert_equal 304, response.code
assert_equal '', response.body
assert_equal t.rfc2822, conn.payload['if-modified-since']
end
def test_user_agent
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r%^RubyGems/\S+ \S+ Ruby/\S+ \(.*?\)%, ua
assert_match %r%RubyGems/#{Regexp.escape Gem::VERSION}%, ua
assert_match %r% #{Regexp.escape Gem::Platform.local.to_s} %, ua
assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}%, ua
assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE} %, ua
end
def test_user_agent_engine
util_save_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, 'vroom'
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r%\) vroom%, ua
ensure
util_restore_version
end
def test_user_agent_engine_ruby
util_save_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, 'ruby'
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r%\)%, ua
ensure
util_restore_version
end
def test_user_agent_patchlevel
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, 5
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r% patchlevel 5\)%, ua
ensure
util_restore_version
end
def test_user_agent_revision
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, -1
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
Object.send :const_set, :RUBY_REVISION, 6
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r% revision 6\)%, ua
assert_match %r%Ruby/#{Regexp.escape RUBY_VERSION}dev%, ua
ensure
util_restore_version
end
def test_user_agent_revision_missing
util_save_version
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, -1
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
ua = Gem::Request.new(nil, nil, nil, nil).user_agent
assert_match %r%\(#{Regexp.escape RUBY_RELEASE_DATE}\)%, ua
ensure
util_restore_version
end
def util_restore_version
Object.send :remove_const, :RUBY_ENGINE if defined?(RUBY_ENGINE)
Object.send :const_set, :RUBY_ENGINE, @orig_RUBY_ENGINE if
defined?(@orig_RUBY_ENGINE)
Object.send :remove_const, :RUBY_PATCHLEVEL
Object.send :const_set, :RUBY_PATCHLEVEL, @orig_RUBY_PATCHLEVEL
Object.send :remove_const, :RUBY_REVISION if defined?(RUBY_REVISION)
Object.send :const_set, :RUBY_REVISION, @orig_RUBY_REVISION if
defined?(@orig_RUBY_REVISION)
end
def util_save_version
@orig_RUBY_ENGINE = RUBY_ENGINE if defined? RUBY_ENGINE
@orig_RUBY_PATCHLEVEL = RUBY_PATCHLEVEL
@orig_RUBY_REVISION = RUBY_REVISION if defined? RUBY_REVISION
end
def util_stub_connection_for hash
def @request.connection= conn
@conn = conn
end
def @request.connection_for uri
@conn
end
@request.connection = Conn.new OpenStruct.new(hash)
end
class Conn
attr_accessor :payload
def initialize(response)
@response = response
self.payload = nil
end
def request(req)
self.payload = req
@response
end
end
end

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