Skip to content

Commit

Permalink
Showing 146 changed files with 8,540 additions and 2,248 deletions.
2 changes: 1 addition & 1 deletion lib/ruby/shared/gauntlet_rubygems.rb
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
# rvsh-0.4.5 : No such file or directory - bin/rvsh
# xen-0.1.2.1 : authors must be Array of Strings

class GemGauntlet < Gauntlet
class GemGauntlet < Gauntlet # :nodoc:
def run(name)
warn name

246 changes: 148 additions & 98 deletions lib/ruby/shared/rubygems.rb

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions lib/ruby/shared/rubygems/available_set.rb
Original file line number Diff line number Diff line change
@@ -4,9 +4,12 @@ class Gem::AvailableSet

Tuple = Struct.new(:spec, :source)

attr_accessor :remote # :nodoc:

def initialize
@set = []
@sorted = nil
@remote = true
end

attr_reader :set
@@ -116,18 +119,18 @@ def to_request_set development = :none

##
#
# Used by the DependencyResolver, the protocol to use a AvailableSet as a
# Used by the Resolver, 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
dep.match? t.spec
end

match.map do |t|
Gem::DependencyResolver::InstalledSpecification.new(self, t.spec, t.source)
Gem::Resolver::LocalSpecification.new(self, t.spec, t.source)
end
end

170 changes: 158 additions & 12 deletions lib/ruby/shared/rubygems/basic_specification.rb
Original file line number Diff line number Diff line change
@@ -4,11 +4,31 @@

class Gem::BasicSpecification

##
# Allows installation of extensions for git: gems.

attr_writer :base_dir # :nodoc:

##
# Sets the directory where extensions for this gem will be installed.

attr_writer :extension_dir # :nodoc:

##
# Is this specification ignored for activation purposes?

attr_writer :ignored # :nodoc:

##
# The path this gemspec was loaded from. This attribute is not persisted.

attr_reader :loaded_from

##
# Allows correct activation of git: and path: gems.

attr_writer :full_gem_path # :nodoc:

def self.default_specifications_dir
File.join(Gem.default_dir, "specifications", "default")
end
@@ -38,20 +58,50 @@ def base_dir
# 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
@contains_requirable_file ||= {}
@contains_requirable_file[file] ||=
begin
if instance_variable_defined?(:@ignored) then
return false
elsif missing_extensions? then
@ignored = true

warn "Ignoring #{full_name} because its extensions are not built. " +
"Try: gem pristine #{name} --version #{version}"
return false
end

suffixes = Gem.suffixes

full_require_paths.any? do |dir|
base = "#{dir}/#{file}"
suffixes.any? { |suf| File.file? "#{base}#{suf}" }
end
end ? :yes : :no
@contains_requirable_file[file] == :yes
end

def default_gem?
loaded_from &&
File.dirname(loaded_from) == self.class.default_specifications_dir
end

##
# Returns full path to the directory where gem's extensions are installed.

def extension_dir
@extension_dir ||= File.expand_path File.join(extensions_dir, full_name)
end

##
# Returns path to the extensions directory.

def extensions_dir
@extensions_dir ||= Gem.default_ext_dir_for(base_dir) ||
File.join(base_dir, 'extensions', Gem::Platform.local.to_s,
Gem.extension_api_version)
end

def find_full_gem_path # :nodoc:
# 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)
@@ -83,6 +133,53 @@ def full_name
end
end

##
# Full paths in the gem to add to <code>$LOAD_PATH</code> when this gem is
# activated.

def full_require_paths
@full_require_paths ||=
begin
full_paths = raw_require_paths.map do |path|
File.join full_gem_path, path
end

full_paths << extension_dir unless @extensions.nil? || @extensions.empty?

full_paths
end
end

##
# Full path of the target library file.
# If the file is not in this gem, return nil.

def to_fullpath path
if activated? then
@paths_map ||= {}
@paths_map[path] ||=
begin
fullpath = nil
suffixes = Gem.suffixes
full_require_paths.find do |dir|
suffixes.find do |suf|
File.file?(fullpath = "#{dir}/#{path}#{suf}")
end
end ? fullpath : nil
end
else
nil
end
end

##
# Returns the full path to this spec's gem directory.
# eg: /usr/local/lib/ruby/1.8/gems/mygem-1.0

def gem_dir
@gem_dir ||= File.expand_path File.join(gems_dir, full_name)
end

##
# Returns the full path to the gems directory containing this spec's
# gem directory. eg: /usr/local/lib/ruby/1.8/gems
@@ -99,9 +196,12 @@ def gems_dir
def loaded_from= path
@loaded_from = path && path.to_s

@full_gem_path = nil
@gems_dir = nil
@base_dir = nil
@extension_dir = nil
@extensions_dir = nil
@full_gem_path = nil
@gem_dir = nil
@gems_dir = nil
@base_dir = nil
end

##
@@ -118,11 +218,49 @@ def platform
raise NotImplementedError
end

def raw_require_paths # :nodoc:
Array(@require_paths)
end

##
# Require paths of the gem
# Paths in the gem to add to <code>$LOAD_PATH</code> when this gem is
# activated.
#
# See also #require_paths=
#
# If you have an extension you do not need to add <code>"ext"</code> to the
# require path, the extension build process will copy the extension files
# into "lib" for you.
#
# The default value is <code>"lib"</code>
#
# Usage:
#
# # If all library files are in the root directory...
# spec.require_path = '.'

def require_paths
raise NotImplementedError
return raw_require_paths if @extensions.nil? || @extensions.empty?

[extension_dir].concat raw_require_paths
end

##
# Returns the paths to the source files for use with analysis and
# documentation tools. These paths are relative to full_gem_path.

def source_paths
paths = raw_require_paths.dup

if @extensions then
ext_dirs = @extensions.map do |extension|
extension.split(File::SEPARATOR, 2).first
end.uniq

paths.concat ext_dirs
end

paths.uniq
end

##
@@ -139,5 +277,13 @@ def version
raise NotImplementedError
end

##
# Whether this specification is stubbed - i.e. we have information
# about the gem from a stub line, without having to evaluate the
# entire gemspec file.
def stubbed?
raise NotImplementedError
end

end

5 changes: 4 additions & 1 deletion lib/ruby/shared/rubygems/command.rb
Original file line number Diff line number Diff line change
@@ -148,6 +148,8 @@ def execute

##
# Display to the user that a gem couldn't be found and reasons why
#--
# TODO: replace +domain+ with a parameter to suppress suggestions

def show_lookup_failure(gem_name, version, errors, domain)
if errors and !errors.empty?
@@ -557,7 +559,8 @@ def wrap(text, width) # :doc:
Further help:
gem help commands list all 'gem' commands
gem help examples show some examples of usage
gem help platforms show information about platforms
gem help gem_dependencies gem dependencies file guide
gem help platforms gem platforms guide
gem help <COMMAND> show help on COMMAND
(e.g. 'gem help install')
gem server present a web page at
3 changes: 2 additions & 1 deletion lib/ruby/shared/rubygems/command_manager.rb
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ class Gem::CommandManager
:list,
:lock,
:mirror,
:open,
:outdated,
:owner,
:pristine,
@@ -136,7 +137,7 @@ def command_names
def run(args, build_args=nil)
process_args(args, build_args)
rescue StandardError, Timeout::Error => ex
alert_error "While executing gem ... (#{ex.class})\n #{ex.to_s}"
alert_error "While executing gem ... (#{ex.class})\n #{ex}"
ui.backtrace ex

terminate_interaction(1)
24 changes: 11 additions & 13 deletions lib/ruby/shared/rubygems/commands/cert_command.rb
Original file line number Diff line number Diff line change
@@ -129,23 +129,21 @@ def build_cert name, key # :nodoc:
end

def build_key # :nodoc:
if options[:key] then
options[:key]
else
passphrase = ask_for_password 'Passphrase for your Private Key:'
say "\n"
return options[:key] if options[:key]

passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
say "\n"
passphrase = ask_for_password 'Passphrase for your Private Key:'
say "\n"

raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
say "\n"

key = Gem::Security.create_key
key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation

return key, key_path
end
key = Gem::Security.create_key
key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase

return key, key_path
end

def certificates_matching filter
4 changes: 2 additions & 2 deletions lib/ruby/shared/rubygems/commands/cleanup_command.rb
Original file line number Diff line number Diff line change
@@ -67,10 +67,10 @@ def execute

say "Clean Up Complete"

if Gem.configuration.really_verbose then
verbose do
skipped = @default_gems.map { |spec| spec.full_name }

say "Skipped default gems: #{skipped.join ', '}"
"Skipped default gems: #{skipped.join ', '}"
end
end

34 changes: 27 additions & 7 deletions lib/ruby/shared/rubygems/commands/contents_command.rb
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@ class Gem::Commands::ContentsCommand < Gem::Command

def initialize
super 'contents', 'Display the contents of the installed gems',
:specdirs => [], :lib_only => false, :prefix => true
:specdirs => [], :lib_only => false, :prefix => true,
:show_install_dir => false

add_version_option

@@ -32,6 +33,11 @@ def initialize
options[:prefix] = prefix
end

add_option( '--[no-]show-install-dir',
'Show only the gem install dir') do |show, options|
options[:show_install_dir] = show
end

@path_kind = nil
@spec_dirs = nil
@version = nil
@@ -65,7 +71,12 @@ def execute
names = gem_names

names.each do |name|
found = gem_contents name
found =
if options[:show_install_dir] then
gem_install_dir name
else
gem_contents name
end

terminate_interaction 1 unless found or names.length > 1
end
@@ -91,14 +102,14 @@ def files_in_gem spec
end

def files_in_default_gem spec
spec.files.sort.map do |file|
spec.files.map do |file|
case file
when /\A#{spec.bindir}\//
[Gem::ConfigMap[:bindir], $POSTMATCH]
[RbConfig::CONFIG['bindir'], $POSTMATCH]
when /\.so\z/
[Gem::ConfigMap[:archdir], file]
[RbConfig::CONFIG['archdir'], file]
else
[Gem::ConfigMap[:rubylibdir], file]
[RbConfig::CONFIG['rubylibdir'], file]
end
end
end
@@ -115,6 +126,16 @@ def gem_contents name
true
end

def gem_install_dir name
spec = spec_for name

return false unless spec

say spec.gem_dir

true
end

def gem_names # :nodoc:
if options[:all] then
Gem::Specification.map(&:name)
@@ -125,7 +146,6 @@ def gem_names # :nodoc:

def path_description spec_dirs # :nodoc:
if spec_dirs.empty? then
spec_dirs = Gem::Specification.dirs
"default gem paths"
else
"specified path"
4 changes: 2 additions & 2 deletions lib/ruby/shared/rubygems/commands/dependency_command.rb
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ def initialize
end

def arguments # :nodoc:
"GEMNAME name of gem to show dependencies for"
"REGEXP show dependencies for gems whose names start with REGEXP"
end

def defaults_str # :nodoc:
@@ -50,7 +50,7 @@ def description # :nodoc:
end

def usage # :nodoc:
"#{program_name} GEMNAME"
"#{program_name} REGEXP"
end

def fetch_remote_specs dependency # :nodoc:
7 changes: 5 additions & 2 deletions lib/ruby/shared/rubygems/commands/environment_command.rb
Original file line number Diff line number Diff line change
@@ -28,8 +28,9 @@ def description # :nodoc:
gemrc files, environment variables and built-in defaults.
Command line argument defaults and some RubyGems defaults can be set in a
~/.gemrc file for individual users and a /etc/gemrc for all users. These
files are YAML files with the following YAML keys:
~/.gemrc file for individual users and a gemrc in the SYSTEM CONFIGURATION
DIRECTORY for all users. These files are YAML files with the following YAML
keys:
:sources: A YAML array of remote gem repositories to install gems from
:verbose: Verbosity of the gem command. false, true, and :really are the
@@ -120,6 +121,8 @@ def show_environment # :nodoc:

out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n"

out << " - SYSTEM CONFIGURATION DIRECTORY: #{Gem::ConfigFile::SYSTEM_CONFIG_PATH}\n"

out << " - RUBYGEMS PLATFORMS:\n"
Gem.platforms.each do |platform|
out << " - #{platform}\n"
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ def description # :nodoc:
end

def execute
# This is always true becasue it's the only way now.
# This is always true because it's the only way now.
options[:build_modern] = true

if not File.exist?(options[:directory]) or
219 changes: 199 additions & 20 deletions lib/ruby/shared/rubygems/commands/help_command.rb
Original file line number Diff line number Diff line change
@@ -52,6 +52,183 @@ class Gem::Commands::HelpCommand < Gem::Command
gem update --system
EOF

GEM_DEPENDENCIES = <<-EOF
A gem dependencies file allows installation of a consistent set of gems across
multiple environments. The RubyGems implementation is designed to be
compatible with Bundler's Gemfile format. You can see additional
documentation on the format at:
http://bundler.io
RubyGems automatically looks for these gem dependencies files:
* gem.deps.rb
* Gemfile
* Isolate
These files are looked up automatically using `gem install -g`, or you can
specify a custom file.
When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies
file the gems from that file will be activated at startup time. Set it to a
specific filename or to "-" to have RubyGems automatically discover the gem
dependencies file by walking up from the current directory.
You can also activate gem dependencies at program startup using
Gem.use_gemdeps.
NOTE: Enabling automatic discovery on multiuser systems can lead to execution
of arbitrary code when used from directories outside your control.
Gem Dependencies
================
Use #gem to declare which gems you directly depend upon:
gem 'rake'
To depend on a specific set of versions:
gem 'rake', '~> 10.3', '>= 10.3.2'
RubyGems will require the gem name when activating the gem using
the RUBYGEMS_GEMDEPS environment variable or Gem::use_gemdeps. Use the
require: option to override this behavior if the gem does not have a file of
that name or you don't want to require those files:
gem 'my_gem', require: 'other_file'
To prevent RubyGems from requiring any files use:
gem 'my_gem', require: false
To load dependencies from a .gemspec file:
gemspec
RubyGems looks for the first .gemspec file in the current directory. To
override this use the name: option:
gemspec name: 'specific_gem'
To look in a different directory use the path: option:
gemspec name: 'specific_gem', path: 'gemspecs'
To depend on a gem unpacked into a local directory:
gem 'modified_gem', path: 'vendor/modified_gem'
To depend on a gem from git:
gem 'private_gem', git: 'git@my.company.example:private_gem.git'
To depend on a gem from github:
gem 'private_gem', github: 'my_company/private_gem'
To depend on a gem from a github gist:
gem 'bang', gist: '1232884'
Git, github and gist support the ref:, branch: and tag: options to specify a
commit reference or hash, branch or tag respectively to use for the gem.
Setting the submodules: option to true for git, github and gist dependencies
causes fetching of submodules when fetching the repository.
You can depend on multiple gems from a single repository with the git method:
git 'https://github.com/rails/rails.git' do
gem 'activesupport'
gem 'activerecord'
end
Gem Sources
===========
RubyGems uses the default sources for regular `gem install` for gem
dependencies files. Unlike bundler, you do need to specify a source.
You can override the sources used for downloading gems with:
source 'https://gem_server.example'
You may specify multiple sources. Unlike bundler the prepend: option is not
supported. Sources are used in-order, to prepend a source place it at the
front of the list.
Gem Platform
============
You can restrict gem dependencies to specific platforms with the #platform
and #platforms methods:
platform :ruby_21 do
gem 'debugger'
end
See the bundler Gemfile manual page for a list of platforms supported in a gem
dependencies file.:
http://bundler.io/v1.6/man/gemfile.5.html
Ruby Version and Engine Dependency
==================================
You can specify the version, engine and engine version of ruby to use with
your gem dependencies file. If you are not running the specified version
RubyGems will raise an exception.
To depend on a specific version of ruby:
ruby '2.1.2'
To depend on a specific ruby engine:
ruby '1.9.3', engine: 'jruby'
To depend on a specific ruby engine version:
ruby '1.9.3', engine: 'jruby', engine_version: '1.7.11'
Grouping Dependencies
=====================
Gem dependencies may be placed in groups that can be excluded from install.
Dependencies required for development or testing of your code may be excluded
when installed in a production environment.
A #gem dependency may be placed in a group using the group: option:
gem 'minitest', group: :test
To install dependencies from a gemfile without specific groups use the
`--without` option for `gem install -g`:
$ gem install -g --without test
The group: option also accepts multiple groups if the gem fits in multiple
categories.
Multiple groups may be excluded during install by comma-separating the groups for `--without` or by specifying `--without` multiple times.
The #group method can also be used to place gems in groups:
group :test do
gem 'minitest'
gem 'minitest-emoji'
end
The #group method allows multiple groups.
The #gemspec development dependencies are placed in the :development group by
default. This may be overridden with the :development_group option:
gemspec development_group: :other
EOF

PLATFORMS = <<-'EOF'
RubyGems platforms are composed of three parts, a CPU, an OS, and a
version. These values are taken from values in rbconfig.rb. You can view
@@ -90,6 +267,16 @@ class Gem::Commands::HelpCommand < Gem::Command
Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
platform.
EOF

# NOTE when updating also update Gem::Command::HELP

SUBCOMMANDS = [
["commands", :show_commands],
["options", Gem::Command::HELP],
["examples", EXAMPLES],
["gem_dependencies", GEM_DEPENDENCIES],
["platforms", PLATFORMS],
]
# :startdoc:

def initialize
@@ -98,35 +285,27 @@ def initialize
@command_manager = Gem::CommandManager.instance
end

def arguments # :nodoc:
args = <<-EOF
commands List all 'gem' commands
examples Show examples of 'gem' usage
<command> Show specific help for <command>
EOF
return args.gsub(/^\s+/, '')
end

def usage # :nodoc:
"#{program_name} ARGUMENT"
end

def execute
arg = options[:args][0]

if begins? "commands", arg then
show_commands

elsif begins? "options", arg then
say Gem::Command::HELP

elsif begins? "examples", arg then
say EXAMPLES
_, help = SUBCOMMANDS.find do |command,|
begins? command, arg
end

elsif begins? "platforms", arg then
say PLATFORMS
if help then
if Symbol === help then
send help
else
say help
end
return
end

elsif options[:help] then
if options[:help] then
show_help

elsif arg then
145 changes: 126 additions & 19 deletions lib/ruby/shared/rubygems/commands/install_command.rb
Original file line number Diff line number Diff line change
@@ -21,7 +21,10 @@ class Gem::Commands::InstallCommand < Gem::Command
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
:format_executable => false,
:lock => true,
:suggest_alternate => true,
:version => Gem::Requirement.default,
:without_groups => [],
})

super 'install', 'Install a gem into the local repository', defaults
@@ -32,19 +35,53 @@ def initialize
add_version_option
add_prerelease_option "to be installed. (Only for listed gems)"

add_option(:"Install/Update", '-g', '--file FILE',
add_option(:"Install/Update", '-g', '--file [FILE]',
'Read from a gem dependencies API file and',
'install the listed gems') do |v,o|
v = Gem::GEM_DEP_FILES.find do |file|
File.exist? file
end unless v

unless v then
message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ', '})"

raise OptionParser::InvalidArgument,
"cannot find gem dependencies file #{message}"
end

o[:gemdeps] = v
end

add_option(:"Install/Update", '--without GROUPS', Array,
'Omit the named groups (comma separated)',
'when installing from a gem dependencies',
'file') do |v,o|
o[:without_groups].concat v.map { |without| without.intern }
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
add_option(:"Install/Update", '--explain',
'Rather than install the gems, indicate which would',
'be installed') do |v,o|
o[:explain] = v
end

add_option(:"Install/Update", '--[no-]lock',
'Create a lock file (when used with -g/--file)') do |v,o|
o[:lock] = v
end

add_option(:"Install/Update", '--[no-]suggestions',
'Suggest alternates when gems are not found') do |v,o|
o[:suggest_alternate] = v
end

@installed_specs = []
end

def arguments # :nodoc:
@@ -53,7 +90,7 @@ def arguments # :nodoc:

def defaults_str # :nodoc:
"--both --version '#{Gem::Requirement.default}' --document --no-force\n" +
"--install-dir #{Gem.dir}"
"--install-dir #{Gem.dir} --lock"
end

def description # :nodoc:
@@ -67,6 +104,25 @@ def description # :nodoc:
For example `rake _0.7.3_ --version` will run rake version 0.7.3 if a newer
version is also installed.
Gem Dependency Files
====================
RubyGems can install a consistent set of gems across multiple environments
using `gem install -g` when a gem dependencies file (gem.deps.rb, Gemfile or
Isolate) is present. If no explicit file is given RubyGems attempts to find
one in the current directory.
When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies
file the gems from that file will be activated at startup time. Set it to a
specific filename or to "-" to have RubyGems automatically discover the gem
dependencies file by walking up from the current directory.
NOTE: Enabling automatic discovery on multiuser systems can lead to
execution of arbitrary code when used from directories outside your control.
Extension Install Failures
==========================
If an extension fails to compile during gem installation the gem
specification is not written out, but the gem remains unpacked in the
repository. You may need to specify the path to the library's headers and
@@ -129,9 +185,9 @@ def check_version # :nodoc:
end

def execute
if gf = options[:gemdeps] then
install_from_gemdeps gf
return
if options.include? :gemdeps then
install_from_gemdeps
return # not reached
end

@installed_specs = []
@@ -147,17 +203,14 @@ def execute

show_installed

raise Gem::SystemExitException, exit_code
terminate_interaction exit_code
end

def install_from_gemdeps gf # :nodoc:
def install_from_gemdeps # :nodoc:
require 'rubygems/request_set'
rs = Gem::RequestSet.new
rs.load_gemdeps gf

rs.resolve

specs = rs.install options do |req, inst|
specs = rs.install_from_gemdeps options do |req, inst|
s = req.full_spec

if inst
@@ -169,19 +222,71 @@ def install_from_gemdeps gf # :nodoc:

@installed_specs = specs

raise Gem::SystemExitException, 0
terminate_interaction
end

def install_gem name, version # :nodoc:
return if options[:conservative] and
not Gem::Dependency.new(name, version).matching_specs.empty?

inst = Gem::DependencyInstaller.new options
inst.install name, Gem::Requirement.create(version)
req = Gem::Requirement.create(version)

if options[:ignore_dependencies] then
install_gem_without_dependencies name, req
else
inst = Gem::DependencyInstaller.new options
request_set = inst.resolve_dependencies name, req

if options[:explain]
puts "Gems to install:"

request_set.sorted_requests.each do |s|
puts " #{s.full_name}"
end

return
else
@installed_specs.concat request_set.install options
end

show_install_errors inst.errors
end
end

def install_gem_without_dependencies name, req # :nodoc:
gem = nil

if local? then
if name =~ /\.gem$/ and File.file? name then
source = Gem::Source::SpecificFile.new name
spec = source.spec
else
source = Gem::Source::Local.new
spec = source.find_gem name, req
end
gem = source.download spec if spec
end

if remote? and not gem then
dependency = Gem::Dependency.new name, req
dependency.prerelease = options[:prerelease]

fetcher = Gem::RemoteFetcher.fetcher
gem = fetcher.download_to_cache dependency
end

inst = Gem::Installer.new gem, options
inst.install

require 'rubygems/dependency_installer'
dinst = Gem::DependencyInstaller.new options
dinst.installed_gems.replace [inst.spec]

@installed_specs.push(*inst.installed_gems)
Gem.done_installing_hooks.each do |hook|
hook.call dinst, [inst.spec]
end unless Gem.done_installing_hooks.empty?

show_install_errors inst.errors
@installed_specs.push(inst.spec)
end

def install_gems # :nodoc:
@@ -195,8 +300,10 @@ def install_gems # :nodoc:
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
rescue Gem::GemNotFoundException => e
show_lookup_failure e.name, e.version, e.errors, options[:domain]
rescue Gem::GemNotFoundException, Gem::UnsatisfiableDependencyError => e
domain = options[:domain]
domain = :local unless options[:suggest_alternate]
show_lookup_failure e.name, e.version, e.errors, domain

exit_code |= 2
end
12 changes: 3 additions & 9 deletions lib/ruby/shared/rubygems/commands/list_command.rb
Original file line number Diff line number Diff line change
@@ -8,13 +8,13 @@
class Gem::Commands::ListCommand < Gem::Commands::QueryCommand

def initialize
super 'list', 'Display local gems whose name starts with STRING'
super 'list', 'Display local gems whose name matches REGEXP'

remove_option('--name-matches')
end

def arguments # :nodoc:
"STRING start of gem name to look for"
"REGEXP regexp to look for in gem name"
end

def defaults_str # :nodoc:
@@ -33,13 +33,7 @@ def description # :nodoc:
end

def usage # :nodoc:
"#{program_name} [STRING]"
end

def execute
string = get_one_optional_argument || ''
options[:name] = /^#{string}/i
super
"#{program_name} [STRING ...]"
end

end
32 changes: 17 additions & 15 deletions lib/ruby/shared/rubygems/commands/mirror_command.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
require 'rubygems/command'

class Gem::Commands::MirrorCommand < Gem::Command
def initialize
super('mirror', 'Mirror all gem files (requires rubygems-mirror)')
begin
Gem::Specification.find_by_name('rubygems-mirror').activate
rescue Gem::LoadError
# no-op
unless defined? Gem::Commands::MirrorCommand
class Gem::Commands::MirrorCommand < Gem::Command
def initialize
super('mirror', 'Mirror all gem files (requires rubygems-mirror)')
begin
Gem::Specification.find_by_name('rubygems-mirror').activate
rescue Gem::LoadError
# no-op
end
end
end

def description # :nodoc:
<<-EOF
def description # :nodoc:
<<-EOF
The mirror command has been moved to the rubygems-mirror gem.
EOF
end
EOF
end

def execute
alert_error "Install the rubygems-mirror gem for the mirror command"
end
def execute
alert_error "Install the rubygems-mirror gem for the mirror command"
end

end
end
76 changes: 76 additions & 0 deletions lib/ruby/shared/rubygems/commands/open_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'English'
require 'rubygems/command'
require 'rubygems/version_option'
require 'rubygems/util'

class Gem::Commands::OpenCommand < Gem::Command

include Gem::VersionOption

def initialize
super 'open', 'Open gem sources in editor'

add_option('-e', '--editor EDITOR', String,
"Opens gem sources in EDITOR") do |editor, options|
options[:editor] = editor || get_env_editor
end
end

def arguments # :nodoc:
"GEMNAME name of gem to open in editor"
end

def defaults_str # :nodoc:
"-e #{get_env_editor}"
end

def description # :nodoc:
<<-EOF
The open command opens gem in editor and changes current path
to gem's source directory. Editor can be specified with -e option,
otherwise rubygems will look for editor in $EDITOR, $VISUAL and
$GEM_EDITOR variables.
EOF
end

def usage # :nodoc:
"#{program_name} GEMNAME [-e EDITOR]"
end

def get_env_editor
ENV['GEM_EDITOR'] ||
ENV['VISUAL'] ||
ENV['EDITOR'] ||
'vi'
end

def execute
@version = options[:version] || Gem::Requirement.default
@editor = options[:editor] || get_env_editor

found = open_gem(get_one_gem_name)

terminate_interaction 1 unless found
end

def open_gem name
spec = spec_for name
return false unless spec

open_editor(spec.full_gem_path)
end

def open_editor path
Dir.chdir(path) do
system(*@editor.split(/\s+/) + [path])
end
end

def spec_for name
spec = Gem::Specification.find_all_by_name(name, @version).last

return spec if spec

say "Unable to find gem '#{name}'"
end
end
2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/commands/outdated_command.rb
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ def initialize

def description # :nodoc:
<<-EOF
The outdated command lists gems you way wish to upgrade to a newer version.
The outdated command lists gems you may wish to upgrade to a newer version.
You can check for dependency mismatches using the dependency command and
update the gems with the update or install commands.
4 changes: 3 additions & 1 deletion lib/ruby/shared/rubygems/commands/owner_command.rb
Original file line number Diff line number Diff line change
@@ -86,7 +86,9 @@ def manage_owners method, name, owners
request.add_field "Authorization", api_key
end

with_response response, "Removing #{owner}"
action = method == :delete ? "Removing" : "Adding"

with_response response, "#{action} #{owner}"
rescue
# ignore
end
31 changes: 29 additions & 2 deletions lib/ruby/shared/rubygems/commands/pristine_command.rb
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ def initialize
'Restores installed gems to pristine condition from files located in the gem cache',
:version => Gem::Requirement.default,
:extensions => true,
:extensions_set => false,
:all => false

add_option('--all',
@@ -23,7 +24,8 @@ def initialize
add_option('--[no-]extensions',
'Restore gems with extensions',
'in addition to regular gems') do |value, options|
options[:extensions] = value
options[:extensions_set] = true
options[:extensions] = value
end

add_option('--only-executables',
@@ -62,6 +64,9 @@ def description # :nodoc:
If --no-extensions is provided pristine will not attempt to restore a gem
with an extension.
If --extensions is given (but not --all or gem names) only gems with
extensions will be restored.
EOF
end

@@ -72,6 +77,14 @@ def usage # :nodoc:
def execute
specs = if options[:all] then
Gem::Specification.map

# `--extensions` must be explicitly given to pristine only gems
# with extensions.
elsif options[:extensions_set] and
options[:extensions] and options[:args].empty? then
Gem::Specification.select do |spec|
spec.extensions and not spec.extensions.empty?
end
else
get_all_gem_names.map do |gem_name|
Gem::Specification.find_all_by_name gem_name, options[:version]
@@ -96,6 +109,11 @@ def execute
next
end

if spec.bundled_gem_in_old_ruby?
say "Skipped #{spec.full_name}, it is bundled with old Ruby"
next
end

unless spec.extensions.empty? or options[:extensions] then
say "Skipped #{spec.full_name}, it needs to compile an extension"
next
@@ -107,8 +125,17 @@ def execute
require 'rubygems/remote_fetcher'

say "Cached gem for #{spec.full_name} not found, attempting to fetch..."

dep = Gem::Dependency.new spec.name, spec.version
Gem::RemoteFetcher.fetcher.download_to_cache dep
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep

if found.empty?
say "Skipped #{spec.full_name}, it was not found from cache and remote sources"
next
end

spec_candidate, source = found.first
Gem::RemoteFetcher.fetcher.download spec_candidate, source.uri.to_s, spec.base_dir
end

env_shebang =
13 changes: 9 additions & 4 deletions lib/ruby/shared/rubygems/commands/push_command.rb
Original file line number Diff line number Diff line change
@@ -69,13 +69,18 @@ def send_gem name
terminate_interaction 1
end

gem_data = Gem::Package.new(name)

unless @host then
if gem_data = Gem::Package.new(name) then
@host = gem_data.spec.metadata['default_gem_server']
end
@host = gem_data.spec.metadata['default_gem_server']
end

args << @host if @host
# Always include this, even if it's nil
args << @host

if gem_data.spec.metadata.has_key?('allowed_push_host')
args << gem_data.spec.metadata['allowed_push_host']
end

say "Pushing gem to #{@host || Gem.host}..."

50 changes: 33 additions & 17 deletions lib/ruby/shared/rubygems/commands/query_command.rb
Original file line number Diff line number Diff line change
@@ -72,16 +72,26 @@ def description # :nodoc:

def execute
exit_code = 0
if options[:args].to_a.empty? and options[:name].source.empty?
name = options[:name]
no_name = true
elsif !options[:name].source.empty?
name = Array(options[:name])
else
name = options[:args].to_a.map{|arg| /#{arg}/i }
end

name = options[:name]
prerelease = options[:prerelease]

unless options[:installed].nil? then
if name.source.empty? then
if no_name then
alert_error "You must specify a gem name"
exit_code |= 4
elsif name.count > 1
alert_error "You must specify only ONE gem!"
exit_code |= 4
else
installed = installed? name, options[:version]
installed = installed? name.first, options[:version]
installed = !installed unless options[:installed]

if installed then
@@ -95,6 +105,22 @@ def execute
terminate_interaction exit_code
end

names = Array(name)
names.each { |n| show_gems n, prerelease }
end

private

def display_header type
if (ui.outs.tty? and Gem.configuration.verbose) or both? then
say
say "*** #{type} GEMS ***"
say
end
end

#Guts of original execute
def show_gems name, prerelease
req = Gem::Requirement.default
# TODO: deprecate for real
dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req }
@@ -105,11 +131,7 @@ def execute
alert_warning "prereleases are always shown locally"
end

if ui.outs.tty? or both? then
say
say "*** LOCAL GEMS ***"
say
end
display_header 'LOCAL'

specs = Gem::Specification.find_all { |s|
s.name =~ name and req =~ s.version
@@ -123,11 +145,7 @@ def execute
end

if remote? then
if ui.outs.tty? or both? then
say
say "*** REMOTE GEMS ***"
say
end
display_header 'REMOTE'

fetcher = Gem::SpecFetcher.fetcher

@@ -143,20 +161,18 @@ def execute
:latest
end

if options[:name].source.empty?
if name.source.empty?
spec_tuples = fetcher.detect(type) { true }
else
spec_tuples = fetcher.detect(type) do |name_tuple|
options[:name] === name_tuple.name
name === name_tuple.name
end
end

output_query_results spec_tuples
end
end

private

##
# Check if gem +name+ version +version+ is installed.

16 changes: 5 additions & 11 deletions lib/ruby/shared/rubygems/commands/search_command.rb
Original file line number Diff line number Diff line change
@@ -4,15 +4,15 @@
class Gem::Commands::SearchCommand < Gem::Commands::QueryCommand

def initialize
super 'search', 'Display remote gems whose name contains STRING'
super 'search', 'Display remote gems whose name matches REGEXP'

remove_option '--name-matches'

defaults[:domain] = :remote
end

def arguments # :nodoc:
"STRING fragment of gem name to search for"
"REGEXP regexp to search for in gem name"
end

def defaults_str # :nodoc:
@@ -21,8 +21,8 @@ def defaults_str # :nodoc:

def description # :nodoc:
<<-EOF
The search command displays remote gems whose name contains the given
string.
The search command displays remote gems whose name matches the given
regexp.
The --details option displays additional details from the gem but will
take a little longer to complete as it must download the information
@@ -33,13 +33,7 @@ def description # :nodoc:
end

def usage # :nodoc:
"#{program_name} [STRING]"
end

def execute
string = get_one_optional_argument
options[:name] = /#{string}/i
super
"#{program_name} [REGEXP]"
end

end
18 changes: 9 additions & 9 deletions lib/ruby/shared/rubygems/commands/setup_command.rb
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ def initialize

super 'setup', 'Install RubyGems',
:format_executable => true, :document => %w[ri],
:site_or_vendor => :sitelibdir,
:site_or_vendor => 'sitelibdir',
:destdir => '', :prefix => '', :previous_version => ''

add_option '--previous-version=VERSION',
@@ -36,7 +36,7 @@ def initialize

add_option '--[no-]vendor',
'Install into vendorlibdir not sitelibdir' do |vendor, options|
options[:site_or_vendor] = vendor ? :vendorlibdir : :sitelibdir
options[:site_or_vendor] = vendor ? 'vendorlibdir' : 'sitelibdir'
end

add_option '--[no-]format-executable',
@@ -343,19 +343,19 @@ def generate_default_dirs(install_destdir)
site_or_vendor = options[:site_or_vendor]

if prefix.empty? then
lib_dir = Gem::ConfigMap[site_or_vendor]
bin_dir = Gem::ConfigMap[:bindir]
lib_dir = RbConfig::CONFIG[site_or_vendor]
bin_dir = RbConfig::CONFIG['bindir']
else
# Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets
# confused about installation location, so switch back to
# sitelibdir/vendorlibdir.
if defined?(APPLE_GEM_HOME) and
# just in case Apple and RubyGems don't get this patched up proper.
(prefix == Gem::ConfigMap[:libdir] or
(prefix == RbConfig::CONFIG['libdir'] or
# this one is important
prefix == File.join(Gem::ConfigMap[:libdir], 'ruby')) then
lib_dir = Gem::ConfigMap[site_or_vendor]
bin_dir = Gem::ConfigMap[:bindir]
prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) then
lib_dir = RbConfig::CONFIG[site_or_vendor]
bin_dir = RbConfig::CONFIG['bindir']
else
lib_dir = File.join prefix, 'lib'
bin_dir = File.join prefix, 'bin'
@@ -446,7 +446,7 @@ def show_release_notes
history.force_encoding Encoding::UTF_8 if
Object.const_defined? :Encoding

history = history.sub(/^# coding:.*?^=/m, '')
history = history.sub(/^# coding:.*?(?=^=)/m, '')

text = history.split(HISTORY_HEADER)
text.shift # correct an off-by-one generated by split
2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/commands/specification_command.rb
Original file line number Diff line number Diff line change
@@ -127,7 +127,7 @@ def execute
end

unless options[:all] then
specs = [specs.sort_by { |s| s.version }.last]
specs = [specs.max_by { |s| s.version }]
end

specs.each do |s|
16 changes: 14 additions & 2 deletions lib/ruby/shared/rubygems/commands/uninstall_command.rb
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class Gem::Commands::UninstallCommand < Gem::Command
def initialize
super 'uninstall', 'Uninstall gems from the local repository',
:version => Gem::Requirement.default, :user_install => true,
:check_dev => false
:check_dev => false, :vendor => false

add_option('-a', '--[no-]all',
'Uninstall all matching versions'
@@ -76,6 +76,18 @@ def initialize

add_version_option
add_platform_option

add_option('--vendor',
'Uninstall gem from the vendor directory.',
'Only for use by gem repackagers.') do |value, options|
unless Gem.vendor_dir then
raise OptionParser::InvalidOption.new 'your platform is not supported'
end

alert_warning 'Use your OS package manager to uninstall vendor gems'
options[:vendor] = true
options[:install_dir] = Gem.vendor_dir
end
end

def arguments # :nodoc:
@@ -112,7 +124,7 @@ def execute
end

def uninstall_all
_, specs = Gem::Specification.partition { |spec| spec.default_gem? }
specs = Gem::Specification.reject { |spec| spec.default_gem? }

specs.each do |spec|
options[:version] = spec.version
2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/commands/unpack_command.rb
Original file line number Diff line number Diff line change
@@ -134,7 +134,7 @@ def get_path dependency

specs = dependency.matching_specs

selected = specs.sort_by { |s| s.version }.last # HACK: hunt last down
selected = specs.max_by { |s| s.version }

return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless
selected
39 changes: 24 additions & 15 deletions lib/ruby/shared/rubygems/commands/update_command.rb
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ class Gem::Commands::UpdateCommand < Gem::Command

attr_reader :installer # :nodoc:

attr_reader :updated # :nodoc:

def initialize
super 'update', 'Update installed gems to the latest version',
:document => %w[rdoc ri],
@@ -45,7 +47,7 @@ def initialize
end

def arguments # :nodoc:
"GEMNAME name of gem to update"
"REGEXP regexp to search for in gem name"
end

def defaults_str # :nodoc:
@@ -56,13 +58,13 @@ def description # :nodoc:
<<-EOF
The update command will update your gems to the latest version.
The update comamnd does not remove the previous version. Use the cleanup
The update command does not remove the previous version. Use the cleanup
command to remove old versions.
EOF
end

def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
"#{program_name} REGEXP [REGEXP ...]"
end

def check_latest_rubygems version # :nodoc:
@@ -82,8 +84,6 @@ def check_update_arguments # :nodoc:
end

def execute
hig = {}

if options[:system] then
update_rubygems
return
@@ -97,10 +97,14 @@ def execute

updated = update_gems gems_to_update

updated_names = updated.map { |spec| spec.name }
not_updated_names = options[:args].uniq - updated_names

if updated.empty? then
say "Nothing to update"
else
say "Gems updated: #{updated.map { |spec| spec.name }.join ' '}"
say "Gems updated: #{updated_names.join(' ')}"
say "Gems already up-to-date: #{not_updated_names.join(' ')}" unless not_updated_names.empty?
end
end

@@ -110,7 +114,11 @@ def fetch_remote_gems spec # :nodoc:

fetcher = Gem::SpecFetcher.fetcher

spec_tuples, _ = fetcher.search_for_dependency dependency
spec_tuples, errors = fetcher.search_for_dependency dependency

error = errors.find { |e| e.respond_to? :exception }

raise error if error

spec_tuples
end
@@ -134,7 +142,7 @@ def highest_remote_version spec # :nodoc:
g.name == spec.name and g.match_platform?
end

highest_remote_gem = matching_gems.sort_by { |g,_| g.version }.last
highest_remote_gem = matching_gems.max_by { |g,_| g.version }

highest_remote_gem ||= [Gem::NameTuple.null]

@@ -193,17 +201,16 @@ def rubygems_target_version
def update_gem name, version = Gem::Requirement.default
return if @updated.any? { |spec| spec.name == name }

@installer ||= Gem::DependencyInstaller.new options
update_options = options.dup
update_options[:prerelease] = version.prerelease?

success = false
@installer = Gem::DependencyInstaller.new update_options

say "Updating #{name}"
begin
@installer.install name, Gem::Requirement.new(version)
success = true
rescue Gem::InstallError => e
rescue Gem::InstallError, Gem::DependencyError => e
alert_error "Error installing #{name}:\n\t#{e.message}"
success = false
end

@installer.installed_gems.each do |spec|
@@ -244,6 +251,9 @@ def update_rubygems_arguments # :nodoc:
args << '--no-rdoc' unless options[:document].include? 'rdoc'
args << '--no-ri' unless options[:document].include? 'ri'
args << '--no-format-executable' if options[:no_format_executable]
args << '--previous-version' << Gem::VERSION if
options[:system] == true or
Gem::Version.new(options[:system]) >= Gem::Version.new(2)
args
end

@@ -252,7 +262,7 @@ def which_to_update highest_installed_gems, gem_names, system = false

highest_installed_gems.each do |l_name, l_spec|
next if not gem_names.empty? and
gem_names.all? { |name| /#{name}/ !~ l_spec.name }
gem_names.none? { |name| name == l_spec.name }

highest_remote_ver = highest_remote_version l_spec

@@ -265,4 +275,3 @@ def which_to_update highest_installed_gems, gem_names, system = false
end

end

13 changes: 5 additions & 8 deletions lib/ruby/shared/rubygems/commands/which_command.rb
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ def description # :nodoc:
end

def execute
found = false
found = true

options[:args].each do |arg|
arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, '')
@@ -45,9 +45,9 @@ def execute

if spec then
if options[:search_gems_first] then
dirs = gem_paths(spec) + $LOAD_PATH
dirs = spec.full_require_paths + $LOAD_PATH
else
dirs = $LOAD_PATH + gem_paths(spec)
dirs = $LOAD_PATH + spec.full_require_paths
end
end

@@ -56,9 +56,10 @@ def execute

if paths.empty? then
alert_error "Can't find ruby library file or shared library #{arg}"

found &&= false
else
say paths
found = true
end
end

@@ -81,10 +82,6 @@ def find_paths(package_name, dirs)
result
end

def gem_paths(spec)
spec.require_paths.collect { |d| File.join spec.full_gem_path, d }
end

def usage # :nodoc:
"#{program_name} FILE [FILE ...]"
end
23 changes: 9 additions & 14 deletions lib/ruby/shared/rubygems/commands/yank_command.rb
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ def description # :nodoc:
data you will need to change them immediately and yank your gem.
If you are yanking a gem due to intellectual property reasons contact
http://help.rubygems.org for permanant removal. Be sure to mention this
http://help.rubygems.org for permanent removal. Be sure to mention this
as the reason for the removal request.
EOF
end
@@ -44,45 +44,40 @@ def initialize
options[:undo] = true
end

add_option('-k', '--key KEY_NAME',
'Use API key from your gem credentials file') do |value, options|
options[:key] = value
end
add_key_option
end

def execute
sign_in

version = get_version_from_requirements(options[:version])
platform = get_platform_from_requirements(options)
api_key = Gem.configuration.rubygems_api_key
api_key = Gem.configuration.api_keys[options[:key].to_sym] if options[:key]

if version then
if options[:undo] then
unyank_gem(version, platform, api_key)
unyank_gem(version, platform)
else
yank_gem(version, platform, api_key)
yank_gem(version, platform)
end
else
say "A version argument is required: #{usage}"
terminate_interaction
end
end

def yank_gem(version, platform, api_key)
def yank_gem(version, platform)
say "Yanking gem from #{self.host}..."
yank_api_request(:delete, version, platform, "api/v1/gems/yank", api_key)
yank_api_request(:delete, version, platform, "api/v1/gems/yank")
end

def unyank_gem(version, platform, api_key)
def unyank_gem(version, platform)
say "Unyanking gem from #{host}..."
yank_api_request(:put, version, platform, "api/v1/gems/unyank", api_key)
yank_api_request(:put, version, platform, "api/v1/gems/unyank")
end

private

def yank_api_request(method, version, platform, api, api_key)
def yank_api_request(method, version, platform, api)
name = get_one_gem_name
response = rubygems_api_request(method, api) do |request|
request.add_field("Authorization", api_key)
10 changes: 7 additions & 3 deletions lib/ruby/shared/rubygems/compatibility.rb
Original file line number Diff line number Diff line change
@@ -20,8 +20,7 @@ module Gem

$LOADED_FEATURES.delete Gem::QuickLoader.path_to_full_rubygems_library

if $LOADED_FEATURES.any? do |path| path.end_with? '/rubygems.rb' end then
# TODO path does not exist here
if path = $LOADED_FEATURES.find {|n| n.end_with? '/rubygems.rb'} then
raise LoadError, "another rubygems is already loaded from #{path}"
end

@@ -33,7 +32,12 @@ class << Gem
module Gem
RubyGemsVersion = VERSION

# TODO remove at RubyGems 3

RbConfigPriorities = %w[
MAJOR
MINOR
TEENY
EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name
ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir
rubylibdir
@@ -42,7 +46,7 @@ module Gem
unless defined?(ConfigMap)
##
# Configuration settings from ::RbConfig
ConfigMap = Hash.new do |cm, key|
ConfigMap = Hash.new do |cm, key| # TODO remove at RubyGems 3
cm[key] = RbConfig::CONFIG[key.to_s]
end
else
22 changes: 17 additions & 5 deletions lib/ruby/shared/rubygems/config_file.rb
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ class Gem::ConfigFile

# :stopdoc:

system_config_path =
SYSTEM_CONFIG_PATH =
begin
require "etc"
Etc.sysconfdir
@@ -86,7 +86,7 @@ class Gem::ConfigFile

# :startdoc:

SYSTEM_WIDE_CONFIG_FILE = File.join system_config_path, 'gemrc'
SYSTEM_WIDE_CONFIG_FILE = File.join SYSTEM_CONFIG_PATH, 'gemrc'

##
# List of arguments supplied to the config file object.
@@ -137,9 +137,10 @@ class Gem::ConfigFile
attr_reader :ssl_verify_mode

##
# Path name of directory or file of openssl CA certificate, used for remote https connection
# Path name of directory or file of openssl CA certificate, used for remote
# https connection

attr_reader :ssl_ca_cert
attr_accessor :ssl_ca_cert

##
# Path name of directory or file of openssl client certificate, used for remote https connection with client authentication
@@ -336,7 +337,7 @@ def load_file(filename)
end
return content
rescue *YAMLErrors => e
warn "Failed to load #{filename}, #{e.to_s}"
warn "Failed to load #{filename}, #{e}"
rescue Errno::EACCES
warn "Failed to load #{filename} due to permissions problem."
end
@@ -382,6 +383,8 @@ def handle_arguments(arg_list)
@backtrace = true
when /^--debug$/ then
$DEBUG = true

warn 'NOTE: Debugging mode prints all exceptions even when rescued'
else
@args << arg
end
@@ -427,6 +430,15 @@ def to_yaml # :nodoc:
DEFAULT_VERBOSITY
end

yaml_hash[:ssl_verify_mode] =
@hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode

yaml_hash[:ssl_ca_cert] =
@hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert

yaml_hash[:ssl_client_cert] =
@hash[:ssl_client_cert] if @hash.key? :ssl_client_cert

keys = yaml_hash.keys.map { |key| key.to_s }
keys << 'debug'
re = Regexp.union(*keys)
24 changes: 22 additions & 2 deletions lib/ruby/shared/rubygems/core_ext/kernel_gem.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
##
# RubyGems adds the #gem method to allow activation of specific gem versions
# and overrides the #require method on Kernel to make gems appear as if they
# live on the <code>$LOAD_PATH</code>. See the documentation of these methods
# for further detail.

module Kernel

# REFACTOR: This should be pulled out into some kind of hacks file.
@@ -20,6 +26,11 @@ module Kernel
# Kernel#gem should be called *before* any require statements (otherwise
# RubyGems may load a conflicting library version).
#
# Kernel#gem only loads prerelease versions when prerelease +requirements+
# are given:
#
# gem 'rake', '>= 1.1.a', '< 2'
#
# In older RubyGems versions, the environment variable GEM_SKIP could be
# used to skip activation of specified gems, for example to test out changes
# that haven't been installed yet. Now RubyGems defers to -I and the
@@ -44,8 +55,17 @@ def gem(gem_name, *requirements) # :doc:
gem_name = gem_name.name
end

spec = Gem::Dependency.new(gem_name, *requirements).to_spec
spec.activate if spec
dep = Gem::Dependency.new(gem_name, *requirements)

loaded = Gem.loaded_specs[gem_name]

return false if loaded && dep.matches_spec?(loaded)

spec = dep.to_spec

Gem::LOADED_SPECS_MUTEX.synchronize {
spec.activate
} if spec
end

private :gem
36 changes: 13 additions & 23 deletions lib/ruby/shared/rubygems/core_ext/kernel_require.rb
Original file line number Diff line number Diff line change
@@ -50,12 +50,8 @@ def require path
# normal require handle loading a gem from the rescue below.

if Gem::Specification.unresolved_deps.empty? then
begin
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.enter
end
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
end

# If +path+ is for a gem that has already been loaded, don't
@@ -70,9 +66,7 @@ def require path

begin
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.enter
return gem_original_require(spec.to_fullpath(path) || path)
end if spec

# Attempt to find +path+ in any unresolved gems...
@@ -105,6 +99,7 @@ def require path
names = found_specs.map(&:name).uniq

if names.size > 1 then
RUBYGEMS_ACTIVATION_MONITOR.exit
raise Gem::LoadError, "#{path} found in multiple gems: #{names.join ', '}"
end

@@ -115,32 +110,27 @@ def require path
unless valid then
le = Gem::LoadError.new "unable to find a version of '#{names.first}' to activate"
le.name = names.first
RUBYGEMS_ACTIVATION_MONITOR.exit
raise le
end

valid.activate
end

begin
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.enter
end
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
rescue LoadError => load_error
RUBYGEMS_ACTIVATION_MONITOR.enter

if load_error.message.start_with?("Could not find") or
(load_error.message.end_with?(path) and Gem.try_activate(path)) then
begin
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
ensure
RUBYGEMS_ACTIVATION_MONITOR.enter
end
RUBYGEMS_ACTIVATION_MONITOR.exit
return gem_original_require(path)
else
RUBYGEMS_ACTIVATION_MONITOR.exit
end

raise load_error
ensure
RUBYGEMS_ACTIVATION_MONITOR.exit
end

private :require
63 changes: 48 additions & 15 deletions lib/ruby/shared/rubygems/defaults.rb
Original file line number Diff line number Diff line change
@@ -29,28 +29,39 @@ def self.default_spec_cache_dir
def self.default_dir
path = if defined? RUBY_FRAMEWORK_VERSION then
[
File.dirname(ConfigMap[:sitedir]),
File.dirname(RbConfig::CONFIG['sitedir']),
'Gems',
ConfigMap[:ruby_version]
RbConfig::CONFIG['ruby_version']
]
elsif ConfigMap[:rubylibprefix] then
elsif RbConfig::CONFIG['rubylibprefix'] then
[
ConfigMap[:rubylibprefix],
RbConfig::CONFIG['rubylibprefix'],
'gems',
ConfigMap[:ruby_version]
RbConfig::CONFIG['ruby_version']
]
else
[
ConfigMap[:libdir],
RbConfig::CONFIG['libdir'],
ruby_engine,
'gems',
ConfigMap[:ruby_version]
RbConfig::CONFIG['ruby_version']
]
end

@default_dir ||= File.join(*path)
end

##
# Returns binary extensions dir for specified RubyGems base dir or nil
# if such directory cannot be determined.
#
# By default, the binary extensions are located side by side with their
# Ruby counterparts, therefore nil is returned

def self.default_ext_dir_for base_dir
nil
end

##
# Paths where RubyGems' .rb files and bin files are installed

@@ -63,7 +74,7 @@ def self.default_rubygems_dirs

def self.user_dir
parts = [Gem.user_home, '.gem', ruby_engine]
parts << ConfigMap[:ruby_version] unless ConfigMap[:ruby_version].empty?
parts << RbConfig::CONFIG['ruby_version'] unless RbConfig::CONFIG['ruby_version'].empty?
File.join parts
end

@@ -78,18 +89,18 @@ def self.path_separator
# Default gem load path

def self.default_path
if Gem.user_home && File.exist?(Gem.user_home) then
[user_dir, default_dir]
else
[default_dir]
end
path = []
path << user_dir if user_home && File.exist?(user_home)
path << default_dir
path << vendor_dir if vendor_dir and File.directory? vendor_dir
path
end

##
# Deduce Ruby's --program-prefix and --program-suffix from its install name

def self.default_exec_format
exec_format = ConfigMap[:ruby_install_name].sub('ruby', '%s') rescue '%s'
exec_format = RbConfig::CONFIG['ruby_install_name'].sub('ruby', '%s') rescue '%s'

unless exec_format =~ /%s/ then
raise Gem::Exception,
@@ -106,7 +117,7 @@ def self.default_bindir
if defined? RUBY_FRAMEWORK_VERSION then # mac framework support
'/usr/bin'
else # generic install
ConfigMap[:bindir]
RbConfig::CONFIG['bindir']
end
end

@@ -141,4 +152,26 @@ def self.default_cert_path
def self.default_gems_use_full_paths?
ruby_engine != 'ruby'
end

##
# Install extensions into lib as well as into the extension directory.

def self.install_extension_in_lib # :nodoc:
true
end

##
# Directory where vendor gems are installed.

def self.vendor_dir # :nodoc:
if vendor_dir = ENV['GEM_VENDOR'] then
return vendor_dir.dup
end

return nil unless RbConfig::CONFIG.key? 'vendordir'

File.join RbConfig::CONFIG['vendordir'], 'gems',
RbConfig::CONFIG['ruby_version']
end

end
59 changes: 40 additions & 19 deletions lib/ruby/shared/rubygems/dependency.rb
Original file line number Diff line number Diff line change
@@ -74,7 +74,7 @@ def hash # :nodoc:
end

def inspect # :nodoc:
if @prerelease
if prerelease? then
"<%s type=%p name=%p requirements=%p prerelease=ok>" %
[self.class, self.type, self.name, requirement.to_s]
else
@@ -145,7 +145,6 @@ def requirement
@requirement = @version_requirements if defined?(@version_requirements)
end

# DOC: this method needs documentation or :nodoc''d
def requirements_list
requirement.as_list
end
@@ -205,9 +204,19 @@ def =~ other

alias === =~

# DOC: this method needs either documented or :nodoc'd
##
# :call-seq:
# dep.match? name => true or false
# dep.match? name, version => true or false
# dep.match? spec => true or false
#
# Does this dependency match the specification described by +name+ and
# +version+ or match +spec+?
#
# NOTE: Unlike #matches_spec? this method does not return true when the
# version is a prerelease version unless this is a prerelease dependency.

def match? obj, version=nil
def match? obj, version=nil, allow_prerelease=false
if !version
name = obj.name
version = obj.version
@@ -216,12 +225,23 @@ def match? obj, version=nil
end

return false unless self.name === name
return true if requirement.none?

requirement.satisfied_by? Gem::Version.new(version)
version = Gem::Version.new version

return true if requirement.none? and not version.prerelease?
return false if version.prerelease? and
not allow_prerelease and
not prerelease?

requirement.satisfied_by? version
end

# DOC: this method needs either documented or :nodoc'd
##
# Does this dependency match +spec+?
#
# NOTE: This is not a convenience method. Unlike #match? this method
# returns true when +spec+ is a prerelease version even if this dependency
# is not a prerelease dependency.

def matches_spec? spec
return false unless name === spec.name
@@ -249,8 +269,6 @@ def merge other
self.class.new name, self_req.as_list.concat(other_req.as_list)
end

# DOC: this method needs either documented or :nodoc'd

def matching_specs platform_only = false
matches = Gem::Specification.stubs.find_all { |spec|
self.name === spec.name and # TODO: == instead of ===
@@ -263,7 +281,7 @@ def matching_specs platform_only = false
}
end

matches = matches.sort_by { |s| s.sort_obj } # HACK: shouldn't be needed
matches.sort_by { |s| s.sort_obj } # HACK: shouldn't be needed
end

##
@@ -273,8 +291,6 @@ def specific?
@requirement.specific?
end

# DOC: this method needs either documented or :nodoc'd

def to_specs
matches = matching_specs true

@@ -287,12 +303,13 @@ def to_specs

if specs.empty?
total = Gem::Specification.to_a.size
error = Gem::LoadError.new \
"Could not find '#{name}' (#{requirement}) among #{total} total gem(s)"
msg = "Could not find '#{name}' (#{requirement}) among #{total} total gem(s)\n"
else
error = Gem::LoadError.new \
"Could not find '#{name}' (#{requirement}) - did find: [#{specs.join ','}]"
msg = "Could not find '#{name}' (#{requirement}) - did find: [#{specs.join ','}]\n"
end
msg << "Checked in 'GEM_PATH=#{Gem.path.join(File::PATH_SEPARATOR)}', execute `gem env` for more information"

error = Gem::LoadError.new(msg)
error.name = self.name
error.requirement = self.requirement
raise error
@@ -303,11 +320,15 @@ def to_specs
matches
end

# DOC: this method needs either documented or :nodoc'd

def to_spec
matches = self.to_specs

matches.find { |spec| spec.activated? } or matches.last
active = matches.find { |spec| spec.activated? }

return active if active

matches.delete_if { |spec| spec.version.prerelease? } unless prerelease?

matches.last
end
end
113 changes: 95 additions & 18 deletions lib/ruby/shared/rubygems/dependency_installer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require 'rubygems'
require 'rubygems/dependency_list'
require 'rubygems/dependency_resolver'
require 'rubygems/package'
require 'rubygems/installer'
require 'rubygems/spec_fetcher'
@@ -73,6 +72,7 @@ class Gem::DependencyInstaller
def initialize options = {}
@only_install_dir = !!options[:install_dir]
@install_dir = options[:install_dir] || Gem.dir
@build_root = options[:build_root]

options = DEFAULT_OPTIONS.merge options

@@ -103,7 +103,7 @@ def initialize options = {}

@cache_dir = options[:cache_dir] || @install_dir

@errors = nil
@errors = []
end

##
@@ -158,6 +158,7 @@ def add_found_dependencies to_do, dependency_list # :nodoc:

dependency_list.remove_specs_unsatisfied_by dependencies
end

##
# Creates an AvailableSet to install from based on +dep_or_name+ and
# +version+
@@ -196,7 +197,7 @@ def consider_remote?
# 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:
def find_gems_with_sources dep, best_only=false # :nodoc:
set = Gem::AvailableSet.new

if consider_local?
@@ -211,23 +212,52 @@ def find_gems_with_sources dep # :nodoc:

if consider_remote?
begin
found, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
# TODO this is pulled from #spec_for_dependency to allow
# us to filter tuples before fetching specs.
#
tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep

if best_only && !tuples.empty?
tuples.sort! do |a,b|
if b[0].version == a[0].version
if b[0].platform != Gem::Platform::RUBY
1
else
-1
end
else
b[0].version <=> a[0].version
end
end
tuples = [tuples.first]
end

specs = []
tuples.each do |tup, source|
begin
spec = source.fetch_spec(tup)
rescue Gem::RemoteFetcher::FetchError => e
errors << Gem::SourceFetchProblem.new(source, e)
else
specs << [spec, source]
end
end

if @errors
@errors += errors
else
@errors = errors
end

set << found
set << specs

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"
verbose do
"Error fetching remote data:\t\t#{e.message}\n" \
"Falling back to local-only install"
end
@domain = :local
end
@@ -250,6 +280,14 @@ def find_spec_by_name_and_version gem_name,
if gem_name =~ /\.gem$/ and File.file? gem_name then
src = Gem::Source::SpecificFile.new(gem_name)
set.add src.spec, src
elsif gem_name =~ /\.gem$/ then
Dir[gem_name].each do |name|
begin
src = Gem::Source::SpecificFile.new name
set.add src.spec, src
rescue Gem::Package::FormatError
end
end
else
local = Gem::Source::Local.new

@@ -263,7 +301,7 @@ def find_spec_by_name_and_version gem_name,
dep = Gem::Dependency.new gem_name, version
dep.prerelease = true if prerelease

set = find_gems_with_sources(dep)
set = find_gems_with_sources(dep, true)
set.match_platform!
end

@@ -278,7 +316,7 @@ def find_spec_by_name_and_version gem_name,
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
#--
# TODO remove, no longer used
# TODO remove at RubyGems 3

def gather_dependencies # :nodoc:
specs = @available.all_specs
@@ -349,13 +387,16 @@ def install dep_or_name, version = Gem::Requirement.default
options = {
:bin_dir => @bin_dir,
:build_args => @build_args,
:document => @document,
:env_shebang => @env_shebang,
:force => @force,
:format_executable => @format_executable,
:ignore_dependencies => @ignore_dependencies,
:prerelease => @prerelease,
:security_policy => @security_policy,
:user_install => @user_install,
:wrappers => @wrappers,
:build_root => @build_root,
:install_as_default => @install_as_default
}
options[:install_dir] = @install_dir if @only_install_dir
@@ -389,23 +430,59 @@ def install_development_deps # :nodoc:
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 = Gem::RequestSet.new
request_set.development = @development
request_set.development_shallow = @dev_shallow
request_set.soft_missing = @force
request_set.prerelease = @prerelease
request_set.remote = false unless consider_remote?

installer_set = Gem::DependencyResolver::InstallerSet.new @domain
installer_set.always_install.concat request_set.always_install
installer_set = Gem::Resolver::InstallerSet.new @domain
installer_set.ignore_installed = @only_install_dir

if consider_local?
if dep_or_name =~ /\.gem$/ and File.file? dep_or_name then
src = Gem::Source::SpecificFile.new dep_or_name
installer_set.add_local dep_or_name, src.spec, src
version = src.spec.version if version == Gem::Requirement.default
elsif dep_or_name =~ /\.gem$/ then
Dir[dep_or_name].each do |name|
begin
src = Gem::Source::SpecificFile.new name
installer_set.add_local dep_or_name, src.spec, src
rescue Gem::Package::FormatError
end
end
# else This is a dependency. InstallerSet handles this case
end
end

dependency =
if spec = installer_set.local?(dep_or_name) then
Gem::Dependency.new spec.name, version
elsif String === dep_or_name then
Gem::Dependency.new dep_or_name, version
else
dep_or_name
end

dependency.prerelease = @prerelease

request_set.import [dependency]

installer_set.add_always_install dependency

request_set.always_install = installer_set.always_install

if @ignore_dependencies then
installer_set.ignore_dependencies = true
request_set.soft_missing = true
request_set.ignore_dependencies = true
request_set.soft_missing = true
end

composed_set = Gem::DependencyResolver.compose_sets as, installer_set
request_set.resolve installer_set

request_set.resolve composed_set
@errors.concat request_set.errors

request_set
end
6 changes: 1 addition & 5 deletions lib/ruby/shared/rubygems/dependency_list.rb
Original file line number Diff line number Diff line change
@@ -219,11 +219,7 @@ def tsort_each_child(node)
dependencies.each do |dep|
specs.each do |spec|
if spec.satisfies_requirement? dep then
begin
yield spec
rescue TSort::Cyclic
# do nothing
end
yield spec
break
end
end
254 changes: 0 additions & 254 deletions lib/ruby/shared/rubygems/dependency_resolver.rb

This file was deleted.

109 changes: 0 additions & 109 deletions lib/ruby/shared/rubygems/dependency_resolver/activation_request.rb

This file was deleted.

65 changes: 0 additions & 65 deletions lib/ruby/shared/rubygems/dependency_resolver/api_set.rb

This file was deleted.

39 changes: 0 additions & 39 deletions lib/ruby/shared/rubygems/dependency_resolver/api_specification.rb

This file was deleted.

18 changes: 0 additions & 18 deletions lib/ruby/shared/rubygems/dependency_resolver/composed_set.rb

This file was deleted.

This file was deleted.

51 changes: 0 additions & 51 deletions lib/ruby/shared/rubygems/dependency_resolver/dependency_request.rb

This file was deleted.

64 changes: 0 additions & 64 deletions lib/ruby/shared/rubygems/dependency_resolver/index_set.rb

This file was deleted.

This file was deleted.

This file was deleted.

154 changes: 0 additions & 154 deletions lib/ruby/shared/rubygems/dependency_resolver/installer_set.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/deprecate.rb
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ def deprecate name, repl, year, month
class_eval {
old = "_deprecated_#{name}"
alias_method old, name
define_method name do |*args, &block| # TODO: really works on 1.8.7?
define_method name do |*args, &block|
klass = self.kind_of? Module
target = klass ? "#{self}." : "#{self.class}#"
msg = [ "NOTE: #{target}#{name} is deprecated",
28 changes: 17 additions & 11 deletions lib/ruby/shared/rubygems/doctor.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require 'rubygems'
require 'rubygems/user_interaction'
require 'pathname'

##
# Cleans up after a partially-failed uninstall or for an invalid
@@ -24,21 +23,25 @@ class Gem::Doctor
['build_info', '.info'],
['cache', '.gem'],
['doc', ''],
['extensions', ''],
['gems', ''],
]

raise 'Update REPOSITORY_EXTENSION_MAP' unless
Gem::REPOSITORY_SUBDIRECTORIES.sort ==
missing =
Gem::REPOSITORY_SUBDIRECTORIES.sort -
REPOSITORY_EXTENSION_MAP.map { |(k,_)| k }.sort

raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ', '}" unless
missing.empty?

##
# Creates a new Gem::Doctor that will clean up +gem_repository+. Only one
# gem repository may be cleaned at a time.
#
# If +dry_run+ is true no files or directories will be removed.

def initialize gem_repository, dry_run = false
@gem_repository = Pathname(gem_repository)
@gem_repository = gem_repository
@dry_run = dry_run

@installed_specs = nil
@@ -96,26 +99,29 @@ def doctor_children # :nodoc:
# Removes files in +sub_directory+ with +extension+

def doctor_child sub_directory, extension # :nodoc:
directory = @gem_repository + sub_directory
directory = File.join(@gem_repository, sub_directory)

Dir.entries(directory).sort.each do |ent|
next if ent == "." || ent == ".."

directory.children.sort.each do |child|
next unless child.exist?
child = File.join(directory, ent)
next unless File.exist?(child)

basename = child.basename(extension).to_s
basename = File.basename(child, extension)
next if installed_specs.include? basename
next if /^rubygems-\d/ =~ basename
next if 'specifications' == sub_directory and 'default' == basename

type = child.directory? ? 'directory' : 'file'
type = File.directory?(child) ? 'directory' : 'file'

action = if @dry_run then
'Extra'
else
child.rmtree
FileUtils.rm_r(child)
'Removed'
end

say "#{action} #{type} #{sub_directory}/#{child.basename}"
say "#{action} #{type} #{sub_directory}/#{File.basename(child)}"
end
rescue Errno::ENOENT
# ignore
54 changes: 51 additions & 3 deletions lib/ruby/shared/rubygems/errors.rb
Original file line number Diff line number Diff line change
@@ -19,8 +19,36 @@ class LoadError < ::LoadError
attr_accessor :requirement
end

# FIX: does this need to exist? The subclass is the only other reference
# I can find.
# Raised when there are conflicting gem specs loaded

class ConflictError < LoadError

##
# A Hash mapping conflicting specifications to the dependencies that
# caused the conflict

attr_reader :conflicts

##
# The specification that had the conflict

attr_reader :target

def initialize target, conflicts
@target = target
@conflicts = conflicts
@name = target.name

reason = conflicts.map { |act, dependencies|
"#{act.full_name} conflicts with #{dependencies.join(", ")}"
}.join ", "

# TODO: improve message by saying who activated `con`

super("Unable to activate #{target.full_name}, because #{reason}")
end
end

class ErrorReason; end

# Generated when trying to lookup a gem to indicate that the gem
@@ -75,15 +103,35 @@ def wordy
# data from a source

class SourceFetchProblem < ErrorReason

##
# Creates a new SourceFetchProblem for the given +source+ and +error+.

def initialize(source, error)
@source = source
@error = error
end

attr_reader :source, :error
##
# The source that had the fetch problem.

attr_reader :source

##
# The fetch error which is an Exception subclass.

attr_reader :error

##
# An English description of the error.

def wordy
"Unable to download data from #{@source.uri} - #{@error.message}"
end

##
# The "exception" alias allows you to call raise on a SourceFetchProblem.

alias exception error
end
end
94 changes: 84 additions & 10 deletions lib/ruby/shared/rubygems/exceptions.rb
Original file line number Diff line number Diff line change
@@ -23,19 +23,19 @@ class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end

##
# Raised by Gem::DependencyResolver when a Gem::DependencyConflict reaches the
# Raised by Gem::Resolver when a Gem::Dependency::Conflict reaches the
# toplevel. Indicates which dependencies were incompatible through #conflict
# and #conflicting_dependencies

class Gem::DependencyResolutionError < Gem::Exception
class Gem::DependencyResolutionError < Gem::DependencyError

attr_reader :conflict

def initialize conflict
@conflict = conflict
a, b = conflicting_dependencies

super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}"
end

def conflicting_dependencies
@@ -81,7 +81,16 @@ class Gem::FormatException < Gem::Exception

class Gem::GemNotFoundException < Gem::Exception; end

##
# Raised by the DependencyInstaller when a specific gem cannot be found

class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException

##
# Creates a new SpecificGemNotFoundException for a gem with the given +name+
# and +version+. Any +errors+ encountered when attempting to find the gem
# are also stored.

def initialize(name, version, errors=nil)
super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository"

@@ -90,11 +99,25 @@ def initialize(name, version, errors=nil)
@errors = errors
end

attr_reader :name, :version, :errors
##
# The name of the gem that could not be found.

attr_reader :name

##
# The version of the gem that could not be found.

attr_reader :version

##
# Errors encountered attempting to find the gem.

attr_reader :errors

end

##
# Raised by Gem::DependencyResolver when dependencies conflict and create the
# Raised by Gem::Resolver when dependencies conflict and create the
# inability to find a valid possible spec for a request.

class Gem::ImpossibleDependenciesError < Gem::Exception
@@ -154,15 +177,31 @@ class Gem::RemoteInstallationSkipped < Gem::Exception; end
# Represents an error communicating via HTTP.
class Gem::RemoteSourceException < Gem::Exception; end

##
# Raised when a gem dependencies file specifies a ruby version that does not
# match the current version.

class Gem::RubyVersionMismatch < Gem::Exception; end

##
# Raised by Gem::Validator when something is not right in a gem.

class Gem::VerificationError < Gem::Exception; end

##
# Raised to indicate that a system exit should occur with the specified
# exit_code

class Gem::SystemExitException < SystemExit

##
# The exit code for the process

attr_accessor :exit_code

##
# Creates a new SystemExitException with the given +exit_code+

def initialize(exit_code)
@exit_code = exit_code

@@ -172,19 +211,54 @@ def initialize(exit_code)
end

##
# Raised by DependencyResolver when a dependency requests a gem for which
# Raised by Resolver when a dependency requests a gem for which
# there is no spec.

class Gem::UnsatisfiableDependencyError < Gem::Exception
class Gem::UnsatisfiableDependencyError < Gem::DependencyError

##
# The unsatisfiable dependency. This is a
# Gem::Resolver::DependencyRequest, not a Gem::Dependency

attr_reader :dependency

def initialize dep
requester = dep.requester ? dep.requester.request : '(unknown)'
##
# Errors encountered which may have contributed to this exception

super "Unable to resolve dependency: #{requester} requires #{dep}"
attr_accessor :errors

##
# Creates a new UnsatisfiableDependencyError for the unsatisfiable
# Gem::Resolver::DependencyRequest +dep+

def initialize dep, platform_mismatch=nil
if platform_mismatch and !platform_mismatch.empty?
plats = platform_mismatch.map { |x| x.platform.to_s }.sort.uniq
super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(', ')}"
else
if dep.explicit?
super "Unable to resolve dependency: user requested '#{dep}'"
else
super "Unable to resolve dependency: '#{dep.request_context}' requires '#{dep}'"
end
end

@dependency = dep
@errors = []
end

##
# The name of the unresolved dependency

def name
@dependency.name
end

##
# The Requirement of the unresolved dependency (not Version).

def version
@dependency.requirement
end

end
3 changes: 1 addition & 2 deletions lib/ruby/shared/rubygems/ext.rb
Original file line number Diff line number Diff line change
@@ -4,13 +4,12 @@
# See LICENSE.txt for permissions.
#++

require 'rubygems'

##
# Classes for building C extensions live here.

module Gem::Ext; end

require 'rubygems/ext/build_error'
require 'rubygems/ext/builder'
require 'rubygems/ext/configure_builder'
require 'rubygems/ext/ext_conf_builder'
6 changes: 6 additions & 0 deletions lib/ruby/shared/rubygems/ext/build_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
##
# Raised when there is an error while building extensions.

class Gem::Ext::BuildError < Gem::InstallError
end

69 changes: 51 additions & 18 deletions lib/ruby/shared/rubygems/ext/builder.rb
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ def self.class_name

def self.make(dest_path, results)
unless File.exist? 'Makefile' then
raise Gem::InstallError, "Makefile not found:\n\n#{results.join "\n"}"
raise Gem::InstallError, 'Makefile not found'
end

# try to find make program from Ruby configure arguments first
@@ -40,14 +40,18 @@ def self.make(dest_path, results)

destdir = '"DESTDIR=%s"' % ENV['DESTDIR'] if RUBY_VERSION > '2.0'

['', 'install'].each do |target|
['clean', '', 'install'].each do |target|
# Pass DESTDIR via command line to override what's in MAKEFLAGS
cmd = [
make_program,
destdir,
target
].join(' ').rstrip
run(cmd, results, "make #{target}".rstrip)
begin
run(cmd, results, "make #{target}".rstrip)
rescue Gem::InstallError
raise unless target == 'clean' # ignore clean failure
end
end
end

@@ -74,18 +78,27 @@ def self.run(command, results, command_name = nil)

unless $?.success? then
results << "Building has failed. See above output for more information on the failure." if verbose
raise Gem::InstallError, "#{command_name || class_name} failed:\n\n#{results.join "\n"}"

exit_reason =
if $?.exited? then
", exit code #{$?.exitstatus}"
elsif $?.signaled? then
", uncaught signal #{$?.termsig}"
end

raise Gem::InstallError, "#{command_name || class_name} failed#{exit_reason}"
end
end

##
# Creates a new extension builder for +spec+ using the given +build_args+.
# The gem for +spec+ is unpacked in +gem_dir+.
# Creates a new extension builder for +spec+. If the +spec+ does not yet
# have build arguments, saved, set +build_args+ which is an ARGV-style
# array.

def initialize spec, build_args
def initialize spec, build_args = spec.build_args
@spec = spec
@build_args = build_args
@gem_dir = spec.gem_dir
@gem_dir = spec.full_gem_path

@ran_rake = nil
end
@@ -113,12 +126,10 @@ def builder_for extension # :nodoc:
end

##
# Logs the build +output+ in +build_dir+, then raises ExtensionBuildError.
# Logs the build +output+ in +build_dir+, then raises Gem::Ext::BuildError.

def build_error build_dir, output, backtrace = nil # :nodoc:
gem_make_out = File.join build_dir, 'gem_make.out'

open gem_make_out, 'wb' do |io| io.puts output end
gem_make_out = write_gem_make_out output

message = <<-EOF
ERROR: Failed to build gem native extension.
@@ -129,14 +140,16 @@ def build_error build_dir, output, backtrace = nil # :nodoc:
Results logged to #{gem_make_out}
EOF

raise Gem::Installer::ExtensionBuildError, message, backtrace
raise Gem::Ext::BuildError, message, backtrace
end

def build_extension extension, dest_path # :nodoc:
results = []

extension ||= '' # I wish I knew why this line existed
extension_dir = File.join @gem_dir, File.dirname(extension)
extension_dir =
File.expand_path File.join @gem_dir, File.dirname(extension)
lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first

builder = builder_for extension

@@ -146,12 +159,15 @@ def build_extension extension, dest_path # :nodoc:
CHDIR_MUTEX.synchronize do
Dir.chdir extension_dir do
results = builder.build(extension, @gem_dir, dest_path,
results, @build_args)
results, @build_args, lib_dir)

say results.join("\n") if Gem.configuration.really_verbose
verbose { results.join("\n") }
end
end
rescue

write_gem_make_out results.join "\n"
rescue => e
results << e.message
build_error extension_dir, results.join("\n"), $@
end
end
@@ -170,7 +186,9 @@ def build_extensions
say "This could take a while..."
end

dest_path = File.join @gem_dir, @spec.require_paths.first
dest_path = @spec.extension_dir

FileUtils.rm_f @spec.gem_build_complete_path

@ran_rake = false # only run rake once

@@ -179,6 +197,21 @@ def build_extensions

build_extension extension, dest_path
end

FileUtils.touch @spec.gem_build_complete_path
end

##
# Writes +output+ to gem_make.out in the extension install directory.

def write_gem_make_out output # :nodoc:
destination = File.join @spec.extension_dir, 'gem_make.out'

FileUtils.mkdir_p @spec.extension_dir

open destination, 'wb' do |io| io.puts output end

destination
end

end
4 changes: 3 additions & 1 deletion lib/ruby/shared/rubygems/ext/cmake_builder.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require 'rubygems/command'

class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
def self.build(extension, directory, dest_path, results)
def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
unless File.exist?('Makefile') then
cmd = "cmake . -DCMAKE_INSTALL_PREFIX=#{dest_path}"
cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty?
4 changes: 1 addition & 3 deletions lib/ruby/shared/rubygems/ext/configure_builder.rb
Original file line number Diff line number Diff line change
@@ -4,11 +4,9 @@
# See LICENSE.txt for permissions.
#++

require 'rubygems/ext/builder'

class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder

def self.build(extension, directory, dest_path, results, args=[])
def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
unless File.exist?('Makefile') then
cmd = "sh ./configure --prefix=#{dest_path}"
cmd << " #{args.join ' '}" unless args.empty?
42 changes: 27 additions & 15 deletions lib/ruby/shared/rubygems/ext/ext_conf_builder.rb
Original file line number Diff line number Diff line change
@@ -4,54 +4,60 @@
# See LICENSE.txt for permissions.
#++

require 'rubygems/ext/builder'
require 'rubygems/command'
require 'fileutils'
require 'tempfile'

class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
FileEntry = FileUtils::Entry_ # :nodoc:

def self.build(extension, directory, dest_path, results, args=[])
tmp_dest = Dir.mktmpdir(".gem.", ".")
def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
# relative path required as some versions of mktmpdir return an absolute
# path which breaks make if it includes a space in the name
tmp_dest = get_relative_path(Dir.mktmpdir(".gem.", "."))

t = nil
Tempfile.open %w"siteconf .rb", "." do |siteconf|
t = siteconf
siteconf.puts "require 'rbconfig'"
siteconf.puts "dest_path = #{(tmp_dest || dest_path).dump}"
siteconf.puts "dest_path = #{tmp_dest.dump}"
%w[sitearchdir sitelibdir].each do |dir|
siteconf.puts "RbConfig::MAKEFILE_CONFIG['#{dir}'] = dest_path"
siteconf.puts "RbConfig::CONFIG['#{dir}'] = dest_path"
end

siteconf.flush

siteconf_path = File.expand_path siteconf.path

rubyopt = ENV["RUBYOPT"]
destdir = ENV["DESTDIR"]

begin
ENV["RUBYOPT"] = ["-r#{siteconf_path}", rubyopt].compact.join(' ')
cmd = [Gem.ruby, File.basename(extension), *args].join ' '
cmd = [Gem.ruby, "-r", get_relative_path(siteconf.path), File.basename(extension), *args].join ' '

run cmd, results
begin
run cmd, results
ensure
FileUtils.mv 'mkmf.log', dest_path if File.exist? 'mkmf.log'
siteconf.unlink
end

ENV["DESTDIR"] = nil
ENV["RUBYOPT"] = rubyopt
siteconf.unlink

make dest_path, results

if tmp_dest
# TODO remove in RubyGems 3
if Gem.install_extension_in_lib and lib_dir then
FileUtils.mkdir_p lib_dir
entries = Dir.entries(tmp_dest) - %w[. ..]
entries = entries.map { |entry| File.join tmp_dest, entry }
FileUtils.cp_r entries, lib_dir, :remove_destination => true
end

FileEntry.new(tmp_dest).traverse do |ent|
destent = ent.class.new(dest_path, ent.rel)
destent.exist? or File.rename(ent.path, destent.path)
destent.exist? or FileUtils.mv(ent.path, destent.path)
end
end
ensure
ENV["RUBYOPT"] = rubyopt
ENV["DESTDIR"] = destdir
end
end
@@ -62,5 +68,11 @@ def self.build(extension, directory, dest_path, results, args=[])
FileUtils.rm_rf tmp_dest if tmp_dest
end

private
def self.get_relative_path(path)
path[0..Dir.pwd.length-1] = '.' if path.start_with?(Dir.pwd)
path
end

end

7 changes: 2 additions & 5 deletions lib/ruby/shared/rubygems/ext/rake_builder.rb
Original file line number Diff line number Diff line change
@@ -4,12 +4,9 @@
# See LICENSE.txt for permissions.
#++

require 'rubygems/ext/builder'
require 'rubygems/command'

class Gem::Ext::RakeBuilder < Gem::Ext::Builder

def self.build(extension, directory, dest_path, results, args=[])
def self.build(extension, directory, dest_path, results, args=[], lib_dir=nil)
if File.basename(extension) =~ /mkrf_conf/i then
cmd = "#{Gem.ruby} #{File.basename extension}"
cmd << " #{args.join " "}" unless args.empty?
@@ -22,7 +19,7 @@ def self.build(extension, directory, dest_path, results, args=[])
rake = ENV['rake']

rake ||= begin
"\"#{Gem.ruby}\" -rubygems #{Gem.bin_path('rake', 'rake')}"
"#{Gem.ruby} -rubygems #{Gem.bin_path('rake', 'rake')}"
rescue Gem::Exception
end

11 changes: 9 additions & 2 deletions lib/ruby/shared/rubygems/gemcutter_utilities.rb
Original file line number Diff line number Diff line change
@@ -56,8 +56,10 @@ def host

##
# Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+.
#
# If +allowed_push_host+ metadata is present, then it will only allow that host.

def rubygems_api_request(method, path, host = nil, &block)
def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &block)
require 'net/http'

self.host = host if host
@@ -66,6 +68,11 @@ def rubygems_api_request(method, path, host = nil, &block)
terminate_interaction 1 # TODO: question this
end

if allowed_push_host and self.host != allowed_push_host
alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}"
terminate_interaction 1
end

uri = URI.parse "#{self.host}/#{path}"

request_method = Net::HTTP.const_get method.to_s.capitalize
@@ -79,7 +86,7 @@ def rubygems_api_request(method, path, host = nil, &block)

def sign_in sign_in_host = nil
sign_in_host ||= self.host
return if Gem.configuration.rubygems_api_key
return if api_key

pretty_host = if Gem::DEFAULT_HOST == sign_in_host then
'RubyGems.org'
2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/indexer.rb
Original file line number Diff line number Diff line change
@@ -235,7 +235,7 @@ def map_gems_to_specs gems
sanitize spec

spec
rescue SignalException => e
rescue SignalException
alert_error "Received signal, exiting"
raise
rescue Exception => e
17 changes: 17 additions & 0 deletions lib/ruby/shared/rubygems/install_update_options.rb
Original file line number Diff line number Diff line change
@@ -59,6 +59,23 @@ def add_install_update_options
end
end

add_option(:"Install/Update", '--build-root DIR',
'Temporary installation root. Useful for building',
'packages. Do not use this when installing remote gems.') do |value, options|
options[:build_root] = File.expand_path(value)
end

add_option(:"Install/Update", '--vendor',
'Install gem into the vendor directory.',
'Only for use by gem repackagers.') do |value, options|
unless Gem.vendor_dir then
raise OptionParser::InvalidOption.new 'your platform is not supported'
end

options[:vendor] = true
options[:install_dir] = Gem.vendor_dir
end

add_option(:"Install/Update", '-N', '--no-document',
'Disable documentation generation') do |value, options|
options[:document] = []
102 changes: 69 additions & 33 deletions lib/ruby/shared/rubygems/installer.rb
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
# See LICENSE.txt for permissions.
#++

require 'rubygems/command'
require 'rubygems/exceptions'
require 'rubygems/package'
require 'rubygems/ext'
@@ -32,20 +33,24 @@ class Gem::Installer
ENV_PATHS = %w[/usr/bin/env /bin/env]

##
# Raised when there is an error while building extensions.
#
class ExtensionBuildError < Gem::InstallError; end
# Deprecated in favor of Gem::Ext::BuildError

ExtensionBuildError = Gem::Ext::BuildError # :nodoc:

include Gem::UserInteraction

# DOC: Missing docs or :nodoc:.
##
# Filename of the gem being installed.

attr_reader :gem

##
# The directory a gem's executables will be installed into

attr_reader :bin_dir

attr_reader :build_root # :nodoc:

##
# The gem repository the gem will be installed into

@@ -56,16 +61,35 @@ class ExtensionBuildError < Gem::InstallError; end

attr_reader :options

##
# Sets the specification for .gem-less installs.

attr_writer :spec

@path_warning = false

@install_lock = Mutex.new

class << self

##
# True if we've warned about PATH not including Gem.bindir

attr_accessor :path_warning

# DOC: Missing docs or :nodoc:.
##
# Certain aspects of the install process are not thread-safe. This lock is
# used to allow multiple threads to install Gems at the same time.

attr_reader :install_lock

##
# Overrides the executable format.
#
# This is a sprintf format with a "%s" which will be replaced with the
# executable name. It is based off the ruby executable name's difference
# from "ruby".

attr_writer :exec_format

# Defaults to use Ruby's program prefix and suffix.
@@ -206,6 +230,8 @@ def spec
def install
pre_install_checks

FileUtils.rm_f File.join gem_home, 'specifications', @spec.spec_name

run_pre_install_hooks

# Completely remove any previous gem files
@@ -232,7 +258,7 @@ def install

say spec.post_install_message unless spec.post_install_message.nil?

Gem::Specification.add_spec spec unless Gem::Specification.include? spec
Gem::Installer.install_lock.synchronize { Gem::Specification.add_spec spec }

run_post_install_hooks

@@ -310,6 +336,7 @@ def ensure_dependency(spec, dependency)
# True if the gems in the system satisfy +dependency+.

def installation_satisfies_dependency?(dependency)
return true if @options[:development] and dependency.type == :development
return true if installed_specs.detect { |s| dependency.matches_spec? s }
return false if @only_install_dir
not dependency.matching_specs.empty?
@@ -345,7 +372,10 @@ def default_spec_file

def write_spec
open spec_file, 'w' do |file|
spec.installed_by_version = Gem.rubygems_version

file.puts spec.to_ruby_for_cache

file.fsync rescue nil # for filesystems without fsync(2)
end
end
@@ -371,12 +401,11 @@ def generate_windows_script(filename, bindir)
file.puts windows_stub_script(bindir, filename)
end

say script_path if Gem.configuration.really_verbose
verbose script_path
end
end

# DOC: Missing docs or :nodoc:.
def generate_bin
def generate_bin # :nodoc:
return if spec.executables.nil? or spec.executables.empty?

Dir.mkdir @bin_dir unless File.exist? @bin_dir
@@ -392,8 +421,8 @@ def generate_bin
next
end

mode = File.stat(bin_path).mode | 0111
FileUtils.chmod mode, bin_path
mode = File.stat(bin_path).mode
FileUtils.chmod mode | 0111, bin_path unless (mode | 0111) == mode

check_executable_overwrite filename

@@ -422,7 +451,7 @@ def generate_bin_script(filename, bindir)
file.print app_script_text(filename)
end

say bin_script_path if Gem.configuration.really_verbose
verbose bin_script_path

generate_windows_script filename, bindir
end
@@ -469,7 +498,7 @@ def generate_bin_symlink(filename, bindir)
#

def shebang(bin_file_name)
ruby_name = Gem::ConfigMap[:ruby_install_name] if @env_shebang
ruby_name = RbConfig::CONFIG['ruby_install_name'] if @env_shebang
path = File.join gem_dir, spec.bindir, bin_file_name
first_line = File.open(path, "rb") {|file| file.gets}

@@ -482,7 +511,7 @@ def shebang(bin_file_name)

if which = Gem.configuration[:custom_shebang]
# replace bin_file_name with "ruby" to avoid endless loops
which = which.gsub(/ #{bin_file_name}$/," #{Gem::ConfigMap[:ruby_install_name]}")
which = which.gsub(/ #{bin_file_name}$/," #{RbConfig::CONFIG['ruby_install_name']}")

which = which.gsub(/\$(\w+)/) do
case $1
@@ -525,17 +554,15 @@ def ensure_loadable_spec
end
end

# DOC: Missing docs or :nodoc:.
def ensure_required_ruby_version_met
def ensure_required_ruby_version_met # :nodoc:
if rrv = spec.required_ruby_version then
unless rrv.satisfied_by? Gem.ruby_version then
raise Gem::InstallError, "#{spec.name} requires Ruby version #{rrv}."
end
end
end

# DOC: Missing docs or :nodoc:.
def ensure_required_rubygems_version_met
def ensure_required_rubygems_version_met # :nodoc:
if rrgv = spec.required_rubygems_version then
unless rrgv.satisfied_by? Gem.rubygems_version then
raise Gem::InstallError,
@@ -545,8 +572,7 @@ def ensure_required_rubygems_version_met
end
end

# DOC: Missing docs or :nodoc:.
def ensure_dependencies_met
def ensure_dependencies_met # :nodoc:
deps = spec.runtime_dependencies
deps |= spec.development_dependencies if @development

@@ -555,8 +581,7 @@ def ensure_dependencies_met
end
end

# DOC: Missing docs or :nodoc:.
def process_options
def process_options # :nodoc:
@options = {
:bin_dir => nil,
:env_shebang => false,
@@ -579,12 +604,20 @@ def process_options
# (or use) a new bin dir under the gem_home.
@bin_dir = options[:bin_dir] || Gem.bindir(gem_home)
@development = options[:development]
@build_root = options[:build_root]

@build_args = options[:build_args] || Gem::Command.build_args

unless @build_root.nil?
require 'pathname'
@build_root = Pathname.new(@build_root).expand_path
@bin_dir = File.join(@build_root, options[:bin_dir] || Gem.bindir(@gem_home))
@gem_home = File.join(@build_root, @gem_home)
alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}"
end
end

# DOC: Missing docs or :nodoc:.
def check_that_user_bin_dir_is_in_path
def check_that_user_bin_dir_is_in_path # :nodoc:
user_bin_dir = @bin_dir || Gem.bindir(gem_home)
user_bin_dir = user_bin_dir.gsub(File::SEPARATOR, File::ALT_SEPARATOR) if
File::ALT_SEPARATOR
@@ -595,16 +628,19 @@ def check_that_user_bin_dir_is_in_path
user_bin_dir = user_bin_dir.downcase
end

unless path.split(File::PATH_SEPARATOR).include? user_bin_dir then
unless self.class.path_warning then
alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables will not run."
self.class.path_warning = true
path = path.split(File::PATH_SEPARATOR)

unless path.include? user_bin_dir then
unless !Gem.win_platform? && (path.include? user_bin_dir.sub(ENV['HOME'], '~'))
unless self.class.path_warning then
alert_warning "You don't have #{user_bin_dir} in your PATH,\n\t gem executables will not run."
self.class.path_warning = true
end
end
end
end

# DOC: Missing docs or :nodoc:.
def verify_gem_home(unpack = false)
def verify_gem_home(unpack = false) # :nodoc:
FileUtils.mkdir_p gem_home
raise Gem::FilePermissionError, gem_home unless
unpack or File.writable?(gem_home)
@@ -630,7 +666,7 @@ def app_script_text(bin_file_name)
if ARGV.first
str = ARGV.first
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
if str =~ /\\A_(.*)_\\z/
if str =~ /\\A_(.*)_\\z/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
@@ -645,7 +681,7 @@ def app_script_text(bin_file_name)
# return the stub script text used to launch the true Ruby script

def windows_stub_script(bindir, bin_file_name)
ruby = File.basename(Gem.ruby).chomp('"')
ruby = Gem.ruby.gsub(/^\"|\"$/, "").tr(File::SEPARATOR, "\\")
return <<-TEXT
@ECHO OFF
IF NOT "%~f0" == "~f0" GOTO :WinNT
@@ -667,7 +703,7 @@ def build_extensions
end

##
# Logs the build +output+ in +build_dir+, then raises ExtensionBuildError.
# Logs the build +output+ in +build_dir+, then raises Gem::Ext::BuildError.
#
# TODO: Delete this for RubyGems 3. It remains for API compatibility

7 changes: 2 additions & 5 deletions lib/ruby/shared/rubygems/installer_test_case.rb
Original file line number Diff line number Diff line change
@@ -56,11 +56,6 @@ class Gem::Installer
##
# Available through requiring rubygems/installer_test_case

attr_writer :spec

##
# Available through requiring rubygems/installer_test_case

attr_writer :wrappers
end

@@ -106,6 +101,8 @@ def setup

@installer = util_installer @spec, @gemhome
@user_installer = util_installer @user_spec, Gem.user_dir, :user

Gem::Installer.path_warning = false
end

def util_gem_bindir spec = @spec # :nodoc:
4 changes: 2 additions & 2 deletions lib/ruby/shared/rubygems/local_remote_options.rb
Original file line number Diff line number Diff line change
@@ -100,8 +100,8 @@ def add_proxy_option
def add_source_option
accept_uri_http

add_option(:"Local/Remote", '--source URL', URI::HTTP,
'Add URL as a remote source for gems') do |source, options|
add_option(:"Local/Remote", '-s', '--source URL', URI::HTTP,
'Append URL to list of remote gem sources') do |source, options|

source << '/' if source !~ /\/\z/

6 changes: 4 additions & 2 deletions lib/ruby/shared/rubygems/name_tuple.rb
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ def full_name
"#{@name}-#{@version}"
else
"#{@name}-#{@version}-#{@platform}"
end
end.untaint
end

##
@@ -90,7 +90,9 @@ def inspect # :nodoc:
alias to_s inspect # :nodoc:

def <=> other
to_a <=> other.to_a
[@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1] <=>
[other.name, other.version,
other.platform == Gem::Platform::RUBY ? -1 : 1]
end

include Comparable
57 changes: 40 additions & 17 deletions lib/ruby/shared/rubygems/package.rb
Original file line number Diff line number Diff line change
@@ -54,10 +54,12 @@ class Error < Gem::Exception; end
class FormatError < Error
attr_reader :path

def initialize message, path = nil
@path = path
def initialize message, source = nil
if source
@path = source.path

message << " in #{path}" if path
message << " in #{path}" if path
end

super message
end
@@ -80,6 +82,7 @@ class TooLongFileName < Error; end

class TarInvalidError < Error; end


attr_accessor :build_time # :nodoc:

##
@@ -114,19 +117,26 @@ def self.build spec, skip_validation=false
end

##
# Creates a new Gem::Package for the file at +gem+.
# Creates a new Gem::Package for the file at +gem+. +gem+ can also be
# provided as an IO object.
#
# If +gem+ is an existing file in the old format a Gem::Package::Old will be
# returned.

def self.new gem
return super unless Gem::Package == self
return super unless File.exist? gem
gem = if gem.is_a?(Gem::Package::Source)
gem
elsif gem.respond_to? :read
Gem::Package::IOSource.new gem
else
Gem::Package::FileSource.new gem
end

start = File.read gem, 20
return super(gem) unless Gem::Package == self
return super unless gem.present?

return super unless start
return super unless start.include? 'MD5SUM ='
return super unless gem.start
return super unless gem.start.include? 'MD5SUM ='

Gem::Package::Old.new gem
end
@@ -227,7 +237,7 @@ def build skip_validation = false

setup_signer

open @gem, 'wb' do |gem_io|
@gem.with_write_io do |gem_io|
Gem::Package::TarWriter.new gem_io do |gem|
add_metadata gem
add_contents gem
@@ -255,7 +265,7 @@ def contents

@contents = []

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
gem_tar = Gem::Package::TarReader.new io

gem_tar.each do |entry|
@@ -312,7 +322,7 @@ def extract_files destination_dir, pattern = "*"

FileUtils.mkdir_p destination_dir

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
reader = Gem::Package::TarReader.new io

reader.each do |entry|
@@ -345,13 +355,23 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc:

FileUtils.rm_rf destination

FileUtils.mkdir_p File.dirname destination
mkdir_options = {}
mkdir_options[:mode] = entry.header.mode if entry.directory?
mkdir =
if entry.directory? then
destination
else
File.dirname destination
end

FileUtils.mkdir_p mkdir, mkdir_options

open destination, 'wb', entry.header.mode do |out|
open destination, 'wb' do |out|
out.write entry.read
end
FileUtils.chmod entry.header.mode, destination
end if entry.file?

say destination if Gem.configuration.really_verbose
verbose destination
end
end
end
@@ -481,7 +501,7 @@ def verify
@files = []
@spec = nil

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
Gem::Package::TarReader.new io do |reader|
read_checksums reader

@@ -583,6 +603,9 @@ def verify_gz entry # :nodoc:
end

require 'rubygems/package/digest_io'
require 'rubygems/package/source'
require 'rubygems/package/file_source'
require 'rubygems/package/io_source'
require 'rubygems/package/old'
require 'rubygems/package/tar_header'
require 'rubygems/package/tar_reader'
33 changes: 33 additions & 0 deletions lib/ruby/shared/rubygems/package/file_source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
##
# The primary source of gems is a file on disk, including all usages
# internal to rubygems.
#
# This is a private class, do not depend on it directly. Instead, pass a path
# object to `Gem::Package.new`.

class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all

attr_reader :path

def initialize path
@path = path
end

def start
@start ||= File.read path, 20
end

def present?
File.exist? path
end

def with_write_io &block
open path, 'wb', &block
end

def with_read_io &block
open path, 'rb', &block
end

end

45 changes: 45 additions & 0 deletions lib/ruby/shared/rubygems/package/io_source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
##
# Supports reading and writing gems from/to a generic IO object. This is
# useful for other applications built on top of rubygems, such as
# rubygems.org.
#
# This is a private class, do not depend on it directly. Instead, pass an IO
# object to `Gem::Package.new`.

class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all

attr_reader :io

def initialize io
@io = io
end

def start
@start ||= begin
if io.pos > 0
raise Gem::Package::Error, "Cannot read start unless IO is at start"
end

value = io.read 20
io.rewind
value
end
end

def present?
true
end

def with_read_io
yield io
end

def with_write_io
yield io
end

def path
end

end

14 changes: 7 additions & 7 deletions lib/ruby/shared/rubygems/package/old.rb
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ def contents

return @contents if @contents

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
read_until_dashes io # spec
header = file_list io

@@ -53,7 +53,7 @@ def extract_files destination_dir

errstr = "Error reading files from gem"

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
read_until_dashes io # spec
header = file_list io
raise Gem::Exception, errstr unless header
@@ -83,7 +83,7 @@ def extract_files destination_dir
out.write file_data
end

say destination if Gem.configuration.really_verbose
verbose destination
end
end
rescue Zlib::DataError
@@ -136,7 +136,7 @@ def spec

yaml = ''

open @gem, 'rb' do |io|
@gem.with_read_io do |io|
skip_ruby io
read_until_dashes io do |line|
yaml << line
@@ -145,18 +145,18 @@ def spec

yaml_error = if RUBY_VERSION < '1.9' then
YAML::ParseError
elsif YAML::ENGINE.yamler == 'syck' then
elsif YAML.const_defined?(:ENGINE) && YAML::ENGINE.yamler == 'syck' then
YAML::ParseError
else
YAML::SyntaxError
end

begin
@spec = Gem::Specification.from_yaml yaml
rescue yaml_error => e
rescue yaml_error
raise Gem::Exception, "Failed to parse gem specification out of gem file"
end
rescue ArgumentError => e
rescue ArgumentError
raise Gem::Exception, "Failed to parse gem specification out of gem file"
end

3 changes: 3 additions & 0 deletions lib/ruby/shared/rubygems/package/source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Gem::Package::Source # :nodoc:
end

2 changes: 1 addition & 1 deletion lib/ruby/shared/rubygems/package/tar_header.rb
Original file line number Diff line number Diff line change
@@ -134,7 +134,7 @@ def initialize(vals)
vals[:gid] ||= 0
vals[:mtime] ||= 0
vals[:checksum] ||= ""
vals[:typeflag] ||= "0"
vals[:typeflag] = "0" if vals[:typeflag].nil? || vals[:typeflag].empty?
vals[:magic] ||= "ustar"
vals[:version] ||= "00"
vals[:uname] ||= "wheel"
2 changes: 2 additions & 0 deletions lib/ruby/shared/rubygems/package/tar_reader/entry.rb
Original file line number Diff line number Diff line change
@@ -129,6 +129,8 @@ def read(len = nil)
ret
end

alias readpartial read # :nodoc:

##
# Rewinds to the beginning of the tar file entry

12 changes: 9 additions & 3 deletions lib/ruby/shared/rubygems/package/tar_writer.rb
Original file line number Diff line number Diff line change
@@ -290,7 +290,9 @@ def mkdir(name, mode)
# Splits +name+ into a name and prefix that can fit in the TarHeader

def split_name(name) # :nodoc:
raise Gem::Package::TooLongFileName if name.bytesize > 256
if name.bytesize > 256
raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")
end

if name.bytesize <= 100 then
prefix = ""
@@ -308,8 +310,12 @@ def split_name(name) # :nodoc:
prefix = (parts + [nxt]).join "/"
name = newname

if name.bytesize > 100 or prefix.bytesize > 155 then
raise Gem::Package::TooLongFileName
if name.bytesize > 100
raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
end

if prefix.bytesize > 155 then
raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)")
end
end

7 changes: 0 additions & 7 deletions lib/ruby/shared/rubygems/path_support.rb
Original file line number Diff line number Diff line change
@@ -42,13 +42,6 @@ def initialize(env=ENV)

private

##
# Set the Gem home directory (as reported by Gem.dir).

def home=(home)
@home = home.to_s
end

##
# Set the Gem search path (as reported by Gem.path).

13 changes: 11 additions & 2 deletions lib/ruby/shared/rubygems/platform.rb
Original file line number Diff line number Diff line change
@@ -16,8 +16,8 @@ class Gem::Platform
attr_accessor :version

def self.local
arch = Gem::ConfigMap[:arch]
arch = "#{arch}_60" if arch =~ /mswin32$/
arch = RbConfig::CONFIG['arch']
arch = "#{arch}_60" if arch =~ /mswin(?:32|64)$/
@local ||= new(arch)
end

@@ -29,6 +29,14 @@ def self.match(platform)
end
end

def self.installable?(spec)
if spec.respond_to? :installable_platform?
spec.installable_platform?
else
match spec.platform
end
end

def self.new(arch) # :nodoc:
case arch
when Gem::Platform::CURRENT then
@@ -165,6 +173,7 @@ def =~(other)
when /^dalvik(\d+)?$/ then [nil, 'dalvik', $1 ]
when /dotnet(\-(\d+\.\d+))?/ then ['universal','dotnet', $2 ]
when /mswin32(\_(\d+))?/ then ['x86', 'mswin32', $2 ]
when /mswin64(\_(\d+))?/ then ['x64', 'mswin64', $2 ]
when 'powerpc-darwin' then ['powerpc', 'darwin', nil ]
when /powerpc-darwin(\d)/ then ['powerpc', 'darwin', $1 ]
when /sparc-solaris2.8/ then ['sparc', 'solaris', '2.8' ]
4 changes: 2 additions & 2 deletions lib/ruby/shared/rubygems/psych_additions.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# This exists just to satify bugs in marshal'd gemspecs that
# This exists just to satisfy bugs in marshal'd gemspecs that
# contain a reference to YAML::PrivateType. We prune these out
# in Specification._load, but if we don't have the constant, Marshal
# blows up.

module Psych # :nodoc:
class PrivateType
class PrivateType # :nodoc:
end
end
7 changes: 3 additions & 4 deletions lib/ruby/shared/rubygems/rdoc.rb
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
# swallow
else
# This will force any deps that 'rdoc' might have
# (such as json) that are ambigious to be activated, which
# (such as json) that are ambiguous to be activated, which
# is important because we end up using Specification.reset
# and we don't want the warning it pops out.
Gem.finish_resolve
@@ -193,7 +193,7 @@ def generate
::RDoc::Parser::C.reset

args = @spec.rdoc_options
args.concat @spec.require_paths
args.concat @spec.source_paths
args.concat @spec.extra_rdoc_files

case config_args = Gem.configuration[:rdoc]
@@ -263,7 +263,7 @@ def legacy_rdoc *args
Gem::Requirement.new('>= 2.4.0') =~ self.class.rdoc_version

r = new_rdoc
say "rdoc #{args.join ' '}" if Gem.configuration.really_verbose
verbose { "rdoc #{args.join ' '}" }

Dir.chdir @spec.full_gem_path do
begin
@@ -279,7 +279,6 @@ def legacy_rdoc *args
ui.errs.puts "... RDOC args: #{args.join(' ')}"
ui.backtrace ex
ui.errs.puts "(continuing with the rest of the installation)"
ensure
end
end
end
121 changes: 90 additions & 31 deletions lib/ruby/shared/rubygems/remote_fetcher.rb
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
require 'rubygems/request'
require 'rubygems/uri_formatter'
require 'rubygems/user_interaction'
require 'rubygems/request/connection_pools'
require 'resolv'

##
@@ -73,12 +74,14 @@ def initialize(proxy=nil, dns=Resolv::DNS.new)
Socket.do_not_reverse_lookup = true

@proxy = proxy
@pools = {}
@pool_lock = Mutex.new
@cert_files = Gem::Request.get_cert_files

@dns = dns
end

##
#
# Given a source at +uri+, calculate what hostname to actually
# connect to query the data for it.

@@ -91,7 +94,7 @@ def api_endpoint(uri)
rescue Resolv::ResolvError
uri
else
URI.parse "#{res.target}#{uri.path}"
URI.parse "#{uri.scheme}://#{res.target}#{uri.path}"
end
end

@@ -100,14 +103,14 @@ def api_endpoint(uri)
# filename. Returns nil if the gem cannot be located.
#--
# Should probably be integrated with #download below, but that will be a
# larger, more emcompassing effort. -erikh
# larger, more encompassing effort. -erikh

def download_to_cache dependency
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency

return if found.empty?

spec, source = found.sort_by { |(s,_)| s.version }.last
spec, source = found.max_by { |(s,_)| s.version }

download spec, source.uri.to_s
end
@@ -132,11 +135,19 @@ def download(spec, source_uri, install_dir = Gem.dir)

FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir

# Always escape URI's to deal with potential spaces and such
unless URI::Generic === source_uri
source_uri = URI.parse(URI.const_defined?(:DEFAULT_PARSER) ?
URI::DEFAULT_PARSER.escape(source_uri.to_s) :
URI.escape(source_uri.to_s))
# Always escape URI's to deal with potential spaces and such
# It should also be considered that source_uri may already be
# a valid URI with escaped characters. e.g. "{DESede}" is encoded
# as "%7BDESede%7D". If this is escaped again the percentage
# symbols will be escaped.
unless source_uri.is_a?(URI::Generic)
begin
source_uri = URI.parse(source_uri)
rescue
source_uri = URI.parse(URI.const_defined?(:DEFAULT_PARSER) ?
URI::DEFAULT_PARSER.escape(source_uri.to_s) :
URI.escape(source_uri.to_s))
end
end

scheme = source_uri.scheme
@@ -147,11 +158,10 @@ def download(spec, source_uri, install_dir = Gem.dir)
# REFACTOR: split this up and dispatch on scheme (eg download_http)
# REFACTOR: be sure to clean up fake fetcher when you do this... cleaner
case scheme
when 'http', 'https' then
when 'http', 'https', 's3' then
unless File.exist? local_gem_path then
begin
say "Downloading gem #{gem_file_name}" if
Gem.configuration.really_verbose
verbose "Downloading gem #{gem_file_name}"

remote_gem_path = source_uri + "gems/#{gem_file_name}"

@@ -161,8 +171,7 @@ def download(spec, source_uri, install_dir = Gem.dir)

alternate_name = "#{spec.original_name}.gem"

say "Failed, downloading gem #{alternate_name}" if
Gem.configuration.really_verbose
verbose "Failed, downloading gem #{alternate_name}"

remote_gem_path = source_uri + "gems/#{alternate_name}"

@@ -181,8 +190,7 @@ def download(spec, source_uri, install_dir = Gem.dir)
local_gem_path = source_uri.to_s
end

say "Using local gem #{local_gem_path}" if
Gem.configuration.really_verbose
verbose "Using local gem #{local_gem_path}"
when nil then # TODO test for local overriding cache
source_path = if Gem.win_platform? && source_uri.scheme &&
!source_uri.path.include?(':') then
@@ -200,8 +208,7 @@ def download(spec, source_uri, install_dir = Gem.dir)
local_gem_path = source_uri.to_s
end

say "Using local gem #{local_gem_path}" if
Gem.configuration.really_verbose
verbose "Using local gem #{local_gem_path}"
else
raise ArgumentError, "unsupported URI scheme #{source_uri.scheme}"
end
@@ -225,6 +232,7 @@ def fetch_http uri, last_modified = nil, head = false, depth = 0

case response
when Net::HTTPOK, Net::HTTPNotModified then
response.uri = uri if response.respond_to? :uri
head ? response : response.body
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
Net::HTTPTemporaryRedirect then
@@ -258,7 +266,7 @@ def fetch_path(uri, mtime = nil, head = false)

data = send "fetch_#{uri.scheme}", uri, mtime, head

if data and !head and uri.to_s =~ /gz$/
if data and !head and uri.to_s =~ /\.gz$/
begin
data = Gem.gunzip data
rescue Zlib::GzipFile::Error
@@ -279,26 +287,32 @@ def fetch_path(uri, mtime = nil, head = false)
end
end

def fetch_s3(uri, mtime = nil, head = false)
public_uri = sign_s3_url(uri)
fetch_https public_uri, mtime, head
end

##
# Downloads +uri+ to +path+ if necessary. If no path is given, it just
# passes the data.

def cache_update_path uri, path = nil, update = true
mtime = path && File.stat(path).mtime rescue nil

if mtime && Net::HTTPNotModified === fetch_path(uri, mtime, true)
Gem.read_binary(path)
else
data = fetch_path(uri)
data = fetch_path(uri, mtime)

if update and path then
open(path, 'wb') do |io|
io.write data
end
end
if data == nil # indicates the server returned 304 Not Modified
return Gem.read_binary(path)
end

data
if update and path
open(path, 'wb') do |io|
io.flock(File::LOCK_EX)
io.write data
end
end

data
end

##
@@ -312,7 +326,7 @@ def fetch_size(uri) # TODO: phase this out

def correct_for_windows_path(path)
if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
path = path[1..-1]
path[1..-1]
else
path
end
@@ -324,7 +338,10 @@ def correct_for_windows_path(path)
# connections to reduce connect overhead.

def request(uri, request_class, last_modified = nil)
request = Gem::Request.new uri, request_class, last_modified, @proxy
proxy = proxy_for @proxy, uri
pool = pools_for(proxy).pool_for uri

request = Gem::Request.new uri, request_class, last_modified, pool

request.fetch do |req|
yield req if block_given?
@@ -335,5 +352,47 @@ def https?(uri)
uri.scheme.downcase == 'https'
end

def close_all
@pools.each_value {|pool| pool.close_all}
end

protected

# we have our own signing code here to avoid a dependency on the aws-sdk gem
# fortunately, a simple GET request isn't too complex to sign properly
def sign_s3_url(uri, expiration = nil)
require 'base64'
require 'openssl'

unless uri.user && uri.password
raise FetchError.new("credentials needed in s3 source, like s3://key:secret@bucket-name/", uri.to_s)
end

expiration ||= s3_expiration
canonical_path = "/#{uri.host}#{uri.path}"
payload = "GET\n\n\n#{expiration}\n#{canonical_path}"
digest = OpenSSL::HMAC.digest('sha1', uri.password, payload)
# URI.escape is deprecated, and there isn't yet a replacement that does quite what we want
signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] }
URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{uri.user}&Expires=#{expiration}&Signature=#{signature}")
end

def s3_expiration
(Time.now + 3600).to_i # one hour from now
end

BASE64_URI_TRANSLATE = { '+' => '%2B', '/' => '%2F', '=' => '%3D' }.freeze

private

def proxy_for proxy, uri
Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme))
end

def pools_for proxy
@pool_lock.synchronize do
@pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files
end
end
end

170 changes: 74 additions & 96 deletions lib/ruby/shared/rubygems/request.rb
Original file line number Diff line number Diff line change
@@ -7,35 +7,43 @@ class Gem::Request

include Gem::UserInteraction

attr_reader :proxy_uri
###
# Legacy. This is used in tests.
def self.create_with_proxy uri, request_class, last_modified, proxy # :nodoc:
cert_files = get_cert_files
proxy ||= get_proxy_from_env(uri.scheme)
pool = ConnectionPools.new proxy_uri(proxy), cert_files

new(uri, request_class, last_modified, pool.pool_for(uri))
end

def self.proxy_uri proxy # :nodoc:
case proxy
when :no_proxy then nil
when URI::HTTP then proxy
else URI.parse(proxy)
end
end

def initialize(uri, request_class, last_modified, proxy)
def initialize(uri, request_class, last_modified, pool)
@uri = uri
@request_class = request_class
@last_modified = last_modified
@requests = Hash.new 0
@connections = {}
@connections_mutex = Mutex.new
@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
@connection_pool = pool
end

def add_rubygems_trusted_certs(store)
def proxy_uri; @connection_pool.proxy_uri; end
def cert_files; @connection_pool.cert_files; end

def self.get_cert_files
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
Dir.glob(pattern)
end

def configure_connection_for_https(connection)
def self.configure_connection_for_https(connection, cert_files)
require 'net/https'
connection.use_ssl = true
connection.verify_mode =
@@ -48,17 +56,19 @@ def configure_connection_for_https(connection)
connection.key = OpenSSL::PKey::RSA.new pem
end

store.set_default_paths
cert_files.each do |ssl_cert_file|
store.add_file ssl_cert_file
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
connection
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
@@ -72,31 +82,7 @@ def configure_connection_for_https(connection)
# 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,
Gem::UriFormatter.new(@proxy_uri.user).unescape,
Gem::UriFormatter.new(@proxy_uri.password).unescape,
]
end

connection_id = [Thread.current.object_id, *net_http_args].join ':'

connection = @connections_mutex.synchronize do
@connections[connection_id] ||= Net::HTTP.new(*net_http_args)
@connections[connection_id]
end

if https?(uri) and not connection.started? then
configure_connection_for_https(connection)
end

connection.start unless connection.started?

connection
@connection_pool.checkout
rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN,
Errno::EHOSTDOWN => e
raise Gem::RemoteFetcher::FetchError.new(e.message, uri)
@@ -106,7 +92,8 @@ 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
request.basic_auth Gem::UriFormatter.new(@uri.user).unescape,
Gem::UriFormatter.new(@uri.password).unescape
end

request.add_field 'User-Agent', @user_agent
@@ -119,6 +106,37 @@ def fetch

yield request if block_given?

perform_request request
end

##
# Returns a proxy URI for the given +scheme+ if one is set in the
# environment variables.

def self.get_proxy_from_env scheme = 'http'
_scheme = scheme.downcase
_SCHEME = scheme.upcase
env_proxy = ENV["#{_scheme}_proxy"] || ENV["#{_SCHEME}_PROXY"]

no_env_proxy = env_proxy.nil? || env_proxy.empty?

return get_proxy_from_env 'http' if no_env_proxy and _scheme != 'http'
return :no_proxy if no_env_proxy

uri = URI(Gem::UriFormatter.new(env_proxy).normalize)

if uri and uri.user.nil? and uri.password.nil? then
user = ENV["#{_scheme}_proxy_user"] || ENV["#{_SCHEME}_PROXY_USER"]
password = ENV["#{_scheme}_proxy_pass"] || ENV["#{_SCHEME}_PROXY_PASS"]

uri.user = Gem::UriFormatter.new(user).escape
uri.password = Gem::UriFormatter.new(password).escape
end

uri
end

def perform_request request # :nodoc:
connection = connection_for @uri

retried = false
@@ -127,8 +145,7 @@ def fetch
begin
@requests[connection.object_id] += 1

say "#{request.method} #{@uri}" if
Gem.configuration.really_verbose
verbose "#{request.method} #{@uri}"

file_name = File.basename(@uri.path)
# perform download progress reporter only for gems
@@ -157,11 +174,10 @@ def fetch
response = connection.request request
end

say "#{response.code} #{response.message}" if
Gem.configuration.really_verbose
verbose "#{response.code} #{response.message}"

rescue Net::HTTPBadResponse
say "bad response" if Gem.configuration.really_verbose
verbose "bad response"

reset connection

@@ -176,8 +192,7 @@ def fetch
Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE

requests = @requests[connection.object_id]
say "connection reset after #{requests} requests, retrying" if
Gem.configuration.really_verbose
verbose "connection reset after #{requests} requests, retrying"

raise Gem::RemoteFetcher::FetchError.new('too many connection resets', @uri) if retried

@@ -188,49 +203,8 @@ def fetch
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
ensure
@connection_pool.checkin connection
end

##
@@ -264,3 +238,7 @@ def user_agent

end

require 'rubygems/request/http_pool'
require 'rubygems/request/https_pool'
require 'rubygems/request/connection_pools'

83 changes: 83 additions & 0 deletions lib/ruby/shared/rubygems/request/connection_pools.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require 'thread'

class Gem::Request::ConnectionPools # :nodoc:

@client = Net::HTTP

class << self
attr_accessor :client
end

def initialize proxy_uri, cert_files
@proxy_uri = proxy_uri
@cert_files = cert_files
@pools = {}
@pool_mutex = Mutex.new
end

def pool_for uri
http_args = net_http_args(uri, @proxy_uri)
key = http_args + [https?(uri)]
@pool_mutex.synchronize do
@pools[key] ||=
if https? uri then
Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri)
else
Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri)
end
end
end

def close_all
@pools.each_value {|pool| pool.close_all}
end

private

##
# 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

def https? uri
uri.scheme.downcase == 'https'
end

def no_proxy? host, env_no_proxy
host = host.downcase

env_no_proxy.any? do |pattern|
pattern = pattern.downcase

host[-pattern.length, pattern.length] == pattern or
(pattern.start_with? '.' and pattern[1..-1] == host)
end
end

def net_http_args uri, proxy_uri
net_http_args = [uri.host, uri.port]

no_proxy = get_no_proxy_from_env

if proxy_uri and not no_proxy?(uri.host, no_proxy) then
net_http_args + [
proxy_uri.host,
proxy_uri.port,
Gem::UriFormatter.new(proxy_uri.user).unescape,
Gem::UriFormatter.new(proxy_uri.password).unescape,
]
elsif no_proxy? uri.host, no_proxy then
net_http_args + [nil, nil]
else
net_http_args
end
end

end

47 changes: 47 additions & 0 deletions lib/ruby/shared/rubygems/request/http_pool.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
##
# A connection "pool" that only manages one connection for now. Provides
# thread safe `checkout` and `checkin` methods. The pool consists of one
# connection that corresponds to `http_args`. This class is private, do not
# use it.

class Gem::Request::HTTPPool # :nodoc:
attr_reader :cert_files, :proxy_uri

def initialize http_args, cert_files, proxy_uri
@http_args = http_args
@cert_files = cert_files
@proxy_uri = proxy_uri
@queue = SizedQueue.new 1
@queue << nil
end

def checkout
@queue.pop || make_connection
end

def checkin connection
@queue.push connection
end

def close_all
until @queue.empty?
if connection = @queue.pop(true) and connection.started?
connection.finish
end
end
@queue.push(nil)
end

private

def make_connection
setup_connection Gem::Request::ConnectionPools.client.new(*@http_args)
end

def setup_connection connection
connection.start
connection
end

end

10 changes: 10 additions & 0 deletions lib/ruby/shared/rubygems/request/https_pool.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Gem::Request::HTTPSPool < Gem::Request::HTTPPool # :nodoc:
private

def setup_connection connection
Gem::Request.configure_connection_for_https(connection, @cert_files)
super
end
end


350 changes: 293 additions & 57 deletions lib/ruby/shared/rubygems/request_set.rb

Large diffs are not rendered by default.

784 changes: 773 additions & 11 deletions lib/ruby/shared/rubygems/request_set/gem_dependency_api.rb

Large diffs are not rendered by default.

238 changes: 238 additions & 0 deletions lib/ruby/shared/rubygems/request_set/lockfile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
##
# Parses a gem.deps.rb.lock file and constructs a LockSet containing the
# dependencies found inside. If the lock file is missing no LockSet is
# constructed.

class Gem::RequestSet::Lockfile
##
# Raised when a lockfile cannot be parsed

class ParseError < Gem::Exception

##
# The column where the error was encountered

attr_reader :column

##
# The line where the error was encountered

attr_reader :line

##
# The location of the lock file

attr_reader :path

##
# Raises a ParseError with the given +message+ which was encountered at a
# +line+ and +column+ while parsing.

def initialize message, column, line, path
@line = line
@column = column
@path = path
super "#{message} (at line #{line} column #{column})"
end
end

##
# Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
# location.

def self.build request_set, gem_deps_file, dependencies = nil
request_set.resolve
dependencies ||= requests_to_deps request_set.sorted_requests
new request_set, gem_deps_file, dependencies
end

def self.requests_to_deps requests # :nodoc:
deps = {}

requests.each do |request|
spec = request.spec
name = request.name
requirement = request.request.dependency.requirement

deps[name] = if [Gem::Resolver::VendorSpecification,
Gem::Resolver::GitSpecification].include? spec.class then
Gem::Requirement.source_set
else
requirement
end
end

deps
end

##
# The platforms for this Lockfile

attr_reader :platforms

def initialize request_set, gem_deps_file, dependencies
@set = request_set
@dependencies = dependencies
@gem_deps_file = File.expand_path(gem_deps_file)
@gem_deps_dir = File.dirname(@gem_deps_file)

@gem_deps_file.untaint unless gem_deps_file.tainted?

@platforms = []
end

def add_DEPENDENCIES out # :nodoc:
out << "DEPENDENCIES"

out.concat @dependencies.sort_by { |name,| name }.map { |name, requirement|
" #{name}#{requirement.for_lockfile}"
}

out << nil
end

def add_GEM out, spec_groups # :nodoc:
return if spec_groups.empty?

source_groups = spec_groups.values.flatten.group_by do |request|
request.spec.source.uri
end

source_groups.sort_by { |group,| group.to_s }.map do |group, requests|
out << "GEM"
out << " remote: #{group}"
out << " specs:"

requests.sort_by { |request| request.name }.each do |request|
next if request.spec.name == 'bundler'
platform = "-#{request.spec.platform}" unless
Gem::Platform::RUBY == request.spec.platform

out << " #{request.name} (#{request.version}#{platform})"

request.full_spec.dependencies.sort.each do |dependency|
next if dependency.type == :development

requirement = dependency.requirement
out << " #{dependency.name}#{requirement.for_lockfile}"
end
end
out << nil
end
end

def add_GIT out, git_requests
return if git_requests.empty?

by_repository_revision = git_requests.group_by do |request|
source = request.spec.source
[source.repository, source.rev_parse]
end

out << "GIT"
by_repository_revision.each do |(repository, revision), requests|
out << " remote: #{repository}"
out << " revision: #{revision}"
out << " specs:"

requests.sort_by { |request| request.name }.each do |request|
out << " #{request.name} (#{request.version})"

dependencies = request.spec.dependencies.sort_by { |dep| dep.name }
dependencies.each do |dep|
out << " #{dep.name}#{dep.requirement.for_lockfile}"
end
end
end

out << nil
end

def relative_path_from dest, base # :nodoc:
dest = File.expand_path(dest)
base = File.expand_path(base)

if dest.index(base) == 0 then
offset = dest[base.size+1..-1]

return '.' unless offset

offset
else
dest
end
end

def add_PATH out, path_requests # :nodoc:
return if path_requests.empty?

out << "PATH"
path_requests.each do |request|
directory = File.expand_path(request.spec.source.uri)

out << " remote: #{relative_path_from directory, @gem_deps_dir}"
out << " specs:"
out << " #{request.name} (#{request.version})"
end

out << nil
end

def add_PLATFORMS out # :nodoc:
out << "PLATFORMS"

platforms = requests.map { |request| request.spec.platform }.uniq

platforms = platforms.sort_by { |platform| platform.to_s }

platforms.sort.each do |platform|
out << " #{platform}"
end

out << nil
end

def spec_groups
requests.group_by { |request| request.spec.class }
end

##
# The contents of the lock file.

def to_s
out = []

groups = spec_groups

add_PATH out, groups.delete(Gem::Resolver::VendorSpecification) { [] }

add_GIT out, groups.delete(Gem::Resolver::GitSpecification) { [] }

add_GEM out, groups

add_PLATFORMS out

add_DEPENDENCIES out

out.join "\n"
end

##
# Writes the lock file alongside the gem dependencies file

def write
content = to_s

open "#{@gem_deps_file}.lock", 'w' do |io|
io.write content
end
end

private

def requests
@set.sorted_requests
end
end

require 'rubygems/request_set/lockfile/tokenizer'
Loading

0 comments on commit e3f23d3

Please sign in to comment.