summaryrefslogtreecommitdiff
path: root/lib/rubygems
diff options
context:
space:
mode:
authornaruse <naruse@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-09-14 08:59:02 +0000
committernaruse <naruse@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-09-14 08:59:02 +0000
commit269503b544247b5b3e30dbe60a0bab4f2ca00e4e (patch)
treea6d0a3a9b34017c4c84d997152a3aaf3086e1ce1 /lib/rubygems
parent2614d9ba2fb5ad171200cccc88f42fa659b527c6 (diff)
Revert r42938 "* lib/rubygems: Update to RubyGems 2.1.3"
It breaks build. http://u64.rubyci.org/~chkbuild/ruby-trunk/log/20130913T200302Z.diff.html.gz git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42941 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems')
-rw-r--r--lib/rubygems/available_set.rb68
-rw-r--r--lib/rubygems/command_manager.rb77
-rw-r--r--lib/rubygems/commands/build_command.rb19
-rw-r--r--lib/rubygems/commands/cert_command.rb107
-rw-r--r--lib/rubygems/commands/check_command.rb7
-rw-r--r--lib/rubygems/commands/cleanup_command.rb14
-rw-r--r--lib/rubygems/commands/contents_command.rb167
-rw-r--r--lib/rubygems/commands/dependency_command.rb158
-rw-r--r--lib/rubygems/commands/environment_command.rb126
-rw-r--r--lib/rubygems/commands/fetch_command.rb16
-rw-r--r--lib/rubygems/commands/help_command.rb140
-rw-r--r--lib/rubygems/commands/install_command.rb126
-rw-r--r--lib/rubygems/commands/list_command.rb13
-rw-r--r--lib/rubygems/commands/mirror_command.rb6
-rw-r--r--lib/rubygems/commands/outdated_command.rb21
-rw-r--r--lib/rubygems/commands/owner_command.rb17
-rw-r--r--lib/rubygems/commands/pristine_command.rb19
-rw-r--r--lib/rubygems/commands/push_command.rb10
-rw-r--r--lib/rubygems/commands/query_command.rb9
-rw-r--r--lib/rubygems/commands/rdoc_command.rb8
-rw-r--r--lib/rubygems/commands/search_command.rb15
-rw-r--r--lib/rubygems/commands/sources_command.rb202
-rw-r--r--lib/rubygems/commands/specification_command.rb16
-rw-r--r--lib/rubygems/commands/stale_command.rb10
-rw-r--r--lib/rubygems/commands/uninstall_command.rb47
-rw-r--r--lib/rubygems/commands/unpack_command.rb18
-rw-r--r--lib/rubygems/commands/update_command.rb195
-rw-r--r--lib/rubygems/commands/which_command.rb11
-rw-r--r--lib/rubygems/commands/yank_command.rb18
-rw-r--r--lib/rubygems/config_file.rb17
-rwxr-xr-xlib/rubygems/core_ext/kernel_require.rb30
-rw-r--r--lib/rubygems/defaults.rb15
-rw-r--r--lib/rubygems/dependency.rb6
-rw-r--r--lib/rubygems/dependency_installer.rb339
-rw-r--r--lib/rubygems/dependency_resolver.rb721
-rw-r--r--lib/rubygems/exceptions.rb89
-rw-r--r--lib/rubygems/ext/builder.rb120
-rw-r--r--lib/rubygems/gem_runner.rb26
-rw-r--r--lib/rubygems/gemcutter_utilities.rb114
-rw-r--r--lib/rubygems/install_update_options.rb3
-rw-r--r--lib/rubygems/installer.rb148
-rw-r--r--lib/rubygems/name_tuple.rb25
-rw-r--r--lib/rubygems/package.rb81
-rw-r--r--lib/rubygems/package/tar_test_case.rb18
-rw-r--r--lib/rubygems/package/tar_writer.rb47
-rw-r--r--lib/rubygems/path_support.rb10
-rw-r--r--lib/rubygems/platform.rb12
-rw-r--r--lib/rubygems/psych_additions.rb2
-rw-r--r--lib/rubygems/remote_fetcher.rb266
-rw-r--r--lib/rubygems/request_set.rb251
-rw-r--r--lib/rubygems/security.rb55
-rw-r--r--lib/rubygems/security/policy.rb49
-rw-r--r--lib/rubygems/security/signer.rb24
-rw-r--r--lib/rubygems/server.rb6
-rw-r--r--lib/rubygems/source.rb36
-rw-r--r--lib/rubygems/source_local.rb91
-rw-r--r--lib/rubygems/source_specific_file.rb28
-rw-r--r--lib/rubygems/spec_fetcher.rb20
-rw-r--r--lib/rubygems/specification.rb291
-rw-r--r--lib/rubygems/test_case.rb200
-rw-r--r--lib/rubygems/uninstaller.rb23
-rw-r--r--lib/rubygems/version.rb16
-rw-r--r--lib/rubygems/version_option.rb10
63 files changed, 2676 insertions, 2173 deletions
diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb
index 80539feee9..bb0b3a3abe 100644
--- a/lib/rubygems/available_set.rb
+++ b/lib/rubygems/available_set.rb
@@ -1,4 +1,7 @@
class Gem::AvailableSet
+
+ include Enumerable
+
Tuple = Struct.new(:spec, :source)
def initialize
@@ -36,6 +39,28 @@ class Gem::AvailableSet
self
end
+ ##
+ # Yields each Tuple in this AvailableSet
+
+ def each
+ return enum_for __method__ unless block_given?
+
+ @set.each do |tuple|
+ yield tuple
+ end
+ end
+
+ ##
+ # Yields the Gem::Specification for each Tuple in this AvailableSet
+
+ def each_spec
+ return enum_for __method__ unless block_given?
+
+ each do |tuple|
+ yield tuple.spec
+ end
+ end
+
def empty?
@set.empty?
end
@@ -66,6 +91,49 @@ class Gem::AvailableSet
f.source
end
+ ##
+ # Converts this AvailableSet into a RequestSet that can be used to install
+ # gems.
+ #
+ # If +development+ is :none then no development dependencies are installed.
+ # Other options are :shallow for only direct development dependencies of the
+ # gems in this set or :all for all development dependencies.
+
+ def to_request_set development = :none
+ request_set = Gem::RequestSet.new
+ request_set.development = :all == development
+
+ each_spec do |spec|
+ request_set.always_install << spec
+
+ request_set.gem spec.name, spec.version
+ request_set.import spec.development_dependencies if
+ :shallow == development
+ end
+
+ request_set
+ end
+
+ ##
+ #
+ # Used by the DependencyResolver, the protocol to use a AvailableSet as a
+ # search Set.
+
+ def find_all(req)
+ dep = req.dependency
+
+ match = @set.find_all do |t|
+ dep.matches_spec? t.spec
+ end
+
+ match.map do |t|
+ Gem::DependencyResolver::InstalledSpecification.new(self, t.spec, t.source)
+ end
+ end
+
+ def prefetch(reqs)
+ end
+
def pick_best!
return self if empty?
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index 2af582177d..fdee064fed 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -33,6 +33,39 @@ class Gem::CommandManager
include Gem::UserInteraction
+ BUILTIN_COMMANDS = [ # :nodoc:
+ :build,
+ :cert,
+ :check,
+ :cleanup,
+ :contents,
+ :dependency,
+ :environment,
+ :fetch,
+ :generate_index,
+ :help,
+ :install,
+ :list,
+ :lock,
+ :mirror,
+ :outdated,
+ :owner,
+ :pristine,
+ :push,
+ :query,
+ :rdoc,
+ :search,
+ :server,
+ :sources,
+ :specification,
+ :stale,
+ :uninstall,
+ :unpack,
+ :update,
+ :which,
+ :yank,
+ ]
+
##
# Return the authoritative instance of the command manager.
@@ -61,36 +94,10 @@ class Gem::CommandManager
def initialize
require 'timeout'
@commands = {}
- register_command :build
- register_command :cert
- register_command :check
- register_command :cleanup
- register_command :contents
- register_command :dependency
- register_command :environment
- register_command :fetch
- register_command :generate_index
- register_command :help
- register_command :install
- register_command :list
- register_command :lock
- register_command :mirror
- register_command :outdated
- register_command :owner
- register_command :pristine
- register_command :push
- register_command :query
- register_command :rdoc
- register_command :search
- register_command :server
- register_command :sources
- register_command :specification
- register_command :stale
- register_command :uninstall
- register_command :unpack
- register_command :update
- register_command :which
- register_command :yank
+
+ BUILTIN_COMMANDS.each do |name|
+ register_command name
+ end
end
##
@@ -132,14 +139,6 @@ class Gem::CommandManager
alert_error "While executing gem ... (#{ex.class})\n #{ex.to_s}"
ui.backtrace ex
- if Gem.configuration.really_verbose and \
- ex.kind_of?(Gem::Exception) and ex.source_exception
- e = ex.source_exception
-
- ui.errs.puts "Because of: (#{e.class})\n #{e.to_s}"
- ui.backtrace e
- end
-
terminate_interaction(1)
rescue Interrupt
alert_error "Interrupted"
@@ -147,8 +146,6 @@ class Gem::CommandManager
end
def process_args(args, build_args=nil)
- args = args.to_str.split(/\s+/) if args.respond_to?(:to_str)
-
if args.empty? then
say Gem::Command::HELP
terminate_interaction 1
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb
index 64563ed3db..d975429fe8 100644
--- a/lib/rubygems/commands/build_command.rb
+++ b/lib/rubygems/commands/build_command.rb
@@ -15,6 +15,25 @@ class Gem::Commands::BuildCommand < Gem::Command
"GEMSPEC_FILE gemspec file name to build a gem for"
end
+ def description # :nodoc:
+ <<-EOF
+The build command allows you to create a gem from a ruby gemspec.
+
+The best way to build a gem is to use a Rakefile and the Gem::PackageTask
+which ships with RubyGems.
+
+The gemspec can either be created by hand or extracted from an existing gem
+with gem spec:
+
+ $ gem unpack my_gem-1.0.gem
+ Unpacked gem: '.../my_gem-1.0'
+ $ gem spec my_gem-1.0.gem --ruby > my_gem-1.0/my_gem-1.0.gemspec
+ $ cd my_gem-1.0
+ [edit gem contents]
+ $ gem build my_gem-1.0.gemspec
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMSPEC_FILE"
end
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index 5a9320f9c4..e417193bca 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -1,6 +1,11 @@
require 'rubygems/command'
require 'rubygems/security'
-require 'openssl'
+begin
+ require 'openssl'
+rescue LoadError => e
+ raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
+ e.message =~ / -- openssl$/
+end
class Gem::Commands::CertCommand < Gem::Command
@@ -21,7 +26,8 @@ class Gem::Commands::CertCommand < Gem::Command
OptionParser.accept OpenSSL::PKey::RSA do |key_file|
begin
- key = OpenSSL::PKey::RSA.new File.read key_file
+ passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
+ key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
rescue Errno::ENOENT
raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
rescue OpenSSL::PKey::RSAError
@@ -79,52 +85,67 @@ class Gem::Commands::CertCommand < Gem::Command
end
end
+ def add_certificate certificate # :nodoc:
+ Gem::Security.trust_dir.trust_cert certificate
+
+ say "Added '#{certificate.subject}'"
+ end
+
def execute
options[:add].each do |certificate|
- Gem::Security.trust_dir.trust_cert certificate
-
- say "Added '#{certificate.subject}'"
+ add_certificate certificate
end
options[:remove].each do |filter|
- certificates_matching filter do |certificate, path|
- FileUtils.rm path
- say "Removed '#{certificate.subject}'"
- end
+ remove_certificates_matching filter
end
options[:list].each do |filter|
- certificates_matching filter do |certificate, _|
- # this could probably be formatted more gracefully
- say certificate.subject.to_s
- end
+ list_certificates_matching filter
end
options[:build].each do |name|
build name
end
- unless options[:sign].empty? then
- load_default_cert unless options[:issuer_cert]
- load_default_key unless options[:key]
- end
-
- options[:sign].each do |cert_file|
- sign cert_file
- end
+ sign_certificates unless options[:sign].empty?
end
def build name
- key = options[:key] || Gem::Security.create_key
+ key, key_path = build_key
+ cert_path = build_cert name, key
+ say "Certificate: #{cert_path}"
+
+ if key_path
+ say "Private Key: #{key_path}"
+ say "Don't forget to move the key file to somewhere private!"
+ end
+ end
+
+ def build_cert name, key # :nodoc:
cert = Gem::Security.create_cert_email name, key
+ Gem::Security.write cert, "gem-public_cert.pem"
+ end
- key_path = Gem::Security.write key, "gem-private_key.pem"
- cert_path = Gem::Security.write cert, "gem-public_cert.pem"
+ def build_key # :nodoc:
+ if options[:key] then
+ options[:key]
+ else
+ passphrase = ask_for_password 'Passphrase for your Private Key:'
+ say "\n"
- say "Certificate: #{cert_path}"
- say "Private Key: #{key_path}"
- say "Don't forget to move the key file to somewhere private!"
+ passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
+ say "\n"
+
+ raise Gem::CommandLineError,
+ "Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
+
+ key = Gem::Security.create_key
+ key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
+
+ return key, key_path
+ end
end
def certificates_matching filter
@@ -179,6 +200,13 @@ For further reading on signing gems see `ri Gem::Security`.
EOF
end
+ def list_certificates_matching filter # :nodoc:
+ certificates_matching filter do |certificate, _|
+ # this could probably be formatted more gracefully
+ say certificate.subject.to_s
+ end
+ end
+
def load_default_cert
cert_file = File.join Gem.default_cert_path
cert = File.read cert_file
@@ -198,7 +226,8 @@ For further reading on signing gems see `ri Gem::Security`.
def load_default_key
key_file = File.join Gem.default_key_path
key = File.read key_file
- options[:key] = OpenSSL::PKey::RSA.new key
+ passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
+ options[:key] = OpenSSL::PKey::RSA.new key, passphrase
rescue Errno::ENOENT
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
@@ -211,6 +240,18 @@ For further reading on signing gems see `ri Gem::Security`.
terminate_interaction 1
end
+ def load_defaults # :nodoc:
+ load_default_cert unless options[:issuer_cert]
+ load_default_key unless options[:key]
+ end
+
+ def remove_certificates_matching filter # :nodoc:
+ certificates_matching filter do |certificate, path|
+ FileUtils.rm path
+ say "Removed '#{certificate.subject}'"
+ end
+ end
+
def sign cert_file
cert = File.read cert_file
cert = OpenSSL::X509::Certificate.new cert
@@ -225,5 +266,13 @@ For further reading on signing gems see `ri Gem::Security`.
Gem::Security.write cert, cert_file, permissions
end
-end
+ def sign_certificates # :nodoc:
+ load_defaults unless options[:sign].empty?
+
+ options[:sign].each do |cert_file|
+ sign cert_file
+ end
+ end
+
+end if defined?(OpenSSL::SSL)
diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb
index d7677d47a1..8893b9c3b2 100644
--- a/lib/rubygems/commands/check_command.rb
+++ b/lib/rubygems/commands/check_command.rb
@@ -79,6 +79,13 @@ class Gem::Commands::CheckCommand < Gem::Command
'--gems --alien'
end
+ def description # :nodoc:
+ <<-EOF
+The check command can list and repair problems with installed gems and
+specifications and will clean up gems that have been partially uninstalled.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} [OPTIONS] [GEMNAME ...]"
end
diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb
index 61f189e449..c8f0082bfb 100644
--- a/lib/rubygems/commands/cleanup_command.rb
+++ b/lib/rubygems/commands/cleanup_command.rb
@@ -6,10 +6,11 @@ class Gem::Commands::CleanupCommand < Gem::Command
def initialize
super 'cleanup',
- 'Clean up old versions of installed gems in the local repository',
+ 'Clean up old versions of installed gems',
:force => false, :install_dir => Gem.dir
- add_option('-d', '--dryrun', "") do |value, options|
+ add_option('-n', '-d', '--dryrun',
+ 'Do not uninstall gems') do |value, options|
options[:dryrun] = true
end
@@ -32,11 +33,11 @@ class Gem::Commands::CleanupCommand < Gem::Command
def description # :nodoc:
<<-EOF
-The cleanup command removes old gems from GEM_HOME. If an older version is
-installed elsewhere in GEM_PATH the cleanup command won't touch it.
+The cleanup command removes old versions of gems from GEM_HOME that are not
+required to meet a dependency. If a gem is installed elsewhere in GEM_PATH
+the cleanup command won't delete it.
-Older gems that are required to satisify the dependencies of gems
-are not removed.
+If no gems are named all gems in GEM_HOME are cleaned.
EOF
end
@@ -162,4 +163,3 @@ are not removed.
end
end
-
diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb
index 42c7fabd86..97218848ed 100644
--- a/lib/rubygems/commands/contents_command.rb
+++ b/lib/rubygems/commands/contents_command.rb
@@ -31,6 +31,10 @@ class Gem::Commands::ContentsCommand < Gem::Command
"Don't include installed path prefix") do |prefix, options|
options[:prefix] = prefix
end
+
+ @path_kind = nil
+ @spec_dirs = nil
+ @version = nil
end
def arguments # :nodoc:
@@ -41,79 +45,126 @@ class Gem::Commands::ContentsCommand < Gem::Command
"--no-lib-only --prefix"
end
+ def description # :nodoc:
+ <<-EOF
+The contents command lists the files in an installed gem. The listing can
+be given as full file names, file names without the installed directory
+prefix or only the files that are requireable.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
def execute
- version = options[:version] || Gem::Requirement.default
+ @version = options[:version] || Gem::Requirement.default
+ @spec_dirs = specification_directories
+ @path_kind = path_description @spec_dirs
- spec_dirs = options[:specdirs].map do |i|
- [i, File.join(i, "specifications")]
- end.flatten
+ names = gem_names
- path_kind = if spec_dirs.empty? then
- spec_dirs = Gem::Specification.dirs
- "default gem paths"
- else
- "specified path"
- end
-
- gem_names = if options[:all] then
- Gem::Specification.map(&:name)
- else
- get_all_gem_names
- end
-
- gem_names.each do |name|
- # HACK: find_by_name fails for some reason... ARGH
- # How many places must we embed our resolve logic?
- spec = Gem::Specification.find_all_by_name(name, version).last
-
- unless spec then
- say "Unable to find gem '#{name}' in #{path_kind}"
-
- if Gem.configuration.verbose then
- say "\nDirectories searched:"
- spec_dirs.sort.each { |dir| say dir }
- end
-
- terminate_interaction 1 if gem_names.length == 1
- end
+ names.each do |name|
+ found = gem_contents name
- if spec.default_gem?
- files = spec.files.sort.map do |file|
- case file
- when /\A#{spec.bindir}\//
- [Gem::ConfigMap[:bindir], $POSTMATCH]
- when /\.so\z/
- [Gem::ConfigMap[:archdir], file]
- else
- [Gem::ConfigMap[:rubylibdir], file]
- end
- end
+ terminate_interaction 1 unless found or names.length > 1
+ end
+ end
+
+ def files_in spec
+ if spec.default_gem? then
+ files_in_default_gem spec
+ else
+ files_in_gem spec
+ end
+ end
+
+ def files_in_gem spec
+ gem_path = spec.full_gem_path
+ extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
+ glob = "#{gem_path}#{extra}/**/*"
+ prefix_re = /#{Regexp.escape(gem_path)}\//
+
+ Dir[glob].map do |file|
+ [gem_path, file.sub(prefix_re, "")]
+ end
+ end
+
+ def files_in_default_gem spec
+ spec.files.sort.map do |file|
+ case file
+ when /\A#{spec.bindir}\//
+ [Gem::ConfigMap[:bindir], $POSTMATCH]
+ when /\.so\z/
+ [Gem::ConfigMap[:archdir], file]
else
- gem_path = spec.full_gem_path
- extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
- glob = "#{gem_path}#{extra}/**/*"
- prefix_re = /#{Regexp.escape(gem_path)}\//
- files = Dir[glob].map do |file|
- [gem_path, file.sub(prefix_re, "")]
- end
+ [Gem::ConfigMap[:rubylibdir], file]
end
+ end
+ end
+
+ def gem_contents name
+ spec = spec_for name
+
+ return false unless spec
+
+ files = files_in spec
- files.sort.each do |prefix, basename|
- absolute_path = File.join(prefix, basename)
- next if File.directory? absolute_path
+ show_files files
- if options[:prefix]
- say absolute_path
- else
- say basename
- end
+ true
+ end
+
+ def gem_names # :nodoc:
+ if options[:all] then
+ Gem::Specification.map(&:name)
+ else
+ get_all_gem_names
+ end
+ end
+
+ def path_description spec_dirs # :nodoc:
+ if spec_dirs.empty? then
+ spec_dirs = Gem::Specification.dirs
+ "default gem paths"
+ else
+ "specified path"
+ end
+ end
+
+ def show_files files
+ files.sort.each do |prefix, basename|
+ absolute_path = File.join(prefix, basename)
+ next if File.directory? absolute_path
+
+ if options[:prefix] then
+ say absolute_path
+ else
+ say basename
end
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}' in #{@path_kind}"
+
+ if Gem.configuration.verbose then
+ say "\nDirectories searched:"
+ @spec_dirs.sort.each { |dir| say dir }
+ end
+
+ return nil
+ end
+
+ def specification_directories # :nodoc:
+ options[:specdirs].map do |i|
+ [i, File.join(i, "specifications")]
+ end.flatten
+ end
+
end
diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb
index 4690b13a94..c5d6dd7d70 100644
--- a/lib/rubygems/commands/dependency_command.rb
+++ b/lib/rubygems/commands/dependency_command.rb
@@ -38,89 +38,121 @@ class Gem::Commands::DependencyCommand < Gem::Command
"--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies"
end
+ def description # :nodoc:
+ <<-EOF
+The dependency commands lists which other gems a given gem depends on. For
+local gems only the reverse dependencies can be shown (which gems depend on
+the named gem).
+
+The dependency list can be displayed in a format suitable for piping for
+use with other commands.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME"
end
- def execute
- if options[:reverse_dependencies] and remote? and not local? then
- alert_error 'Only reverse dependencies for local gems are supported.'
- terminate_interaction 1
- end
+ def fetch_remote_specs dependency # :nodoc:
+ fetcher = Gem::SpecFetcher.fetcher
+
+ ss, = fetcher.spec_for_dependency dependency
+
+ ss.map { |spec, _| spec }
+ end
+
+ def fetch_specs dependency # :nodoc:
+ specs = []
+
+ specs.concat dependency.matching_specs if local?
+ specs.concat fetch_remote_specs dependency if remote?
+
+ ensure_specs specs
- options[:args] << '' if options[:args].empty?
+ specs.uniq.sort
+ end
+
+ def gem_dependency args, version, prerelease # :nodoc:
+ args << '' if args.empty?
- pattern = if options[:args].length == 1 and
- options[:args].first =~ /\A\/(.*)\/(i)?\z/m then
+ pattern = if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m then
flags = $2 ? Regexp::IGNORECASE : nil
Regexp.new $1, flags
else
- /\A#{Regexp.union(*options[:args])}/
+ /\A#{Regexp.union(*args)}/
end
- # TODO: deprecate for real damnit
dependency = Gem::Deprecate.skip_during {
- Gem::Dependency.new pattern, options[:version]
+ Gem::Dependency.new pattern, version
}
- dependency.prerelease = options[:prerelease]
- specs = []
+ dependency.prerelease = prerelease
- specs.concat dependency.matching_specs if local?
+ dependency
+ end
- if remote? and not options[:reverse_dependencies] then
- fetcher = Gem::SpecFetcher.fetcher
+ def display_pipe specs # :nodoc:
+ specs.each do |spec|
+ unless spec.dependencies.empty? then
+ spec.dependencies.sort_by { |dep| dep.name }.each do |dep|
+ say "#{dep.name} --version '#{dep.requirement}'"
+ end
+ end
+ end
+ end
- ss, _ = fetcher.spec_for_dependency dependency
+ def display_readable specs, reverse # :nodoc:
+ response = ''
- ss.each { |s,o| specs << s }
+ specs.each do |spec|
+ response << print_dependencies(spec)
+ unless reverse[spec.full_name].empty? then
+ response << " Used by\n"
+ reverse[spec.full_name].each do |sp, dep|
+ response << " #{sp} (#{dep})\n"
+ end
+ end
+ response << "\n"
end
- if specs.empty? then
- patterns = options[:args].join ','
- say "No gems found matching #{patterns} (#{options[:version]})" if
- Gem.configuration.verbose
+ say response
+ end
- terminate_interaction 1
- end
+ def execute
+ ensure_local_only_reverse_dependencies
- specs = specs.uniq.sort
+ dependency =
+ gem_dependency options[:args], options[:version], options[:prerelease]
- reverse = Hash.new { |h, k| h[k] = [] }
+ specs = fetch_specs dependency
- if options[:reverse_dependencies] then
- specs.each do |spec|
- reverse[spec.full_name] = find_reverse_dependencies spec
- end
- end
+ reverse = reverse_dependencies specs
if options[:pipe_format] then
- specs.each do |spec|
- unless spec.dependencies.empty?
- spec.dependencies.sort_by { |dep| dep.name }.each do |dep|
- say "#{dep.name} --version '#{dep.requirement}'"
- end
- end
- end
+ display_pipe specs
else
- response = ''
-
- specs.each do |spec|
- response << print_dependencies(spec)
- unless reverse[spec.full_name].empty? then
- response << " Used by\n"
- reverse[spec.full_name].each do |sp, dep|
- response << " #{sp} (#{dep})\n"
- end
- end
- response << "\n"
- end
+ display_readable specs, reverse
+ end
+ end
- say response
+ def ensure_local_only_reverse_dependencies # :nodoc:
+ if options[:reverse_dependencies] and remote? and not local? then
+ alert_error 'Only reverse dependencies for local gems are supported.'
+ terminate_interaction 1
end
end
- def print_dependencies(spec, level = 0)
+ def ensure_specs specs # :nodoc:
+ return unless specs.empty?
+
+ patterns = options[:args].join ','
+ say "No gems found matching #{patterns} (#{options[:version]})" if
+ Gem.configuration.verbose
+
+ terminate_interaction 1
+ end
+
+ def print_dependencies(spec, level = 0) # :nodoc:
response = ''
response << ' ' * level + "Gem #{spec.full_name}\n"
unless spec.dependencies.empty? then
@@ -131,10 +163,30 @@ class Gem::Commands::DependencyCommand < Gem::Command
response
end
+ def remote_specs dependency # :nodoc:
+ fetcher = Gem::SpecFetcher.fetcher
+
+ ss, _ = fetcher.spec_for_dependency dependency
+
+ ss.map { |s,o| s }
+ end
+
+ def reverse_dependencies specs # :nodoc:
+ reverse = Hash.new { |h, k| h[k] = [] }
+
+ return reverse unless options[:reverse_dependencies]
+
+ specs.each do |spec|
+ reverse[spec.full_name] = find_reverse_dependencies spec
+ end
+
+ reverse
+ end
+
##
# Returns an Array of [specification, dep] that are satisfied by +spec+.
- def find_reverse_dependencies(spec)
+ def find_reverse_dependencies spec # :nodoc:
result = []
Gem::Specification.each do |sp|
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index 40e71cf094..d32d12b757 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -21,6 +21,9 @@ class Gem::Commands::EnvironmentCommand < Gem::Command
def description # :nodoc:
<<-EOF
+The environment command lets you query rubygems for its configuration for
+use in shell scripts or as a debugging aid.
+
The RubyGems environment can be controlled through command line arguments,
gemrc files, environment variables and built-in defaults.
@@ -69,66 +72,83 @@ lib/rubygems/defaults/operating_system.rb
def execute
out = ''
arg = options[:args][0]
- case arg
- when /^packageversion/ then
- out << Gem::RubyGemsPackageVersion
- when /^version/ then
- out << Gem::VERSION
- when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then
- out << Gem.dir
- when /^gempath/, /^path/, /^GEM_PATH/ then
- out << Gem.path.join(File::PATH_SEPARATOR)
- when /^remotesources/ then
- out << Gem.sources.to_a.join("\n")
- when /^platform/ then
- out << Gem.platforms.join(File::PATH_SEPARATOR)
- when nil then
- out = "RubyGems Environment:\n"
-
- out << " - RUBYGEMS VERSION: #{Gem::VERSION}\n"
-
- out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
- out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
- out << ") [#{RUBY_PLATFORM}]\n"
-
- out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
-
- out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil?
-
- out << " - RUBY EXECUTABLE: #{Gem.ruby}\n"
-
- out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n"
-
- out << " - RUBYGEMS PLATFORMS:\n"
- Gem.platforms.each do |platform|
- out << " - #{platform}\n"
+ out <<
+ case arg
+ when /^packageversion/ then
+ Gem::RubyGemsPackageVersion
+ when /^version/ then
+ Gem::VERSION
+ when /^gemdir/, /^gemhome/, /^home/, /^GEM_HOME/ then
+ Gem.dir
+ when /^gempath/, /^path/, /^GEM_PATH/ then
+ Gem.path.join(File::PATH_SEPARATOR)
+ when /^remotesources/ then
+ Gem.sources.to_a.join("\n")
+ when /^platform/ then
+ Gem.platforms.join(File::PATH_SEPARATOR)
+ when nil then
+ show_environment
+ else
+ raise Gem::CommandLineError, "Unknown environment option [#{arg}]"
end
+ say out
+ true
+ end
- out << " - GEM PATHS:\n"
- out << " - #{Gem.dir}\n"
+ def add_path out, path
+ path.each do |component|
+ out << " - #{component}\n"
+ end
+ end
- path = Gem.path.dup
- path.delete Gem.dir
- path.each do |p|
- out << " - #{p}\n"
- end
+ def show_environment # :nodoc:
+ out = "RubyGems Environment:\n"
- out << " - GEM CONFIGURATION:\n"
- Gem.configuration.each do |name, value|
- value = value.gsub(/./, '*') if name == 'gemcutter_key'
- out << " - #{name.inspect} => #{value.inspect}\n"
- end
+ out << " - RUBYGEMS VERSION: #{Gem::VERSION}\n"
- out << " - REMOTE SOURCES:\n"
- Gem.sources.each do |s|
- out << " - #{s}\n"
- end
+ out << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}"
+ out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL
+ out << ") [#{RUBY_PLATFORM}]\n"
+
+ out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
+
+ out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil?
+
+ out << " - RUBY EXECUTABLE: #{Gem.ruby}\n"
+
+ out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n"
- else
- raise Gem::CommandLineError, "Unknown environment option [#{arg}]"
+ out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n"
+
+ out << " - RUBYGEMS PLATFORMS:\n"
+ Gem.platforms.each do |platform|
+ out << " - #{platform}\n"
end
- say out
- true
+
+ out << " - GEM PATHS:\n"
+ out << " - #{Gem.dir}\n"
+
+ gem_path = Gem.path.dup
+ gem_path.delete Gem.dir
+ add_path out, gem_path
+
+ out << " - GEM CONFIGURATION:\n"
+ Gem.configuration.each do |name, value|
+ value = value.gsub(/./, '*') if name == 'gemcutter_key'
+ out << " - #{name.inspect} => #{value.inspect}\n"
+ end
+
+ out << " - REMOTE SOURCES:\n"
+ Gem.sources.each do |s|
+ out << " - #{s}\n"
+ end
+
+ out << " - SHELL PATH:\n"
+
+ shell_path = ENV['PATH'].split(File::PATH_SEPARATOR)
+ add_path out, shell_path
+
+ out
end
end
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index ec021359b6..c57ab0089a 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -28,6 +28,16 @@ class Gem::Commands::FetchCommand < Gem::Command
"--version '#{Gem::Requirement.default}'"
end
+ def description # :nodoc:
+ <<-EOF
+The fetch command fetches gem files that can be stored for later use or
+unpacked to examine their contents.
+
+See the build command help for an example of unpacking a gem, modifying it,
+then repackaging it.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
@@ -42,13 +52,15 @@ class Gem::Commands::FetchCommand < Gem::Command
dep = Gem::Dependency.new gem_name, version
dep.prerelease = options[:prerelease]
- specs_and_sources, errors = Gem::SpecFetcher.fetcher.spec_for_dependency dep
+ specs_and_sources, errors =
+ Gem::SpecFetcher.fetcher.spec_for_dependency dep
+
if platform then
filtered = specs_and_sources.select { |s,| s.platform == platform }
specs_and_sources = filtered unless filtered.empty?
end
- spec, source = specs_and_sources.sort_by { |s,| s.version }.first
+ spec, source = specs_and_sources.max_by { |s,| s.version }
if spec.nil? then
show_lookup_failure gem_name, version, errors, options[:domain]
diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb
index 7f1fb486e0..ed7be903ac 100644
--- a/lib/rubygems/commands/help_command.rb
+++ b/lib/rubygems/commands/help_command.rb
@@ -46,6 +46,10 @@ Some examples of 'gem' usage.
* Update all gems on your system:
gem update
+
+* Update your local version of RubyGems
+
+ gem update --system
EOF
PLATFORMS = <<-'EOF'
@@ -55,8 +59,9 @@ your current platform by running `gem environment`.
RubyGems matches platforms as follows:
- * The CPU must match exactly, unless one of the platforms has
- "universal" as the CPU.
+ * The CPU must match exactly unless one of the platforms has
+ "universal" as the CPU or the local CPU starts with "arm" and the gem's
+ CPU is exactly "arm" (for gems that support generic ARM architecture).
* The OS must match exactly.
* The versions must match exactly unless one of the versions is nil.
@@ -66,11 +71,20 @@ you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin
platforms, the version is the compiler version, not the OS version. (Ruby
compiled with VC6 uses "60" as the compiler version, VC8 uses "80".)
+For the ARM architecture, gems with a platform of "arm-linux" should run on a
+reasonable set of ARM CPUs and not depend on instructions present on a limited
+subset of the architecture. For example, the binary should run on platforms
+armv5, armv6hf, armv6l, armv7, etc. If you use the "arm-linux" platform
+please test your gem on a variety of ARM hardware before release to ensure it
+functions correctly.
+
Example platforms:
x86-freebsd # Any FreeBSD version on an x86 CPU
universal-darwin-8 # Darwin 8 only gems that run on any CPU
x86-mswin32-80 # Windows gems compiled with VC8
+ armv7-linux # Gem complied for an ARMv7 CPU running linux
+ arm-linux # Gem compiled for any ARM CPU running linux
When building platform gems, set the platform in the gem specification to
Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
@@ -80,6 +94,8 @@ platform.
def initialize
super 'help', "Provide help on the 'gem' command"
+
+ @command_manager = Gem::CommandManager.instance
end
def arguments # :nodoc:
@@ -96,46 +112,10 @@ platform.
end
def execute
- command_manager = Gem::CommandManager.instance
arg = options[:args][0]
if begins? "commands", arg then
- out = []
- out << "GEM commands are:"
- out << nil
-
- margin_width = 4
-
- desc_width = command_manager.command_names.map { |n| n.size }.max + 4
-
- summary_width = 80 - margin_width - desc_width
- wrap_indent = ' ' * (margin_width + desc_width)
- format = "#{' ' * margin_width}%-#{desc_width}s%s"
-
- command_manager.command_names.each do |cmd_name|
- command = command_manager[cmd_name]
-
- summary =
- if command then
- command.summary
- else
- "[No command found for #{cmd_name}, bug?]"
- end
-
- summary = wrap(summary, summary_width).split "\n"
- out << sprintf(format, cmd_name, summary.shift)
- until summary.empty? do
- out << "#{wrap_indent}#{summary.shift}"
- end
- end
-
- out << nil
- out << "For help on a particular command, use 'gem help COMMAND'."
- out << nil
- out << "Commands may be abbreviated, so long as they are unambiguous."
- out << "e.g. 'gem i rake' is short for 'gem install rake'."
-
- say out.join("\n")
+ show_commands
elsif begins? "options", arg then
say Gem::Command::HELP
@@ -147,29 +127,79 @@ platform.
say PLATFORMS
elsif options[:help] then
- command = command_manager[options[:help]]
- if command
- # help with provided command
- command.invoke("--help")
- else
- alert_error "Unknown command #{options[:help]}. Try 'gem help commands'"
- end
+ show_help
elsif arg then
- possibilities = command_manager.find_command_possibilities(arg.downcase)
- if possibilities.size == 1
- command = command_manager[possibilities.first]
- command.invoke("--help")
- elsif possibilities.size > 1
- alert_warning "Ambiguous command #{arg} (#{possibilities.join(', ')})"
- else
- alert_warning "Unknown command #{arg}. Try gem help commands"
- end
+ show_command_help arg
else
say Gem::Command::HELP
end
end
+ def show_commands # :nodoc:
+ out = []
+ out << "GEM commands are:"
+ out << nil
+
+ margin_width = 4
+
+ desc_width = @command_manager.command_names.map { |n| n.size }.max + 4
+
+ summary_width = 80 - margin_width - desc_width
+ wrap_indent = ' ' * (margin_width + desc_width)
+ format = "#{' ' * margin_width}%-#{desc_width}s%s"
+
+ @command_manager.command_names.each do |cmd_name|
+ command = @command_manager[cmd_name]
+
+ summary =
+ if command then
+ command.summary
+ else
+ "[No command found for #{cmd_name}]"
+ end
+
+ summary = wrap(summary, summary_width).split "\n"
+ out << sprintf(format, cmd_name, summary.shift)
+ until summary.empty? do
+ out << "#{wrap_indent}#{summary.shift}"
+ end
+ end
+
+ out << nil
+ out << "For help on a particular command, use 'gem help COMMAND'."
+ out << nil
+ out << "Commands may be abbreviated, so long as they are unambiguous."
+ out << "e.g. 'gem i rake' is short for 'gem install rake'."
+
+ say out.join("\n")
+ end
+
+ def show_command_help command_name # :nodoc:
+ command_name = command_name.downcase
+
+ possibilities = @command_manager.find_command_possibilities command_name
+
+ if possibilities.size == 1 then
+ command = @command_manager[possibilities.first]
+ command.invoke("--help")
+ elsif possibilities.size > 1 then
+ alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})"
+ else
+ alert_warning "Unknown command #{command_name}. Try: gem help commands"
+ end
+ end
+
+ def show_help # :nodoc:
+ command = @command_manager[options[:help]]
+ if command then
+ # help with provided command
+ command.invoke("--help")
+ else
+ alert_error "Unknown command #{options[:help]}. Try 'gem help commands'"
+ end
+ end
+
end
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 0b58fa665e..f02b12906d 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -4,8 +4,6 @@ require 'rubygems/dependency_installer'
require 'rubygems/local_remote_options'
require 'rubygems/validator'
require 'rubygems/version_option'
-require 'rubygems/install_message' # must come before rdoc for messaging
-require 'rubygems/rdoc'
##
# Gem installer command line tool
@@ -40,6 +38,12 @@ class Gem::Commands::InstallCommand < Gem::Command
o[:gemdeps] = v
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
end
@@ -109,7 +113,44 @@ to write the specification by hand. For example:
"#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags"
end
- def install_from_gemdeps(gf)
+ def check_install_dir # :nodoc:
+ if options[:install_dir] and options[:user_install] then
+ alert_error "Use --install-dir or --user-install but not both"
+ terminate_interaction 1
+ end
+ end
+
+ def check_version # :nodoc:
+ if options[:version] != Gem::Requirement.default and
+ get_all_gem_names.size > 1 then
+ alert_error "Can't use --version w/ multiple gems. Use name:ver instead."
+ terminate_interaction 1
+ end
+ end
+
+ def execute
+ if gf = options[:gemdeps] then
+ install_from_gemdeps gf
+ return
+ end
+
+ @installed_specs = []
+
+ ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
+
+ check_install_dir
+ check_version
+
+ load_hooks
+
+ exit_code = install_gems
+
+ show_installed
+
+ raise Gem::SystemExitException, exit_code
+ end
+
+ def install_from_gemdeps gf # :nodoc:
require 'rubygems/request_set'
rs = Gem::RequestSet.new
rs.load_gemdeps gf
@@ -131,51 +172,26 @@ to write the specification by hand. For example:
raise Gem::SystemExitException, 0
end
- def execute
- if gf = options[:gemdeps] then
- install_from_gemdeps gf
- return
- end
+ def install_gem name, version # :nodoc:
+ return if options[:conservative] and
+ not Gem::Dependency.new(name, version).matching_specs.empty?
- @installed_specs = []
+ inst = Gem::DependencyInstaller.new options
+ inst.install name, Gem::Requirement.create(version)
- ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
+ @installed_specs.push(*inst.installed_gems)
- if options[:install_dir] and options[:user_install]
- alert_error "Use --install-dir or --user-install but not both"
- terminate_interaction 1
- end
+ show_install_errors inst.errors
+ end
+ def install_gems # :nodoc:
exit_code = 0
- if options[:version] != Gem::Requirement.default &&
- get_all_gem_names.size > 1 then
- alert_error "Can't use --version w/ multiple gems. Use name:ver instead."
- terminate_interaction 1
- end
-
-
get_all_gem_names_and_versions.each do |gem_name, gem_version|
gem_version ||= options[:version]
begin
- next if options[:conservative] and
- not Gem::Dependency.new(gem_name, gem_version).matching_specs.empty?
-
- inst = Gem::DependencyInstaller.new options
- inst.install gem_name, Gem::Requirement.create(gem_version)
-
- @installed_specs.push(*inst.installed_gems)
-
- next unless errs = inst.errors
-
- errs.each do |x|
- next unless Gem::SourceFetchProblem === x
-
- msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}"
-
- alert_warning msg
- end
+ install_gem gem_name, gem_version
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
@@ -186,12 +202,38 @@ to write the specification by hand. For example:
end
end
- unless @installed_specs.empty? then
- gems = @installed_specs.length == 1 ? 'gem' : 'gems'
- say "#{@installed_specs.length} #{gems} installed"
+ exit_code
+ end
+
+ ##
+ # Loads post-install hooks
+
+ def load_hooks # :nodoc:
+ if options[:install_as_default]
+ require 'rubygems/install_default_message'
+ else
+ require 'rubygems/install_message'
end
+ require 'rubygems/rdoc'
+ end
- raise Gem::SystemExitException, exit_code
+ def show_install_errors errors # :nodoc:
+ return unless errors
+
+ errors.each do |x|
+ return unless Gem::SourceFetchProblem === x
+
+ msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}"
+
+ alert_warning msg
+ end
+ end
+
+ def show_installed # :nodoc:
+ return if @installed_specs.empty?
+
+ gems = @installed_specs.length == 1 ? 'gem' : 'gems'
+ say "#{@installed_specs.length} #{gems} installed"
end
end
diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb
index f3e5da9551..0d15950475 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -8,7 +8,7 @@ require 'rubygems/commands/query_command'
class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
def initialize
- super 'list', 'Display gems whose name starts with STRING'
+ super 'list', 'Display local gems whose name starts with STRING'
remove_option('--name-matches')
end
@@ -21,6 +21,17 @@ class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
"--local --no-details"
end
+ def description # :nodoc:
+ <<-EOF
+The list command is used to view the gems you have installed locally.
+
+The --details option displays additional details including the summary, the
+homepage, the author, the locations of different versions of the gem.
+
+To search for remote gems use the search command.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} [STRING]"
end
diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb
index 0f98077cbd..75419c857a 100644
--- a/lib/rubygems/commands/mirror_command.rb
+++ b/lib/rubygems/commands/mirror_command.rb
@@ -10,6 +10,12 @@ class Gem::Commands::MirrorCommand < Gem::Command
end
end
+ def description # :nodoc:
+ <<-EOF
+The mirror command has been moved to the rubygems-mirror gem.
+ EOF
+ end
+
def execute
alert_error "Install the rubygems-mirror gem for the mirror command"
end
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index 887faab0a2..f51bc5e93f 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -15,19 +15,18 @@ class Gem::Commands::OutdatedCommand < Gem::Command
add_platform_option
end
- def execute
- Gem::Specification.outdated.sort.each do |name|
- local = Gem::Specification.find_all_by_name(name).max
- dep = Gem::Dependency.new local.name, ">= #{local.version}"
- remotes, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep
-
- next if remotes.empty?
-
- remotes.sort! { |a,b| a[0].version <=> b[0].version }
+ def description # :nodoc:
+ <<-EOF
+The outdated command lists gems you way wish to upgrade to a newer version.
- highest = remotes.last.first
+You can check for dependency mismatches using the dependency command and
+update the gems with the update or install commands.
+ EOF
+ end
- say "#{local.name} (#{local.version} < #{highest.version})"
+ def execute
+ Gem::Specification.outdated_and_latest_version.each do |spec, remote_version|
+ say "#{spec.name} (#{spec.version} < #{remote_version})"
end
end
end
diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb
index 11e6e026fd..13b8793021 100644
--- a/lib/rubygems/commands/owner_command.rb
+++ b/lib/rubygems/commands/owner_command.rb
@@ -7,7 +7,14 @@ class Gem::Commands::OwnerCommand < Gem::Command
include Gem::GemcutterUtilities
def description # :nodoc:
- 'Manage gem owners on RubyGems.org.'
+ <<-EOF
+The owner command lets you add and remove owners of a gem on a push
+server (the default is https://rubygems.org).
+
+The owner of a gem has the permission to push new versions, yank existing
+versions or edit the HTML page of the gem. Be careful of who you give push
+permission to.
+ EOF
end
def arguments # :nodoc:
@@ -19,7 +26,7 @@ class Gem::Commands::OwnerCommand < Gem::Command
end
def initialize
- super 'owner', description
+ super 'owner', 'Manage gem owners of a gem on the push server'
add_proxy_option
add_key_option
defaults.merge! :add => [], :remove => []
@@ -31,9 +38,15 @@ class Gem::Commands::OwnerCommand < Gem::Command
add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options|
options[:remove] << value
end
+
+ add_option '-h', '--host HOST', 'Use another gemcutter-compatible host' do |value, options|
+ options[:host] = value
+ end
end
def execute
+ @host = options[:host]
+
sign_in
name = get_one_gem_name
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index 8d479211ac..3f3bca45be 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -31,6 +31,12 @@ class Gem::Commands::PristineCommand < Gem::Command
options[:only_executables] = value
end
+ add_option('-E', '--[no-]env-shebang',
+ 'Rewrite executables with a shebang',
+ 'of /usr/bin/env') do |value, options|
+ options[:env_shebang] = value
+ end
+
add_version_option('restore to', 'pristine condition')
end
@@ -105,16 +111,21 @@ with an extension.
Gem::RemoteFetcher.fetcher.download_to_cache dep
end
- # TODO use installer options
- install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install']
- installer_env_shebang = install_defaults.to_s['--env-shebang']
+ env_shebang =
+ if options.include? :env_shebang then
+ options[:env_shebang]
+ else
+ install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install']
+ install_defaults.to_s['--env-shebang']
+ end
installer = Gem::Installer.new(gem,
:wrappers => true,
:force => true,
:install_dir => spec.base_dir,
- :env_shebang => installer_env_shebang,
+ :env_shebang => env_shebang,
:build_args => spec.build_args)
+
if options[:only_executables] then
installer.generate_bin
else
diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb
index fccad206fa..b90be7bd10 100644
--- a/lib/rubygems/commands/push_command.rb
+++ b/lib/rubygems/commands/push_command.rb
@@ -8,7 +8,13 @@ class Gem::Commands::PushCommand < Gem::Command
include Gem::GemcutterUtilities
def description # :nodoc:
- 'Push a gem up to RubyGems.org'
+ <<-EOF
+The push command uploads a gem to the push server (the default is
+https://rubygems.org) and adds it to the index.
+
+The gem can be removed from the index (but only the index) using the yank
+command. For further discussion see the help for the yank command.
+ EOF
end
def arguments # :nodoc:
@@ -20,7 +26,7 @@ class Gem::Commands::PushCommand < Gem::Command
end
def initialize
- super 'push', description, :host => self.host
+ super 'push', 'Push a gem up to the gem server', :host => self.host
add_proxy_option
add_key_option
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
index 05b214bb63..c9c3014975 100644
--- a/lib/rubygems/commands/query_command.rb
+++ b/lib/rubygems/commands/query_command.rb
@@ -61,6 +61,15 @@ class Gem::Commands::QueryCommand < Gem::Command
"--local --name-matches // --no-details --versions --no-installed"
end
+ def description # :nodoc:
+ <<-EOF
+The query command is the basis for the list and search commands.
+
+You should really use the list and search commands instead. This command
+is too hard to use.
+ EOF
+ end
+
def execute
exit_code = 0
diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb
index df00f3a5df..86597f99a6 100644
--- a/lib/rubygems/commands/rdoc_command.rb
+++ b/lib/rubygems/commands/rdoc_command.rb
@@ -45,8 +45,12 @@ class Gem::Commands::RdocCommand < Gem::Command
def description # :nodoc:
<<-DESC
-The rdoc command builds RDoc and RI documentation for installed gems. Use
---overwrite to force rebuilding of documentation.
+The rdoc command builds documentation for installed gems. By default
+only documentation is built using rdoc, but additional types of
+documentation may be built through rubygems plugins and the
+Gem.post_installs hook.
+
+Use --overwrite to force rebuilding of documentation.
DESC
end
diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb
index c125715fe2..5bc9650672 100644
--- a/lib/rubygems/commands/search_command.rb
+++ b/lib/rubygems/commands/search_command.rb
@@ -4,7 +4,7 @@ require 'rubygems/commands/query_command'
class Gem::Commands::SearchCommand < Gem::Commands::QueryCommand
def initialize
- super 'search', 'Display all gems whose name contains STRING'
+ super 'search', 'Display remote gems whose name contains STRING'
remove_option '--name-matches'
@@ -19,6 +19,19 @@ class Gem::Commands::SearchCommand < Gem::Commands::QueryCommand
"--remote --no-details"
end
+ def description # :nodoc:
+ <<-EOF
+The search command displays remote gems whose name contains the given
+string.
+
+The --details option displays additional details from the gem but will
+take a little longer to complete as it must download the information
+individually from the index.
+
+To list local gems use the list command.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} [STRING]"
end
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index f4cc3e57ae..60d96c5828 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -37,103 +37,165 @@ class Gem::Commands::SourcesCommand < Gem::Command
add_proxy_option
end
- def defaults_str
- '--list'
- end
-
- def execute
- options[:list] = !(options[:add] ||
- options[:clear_all] ||
- options[:remove] ||
- options[:update])
+ def add_source source_uri # :nodoc:
+ check_rubygems_https source_uri
- if options[:clear_all] then
- path = File.join Gem.user_home, '.gem', 'specs'
- FileUtils.rm_rf path
+ source = Gem::Source.new source_uri
- unless File.exist? path then
- say "*** Removed specs cache ***"
+ begin
+ if Gem.sources.include? source_uri then
+ say "source #{source_uri} already present in the cache"
else
- unless File.writable? path then
- say "*** Unable to remove source cache (write protected) ***"
- else
- say "*** Unable to remove source cache ***"
- end
+ source.load_specs :released
+ Gem.sources << source
+ Gem.configuration.write
- terminate_interaction 1
+ say "#{source_uri} added to sources"
end
+ rescue URI::Error, ArgumentError
+ say "#{source_uri} is not a URI"
+ terminate_interaction 1
+ rescue Gem::RemoteFetcher::FetchError => e
+ say "Error fetching #{source_uri}:\n\t#{e.message}"
+ terminate_interaction 1
end
+ end
- if source_uri = options[:add] then
- uri = URI source_uri
+ def check_rubygems_https source_uri # :nodoc:
+ uri = URI source_uri
- if uri.scheme and uri.scheme.downcase == 'http' and
- uri.host.downcase == 'rubygems.org' then
- question = <<-QUESTION.chomp
+ if uri.scheme and uri.scheme.downcase == 'http' and
+ uri.host.downcase == 'rubygems.org' then
+ question = <<-QUESTION.chomp
https://rubygems.org is recommended for security over #{uri}
Do you want to add this insecure source?
- QUESTION
+ QUESTION
- terminate_interaction 1 unless ask_yes_no question
- end
-
- source = Gem::Source.new source_uri
-
- begin
- if Gem.sources.include? source_uri then
- say "source #{source_uri} already present in the cache"
- else
- source.load_specs :released
- Gem.sources << source
- Gem.configuration.write
-
- say "#{source_uri} added to sources"
- end
- rescue URI::Error, ArgumentError
- say "#{source_uri} is not a URI"
- terminate_interaction 1
- rescue Gem::RemoteFetcher::FetchError => e
- say "Error fetching #{source_uri}:\n\t#{e.message}"
- terminate_interaction 1
- end
+ terminate_interaction 1 unless ask_yes_no question
end
+ end
- if options[:remove] then
- source_uri = options[:remove]
+ def clear_all # :nodoc:
+ path = Gem.spec_cache_dir
+ FileUtils.rm_rf path
- unless Gem.sources.include? source_uri then
- say "source #{source_uri} not present in cache"
+ unless File.exist? path then
+ say "*** Removed specs cache ***"
+ else
+ unless File.writable? path then
+ say "*** Unable to remove source cache (write protected) ***"
else
- Gem.sources.delete source_uri
- Gem.configuration.write
-
- say "#{source_uri} removed from sources"
+ say "*** Unable to remove source cache ***"
end
+
+ terminate_interaction 1
end
+ end
- if options[:update] then
- Gem.sources.each_source do |src|
- src.load_specs :released
- src.load_specs :latest
- end
+ def defaults_str # :nodoc:
+ '--list'
+ end
+
+ def description # :nodoc:
+ <<-EOF
+RubyGems fetches gems from the sources you have configured (stored in your
+~/.gemrc).
+
+The default source is https://rubygems.org, but you may have older sources
+configured. This guide will help you update your sources or configure
+yourself to use your own gem server.
+
+Without any arguments the sources lists your currently configured sources:
+
+ $ gem sources
+ *** CURRENT SOURCES ***
+
+ https://rubygems.org
+
+This may list multiple sources or non-rubygems sources. You probably
+configured them before or have an old `~/.gemrc`. If you have sources you
+do not recognize you should remove them.
+
+RubyGems has been configured to serve gems via the following URLs through
+its history:
- say "source cache successfully updated"
+* http://gems.rubyforge.org (RubyGems 1.3.6 and earlier)
+* http://rubygems.org (RubyGems 1.3.7 through 1.8.25)
+* https://rubygems.org (RubyGems 2.0.1 and newer)
+
+Since all of these sources point to the same set of gems you only need one
+of them in your list. https://rubygems.org is recommended as it brings the
+protections of an SSL connection to gem downloads.
+
+To add a source use the --add argument:
+
+ $ gem sources --add https://rubygems.org
+ https://rubygems.org added to sources
+
+RubyGems will check to see if gems can be installed from the source given
+before it is added.
+
+To remove a source use the --remove argument:
+
+ $ gem sources --remove http://rubygems.org
+ http://rubygems.org removed from sources
+
+ EOF
+ end
+
+ def list # :nodoc:
+ say "*** CURRENT SOURCES ***"
+ say
+
+ Gem.sources.each do |src|
+ say src
end
+ end
- if options[:list] then
- say "*** CURRENT SOURCES ***"
- say
+ def list? # :nodoc:
+ !(options[:list] ||
+ options[:add] ||
+ options[:clear_all] ||
+ options[:remove] ||
+ options[:update])
+ end
- Gem.sources.each do |src|
- say src
- end
+ def execute
+ clear_all if options[:clear_all]
+
+ source_uri = options[:add]
+ add_source source_uri if source_uri
+
+ source_uri = options[:remove]
+ remove_source source_uri if source_uri
+
+ update if options[:update]
+
+ list if list?
+ end
+
+ def remove_source source_uri # :nodoc:
+ unless Gem.sources.include? source_uri then
+ say "source #{source_uri} not present in cache"
+ else
+ Gem.sources.delete source_uri
+ Gem.configuration.write
+
+ say "#{source_uri} removed from sources"
end
end
- private
+ def update # :nodoc:
+ Gem.sources.each_source do |src|
+ src.load_specs :released
+ src.load_specs :latest
+ end
+
+ say "source cache successfully updated"
+ end
- def remove_cache_file(desc, path)
+ def remove_cache_file desc, path # :nodoc:
FileUtils.rm_rf path
if not File.exist?(path) then
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index b40dfd5f3c..d96c8b8627 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -50,6 +50,22 @@ FIELD name of gemspec field to show
"--local --version '#{Gem::Requirement.default}' --yaml"
end
+ def description # :nodoc:
+ <<-EOF
+The specification command allows you to extract the specification from
+a gem for examination.
+
+The specification can be output in YAML, ruby or Marshal formats.
+
+Specific fields in the specification can be extracted in YAML format:
+
+ $ gem spec rake summary
+ --- Ruby based make-like utility.
+ ...
+
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} [GEMFILE] [FIELD]"
end
diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb
index 36c517e27c..0ef0755960 100644
--- a/lib/rubygems/commands/stale_command.rb
+++ b/lib/rubygems/commands/stale_command.rb
@@ -5,6 +5,16 @@ class Gem::Commands::StaleCommand < Gem::Command
super('stale', 'List gems along with access times')
end
+ def description # :nodoc:
+ <<-EOF
+The stale command lists the latest access time for all the files in your
+installed gems.
+
+You can use this command to discover gems and gem versions you are no
+longer using.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name}"
end
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index 56aa8ee57f..8e6af2ba65 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -1,6 +1,7 @@
require 'rubygems/command'
require 'rubygems/version_option'
require 'rubygems/uninstaller'
+require 'fileutils'
##
# Gem uninstaller command line tool
@@ -14,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
+ :install_dir => Gem.dir, :check_dev => false
add_option('-a', '--[no-]all',
'Uninstall all matching versions'
@@ -67,6 +68,12 @@ class Gem::Commands::UninstallCommand < Gem::Command
options[:force] = value
end
+ add_option('--[no-]abort-on-dependent',
+ 'Prevent uninstalling gems that are',
+ 'depended on by other gems.') do |value, options|
+ options[:abort_on_dependent] = value
+ end
+
add_version_option
add_platform_option
end
@@ -81,13 +88,49 @@ class Gem::Commands::UninstallCommand < Gem::Command
"--user-install"
end
+ def description # :nodoc:
+ <<-EOF
+The uninstall command removes a previously installed gem.
+
+RubyGems will ask for confirmation if you are attempting to uninstall a gem
+that is a dependency of an existing gem. You can use the
+--ignore-dependencies option to skip this check.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
def execute
- # REFACTOR: stolen from cleanup_command
+ if options[:all] and not options[:args].empty? then
+ alert_error 'Gem names and --all may not be used together'
+ terminate_interaction 1
+ elsif options[:all] then
+ uninstall_all
+ else
+ uninstall_specific
+ end
+ end
+
+ def uninstall_all
+ _, specs = Gem::Specification.partition { |spec| spec.default_gem? }
+
+ specs.each do |spec|
+ options[:version] = spec.version
+
+ begin
+ Gem::Uninstaller.new(spec.name, options).uninstall
+ rescue Gem::InstallError
+ end
+ end
+
+ alert "Uninstalled all gems in #{options[:install_dir]}"
+ end
+
+ def uninstall_specific
deplist = Gem::DependencyList.new
+
get_all_gem_names.uniq.each do |name|
Gem::Specification.find_all_by_name(name).each do |spec|
deplist.add spec
diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb
index 7eefd32a6e..e60e7d90fd 100644
--- a/lib/rubygems/commands/unpack_command.rb
+++ b/lib/rubygems/commands/unpack_command.rb
@@ -34,6 +34,24 @@ class Gem::Commands::UnpackCommand < Gem::Command
"--version '#{Gem::Requirement.default}'"
end
+ def description
+ <<-EOF
+The unpack command allows you to examine the contents of a gem or modify
+them to help diagnose a bug.
+
+You can add the contents of the unpacked gem to the load path using the
+RUBYLIB environment variable or -I:
+
+ $ gem unpack my_gem
+ Unpacked gem: '.../my_gem-1.0'
+ [edit my_gem-1.0/lib/my_gem.rb]
+ $ ruby -Imy_gem-1.0/lib -S other_program
+
+You can repackage an unpacked gem using the build command. See the build
+command help for an example.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME"
end
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index a31de0071a..77bf5edb45 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -52,27 +52,46 @@ class Gem::Commands::UpdateCommand < Gem::Command
"--document --no-force --install-dir #{Gem.dir}"
end
+ 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
+command to remove old versions.
+ EOF
+ end
+
def usage # :nodoc:
"#{program_name} GEMNAME [GEMNAME ...]"
end
+ def check_latest_rubygems version # :nodoc:
+ if Gem.rubygems_version == version then
+ say "Latest version currently installed. Aborting."
+ terminate_interaction
+ end
+
+ options[:user_install] = false
+ end
+
+ def check_update_arguments # :nodoc:
+ unless options[:args].empty? then
+ alert_error "Gem names are not allowed with the --system option"
+ terminate_interaction 1
+ end
+ end
+
def execute
hig = {}
if options[:system] then
update_rubygems
return
- else
- say "Updating installed gems"
+ end
- hig = {} # highest installed gems
+ say "Updating installed gems"
- Gem::Specification.each do |spec|
- if hig[spec.name].nil? or hig[spec.name].version < spec.version then
- hig[spec.name] = spec
- end
- end
- end
+ hig = highest_installed_gems
gems_to_update = which_to_update hig, options[:args].uniq
@@ -85,51 +104,65 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
end
- def update_gem name, version = Gem::Requirement.default
- return if @updated.any? { |spec| spec.name == name }
+ def fetch_remote_gems spec # :nodoc:
+ dependency = Gem::Dependency.new spec.name, "> #{spec.version}"
+ dependency.prerelease = options[:prerelease]
- @installer ||= Gem::DependencyInstaller.new options
+ fetcher = Gem::SpecFetcher.fetcher
- success = false
+ spec_tuples, _ = fetcher.search_for_dependency dependency
- say "Updating #{name}"
- begin
- @installer.install name, Gem::Requirement.new(version)
- success = true
- rescue Gem::InstallError => e
- alert_error "Error installing #{name}:\n\t#{e.message}"
- success = false
- end
+ spec_tuples
+ end
- @installer.installed_gems.each do |spec|
- @updated << spec
+ def highest_installed_gems # :nodoc:
+ hig = {} # highest installed gems
+
+ Gem::Specification.each do |spec|
+ if hig[spec.name].nil? or hig[spec.name].version < spec.version then
+ hig[spec.name] = spec
+ end
end
+
+ hig
end
- def update_gems gems_to_update
- gems_to_update.uniq.sort.each do |(name, version)|
- update_gem name, version
+ def highest_remote_version spec # :nodoc:
+ spec_tuples = fetch_remote_gems spec
+
+ matching_gems = spec_tuples.select do |g,_|
+ g.name == spec.name and g.match_platform?
end
- @updated
+ highest_remote_gem = matching_gems.sort_by { |g,_| g.version }.last
+
+ highest_remote_gem ||= [Gem::NameTuple.null]
+
+ highest_remote_gem.first.version
end
- ##
- # Update RubyGems software to the latest version.
+ def install_rubygems version # :nodoc:
+ args = update_rubygems_arguments
- def update_rubygems
- unless options[:args].empty? then
- alert_error "Gem names are not allowed with the --system option"
- terminate_interaction 1
- end
+ update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
- options[:user_install] = false
+ Dir.chdir update_dir do
+ say "Installing RubyGems #{version}"
- # TODO: rename version and other variable name conflicts
- # TODO: get rid of all this indirection on name and other BS
+ # Make sure old rubygems isn't loaded
+ old = ENV["RUBYOPT"]
+ ENV.delete("RUBYOPT") if old
+ installed = system Gem.ruby, 'setup.rb', *args
+ say "RubyGems system software updated" if installed
+ ENV["RUBYOPT"] = old if old
+ end
+ end
+ def rubygems_target_version
version = options[:system]
- if version == true then
+ update_latest = version == true
+
+ if update_latest then
version = Gem::Version.new Gem::VERSION
requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
else
@@ -146,46 +179,72 @@ class Gem::Commands::UpdateCommand < Gem::Command
}
gems_to_update = which_to_update hig, options[:args], :system
- name, up_ver = gems_to_update.first
- current_ver = Gem.rubygems_version
+ _, up_ver = gems_to_update.first
- target = if options[:system] == true then
+ target = if update_latest then
up_ver
else
version
end
- if current_ver == target then
- # if options[:system] != true and version == current_ver then
- say "Latest version currently installed. Aborting."
- terminate_interaction
+ return target, requirement
+ end
+
+ def update_gem name, version = Gem::Requirement.default
+ return if @updated.any? { |spec| spec.name == name }
+
+ @installer ||= Gem::DependencyInstaller.new options
+
+ success = false
+
+ say "Updating #{name}"
+ begin
+ @installer.install name, Gem::Requirement.new(version)
+ success = true
+ rescue Gem::InstallError => e
+ alert_error "Error installing #{name}:\n\t#{e.message}"
+ success = false
end
- update_gem name, target
+ @installer.installed_gems.each do |spec|
+ @updated << spec
+ end
+ end
+
+ def update_gems gems_to_update
+ gems_to_update.uniq.sort.each do |(name, version)|
+ update_gem name, version
+ end
+
+ @updated
+ end
+
+ ##
+ # Update RubyGems software to the latest version.
+
+ def update_rubygems
+ check_update_arguments
+
+ version, requirement = rubygems_target_version
+
+ check_latest_rubygems version
+
+ update_gem 'rubygems-update', version
installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
version = installed_gems.last.version
+ install_rubygems version
+ end
+
+ def update_rubygems_arguments # :nodoc:
args = []
args << '--prefix' << Gem.prefix if Gem.prefix
# TODO use --document for >= 1.9 , --no-rdoc --no-ri < 1.9
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]
-
- update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
-
- Dir.chdir update_dir do
- say "Installing RubyGems #{version}"
- setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}"
-
- # Make sure old rubygems isn't loaded
- old = ENV["RUBYOPT"]
- ENV.delete("RUBYOPT") if old
- installed = system setup_cmd
- say "RubyGems system software updated" if installed
- ENV["RUBYOPT"] = old if old
- end
+ args
end
def which_to_update highest_installed_gems, gem_names, system = false
@@ -195,21 +254,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
next if not gem_names.empty? and
gem_names.all? { |name| /#{name}/ !~ l_spec.name }
- dependency = Gem::Dependency.new l_spec.name, "> #{l_spec.version}"
- dependency.prerelease = options[:prerelease]
-
- fetcher = Gem::SpecFetcher.fetcher
-
- spec_tuples, _ = fetcher.search_for_dependency dependency
-
- matching_gems = spec_tuples.select do |g,_|
- g.name == l_name and g.match_platform?
- end
-
- highest_remote_gem = matching_gems.sort_by { |g,_| g.version }.last
-
- highest_remote_gem ||= [Gem::NameTuple.null]
- highest_remote_ver = highest_remote_gem.first.version
+ highest_remote_ver = highest_remote_version l_spec
if system or (l_spec.version < highest_remote_ver) then
result << [l_spec.name, [l_spec.version, highest_remote_ver].max]
diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb
index 6495278a87..99b9085b2b 100644
--- a/lib/rubygems/commands/which_command.rb
+++ b/lib/rubygems/commands/which_command.rb
@@ -23,6 +23,17 @@ class Gem::Commands::WhichCommand < Gem::Command
"--no-gems-first --no-all"
end
+ def description # :nodoc:
+ <<-EOF
+The which command is like the shell which command and shows you where
+the file you wish to require lives.
+
+You can use the which command to help determine why you are requiring a
+version you did not expect or to look at the content of a file you are
+requiring to see why it does not behave as you expect.
+ EOF
+ end
+
def execute
found = false
diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb
index df4142d395..2285bb4017 100644
--- a/lib/rubygems/commands/yank_command.rb
+++ b/lib/rubygems/commands/yank_command.rb
@@ -9,7 +9,21 @@ class Gem::Commands::YankCommand < Gem::Command
include Gem::GemcutterUtilities
def description # :nodoc:
- 'Remove a specific gem version release from RubyGems.org'
+ <<-EOF
+The yank command removes a gem you pushed to a server from the server's
+index.
+
+Note that if you push a gem to rubygems.org the yank command does not
+prevent other people from downloading the gem via the download link.
+
+Once you have pushed a gem several downloads will happen automatically
+via the webhooks. If you accidentally pushed passwords or other sensitive
+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
+as the reason for the removal request.
+ EOF
end
def arguments # :nodoc:
@@ -21,7 +35,7 @@ class Gem::Commands::YankCommand < Gem::Command
end
def initialize
- super 'yank', description
+ super 'yank', 'Remove a pushed gem from the index'
add_version_option("remove")
add_platform_option("remove")
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index 244e845e6f..d0699dcb29 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -141,6 +141,11 @@ class Gem::ConfigFile
attr_reader :ssl_ca_cert
##
+ # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication
+
+ attr_reader :ssl_client_cert
+
+ ##
# Create the config file object. +args+ is the list of arguments
# from the command line.
#
@@ -210,6 +215,7 @@ class Gem::ConfigFile
@ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
@ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
+ @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert
@api_keys = nil
@rubygems_api_key = nil
@@ -246,6 +252,10 @@ Your gem push credentials file located at:
has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required.
+To fix this error run:
+
+\tchmod 0600 #{credentials_path}
+
You should reset your credentials at:
\thttps://rubygems.org/profile/edit
@@ -309,6 +319,9 @@ if you believe they were disclosed to a third party.
@rubygems_api_key = api_key
end
+ YAMLErrors = [ArgumentError]
+ YAMLErrors << Psych::SyntaxError if defined?(Psych::SyntaxError)
+
def load_file(filename)
Gem.load_yaml
@@ -321,8 +334,8 @@ if you believe they were disclosed to a third party.
return {}
end
return content
- rescue ArgumentError
- warn "Failed to load #{filename}"
+ rescue *YAMLErrors => e
+ warn "Failed to load #{filename}, #{e.to_s}"
rescue Errno::EACCES
warn "Failed to load #{filename} due to permissions problem."
end
diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb
index f4f7fc8393..0416644920 100755
--- a/lib/rubygems/core_ext/kernel_require.rb
+++ b/lib/rubygems/core_ext/kernel_require.rb
@@ -48,7 +48,12 @@ module Kernel
# normal require handle loading a gem from the rescue below.
if Gem::Specification.unresolved_deps.empty? then
- return gem_original_require(path)
+ begin
+ RUBYGEMS_ACTIVATION_MONITOR.exit
+ return gem_original_require(path)
+ ensure
+ RUBYGEMS_ACTIVATION_MONITOR.enter
+ end
end
# If +path+ is for a gem that has already been loaded, don't
@@ -57,11 +62,16 @@ module Kernel
#--
# TODO request access to the C implementation of this to speed up RubyGems
- spec = Gem::Specification.find { |s|
+ spec = Gem::Specification.stubs.find { |s|
s.activated? and s.contains_requirable_file? path
}
- return gem_original_require(path) if spec
+ begin
+ RUBYGEMS_ACTIVATION_MONITOR.exit
+ return gem_original_require(path)
+ ensure
+ RUBYGEMS_ACTIVATION_MONITOR.enter
+ end if spec
# Attempt to find +path+ in any unresolved gems...
@@ -109,11 +119,21 @@ module Kernel
valid.activate
end
- gem_original_require path
+ begin
+ RUBYGEMS_ACTIVATION_MONITOR.exit
+ return gem_original_require(path)
+ ensure
+ RUBYGEMS_ACTIVATION_MONITOR.enter
+ end
rescue LoadError => load_error
if load_error.message.start_with?("Could not find") or
(load_error.message.end_with?(path) and Gem.try_activate(path)) then
- return gem_original_require(path)
+ begin
+ RUBYGEMS_ACTIVATION_MONITOR.exit
+ return gem_original_require(path)
+ ensure
+ RUBYGEMS_ACTIVATION_MONITOR.enter
+ end
end
raise load_error
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index cc8dc722fc..591580b7da 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -15,6 +15,14 @@ module Gem
end
##
+ # Default spec directory path to be used if an alternate value is not
+ # specified in the environment
+
+ def self.default_spec_cache_dir
+ File.join Gem.user_home, '.gem', 'specs'
+ end
+
+ ##
# Default home directory path to be used if an alternate value is not
# specified in the environment
@@ -126,4 +134,11 @@ module Gem
def self.default_cert_path
File.join Gem.user_home, ".gem", "gem-public_cert.pem"
end
+
+ ##
+ # Whether to expect full paths in default gems - true for non-MRI
+ # ruby implementations
+ def self.default_gems_use_full_paths?
+ ruby_engine != 'ruby'
+ end
end
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index 1e3cc168a8..a96d67c3e5 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -203,6 +203,8 @@ class Gem::Dependency
requirement.satisfied_by? version
end
+ alias === =~
+
# DOC: this method needs either documented or :nodoc'd
def match? obj, version=nil
@@ -250,10 +252,10 @@ class Gem::Dependency
# DOC: this method needs either documented or :nodoc'd
def matching_specs platform_only = false
- matches = Gem::Specification.find_all { |spec|
+ matches = Gem::Specification.stubs.find_all { |spec|
self.name === spec.name and # TODO: == instead of ===
requirement.satisfied_by? spec.version
- }
+ }.map(&:to_spec)
if platform_only
matches.reject! { |spec|
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index 6f19a310f7..e7c489a759 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,11 +1,11 @@
require 'rubygems'
require 'rubygems/dependency_list'
+require 'rubygems/dependency_resolver'
require 'rubygems/package'
require 'rubygems/installer'
require 'rubygems/spec_fetcher'
require 'rubygems/user_interaction'
-require 'rubygems/source_local'
-require 'rubygems/source_specific_file'
+require 'rubygems/source'
require 'rubygems/available_set'
##
@@ -15,15 +15,7 @@ class Gem::DependencyInstaller
include Gem::UserInteraction
- attr_reader :gems_to_install
- attr_reader :installed_gems
-
- ##
- # Documentation types. For use by the Gem.done_installing hook
-
- attr_reader :document
-
- DEFAULT_OPTIONS = {
+ DEFAULT_OPTIONS = { # :nodoc:
:env_shebang => false,
:document => %w[ri],
:domain => :both, # HACK dup
@@ -35,9 +27,31 @@ class Gem::DependencyInstaller
:wrappers => true,
:build_args => nil,
:build_docs_in_background => false,
+ :install_as_default => false
}.freeze
##
+ # Documentation types. For use by the Gem.done_installing hook
+
+ attr_reader :document
+
+ ##
+ # Errors from SpecFetcher while searching for remote specifications
+
+ attr_reader :errors
+
+ ##
+ #--
+ # TODO remove, no longer used
+
+ attr_reader :gems_to_install # :nodoc:
+
+ ##
+ # List of gems installed by #install in alphabetic order
+
+ attr_reader :installed_gems
+
+ ##
# Creates a new installer instance.
#
# Options are:
@@ -56,7 +70,8 @@ class Gem::DependencyInstaller
# :wrappers:: See Gem::Installer::new
# :build_args:: See Gem::Installer::new
- def initialize(options = {})
+ def initialize options = {}
+ @only_install_dir = !!options[:install_dir]
@install_dir = options[:install_dir] || Gem.dir
if options[:install_dir] then
@@ -82,6 +97,7 @@ class Gem::DependencyInstaller
@wrappers = options[:wrappers]
@build_args = options[:build_args]
@build_docs_in_background = options[:build_docs_in_background]
+ @install_as_default = options[:install_as_default]
# Indicates that we should not try to update any deps unless
# we absolutely must.
@@ -93,13 +109,61 @@ class Gem::DependencyInstaller
@cache_dir = options[:cache_dir] || @install_dir
- # Set with any errors that SpecFetcher finds while search through
- # gemspecs for a dep
@errors = nil
end
- attr_reader :errors
+ ##
+ #--
+ # TODO remove, no longer used
+
+ def add_found_dependencies to_do, dependency_list # :nodoc:
+ seen = {}
+ dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name }
+
+ until to_do.empty? do
+ spec = to_do.shift
+
+ # HACK why is spec nil?
+ next if spec.nil? or seen[spec.name]
+ seen[spec.name] = true
+
+ deps = spec.runtime_dependencies
+
+ if @development
+ if @dev_shallow
+ if @toplevel_specs.include? spec.full_name
+ deps |= spec.development_dependencies
+ end
+ else
+ deps |= spec.development_dependencies
+ end
+ end
+
+ deps.each do |dep|
+ dependencies[dep.name] = dependencies[dep.name].merge dep
+
+ if @minimal_deps
+ next if Gem::Specification.any? do |installed_spec|
+ dep.name == installed_spec.name and
+ dep.requirement.satisfied_by? installed_spec.version
+ end
+ end
+
+ results = find_gems_with_sources(dep)
+ results.sorted.each do |t|
+ to_do.push t.spec
+ end
+
+ results.remove_installed! dep
+
+ @available << results
+ results.inject_into_list dependency_list
+ end
+ end
+
+ dependency_list.remove_specs_unsatisfied_by dependencies
+ end
##
# Creates an AvailableSet to install from based on +dep_or_name+ and
# +version+
@@ -138,7 +202,7 @@ class Gem::DependencyInstaller
# sources. Gems are sorted with newer gems preferred over older gems, and
# local gems preferred over remote gems.
- def find_gems_with_sources(dep)
+ def find_gems_with_sources dep # :nodoc:
set = Gem::AvailableSet.new
if consider_local?
@@ -179,10 +243,50 @@ class Gem::DependencyInstaller
end
##
+ # Finds a spec and the source_uri it came from for gem +gem_name+ and
+ # +version+. Returns an Array of specs and sources required for
+ # installation of the gem.
+
+ def find_spec_by_name_and_version gem_name,
+ version = Gem::Requirement.default,
+ prerelease = false
+ set = Gem::AvailableSet.new
+
+ if consider_local?
+ if gem_name =~ /\.gem$/ and File.file? gem_name then
+ src = Gem::Source::SpecificFile.new(gem_name)
+ set.add src.spec, src
+ else
+ local = Gem::Source::Local.new
+
+ if s = local.find_gem(gem_name, version)
+ set.add s, local
+ end
+ end
+ end
+
+ if set.empty?
+ dep = Gem::Dependency.new gem_name, version
+ dep.prerelease = true if prerelease
+
+ set = find_gems_with_sources(dep)
+ set.match_platform!
+ end
+
+ if set.empty?
+ raise Gem::SpecificGemNotFoundException.new(gem_name, version, @errors)
+ end
+
+ @available = set
+ end
+
+ ##
# Gathers all dependencies necessary for the installation from local and
# remote sources unless the ignore_dependencies was given.
+ #--
+ # TODO remove, no longer used
- def gather_dependencies
+ def gather_dependencies # :nodoc:
specs = @available.all_specs
# these gems were listed by the user, always install them
@@ -214,93 +318,19 @@ class Gem::DependencyInstaller
@gems_to_install = dependency_list.dependency_order.reverse
end
- def add_found_dependencies to_do, dependency_list
- seen = {}
- dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name }
-
- until to_do.empty? do
- spec = to_do.shift
-
- # HACK why is spec nil?
- next if spec.nil? or seen[spec.name]
- seen[spec.name] = true
-
- deps = spec.runtime_dependencies
-
- if @development
- if @dev_shallow
- if @toplevel_specs.include? spec.full_name
- deps |= spec.development_dependencies
- end
- else
- deps |= spec.development_dependencies
- end
- end
-
- deps.each do |dep|
- dependencies[dep.name] = dependencies[dep.name].merge dep
-
- if @minimal_deps
- next if Gem::Specification.any? do |installed_spec|
- dep.name == installed_spec.name and
- dep.requirement.satisfied_by? installed_spec.version
- end
- end
-
- results = find_gems_with_sources(dep)
-
- results.sorted.each do |t|
- to_do.push t.spec
- end
-
- results.remove_installed! dep
-
- @available << results
- results.inject_into_list dependency_list
- end
- end
-
- dependency_list.remove_specs_unsatisfied_by dependencies
- end
-
- ##
- # Finds a spec and the source_uri it came from for gem +gem_name+ and
- # +version+. Returns an Array of specs and sources required for
- # installation of the gem.
-
- def find_spec_by_name_and_version(gem_name,
- version = Gem::Requirement.default,
- prerelease = false)
-
- set = Gem::AvailableSet.new
-
- if consider_local?
- if gem_name =~ /\.gem$/ and File.file? gem_name then
- src = Gem::Source::SpecificFile.new(gem_name)
- set.add src.spec, src
- else
- local = Gem::Source::Local.new
-
- if s = local.find_gem(gem_name, version)
- set.add s, local
+ def in_background what # :nodoc:
+ fork_happened = false
+ if @build_docs_in_background and Process.respond_to?(:fork)
+ begin
+ Process.fork do
+ yield
end
+ fork_happened = true
+ say "#{what} in a background process."
+ rescue NotImplementedError
end
end
-
- if set.empty?
- dep = Gem::Dependency.new gem_name, version
- # HACK Dependency objects should be immutable
- dep.prerelease = true if prerelease
-
- set = find_gems_with_sources(dep)
- set.match_platform!
- end
-
- if set.empty?
- raise Gem::SpecificGemNotFoundException.new(gem_name, version, @errors)
- end
-
- @available = set
+ yield unless fork_happened
end
##
@@ -318,61 +348,30 @@ class Gem::DependencyInstaller
# separately.
def install dep_or_name, version = Gem::Requirement.default
- available_set_for dep_or_name, version
+ request_set = resolve_dependencies dep_or_name, version
@installed_gems = []
- gather_dependencies
-
- # REFACTOR is the last gem always the one that the user requested?
- # This code assumes that but is that actually validated by the code?
-
- last = @gems_to_install.size - 1
- @gems_to_install.each_with_index do |spec, index|
- # REFACTOR more current spec set hardcoding, should be abstracted?
- next if Gem::Specification.include?(spec) and index != last
-
- # TODO: make this sorta_verbose so other users can benefit from it
- say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose
-
- source = @available.source_for spec
-
- begin
- # REFACTOR make the fetcher to use configurable
- local_gem_path = source.download spec, @cache_dir
- rescue Gem::RemoteFetcher::FetchError
- # TODO I doubt all fetch errors are recoverable, we should at least
- # report the errors probably.
- next if @force
- raise
- end
-
- if @development
- if @dev_shallow
- is_dev = @toplevel_specs.include? spec.full_name
- else
- is_dev = true
- end
- end
+ options = {
+ :bin_dir => @bin_dir,
+ :build_args => @build_args,
+ :env_shebang => @env_shebang,
+ :force => @force,
+ :format_executable => @format_executable,
+ :ignore_dependencies => @ignore_dependencies,
+ :security_policy => @security_policy,
+ :user_install => @user_install,
+ :wrappers => @wrappers,
+ :install_as_default => @install_as_default
+ }
+ options[:install_dir] = @install_dir if @only_install_dir
- inst = Gem::Installer.new local_gem_path,
- :bin_dir => @bin_dir,
- :development => is_dev,
- :env_shebang => @env_shebang,
- :force => @force,
- :format_executable => @format_executable,
- :ignore_dependencies => @ignore_dependencies,
- :install_dir => @install_dir,
- :security_policy => @security_policy,
- :user_install => @user_install,
- :wrappers => @wrappers,
- :build_args => @build_args
-
- spec = inst.install
-
- @installed_gems << spec
+ request_set.install options do |_, installer|
+ @installed_gems << installer.spec if installer
end
+ @installed_gems.sort!
+
# Since this is currently only called for docs, we can be lazy and just say
# it's documentation. Ideally the hook adder could decide whether to be in
# the background or not, and what to call it.
@@ -385,18 +384,34 @@ class Gem::DependencyInstaller
@installed_gems
end
- def in_background what
- fork_happened = false
- if @build_docs_in_background and Process.respond_to?(:fork)
- begin
- Process.fork do
- yield
- end
- fork_happened = true
- say "#{what} in a background process."
- rescue NotImplementedError
- end
+ def install_development_deps # :nodoc:
+ if @development and @dev_shallow then
+ :shallow
+ elsif @development then
+ :all
+ else
+ :none
end
- yield unless fork_happened
end
+
+ def resolve_dependencies dep_or_name, version # :nodoc:
+ as = available_set_for dep_or_name, version
+
+ request_set = as.to_request_set install_development_deps
+ request_set.soft_missing = @force
+
+ installer_set = Gem::DependencyResolver::InstallerSet.new @domain
+ installer_set.always_install.concat request_set.always_install
+ installer_set.ignore_installed = @only_install_dir
+
+ if @ignore_dependencies then
+ installer_set.ignore_dependencies = true
+ request_set.soft_missing = true
+ end
+
+ request_set.resolve Gem::DependencyResolver.compose_sets(as, installer_set)
+
+ request_set
+ end
+
end
diff --git a/lib/rubygems/dependency_resolver.rb b/lib/rubygems/dependency_resolver.rb
index 66f55eb9ad..abce692920 100644
--- a/lib/rubygems/dependency_resolver.rb
+++ b/lib/rubygems/dependency_resolver.rb
@@ -1,575 +1,254 @@
require 'rubygems'
require 'rubygems/dependency'
require 'rubygems/exceptions'
+require 'rubygems/util/list'
require 'uri'
require 'net/http'
-module Gem
+##
+# Given a set of Gem::Dependency objects as +needed+ and a way to query the
+# set of available specs via +set+, calculates a set of ActivationRequest
+# objects which indicate all the specs that should be activated to meet the
+# all the requirements.
- # Raised when a DependencyConflict reaches the toplevel.
- # Indicates which dependencies were incompatible.
- #
- class DependencyResolutionError < Gem::Exception
- def initialize(conflict)
- @conflict = conflict
- a, b = conflicting_dependencies
+class Gem::DependencyResolver
- super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
- end
+ ##
+ # Contains all the conflicts encountered while doing resolution
- attr_reader :conflict
+ attr_reader :conflicts
- def conflicting_dependencies
- @conflict.conflicting_dependencies
- end
- end
+ attr_accessor :development
- # Raised when a dependency requests a gem for which there is
- # no spec.
- #
- class UnsatisfiableDepedencyError < Gem::Exception
- def initialize(dep)
- super "unable to find any gem matching dependency '#{dep}'"
+ attr_reader :missing
- @dependency = dep
- end
+ ##
+ # When a missing dependency, don't stop. Just go on and record what was
+ # missing.
- attr_reader :dependency
- end
+ attr_accessor :soft_missing
- # Raised when dependencies conflict and create the inability to
- # find a valid possible spec for a request.
- #
- class ImpossibleDependenciesError < Gem::Exception
- def initialize(request, conflicts)
- s = conflicts.size == 1 ? "" : "s"
- super "detected #{conflicts.size} conflict#{s} with dependency '#{request.dependency}'"
- @request = request
- @conflicts = conflicts
- end
+ def self.compose_sets *sets
+ Gem::DependencyResolver::ComposedSet.new(*sets)
+ end
- def dependency
- @request.dependency
- end
+ ##
+ # Provide a DependencyResolver that queries only against the already
+ # installed gems.
- attr_reader :conflicts
+ def self.for_current_gems needed
+ new needed, Gem::DependencyResolver::CurrentSet.new
end
- # Given a set of Gem::Dependency objects as +needed+ and a way
- # to query the set of available specs via +set+, calculates
- # a set of ActivationRequest objects which indicate all the specs
- # that should be activated to meet the all the requirements.
+ ##
+ # Create DependencyResolver object which will resolve the tree starting
+ # with +needed+ Depedency objects.
#
- class DependencyResolver
-
- # Represents a specification retrieved via the rubygems.org
- # API. This is used to avoid having to load the full
- # Specification object when all we need is the name, version,
- # and dependencies.
- #
- class APISpecification
- attr_reader :set # :nodoc:
-
- def initialize(set, api_data)
- @set = set
- @name = api_data[:name]
- @version = Gem::Version.new api_data[:number]
- @dependencies = api_data[:dependencies].map do |name, ver|
- Gem::Dependency.new name, ver.split(/\s*,\s*/)
- end
- end
-
- attr_reader :name, :version, :dependencies
-
- def == other # :nodoc:
- self.class === other and
- @set == other.set and
- @name == other.name and
- @version == other.version and
- @dependencies == other.dependencies
- end
-
- def full_name
- "#{@name}-#{@version}"
- end
- end
-
- # The global rubygems pool, available via the rubygems.org API.
- # Returns instances of APISpecification.
- #
- class APISet
- def initialize
- @data = Hash.new { |h,k| h[k] = [] }
- @dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
- end
-
- # Return data for all versions of the gem +name+.
- #
- def versions(name)
- if @data.key?(name)
- return @data[name]
- end
-
- uri = @dep_uri + "?gems=#{name}"
- str = Gem::RemoteFetcher.fetcher.fetch_path uri
-
- Marshal.load(str).each do |ver|
- @data[ver[:name]] << ver
- end
-
- @data[name]
- end
-
- # Return an array of APISpecification objects matching
- # DependencyRequest +req+.
- #
- def find_all(req)
- res = []
-
- versions(req.name).each do |ver|
- if req.dependency.match? req.name, ver[:number]
- res << APISpecification.new(self, ver)
- end
- end
-
- res
- end
-
- # A hint run by the resolver to allow the Set to fetch
- # data for DependencyRequests +reqs+.
- #
- def prefetch(reqs)
- names = reqs.map { |r| r.dependency.name }
- needed = names.find_all { |d| !@data.key?(d) }
-
- return if needed.empty?
-
- uri = @dep_uri + "?gems=#{needed.sort.join ','}"
- str = Gem::RemoteFetcher.fetcher.fetch_path uri
-
- Marshal.load(str).each do |ver|
- @data[ver[:name]] << ver
- end
- end
- end
-
- # Represents a possible Specification object returned
- # from IndexSet. Used to delay needed to download full
- # Specification objects when only the +name+ and +version+
- # are needed.
- #
- class IndexSpecification
- def initialize(set, name, version, source, plat)
- @set = set
- @name = name
- @version = version
- @source = source
- @platform = plat
-
- @spec = nil
- end
-
- attr_reader :name, :version, :source
-
- def full_name
- "#{@name}-#{@version}"
- end
-
- def spec
- @spec ||= @set.load_spec(@name, @version, @source)
- end
-
- def dependencies
- spec.dependencies
- end
- end
-
- # The global rubygems pool represented via the traditional
- # source index.
- #
- class IndexSet
- def initialize
- @f = Gem::SpecFetcher.fetcher
-
- @all = Hash.new { |h,k| h[k] = [] }
-
- list, _ = @f.available_specs(:released)
- list.each do |uri, specs|
- specs.each do |n|
- @all[n.name] << [uri, n]
- end
- end
-
- @specs = {}
- end
-
- # Return an array of IndexSpecification objects matching
- # DependencyRequest +req+.
- #
- def find_all(req)
- res = []
-
- name = req.dependency.name
-
- @all[name].each do |uri, n|
- if req.dependency.match? n
- res << IndexSpecification.new(self, n.name, n.version,
- uri, n.platform)
- end
- end
-
- res
- end
-
- # No prefetching needed since we load the whole index in
- # initially.
- #
- def prefetch(gems)
- end
-
- # Called from IndexSpecification to get a true Specification
- # object.
- #
- def load_spec(name, ver, source)
- key = "#{name}-#{ver}"
- @specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver))
- end
- end
-
- # A set which represents the installed gems. Respects
- # all the normal settings that control where to look
- # for installed gems.
- #
- class CurrentSet
- def find_all(req)
- req.dependency.matching_specs
- end
-
- def prefetch(gems)
- end
- end
+ # +set+ is an object that provides where to look for specifications to
+ # satisify the Dependencies. This defaults to IndexSet, which will query
+ # rubygems.org.
+
+ def initialize needed, set = nil
+ @set = set || Gem::DependencyResolver::IndexSet.new
+ @needed = needed
+
+ @conflicts = nil
+ @development = false
+ @missing = []
+ @soft_missing = false
+ end
- # Create DependencyResolver object which will resolve
- # the tree starting with +needed+ Depedency objects.
- #
- # +set+ is an object that provides where to look for
- # specifications to satisify the Dependencies. This
- # defaults to IndexSet, which will query rubygems.org.
- #
- def initialize(needed, set=IndexSet.new)
- @set = set || IndexSet.new # Allow nil to mean IndexSet
- @needed = needed
-
- @conflicts = nil
+ def requests s, act, reqs=nil
+ s.dependencies.reverse_each do |d|
+ next if d.type == :development and not @development
+ reqs = Gem::List.new Gem::DependencyResolver::DependencyRequest.new(d, act), reqs
end
- # Provide a DependencyResolver that queries only against
- # the already installed gems.
- #
- def self.for_current_gems(needed)
- new needed, CurrentSet.new
- end
+ @set.prefetch reqs
- # Contains all the conflicts encountered while doing resolution
- #
- attr_reader :conflicts
+ reqs
+ end
- # Proceed with resolution! Returns an array of ActivationRequest
- # objects.
- #
- def resolve
- @conflicts = []
+ ##
+ # Proceed with resolution! Returns an array of ActivationRequest objects.
- needed = @needed.map { |n| DependencyRequest.new(n, nil) }
+ def resolve
+ @conflicts = []
- res = resolve_for needed, []
+ needed = nil
- if res.kind_of? DependencyConflict
- raise DependencyResolutionError.new(res)
- end
+ @needed.reverse_each do |n|
+ request = Gem::DependencyResolver::DependencyRequest.new n, nil
- res
+ needed = Gem::List.new request, needed
end
- # Used internally to indicate that a dependency conflicted
- # with a spec that would be activated.
- #
- class DependencyConflict
- def initialize(dependency, activated, failed_dep=dependency)
- @dependency = dependency
- @activated = activated
- @failed_dep = failed_dep
- end
-
- attr_reader :dependency, :activated
+ res = resolve_for needed, nil
- # Return the Specification that listed the dependency
- #
- def requester
- @failed_dep.requester
- end
-
- def for_spec?(spec)
- @dependency.name == spec.name
- end
+ raise Gem::DependencyResolutionError, res if
+ res.kind_of? Gem::DependencyResolver::DependencyConflict
- # Return the 2 dependency objects that conflicted
- #
- def conflicting_dependencies
- [@failed_dep.dependency, @activated.request.dependency]
- end
- end
-
- # Used Internally. Wraps a Depedency object to also track
- # which spec contained the Dependency.
- #
- class DependencyRequest
- def initialize(dep, act)
- @dependency = dep
- @requester = act
- end
-
- attr_reader :dependency, :requester
-
- def name
- @dependency.name
- end
-
- def matches_spec?(spec)
- @dependency.matches_spec? spec
- end
-
- def to_s
- @dependency.to_s
- end
+ res.to_a
+ end
- def ==(other)
- case other
- when Dependency
- @dependency == other
- when DependencyRequest
- @dependency == other.dependency && @requester == other.requester
+ ##
+ # The meat of the algorithm. Given +needed+ DependencyRequest objects and
+ # +specs+ being a list to ActivationRequest, calculate a new list of
+ # ActivationRequest objects.
+
+ def resolve_for needed, specs
+ while needed
+ dep = needed.value
+ needed = needed.tail
+
+ # If there is already a spec activated for the requested name...
+ if specs && existing = specs.find { |s| dep.name == s.name }
+
+ # then we're done since this new dep matches the
+ # existing spec.
+ next if dep.matches_spec? existing
+
+ # There is a conflict! We return the conflict
+ # object which will be seen by the caller and be
+ # handled at the right level.
+
+ # If the existing activation indicates that there
+ # are other possibles for it, then issue the conflict
+ # on the dep for the activation itself. Otherwise, issue
+ # it on the requester's request itself.
+ #
+ if existing.others_possible?
+ conflict =
+ Gem::DependencyResolver::DependencyConflict.new dep, existing
else
- false
+ depreq = existing.request.requester.request
+ conflict =
+ Gem::DependencyResolver::DependencyConflict.new depreq, existing, dep
end
- end
- end
-
- # Specifies a Specification object that should be activated.
- # Also contains a dependency that was used to introduce this
- # activation.
- #
- class ActivationRequest
- def initialize(spec, req, others_possible=true)
- @spec = spec
- @request = req
- @others_possible = others_possible
- end
-
- attr_reader :spec, :request
-
- # Indicate if this activation is one of a set of possible
- # requests for the same Dependency request.
- #
- def others_possible?
- @others_possible
- end
-
- # Return the ActivationRequest that contained the dependency
- # that we were activated for.
- #
- def parent
- @request.requester
- end
+ @conflicts << conflict
- def name
- @spec.name
+ return conflict
end
- def full_name
- @spec.full_name
- end
-
- def version
- @spec.version
- end
-
- def full_spec
- Gem::Specification === @spec ? @spec : @spec.spec
- end
-
- def download(path)
- if @spec.respond_to? :source
- source = @spec.source
- else
- source = Gem.sources.first
- end
-
- Gem.ensure_gem_subdirectories path
+ # Get a list of all specs that satisfy dep and platform
+ possible = @set.find_all dep
+ possible = select_local_platforms possible
- source.download full_spec, path
- end
+ case possible.size
+ when 0
+ @missing << dep
- def ==(other)
- case other
- when Gem::Specification
- @spec == other
- when ActivationRequest
- @spec == other.spec && @request == other.request
- else
- false
+ unless @soft_missing
+ # If there are none, then our work here is done.
+ raise Gem::UnsatisfiableDependencyError, dep
end
- end
-
- ##
- # Indicates if the requested gem has already been installed.
-
- def installed?
- this_spec = full_spec
-
- Gem::Specification.any? do |s|
- s == this_spec
+ when 1
+ # If there is one, then we just add it to specs
+ # and process the specs dependencies by adding
+ # them to needed.
+
+ spec = possible.first
+ act = Gem::DependencyResolver::ActivationRequest.new spec, dep, false
+
+ specs = Gem::List.prepend specs, act
+
+ # Put the deps for at the beginning of needed
+ # rather than the end to match the depth first
+ # searching done by the multiple case code below.
+ #
+ # This keeps the error messages consistent.
+ needed = requests(spec, act, needed)
+ else
+ # There are multiple specs for this dep. This is
+ # the case that this class is built to handle.
+
+ # Sort them so that we try the highest versions
+ # first.
+ possible = possible.sort_by do |s|
+ [s.source, s.version, s.platform == Gem::Platform::RUBY ? -1 : 1]
end
- end
- end
- def requests(s, act)
- reqs = []
- s.dependencies.each do |d|
- next unless d.type == :runtime
- reqs << DependencyRequest.new(d, act)
- end
-
- @set.prefetch(reqs)
-
- reqs
- end
-
- # The meat of the algorithm. Given +needed+ DependencyRequest objects
- # and +specs+ being a list to ActivationRequest, calculate a new list
- # of ActivationRequest objects.
- #
- def resolve_for(needed, specs)
- until needed.empty?
- dep = needed.shift
-
- # If there is already a spec activated for the requested name...
- if existing = specs.find { |s| dep.name == s.name }
-
- # then we're done since this new dep matches the
- # existing spec.
- next if dep.matches_spec? existing
-
- # There is a conflict! We return the conflict
- # object which will be seen by the caller and be
- # handled at the right level.
-
- # If the existing activation indicates that there
- # are other possibles for it, then issue the conflict
- # on the dep for the activation itself. Otherwise, issue
- # it on the requester's request itself.
- #
- if existing.others_possible?
- conflict = DependencyConflict.new(dep, existing)
+ # We track the conflicts seen so that we can report them
+ # to help the user figure out how to fix the situation.
+ conflicts = []
+
+ # To figure out which to pick, we keep resolving
+ # given each one being activated and if there isn't
+ # a conflict, we know we've found a full set.
+ #
+ # We use an until loop rather than #reverse_each
+ # to keep the stack short since we're using a recursive
+ # algorithm.
+ #
+ until possible.empty?
+ s = possible.pop
+
+ # Recursively call #resolve_for with this spec
+ # and add it's dependencies into the picture...
+
+ act = Gem::DependencyResolver::ActivationRequest.new s, dep
+
+ try = requests(s, act, needed)
+
+ res = resolve_for try, Gem::List.prepend(specs, act)
+
+ # While trying to resolve these dependencies, there may
+ # be a conflict!
+
+ if res.kind_of? Gem::DependencyResolver::DependencyConflict
+ # The conflict might be created not by this invocation
+ # but rather one up the stack, so if we can't attempt
+ # to resolve this conflict (conflict isn't with the spec +s+)
+ # then just return it so the caller can try to sort it out.
+ return res unless res.for_spec? s
+
+ # Otherwise, this is a conflict that we can attempt to fix
+ conflicts << [s, res]
+
+ # Optimization:
+ #
+ # Because the conflict indicates the dependency that trigger
+ # it, we can prune possible based on this new information.
+ #
+ # This cuts down on the number of iterations needed.
+ possible.delete_if { |x| !res.dependency.matches_spec? x }
else
- depreq = existing.request.requester.request
- conflict = DependencyConflict.new(depreq, existing, dep)
+ # No conflict, return the specs
+ return res
end
- @conflicts << conflict
-
- return conflict
end
- # Get a list of all specs that satisfy dep
- possible = @set.find_all(dep)
+ # We tried all possibles and nothing worked, so we let the user
+ # know and include as much information about the problem since
+ # the user is going to have to take action to fix this.
+ raise Gem::ImpossibleDependenciesError.new(dep, conflicts)
+ end
+ end
- case possible.size
- when 0
- # If there are none, then our work here is done.
- raise UnsatisfiableDepedencyError.new(dep)
- when 1
- # If there is one, then we just add it to specs
- # and process the specs dependencies by adding
- # them to needed.
-
- spec = possible.first
- act = ActivationRequest.new(spec, dep, false)
-
- specs << act
-
- # Put the deps for at the beginning of needed
- # rather than the end to match the depth first
- # searching done by the multiple case code below.
- #
- # This keeps the error messages consistent.
- needed = requests(spec, act) + needed
- else
- # There are multiple specs for this dep. This is
- # the case that this class is built to handle.
-
- # Sort them so that we try the highest versions
- # first.
- possible = possible.sort_by { |s| s.version }
-
- # We track the conflicts seen so that we can report them
- # to help the user figure out how to fix the situation.
- conflicts = []
-
- # To figure out which to pick, we keep resolving
- # given each one being activated and if there isn't
- # a conflict, we know we've found a full set.
- #
- # We use an until loop rather than #reverse_each
- # to keep the stack short since we're using a recursive
- # algorithm.
- #
- until possible.empty?
- s = possible.pop
-
- # Recursively call #resolve_for with this spec
- # and add it's dependencies into the picture...
-
- act = ActivationRequest.new(s, dep)
-
- try = requests(s, act) + needed
-
- res = resolve_for(try, specs + [act])
-
- # While trying to resolve these dependencies, there may
- # be a conflict!
-
- if res.kind_of? DependencyConflict
- # The conflict might be created not by this invocation
- # but rather one up the stack, so if we can't attempt
- # to resolve this conflict (conflict isn't with the spec +s+)
- # then just return it so the caller can try to sort it out.
- return res unless res.for_spec? s
-
- # Otherwise, this is a conflict that we can attempt to fix
- conflicts << [s, res]
-
- # Optimization:
- #
- # Because the conflict indicates the dependency that trigger
- # it, we can prune possible based on this new information.
- #
- # This cuts down on the number of iterations needed.
- possible.delete_if { |x| !res.dependency.matches_spec? x }
- else
- # No conflict, return the specs
- return res
- end
- end
+ specs
+ end
- # We tried all possibles and nothing worked, so we let the user
- # know and include as much information about the problem since
- # the user is going to have to take action to fix this.
- raise ImpossibleDependenciesError.new(dep, conflicts)
- end
- end
+ ##
+ # Returns the gems in +specs+ that match the local platform.
- specs
+ def select_local_platforms specs # :nodoc:
+ specs.select do |spec|
+ Gem::Platform.match spec.platform
end
end
+
end
+
+require 'rubygems/dependency_resolver/api_set'
+require 'rubygems/dependency_resolver/api_specification'
+require 'rubygems/dependency_resolver/activation_request'
+require 'rubygems/dependency_resolver/composed_set'
+require 'rubygems/dependency_resolver/current_set'
+require 'rubygems/dependency_resolver/dependency_conflict'
+require 'rubygems/dependency_resolver/dependency_request'
+require 'rubygems/dependency_resolver/index_set'
+require 'rubygems/dependency_resolver/index_specification'
+require 'rubygems/dependency_resolver/installed_specification'
+require 'rubygems/dependency_resolver/installer_set'
+
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index ff389b320b..4a988f9edf 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -7,7 +7,13 @@
# Base exception class for RubyGems. All exception raised by RubyGems are a
# subclass of this one.
class Gem::Exception < RuntimeError
- attr_accessor :source_exception
+
+ ##
+ #--
+ # TODO: remove in RubyGems 3, nobody sets this
+
+ attr_accessor :source_exception # :nodoc:
+
end
class Gem::CommandLineError < Gem::Exception; end
@@ -17,6 +23,28 @@ class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end
##
+# Raised by Gem::DependencyResolver when a Gem::DependencyConflict reaches the
+# toplevel. Indicates which dependencies were incompatible through #conflict
+# and #conflicting_dependencies
+
+class Gem::DependencyResolutionError < Gem::Exception
+
+ attr_reader :conflict
+
+ def initialize conflict
+ @conflict = conflict
+ a, b = conflicting_dependencies
+
+ super "unable to resolve conflicting dependencies '#{a}' and '#{b}'"
+ end
+
+ def conflicting_dependencies
+ @conflict.conflicting_dependencies
+ end
+
+end
+
+##
# Raised when attempting to uninstall a gem that isn't in GEM_HOME.
class Gem::GemNotInHomeException < Gem::Exception
@@ -65,6 +93,42 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException
attr_reader :name, :version, :errors
end
+##
+# Raised by Gem::DependencyResolver when dependencies conflict and create the
+# inability to find a valid possible spec for a request.
+
+class Gem::ImpossibleDependenciesError < Gem::Exception
+
+ attr_reader :conflicts
+ attr_reader :request
+
+ def initialize request, conflicts
+ @request = request
+ @conflicts = conflicts
+
+ super build_message
+ end
+
+ def build_message # :nodoc:
+ requester = @request.requester
+ requester = requester ? requester.spec.full_name : 'The user'
+ dependency = @request.dependency
+
+ message = "#{requester} requires #{dependency} but it conflicted:\n"
+
+ @conflicts.each do |_, conflict|
+ message << conflict.explanation
+ end
+
+ message
+ end
+
+ def dependency
+ @request.dependency
+ end
+
+end
+
class Gem::InstallError < Gem::Exception; end
##
@@ -107,3 +171,26 @@ class Gem::SystemExitException < SystemExit
end
+##
+# Raised by DependencyResolver when a dependency requests a gem for which
+# there is no spec.
+
+class Gem::UnsatisfiableDependencyError < Gem::Exception
+
+ attr_reader :dependency
+
+ def initialize dep
+ requester = dep.requester ? dep.requester.request : '(unknown)'
+
+ super "Unable to resolve dependency: #{requester} requires #{dep}"
+
+ @dependency = dep
+ end
+
+end
+
+##
+# Backwards compatible typo'd exception class for early RubyGems 2.0.x
+
+Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc:
+
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 79cae906ee..8c05723573 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -4,8 +4,23 @@
# See LICENSE.txt for permissions.
#++
+require 'rubygems/user_interaction'
+require 'thread'
+
class Gem::Ext::Builder
+ include Gem::UserInteraction
+
+ ##
+ # The builder shells-out to run various commands after changing the
+ # directory. This means multiple installations cannot be allowed to build
+ # extensions in parallel as they may change each other's directories leading
+ # to broken extensions or failed installations.
+
+ CHDIR_MUTEX = Mutex.new # :nodoc:
+
+ attr_accessor :build_args # :nodoc:
+
def self.class_name
name =~ /Ext::(.*)Builder/
$1.downcase
@@ -18,7 +33,7 @@ class Gem::Ext::Builder
# try to find make program from Ruby configure arguments first
RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
- make_program = $1 || ENV['MAKE'] || ENV['make']
+ make_program = ENV['MAKE'] || ENV['make'] || $1
unless make_program then
make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end
@@ -63,5 +78,108 @@ class Gem::Ext::Builder
end
end
+ ##
+ # Creates a new extension builder for +spec+ using the given +build_args+.
+ # The gem for +spec+ is unpacked in +gem_dir+.
+
+ def initialize spec, build_args
+ @spec = spec
+ @build_args = build_args
+ @gem_dir = spec.gem_dir
+
+ @ran_rake = nil
+ end
+
+ ##
+ # Chooses the extension builder class for +extension+
+
+ def builder_for extension # :nodoc:
+ case extension
+ when /extconf/ then
+ Gem::Ext::ExtConfBuilder
+ when /configure/ then
+ Gem::Ext::ConfigureBuilder
+ when /rakefile/i, /mkrf_conf/i then
+ @ran_rake = true
+ Gem::Ext::RakeBuilder
+ when /CMakeLists.txt/ then
+ Gem::Ext::CmakeBuilder
+ else
+ extension_dir = File.join @gem_dir, File.dirname(extension)
+
+ message = "No builder for extension '#{extension}'"
+ build_error extension_dir, message
+ end
+ end
+
+ ##
+ # Logs the build +output+ in +build_dir+, then raises ExtensionBuildError.
+
+ 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
+
+ message = <<-EOF
+ERROR: Failed to build gem native extension.
+
+ #{output}
+
+Gem files will remain installed in #{@gem_dir} for inspection.
+Results logged to #{gem_make_out}
+EOF
+
+ raise Gem::Installer::ExtensionBuildError, 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)
+
+ builder = builder_for extension
+
+ begin
+ FileUtils.mkdir_p dest_path
+
+ CHDIR_MUTEX.synchronize do
+ Dir.chdir extension_dir do
+ results = builder.build(extension, @gem_dir, dest_path,
+ results, @build_args)
+
+ say results.join("\n") if Gem.configuration.really_verbose
+ end
+ end
+ rescue
+ build_error extension_dir, results.join("\n"), $@
+ end
+ end
+
+ ##
+ # Builds extensions. Valid types of extensions are extconf.rb files,
+ # configure scripts and rakefiles or mkrf_conf files.
+
+ def build_extensions
+ return if @spec.extensions.empty?
+
+ if @build_args.empty?
+ say "Building native extensions. This could take a while..."
+ else
+ say "Building native extensions with: '#{@build_args.join ' '}'"
+ say "This could take a while..."
+ end
+
+ dest_path = File.join @gem_dir, @spec.require_paths.first
+
+ @ran_rake = false # only run rake once
+
+ @spec.extensions.each do |extension|
+ break if @ran_rake
+
+ build_extension extension, dest_path
+ end
+ end
+
end
diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb
index 8060e15312..7a3fd6b116 100644
--- a/lib/rubygems/gem_runner.rb
+++ b/lib/rubygems/gem_runner.rb
@@ -33,17 +33,11 @@ class Gem::GemRunner
##
# Run the gem command with the following arguments.
- def run(args)
- if args.include?('--')
- # We need to preserve the original ARGV to use for passing gem options
- # to source gems. If there is a -- in the line, strip all options after
- # it...its for the source building process.
- # TODO use slice!
- build_args = args[args.index("--") + 1...args.length]
- args = args[0...args.index("--")]
- end
+ def run args
+ build_args = extract_build_args args
do_configuration args
+
cmd = @command_manager_class.instance
cmd.command_names.each do |command_name|
@@ -60,6 +54,20 @@ class Gem::GemRunner
cmd.run Gem.configuration.args, build_args
end
+ ##
+ # Separates the build arguments (those following <code>--</code>) from the
+ # other arguments in the list.
+
+ def extract_build_args args # :nodoc:
+ return [] unless offset = args.index('--')
+
+ build_args = args.slice!(offset...args.length)
+
+ build_args.shift
+
+ build_args
+ end
+
private
def do_configuration(args)
diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb
index 04d7cd300f..9dbc18ba98 100644
--- a/lib/rubygems/gemcutter_utilities.rb
+++ b/lib/rubygems/gemcutter_utilities.rb
@@ -1,11 +1,17 @@
require 'rubygems/remote_fetcher'
+##
+# Utility methods for using the RubyGems API.
+
module Gem::GemcutterUtilities
+
# TODO: move to Gem::Command
OptionParser.accept Symbol do |value|
value.to_sym
end
+ attr_writer :host
+
##
# Add the --key option
@@ -17,6 +23,9 @@ module Gem::GemcutterUtilities
end
end
+ ##
+ # The API key from the command options or from the user's configuration.
+
def api_key
if options[:key] then
verify_api_key options[:key]
@@ -27,35 +36,10 @@ module Gem::GemcutterUtilities
end
end
- def sign_in sign_in_host = self.host
- return if Gem.configuration.rubygems_api_key
-
- pretty_host = if Gem::DEFAULT_HOST == sign_in_host then
- 'RubyGems.org'
- else
- sign_in_host
- end
-
- say "Enter your #{pretty_host} credentials."
- say "Don't have an account yet? " +
- "Create one at #{sign_in_host}/sign_up"
-
- email = ask " Email: "
- password = ask_for_password "Password: "
- say "\n"
-
- response = rubygems_api_request(:get, "api/v1/api_key",
- sign_in_host) do |request|
- request.basic_auth email, password
- end
-
- with_response response do |resp|
- say "Signed in."
- Gem.configuration.rubygems_api_key = resp.body
- end
- end
+ ##
+ # The host to connect to either from the RUBYGEMS_HOST environment variable
+ # or from the user's configuration
- attr_writer :host
def host
configured_host = Gem.host unless
Gem.configuration.disable_default_gem_server
@@ -70,6 +54,9 @@ module Gem::GemcutterUtilities
end
end
+ ##
+ # Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+.
+
def rubygems_api_request(method, path, host = nil, &block)
require 'net/http'
@@ -86,23 +73,43 @@ module Gem::GemcutterUtilities
Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
end
- def with_response resp, error_prefix = nil
- case resp
- when Net::HTTPSuccess then
- if block_given? then
- yield resp
- else
- say resp.body
- end
- else
- message = resp.body
- message = "#{error_prefix}: #{message}" if error_prefix
+ ##
+ # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API
+ # key.
- say message
- terminate_interaction 1 # TODO: question this
+ def sign_in sign_in_host = nil
+ sign_in_host ||= self.host
+ return if Gem.configuration.rubygems_api_key
+
+ pretty_host = if Gem::DEFAULT_HOST == sign_in_host then
+ 'RubyGems.org'
+ else
+ sign_in_host
+ end
+
+ say "Enter your #{pretty_host} credentials."
+ say "Don't have an account yet? " +
+ "Create one at #{sign_in_host}/sign_up"
+
+ email = ask " Email: "
+ password = ask_for_password "Password: "
+ say "\n"
+
+ response = rubygems_api_request(:get, "api/v1/api_key",
+ sign_in_host) do |request|
+ request.basic_auth email, password
+ end
+
+ with_response response do |resp|
+ say "Signed in."
+ Gem.configuration.rubygems_api_key = resp.body
end
end
+ ##
+ # Retrieves the pre-configured API key +key+ or terminates interaction with
+ # an error.
+
def verify_api_key(key)
if Gem.configuration.api_keys.key? key then
Gem.configuration.api_keys[key]
@@ -112,4 +119,29 @@ module Gem::GemcutterUtilities
end
end
+ ##
+ # If +response+ is an HTTP Success (2XX) response, yields the response if a
+ # block was given or shows the response body to the user.
+ #
+ # If the response was not successful, shows an error to the user including
+ # the +error_prefix+ and the response body.
+
+ def with_response response, error_prefix = nil
+ case response
+ when Net::HTTPSuccess then
+ if block_given? then
+ yield response
+ else
+ say response.body
+ end
+ else
+ message = response.body
+ message = "#{error_prefix}: #{message}" if error_prefix
+
+ say message
+ terminate_interaction 1 # TODO: question this
+ end
+ end
+
end
+
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index ffa8f910df..d3f55cd5ea 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -26,6 +26,9 @@ module Gem::InstallUpdateOptions
OptionParser.accept Gem::Security::Policy do |value|
require 'rubygems/security'
+ raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless
+ defined?(Gem::Security::HighSecurity)
+
value = Gem::Security::Policies[value]
valid = Gem::Security::Policies.keys.sort
message = "#{value} (#{valid.join ', '} are valid)"
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 0ccf7ad52f..261af890c8 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -9,7 +9,6 @@ require 'rubygems/package'
require 'rubygems/ext'
require 'rubygems/user_interaction'
require 'fileutils'
-require 'thread'
##
# The installer installs the files contained in the .gem into the Gem.home.
@@ -33,14 +32,6 @@ class Gem::Installer
ENV_PATHS = %w[/usr/bin/env /bin/env]
##
- # The builder shells-out to run various commands after changing the
- # directory. This means multiple installations cannot be allowed to build
- # extensions in parallel as they may change each other's directories leading
- # to broken extensions or failed installations.
-
- CHDIR_MUTEX = Mutex.new # :nodoc:
-
- ##
# Raised when there is an error while building extensions.
#
class ExtensionBuildError < Gem::InstallError; end
@@ -93,8 +84,8 @@ class Gem::Installer
# :env_shebang:: Use /usr/bin/env in bin wrappers.
# :force:: Overrides all version checks and security policy checks, except
# for a signed-gems-only policy.
- # :format_executable:: Format the executable the same as the ruby executable.
- # If your ruby is ruby18, foo_exec will be installed as
+ # :format_executable:: Format the executable the same as the Ruby executable.
+ # If your Ruby is ruby18, foo_exec will be installed as
# foo_exec18.
# :ignore_dependencies:: Don't raise if a dependency is missing.
# :install_dir:: The directory to install the gem into.
@@ -153,7 +144,7 @@ class Gem::Installer
io.gets # blankline
# TODO detect a specially formatted comment instead of trying
- # to run a regexp against ruby code.
+ # to run a regexp against Ruby code.
next unless io.gets =~ /This file was generated by RubyGems/
ruby_executable = true
@@ -222,19 +213,24 @@ class Gem::Installer
FileUtils.mkdir_p gem_dir
- extract_files
+ spec.loaded_from = spec_file
- build_extensions
- write_build_info_file
- run_post_build_hooks
+ if @options[:install_as_default]
+ extract_bin
+ write_default_spec
+ else
+ extract_files
- generate_bin
- write_spec
- write_cache_file
+ build_extensions
+ write_build_info_file
+ run_post_build_hooks
- say spec.post_install_message unless spec.post_install_message.nil?
+ generate_bin
+ write_spec
+ write_cache_file
+ end
- spec.loaded_from = spec_file
+ say spec.post_install_message unless spec.post_install_message.nil?
Gem::Specification.add_spec spec unless Gem::Specification.include? spec
@@ -336,6 +332,14 @@ class Gem::Installer
end
##
+ # The location of of the default spec file for default gems.
+ #
+
+ def default_spec_file
+ File.join gem_home, "specifications/default", "#{spec.full_name}.gemspec"
+ end
+
+ ##
# Writes the .gemspec specification (in Ruby) to the gem home's
# specifications directory.
@@ -347,6 +351,16 @@ class Gem::Installer
end
##
+ # Writes the full .gemspec specification (in Ruby) to the gem home's
+ # specifications/default directory.
+
+ def write_default_spec
+ File.open(default_spec_file, "w") do |file|
+ file.puts spec.to_ruby
+ end
+ end
+
+ ##
# Creates windows .bat files for easy running of commands
def generate_windows_script(filename, bindir)
@@ -547,13 +561,13 @@ class Gem::Installer
:bin_dir => nil,
:env_shebang => false,
:force => false,
- :install_dir => Gem.dir,
:only_install_dir => false
}.merge options
@env_shebang = options[:env_shebang]
@force = options[:force]
- @gem_home = options[:install_dir]
+ @install_dir = options[:install_dir]
+ @gem_home = options[:install_dir] || Gem.dir
@ignore_dependencies = options[:ignore_dependencies]
@format_executable = options[:format_executable]
@security_policy = options[:security_policy]
@@ -628,7 +642,7 @@ TEXT
end
##
- # return the stub script text used to launch the true ruby script
+ # 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('"')
@@ -647,75 +661,20 @@ TEXT
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
- return if spec.extensions.empty?
+ builder = Gem::Ext::Builder.new spec, @build_args
- if @build_args.empty?
- say "Building native extensions. This could take a while..."
- else
- say "Building native extensions with: '#{@build_args.join(' ')}'"
- say "This could take a while..."
- end
-
- dest_path = File.join gem_dir, spec.require_paths.first
- ran_rake = false # only run rake once
-
- spec.extensions.each do |extension|
- break if ran_rake
- results = []
-
- extension ||= ""
- extension_dir = File.join gem_dir, File.dirname(extension)
-
- builder = case extension
- when /extconf/ then
- Gem::Ext::ExtConfBuilder
- when /configure/ then
- Gem::Ext::ConfigureBuilder
- when /rakefile/i, /mkrf_conf/i then
- ran_rake = true
- Gem::Ext::RakeBuilder
- when /CMakeLists.txt/ then
- Gem::Ext::CmakeBuilder
- else
- message = "No builder for extension '#{extension}'"
- extension_build_error extension_dir, message
- end
-
- begin
- FileUtils.mkdir_p dest_path
-
- CHDIR_MUTEX.synchronize do
- Dir.chdir extension_dir do
- results = builder.build(extension, gem_dir, dest_path,
- results, @build_args)
-
- say results.join("\n") if Gem.configuration.really_verbose
- end
- end
- rescue
- extension_build_error(extension_dir, results.join("\n"), $@)
- end
- end
+ builder.build_extensions
end
##
# Logs the build +output+ in +build_dir+, then raises ExtensionBuildError.
+ #
+ # TODO: Delete this for RubyGems 3. It remains for API compatibility
- def extension_build_error(build_dir, output, backtrace = nil)
- gem_make_out = File.join build_dir, 'gem_make.out'
-
- open gem_make_out, 'wb' do |io| io.puts output end
-
- message = <<-EOF
-ERROR: Failed to build gem native extension.
-
- #{output}
-
-Gem files will remain installed in #{gem_dir} for inspection.
-Results logged to #{gem_make_out}
-EOF
+ def extension_build_error(build_dir, output, backtrace = nil) # :nodoc:
+ builder = Gem::Ext::Builder.new spec, @build_args
- raise ExtensionBuildError, message, backtrace
+ builder.build_error build_dir, output, backtrace
end
##
@@ -728,6 +687,15 @@ EOF
end
##
+ # Extracts only the bin/ files from the gem into the gem directory.
+ # This is used by default gems to allow a gem-aware stub to function
+ # without the full gem installed.
+
+ def extract_bin
+ @package.extract_files gem_dir, "bin/*"
+ end
+
+ ##
# Prefix and suffix the program filename the same as ruby.
def formatted_program_filename(filename)
@@ -750,7 +718,7 @@ EOF
##
# Performs various checks before installing the gem such as the install
- # repository is writable and its directories exist, required ruby and
+ # repository is writable and its directories exist, required Ruby and
# rubygems versions are met and that dependencies are installed.
#
# Version and dependency checks are skipped if this install is forced.
@@ -767,7 +735,11 @@ EOF
ensure_loadable_spec
- Gem.ensure_gem_subdirectories gem_home
+ if options[:install_as_default]
+ Gem.ensure_default_gem_subdirectories gem_home
+ else
+ Gem.ensure_gem_subdirectories gem_home
+ end
return true if @force
diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb
index d16fad26f1..f16ab369fa 100644
--- a/lib/rubygems/name_tuple.rb
+++ b/lib/rubygems/name_tuple.rb
@@ -43,6 +43,20 @@ class Gem::NameTuple
end
##
+ # Returns the full name (name-version) of this Gem. Platform information is
+ # included if it is not the default Ruby platform. This mimics the behavior
+ # of Gem::Specification#full_name.
+
+ def full_name
+ case @platform
+ when nil, 'ruby', ''
+ "#{@name}-#{@version}"
+ else
+ "#{@name}-#{@version}-#{@platform}"
+ end
+ end
+
+ ##
# Indicate if this NameTuple matches the current platform.
def match_platform?
@@ -59,12 +73,7 @@ class Gem::NameTuple
# Return the name that the gemspec file would be
def spec_name
- case @platform
- when nil, 'ruby', ''
- "#{@name}-#{@version}.gemspec"
- else
- "#{@name}-#{@version}-#{@platform}.gemspec"
- end
+ "#{full_name}.gemspec"
end
##
@@ -74,10 +83,12 @@ class Gem::NameTuple
[@name, @version, @platform]
end
- def to_s
+ def inspect # :nodoc:
"#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>"
end
+ alias to_s inspect # :nodoc:
+
def <=> other
to_a <=> other.to_a
end
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 957446257d..ba379c24cb 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -37,7 +37,7 @@
# the_gem.spec # get the spec out of the gem
# the_gem.verify # check the gem is OK (contains valid gem specification, contains a not corrupt contents archive)
#
-# #files are the files in the .gem tar file, not the ruby files in the gem
+# #files are the files in the .gem tar file, not the Ruby files in the gem
# #extract_files and #contents automatically call #verify
require 'rubygems/security'
@@ -280,11 +280,16 @@ EOM
algorithms = if @checksums then
@checksums.keys
else
- [Gem::Security::DIGEST_NAME]
+ [Gem::Security::DIGEST_NAME].compact
end
algorithms.each do |algorithm|
- digester = OpenSSL::Digest.new algorithm
+ digester =
+ if defined?(OpenSSL::Digest) then
+ OpenSSL::Digest.new algorithm
+ else
+ Digest.const_get(algorithm).new
+ end
digester << entry.read(16384) until entry.eof?
@@ -298,8 +303,11 @@ EOM
##
# Extracts the files in this package into +destination_dir+
+ #
+ # If +pattern+ is specified, only entries matching that glob will be
+ # extracted.
- def extract_files destination_dir
+ def extract_files destination_dir, pattern = "*"
verify unless @spec
FileUtils.mkdir_p destination_dir
@@ -310,7 +318,7 @@ EOM
reader.each do |entry|
next unless entry.full_name == 'data.tar.gz'
- extract_tar_gz entry, destination_dir
+ extract_tar_gz entry, destination_dir, pattern
return # ignore further entries
end
@@ -324,11 +332,20 @@ EOM
# If an entry in the archive contains a relative path above
# +destination_dir+ or an absolute path is encountered an exception is
# raised.
+ #
+ # If +pattern+ is specified, only entries matching that glob will be
+ # extracted.
- def extract_tar_gz io, destination_dir # :nodoc:
+ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc:
open_tar_gz io do |tar|
tar.each do |entry|
- destination = install_location entry.full_name, destination_dir
+ # Some entries start with "./" which fnmatch does not like, see github
+ # issue #644
+ full_name = entry.full_name.sub %r%\A\./%, ''
+
+ next unless File.fnmatch pattern, full_name
+
+ destination = install_location full_name, destination_dir
FileUtils.rm_rf destination
@@ -428,12 +445,13 @@ EOM
# certificate and key are not present only checksum generation is set up.
def setup_signer
+ passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
if @spec.signing_key then
- @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain
+ @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain, passphrase
@spec.signing_key = nil
@spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s }
else
- @signer = Gem::Security::Signer.new nil, nil
+ @signer = Gem::Security::Signer.new nil, nil, passphrase
@spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if
@signer.cert_chain
end
@@ -510,27 +528,38 @@ EOM
end
##
+ # Verifies +entry+ in a .gem file.
+
+ def verify_entry entry
+ file_name = entry.full_name
+ @files << file_name
+
+ case file_name
+ when /\.sig$/ then
+ @signatures[$`] = entry.read if @security_policy
+ return
+ else
+ digest entry
+ end
+
+ case file_name
+ when /^metadata(.gz)?$/ then
+ load_spec entry
+ when 'data.tar.gz' then
+ verify_gz entry
+ end
+ rescue => e
+ message = "package is corrupt, exception while verifying: " +
+ "#{e.message} (#{e.class})"
+ raise Gem::Package::FormatError.new message, @gem
+ end
+
+ ##
# Verifies the files of the +gem+
def verify_files gem
gem.each do |entry|
- file_name = entry.full_name
- @files << file_name
-
- case file_name
- when /\.sig$/ then
- @signatures[$`] = entry.read if @security_policy
- next
- else
- digest entry
- end
-
- case file_name
- when /^metadata(.gz)?$/ then
- load_spec entry
- when 'data.tar.gz' then
- verify_gz entry
- end
+ verify_entry entry
end
unless @spec then
diff --git a/lib/rubygems/package/tar_test_case.rb b/lib/rubygems/package/tar_test_case.rb
index 4601f1328f..5253e32f36 100644
--- a/lib/rubygems/package/tar_test_case.rb
+++ b/lib/rubygems/package/tar_test_case.rb
@@ -71,7 +71,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
SP(Z(to_oct(sum, 6)))
end
- def header(type, fname, dname, length, mode, checksum = nil)
+ def header(type, fname, dname, length, mode, mtime, checksum = nil)
checksum ||= " " * 8
arr = [ # struct tarfile_entry_posix
@@ -80,7 +80,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
Z(to_oct(0, 7)), # char uid[8]; ditto
Z(to_oct(0, 7)), # char gid[8]; ditto
Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null
- Z(to_oct(0, 11)), # char mtime[12]; 0 padded, octal, null
+ Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null
checksum, # char checksum[8]; 0 padded, octal, null, space
type, # char typeflag[1]; file: "0" dir: "5"
"\0" * 100, # char linkname[100]; ASCII + (Z unless filled)
@@ -105,16 +105,16 @@ class Gem::Package::TarTestCase < Gem::TestCase
ret
end
- def tar_dir_header(name, prefix, mode)
- h = header("5", name, prefix, 0, mode)
+ def tar_dir_header(name, prefix, mode, mtime)
+ h = header("5", name, prefix, 0, mode, mtime)
checksum = calc_checksum(h)
- header("5", name, prefix, 0, mode, checksum)
+ header("5", name, prefix, 0, mode, mtime, checksum)
end
- def tar_file_header(fname, dname, mode, length)
- h = header("0", fname, dname, length, mode)
+ def tar_file_header(fname, dname, mode, length, mtime)
+ h = header("0", fname, dname, length, mode, mtime)
checksum = calc_checksum(h)
- header("0", fname, dname, length, mode, checksum)
+ header("0", fname, dname, length, mode, mtime, checksum)
end
def to_oct(n, pad_size)
@@ -130,7 +130,7 @@ class Gem::Package::TarTestCase < Gem::TestCase
end
def util_dir_entry
- util_entry tar_dir_header("foo", "bar", 0)
+ util_entry tar_dir_header("foo", "bar", 0, Time.now)
end
end
diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb
index f2c11e3544..e1b38ad6b5 100644
--- a/lib/rubygems/package/tar_writer.rb
+++ b/lib/rubygems/package/tar_writer.rb
@@ -4,6 +4,8 @@
# See LICENSE.txt for additional licensing information.
#++
+require 'digest'
+
##
# Allows writing of tar files
@@ -121,7 +123,8 @@ class Gem::Package::TarWriter
@io.pos = init_pos
header = Gem::Package::TarHeader.new :name => name, :mode => mode,
- :size => size, :prefix => prefix
+ :size => size, :prefix => prefix,
+ :mtime => Time.now
@io.write header
@io.pos = final_pos
@@ -140,7 +143,15 @@ class Gem::Package::TarWriter
def add_file_digest name, mode, digest_algorithms # :yields: io
digests = digest_algorithms.map do |digest_algorithm|
digest = digest_algorithm.new
- [digest.name, digest]
+ digest_name =
+ if digest.respond_to? :name then
+ digest.name
+ else
+ /::([^:]+)$/ =~ digest_algorithm.name
+ $1
+ end
+
+ [digest_name, digest]
end
digests = Hash[*digests.flatten]
@@ -165,22 +176,32 @@ class Gem::Package::TarWriter
def add_file_signed name, mode, signer
digest_algorithms = [
signer.digest_algorithm,
- OpenSSL::Digest::SHA512,
- ].uniq
+ Digest::SHA512,
+ ].compact.uniq
digests = add_file_digest name, mode, digest_algorithms do |io|
yield io
end
- signature_digest = digests.values.find do |digest|
- digest.name == signer.digest_name
+ signature_digest = digests.values.compact.find do |digest|
+ digest_name =
+ if digest.respond_to? :name then
+ digest.name
+ else
+ /::([^:]+)$/ =~ digest.class.name
+ $1
+ end
+
+ digest_name == signer.digest_name
end
- signature = signer.sign signature_digest.digest
+ if signer.key then
+ signature = signer.sign signature_digest.digest
- add_file_simple "#{name}.sig", 0444, signature.length do |io|
- io.write signature
- end if signature
+ add_file_simple "#{name}.sig", 0444, signature.length do |io|
+ io.write signature
+ end
+ end
digests
end
@@ -195,7 +216,8 @@ class Gem::Package::TarWriter
name, prefix = split_name name
header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
- :size => size, :prefix => prefix).to_s
+ :size => size, :prefix => prefix,
+ :mtime => Time.now).to_s
@io.write header
os = BoundedStream.new @io, size
@@ -256,7 +278,8 @@ class Gem::Package::TarWriter
header = Gem::Package::TarHeader.new :name => name, :mode => mode,
:typeflag => "5", :size => 0,
- :prefix => prefix
+ :prefix => prefix,
+ :mtime => Time.now
@io.write header
diff --git a/lib/rubygems/path_support.rb b/lib/rubygems/path_support.rb
index 7195b322ef..2af303eecf 100644
--- a/lib/rubygems/path_support.rb
+++ b/lib/rubygems/path_support.rb
@@ -13,6 +13,10 @@ class Gem::PathSupport
attr_reader :path
##
+ # Directory with spec cache
+ attr_reader :spec_cache_dir # :nodoc:
+
+ ##
#
# Constructor. Takes a single argument which is to be treated like a
# hashtable, or defaults to ENV, the system environment.
@@ -28,6 +32,12 @@ class Gem::PathSupport
end
self.path = env["GEM_PATH"] || ENV["GEM_PATH"]
+
+ @spec_cache_dir =
+ env["GEM_SPEC_CACHE"] || ENV["GEM_SPEC_CACHE"] ||
+ Gem.default_spec_cache_dir
+
+ @spec_cache_dir = @spec_cache_dir.dup.untaint
end
private
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 4a4674b72f..247ee6ed3e 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -2,6 +2,8 @@ require "rubygems/deprecate"
##
# Available list of platforms for targeting Gem installations.
+#
+# See `gem help platform` for information on platform matching.
class Gem::Platform
@@ -129,12 +131,16 @@ class Gem::Platform
# Does +other+ match this platform? Two platforms match if they have the
# same CPU, or either has a CPU of 'universal', they have the same OS, and
# they have the same version, or either has no version.
+ #
+ # Additionally, the platform will match if the local CPU is 'arm' and the
+ # other CPU starts with "arm" (for generic ARM family support).
def ===(other)
return nil unless Gem::Platform === other
# cpu
- (@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu) and
+ (@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu or
+ (@cpu == 'arm' and other.cpu =~ /\Aarm/)) and
# os
@os == other.os and
@@ -175,13 +181,13 @@ class Gem::Platform
end
##
- # A pure-ruby gem that may use Gem::Specification#extensions to build
+ # A pure-Ruby gem that may use Gem::Specification#extensions to build
# binary files.
RUBY = 'ruby'
##
- # A platform-specific gem that is built for the packaging ruby's platform.
+ # A platform-specific gem that is built for the packaging Ruby's platform.
# This will be replaced with Gem::Platform::local.
CURRENT = 'current'
diff --git a/lib/rubygems/psych_additions.rb b/lib/rubygems/psych_additions.rb
index 08a5cb37ea..dcc13fdf2e 100644
--- a/lib/rubygems/psych_additions.rb
+++ b/lib/rubygems/psych_additions.rb
@@ -3,7 +3,7 @@
# in Specification._load, but if we don't have the constant, Marshal
# blows up.
-module Psych
+module Psych # :nodoc:
class PrivateType
end
end
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 86bad9de41..6abd6bd9db 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -1,7 +1,7 @@
require 'rubygems'
+require 'rubygems/request'
+require 'rubygems/uri_formatter'
require 'rubygems/user_interaction'
-require 'thread'
-require 'uri'
require 'resolv'
##
@@ -72,18 +72,7 @@ class Gem::RemoteFetcher
Socket.do_not_reverse_lookup = true
- @connections = {}
- @connections_mutex = Mutex.new
- @requests = Hash.new 0
- @proxy_uri =
- case proxy
- when :no_proxy then nil
- when nil then get_proxy_from_env
- when URI::HTTP then proxy
- else URI.parse(proxy)
- end
- @user_agent = user_agent
- @env_no_proxy = get_no_proxy_from_env
+ @proxy = proxy
@dns = dns
end
@@ -202,7 +191,7 @@ class Gem::RemoteFetcher
source_uri.path
end
- source_path = unescape source_path
+ source_path = Gem::UriFormatter.new(source_path).unescape
begin
FileUtils.cp source_path, local_gem_path unless
@@ -321,128 +310,6 @@ class Gem::RemoteFetcher
response['content-length'].to_i
end
- def escape(str)
- return unless str
- @uri_parser ||= uri_escaper
- @uri_parser.escape str
- end
-
- def unescape(str)
- return unless str
- @uri_parser ||= uri_escaper
- @uri_parser.unescape str
- end
-
- def uri_escaper
- URI::Parser.new
- rescue NameError
- URI
- end
-
- ##
- # Returns list of no_proxy entries (if any) from the environment
-
- def get_no_proxy_from_env
- env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
-
- return [] if env_no_proxy.nil? or env_no_proxy.empty?
-
- env_no_proxy.split(/\s*,\s*/)
- end
-
- ##
- # Returns an HTTP proxy URI if one is set in the environment variables.
-
- def get_proxy_from_env
- env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
-
- return nil if env_proxy.nil? or env_proxy.empty?
-
- uri = URI.parse(normalize_uri(env_proxy))
-
- if uri and uri.user.nil? and uri.password.nil? then
- # Probably we have http_proxy_* variables?
- uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
- uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
- end
-
- uri
- end
-
- ##
- # Normalize the URI by adding "http://" if it is missing.
-
- def normalize_uri(uri)
- (uri =~ /^(https?|ftp|file):/i) ? uri : "http://#{uri}"
- end
-
- ##
- # Creates or an HTTP connection based on +uri+, or retrieves an existing
- # connection, using a proxy if needed.
-
- def connection_for(uri)
- net_http_args = [uri.host, uri.port]
-
- if @proxy_uri and not no_proxy?(uri.host) then
- net_http_args += [
- @proxy_uri.host,
- @proxy_uri.port,
- @proxy_uri.user,
- @proxy_uri.password
- ]
- end
-
- connection_id = [Thread.current.object_id, *net_http_args].join ':'
-
- 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
- rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN,
- Errno::EHOSTDOWN => e
- raise FetchError.new(e.message, uri)
- end
-
- def configure_connection_for_https(connection)
- require 'net/https'
- connection.use_ssl = true
- connection.verify_mode =
- Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
- store = OpenSSL::X509::Store.new
- if Gem.configuration.ssl_ca_cert
- if File.directory? Gem.configuration.ssl_ca_cert
- store.add_path Gem.configuration.ssl_ca_cert
- else
- store.add_file Gem.configuration.ssl_ca_cert
- end
- else
- store.set_default_paths
- add_rubygems_trusted_certs(store)
- end
- connection.cert_store = store
- rescue LoadError => e
- raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
- e.message =~ / -- openssl$/
-
- raise Gem::Exception.new(
- 'Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources')
- end
-
- def add_rubygems_trusted_certs(store)
- pattern = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__))
- Dir.glob(pattern).each do |ssl_cert_file|
- store.add_file ssl_cert_file
- end
- end
-
def correct_for_windows_path(path)
if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
path = path[1..-1]
@@ -451,136 +318,17 @@ class Gem::RemoteFetcher
end
end
- def no_proxy? host
- host = host.downcase
- @env_no_proxy.each do |pattern|
- pattern = pattern.downcase
- return true if host[-pattern.length, pattern.length ] == pattern
- end
- return false
- end
-
##
# Performs a Net::HTTP request of type +request_class+ on +uri+ returning
# a Net::HTTP response object. request maintains a table of persistent
# connections to reduce connect overhead.
def request(uri, request_class, last_modified = nil)
- request = request_class.new uri.request_uri
-
- unless uri.nil? || uri.user.nil? || uri.user.empty? then
- request.basic_auth uri.user, uri.password
- end
-
- request.add_field 'User-Agent', @user_agent
- request.add_field 'Connection', 'keep-alive'
- request.add_field 'Keep-Alive', '30'
-
- if last_modified then
- last_modified = last_modified.utc
- request.add_field 'If-Modified-Since', last_modified.rfc2822
- end
-
- yield request if block_given?
-
- connection = connection_for uri
-
- retried = false
- bad_response = false
-
- begin
- @requests[connection.object_id] += 1
-
- say "#{request.method} #{uri}" if
- Gem.configuration.really_verbose
-
- file_name = File.basename(uri.path)
- # perform download progress reporter only for gems
- if request.response_body_permitted? && file_name =~ /\.gem$/
- reporter = ui.download_reporter
- response = connection.request(request) do |incomplete_response|
- if Net::HTTPOK === incomplete_response
- reporter.fetch(file_name, incomplete_response.content_length)
- downloaded = 0
- data = ''
-
- incomplete_response.read_body do |segment|
- data << segment
- downloaded += segment.length
- reporter.update(downloaded)
- end
- reporter.done
- if incomplete_response.respond_to? :body=
- incomplete_response.body = data
- else
- incomplete_response.instance_variable_set(:@body, data)
- end
- end
- end
- else
- response = connection.request request
- end
-
- say "#{response.code} #{response.message}" if
- Gem.configuration.really_verbose
-
- rescue Net::HTTPBadResponse
- say "bad response" if Gem.configuration.really_verbose
-
- reset connection
-
- raise FetchError.new('too many bad responses', uri) if bad_response
-
- bad_response = true
- retry
- # HACK work around EOFError bug in Net::HTTP
- # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
- # to install gems.
- rescue EOFError, Timeout::Error,
- Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
-
- requests = @requests[connection.object_id]
- say "connection reset after #{requests} requests, retrying" if
- Gem.configuration.really_verbose
-
- raise FetchError.new('too many connection resets', uri) if retried
-
- reset connection
-
- retried = true
- retry
- end
+ request = Gem::Request.new uri, request_class, last_modified, @proxy
- response
- end
-
- ##
- # Resets HTTP connection +connection+.
-
- def reset(connection)
- @requests.delete connection.object_id
-
- connection.finish
- connection.start
- end
-
- def user_agent
- ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}"
-
- ruby_version = RUBY_VERSION
- ruby_version += 'dev' if RUBY_PATCHLEVEL == -1
-
- ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}"
- if RUBY_PATCHLEVEL >= 0 then
- ua << " patchlevel #{RUBY_PATCHLEVEL}"
- elsif defined?(RUBY_REVISION) then
- ua << " revision #{RUBY_REVISION}"
+ request.fetch do |req|
+ yield req if block_given?
end
- ua << ")"
-
- ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby'
-
- ua
end
def https?(uri)
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 6c52b90c40..a45c64e0b4 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -5,178 +5,179 @@ require 'rubygems/dependency_list'
require 'rubygems/installer'
require 'tsort'
-module Gem
- class RequestSet
+class Gem::RequestSet
- include TSort
+ include TSort
- def initialize(*deps)
- @dependencies = deps
+ ##
+ # Array of gems to install even if already installed
- yield self if block_given?
- end
+ attr_reader :always_install
- attr_reader :dependencies
+ attr_reader :dependencies
- # Declare that a gem of name +name+ with +reqs+ requirements
- # is needed.
- #
- def gem(name, *reqs)
- @dependencies << Gem::Dependency.new(name, reqs)
- end
+ attr_accessor :development
- # Add +deps+ Gem::Depedency objects to the set.
- #
- def import(deps)
- @dependencies += deps
- end
+ ##
+ # Treat missing dependencies as silent errors
- # Resolve the requested dependencies and return an Array of
- # Specification objects to be activated.
- #
- def resolve(set=nil)
- r = Gem::DependencyResolver.new(@dependencies, set)
- @requests = r.resolve
- end
+ attr_accessor :soft_missing
- # Resolve the requested dependencies against the gems
- # available via Gem.path and return an Array of Specification
- # objects to be activated.
- #
- def resolve_current
- resolve DependencyResolver::CurrentSet.new
- end
+ def initialize *deps
+ @dependencies = deps
- # Load a dependency management file.
- #
- def load_gemdeps(path)
- gf = GemDepedencyAPI.new(self, path)
- gf.load
- end
+ @always_install = []
+ @development = false
+ @requests = []
+ @soft_missing = false
+ @sorted = nil
+ @specs = nil
- def specs
- @specs ||= @requests.map { |r| r.full_spec }
- end
+ yield self if block_given?
+ end
- def tsort_each_node(&block)
- @requests.each(&block)
- end
+ ##
+ # Declare that a gem of name +name+ with +reqs+ requirements is needed.
- def tsort_each_child(node)
- node.spec.dependencies.each do |dep|
- next if dep.type == :development
-
- match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
- if match
- begin
- yield match
- rescue TSort::Cyclic
- end
- else
- raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}"
- end
- end
- end
+ def gem name, *reqs
+ @dependencies << Gem::Dependency.new(name, reqs)
+ end
+
+ ##
+ # Add +deps+ Gem::Dependency objects to the set.
+
+ def import deps
+ @dependencies += deps
+ end
- def sorted_requests
- @sorted ||= strongly_connected_components.flatten
+ def install options, &block
+ if dir = options[:install_dir]
+ return install_into dir, false, options, &block
end
- def specs_in(dir)
- Dir["#{dir}/specifications/*.gemspec"].map do |g|
- Gem::Specification.load g
+ cache_dir = options[:cache_dir] || Gem.dir
+
+ specs = []
+
+ sorted_requests.each do |req|
+ if req.installed? and
+ @always_install.none? { |spec| spec == req.spec.spec } then
+ yield req, nil if block_given?
+ next
end
- end
- def install_into(dir, force=true, &b)
- existing = force ? [] : specs_in(dir)
+ path = req.download cache_dir
- dir = File.expand_path dir
+ inst = Gem::Installer.new path, options
- installed = []
+ yield req, inst if block_given?
- sorted_requests.each do |req|
- if existing.find { |s| s.full_name == req.spec.full_name }
- b.call req, nil if b
- next
- end
+ specs << inst.install
+ end
- path = req.download(dir)
+ specs
+ end
- inst = Gem::Installer.new path, :install_dir => dir,
- :only_install_dir => true
+ def install_into dir, force = true, options = {}
+ existing = force ? [] : specs_in(dir)
+ existing.delete_if { |s| @always_install.include? s }
- b.call req, inst if b
+ dir = File.expand_path dir
- inst.install
+ installed = []
- installed << req
+ sorted_requests.each do |req|
+ if existing.find { |s| s.full_name == req.spec.full_name }
+ yield req, nil if block_given?
+ next
end
- installed
- end
+ path = req.download(dir)
- def install(options, &b)
- if dir = options[:install_dir]
- return install_into(dir, false, &b)
+ unless path then # already installed
+ yield req, nil if block_given?
+ next
end
- cache_dir = options[:cache_dir] || Gem.dir
+ options[:install_dir] = dir
+ options[:only_install_dir] = true
- specs = []
+ inst = Gem::Installer.new path, options
- sorted_requests.each do |req|
- if req.installed?
- b.call req, nil if b
- next
- end
+ yield req, inst if block_given?
- path = req.download cache_dir
+ inst.install
- inst = Gem::Installer.new path, options
+ installed << req
+ end
- b.call req, inst if b
+ installed
+ end
- specs << inst.install
- end
+ ##
+ # Load a dependency management file.
- specs
- end
+ def load_gemdeps path
+ gf = Gem::RequestSet::GemDepedencyAPI.new self, path
+ gf.load
+ end
- # A semi-compatible DSL for Bundler's Gemfile format
- #
- class GemDepedencyAPI
- def initialize(set, path)
- @set = set
- @path = path
- end
+ ##
+ # Resolve the requested dependencies and return an Array of Specification
+ # objects to be activated.
- def load
- instance_eval File.read(@path).untaint, @path, 1
- end
+ def resolve set = nil
+ resolver = Gem::DependencyResolver.new @dependencies, set
+ resolver.development = @development
+ resolver.soft_missing = @soft_missing
- # DSL
+ @requests = resolver.resolve
+ end
- def source(url)
- end
+ ##
+ # Resolve the requested dependencies against the gems available via Gem.path
+ # and return an Array of Specification objects to be activated.
- def gem(name, *reqs)
- # Ignore the opts for now.
- reqs.pop if reqs.last.kind_of?(Hash)
+ def resolve_current
+ resolve Gem::DependencyResolver::CurrentSet.new
+ end
- @set.gem name, *reqs
- end
+ def sorted_requests
+ @sorted ||= strongly_connected_components.flatten
+ end
- def platform(what)
- if what == :ruby
- yield
- end
- end
+ def specs
+ @specs ||= @requests.map { |r| r.full_spec }
+ end
+
+ def specs_in dir
+ Dir["#{dir}/specifications/*.gemspec"].map do |g|
+ Gem::Specification.load g
+ end
+ end
+
+ def tsort_each_node &block # :nodoc:
+ @requests.each(&block)
+ end
- alias_method :platforms, :platform
+ def tsort_each_child node # :nodoc:
+ node.spec.dependencies.each do |dep|
+ next if dep.type == :development and not @development
- def group(*what)
+ match = @requests.find { |r| dep.match? r.spec.name, r.spec.version }
+ if match
+ begin
+ yield match
+ rescue TSort::Cyclic
+ end
+ else
+ unless @soft_missing
+ raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}"
+ end
end
end
end
+
end
+
+require 'rubygems/request_set/gem_dependency_api'
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index bed47ab9f3..bfd6fd225b 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -12,20 +12,6 @@ begin
rescue LoadError => e
raise unless (e.respond_to?(:path) && e.path == 'openssl') ||
e.message =~ / -- openssl$/
-
- module OpenSSL # :nodoc:
- class Digest # :nodoc:
- class SHA1 # :nodoc:
- def name
- 'SHA1'
- end
- end
- end
- module PKey # :nodoc:
- class RSA # :nodoc:
- end
- end
- end
end
##
@@ -352,17 +338,26 @@ module Gem::Security
##
# Digest algorithm used to sign gems
- DIGEST_ALGORITHM = OpenSSL::Digest::SHA1
+ DIGEST_ALGORITHM =
+ if defined?(OpenSSL::Digest) then
+ OpenSSL::Digest::SHA1
+ end
##
# Used internally to select the signing digest from all computed digests
- DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc:
+ DIGEST_NAME = # :nodoc:
+ if DIGEST_ALGORITHM then
+ DIGEST_ALGORITHM.new.name
+ end
##
# Algorithm for creating the key pair used to sign gems
- KEY_ALGORITHM = OpenSSL::PKey::RSA
+ KEY_ALGORITHM =
+ if defined?(OpenSSL::PKey) then
+ OpenSSL::PKey::RSA
+ end
##
# Length of keys created by KEY_ALGORITHM
@@ -370,6 +365,12 @@ module Gem::Security
KEY_LENGTH = 2048
##
+ # Cipher used to encrypt the key pair used to sign gems.
+ # Must be in the list returned by OpenSSL::Cipher.ciphers
+
+ KEY_CIPHER = OpenSSL::Cipher.new('AES-256-CBC') if defined?(OpenSSL::Cipher)
+
+ ##
# One year in seconds
ONE_YEAR = 86400 * 365
@@ -563,13 +564,18 @@ module Gem::Security
##
# Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given
- # +permissions+.
+ # +permissions+. If passed +cipher+ and +passphrase+ those arguments will be
+ # passed to +to_pem+.
- def self.write pemmable, path, permissions = 0600
+ def self.write pemmable, path, permissions = 0600, passphrase = nil, cipher = KEY_CIPHER
path = File.expand_path path
open path, 'wb', permissions do |io|
- io.write pemmable.to_pem
+ if passphrase and cipher
+ io.write pemmable.to_pem cipher, passphrase
+ else
+ io.write pemmable.to_pem
+ end
end
path
@@ -579,8 +585,11 @@ module Gem::Security
end
-require 'rubygems/security/policy'
-require 'rubygems/security/policies'
+if defined?(OpenSSL::SSL) then
+ require 'rubygems/security/policy'
+ require 'rubygems/security/policies'
+ require 'rubygems/security/trust_dir'
+end
+
require 'rubygems/security/signer'
-require 'rubygems/security/trust_dir'
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 467ee932b5..7238b2e477 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -1,3 +1,5 @@
+require 'rubygems/user_interaction'
+
##
# A Gem::Security::Policy object encapsulates the settings for verifying
# signed gem files. This is the base class. You can either declare an
@@ -6,6 +8,8 @@
class Gem::Security::Policy
+ include Gem::UserInteraction
+
attr_reader :name
attr_accessor :only_signed
@@ -175,6 +179,19 @@ class Gem::Security::Policy
true
end
+ ##
+ # Extracts the email or subject from +certificate+
+
+ def subject certificate # :nodoc:
+ certificate.extensions.each do |extension|
+ next unless extension.oid == 'subjectAltName'
+
+ return extension.value
+ end
+
+ certificate.subject.to_s
+ end
+
def inspect # :nodoc:
("[Policy: %s - data: %p signer: %p chain: %p root: %p " +
"signed-only: %p trusted-only: %p]") % [
@@ -184,16 +201,24 @@ class Gem::Security::Policy
end
##
- # Verifies the certificate +chain+ is valid, the +digests+ match the
- # signatures +signatures+ created by the signer depending on the +policy+
- # settings.
+ # For +full_name+, verifies the certificate +chain+ is valid, the +digests+
+ # match the signatures +signatures+ created by the signer depending on the
+ # +policy+ settings.
#
# If +key+ is given it is used to validate the signing certificate.
- def verify chain, key = nil, digests = {}, signatures = {}
- if @only_signed and signatures.empty? then
- raise Gem::Security::Exception,
- "unsigned gems are not allowed by the #{name} policy"
+ def verify chain, key = nil, digests = {}, signatures = {},
+ full_name = '(unknown)'
+ if signatures.empty? then
+ if @only_signed then
+ raise Gem::Security::Exception,
+ "unsigned gems are not allowed by the #{name} policy"
+ elsif digests.empty? then
+ # lack of signatures is irrelevant if there is nothing to check
+ # against
+ else
+ alert_warning "#{full_name} is not signed"
+ end
end
opt = @opt
@@ -222,7 +247,13 @@ class Gem::Security::Policy
check_root chain, time if @verify_root
- check_trust chain, digester, trust_dir if @only_trusted
+ if @only_trusted then
+ check_trust chain, digester, trust_dir
+ elsif signatures.empty? and digests.empty? then
+ # trust is irrelevant if there's no signatures to verify
+ else
+ alert_warning "#{subject signer} is not trusted for #{full_name}"
+ end
signatures.each do |file, _|
digest = signer_digests[file]
@@ -252,7 +283,7 @@ class Gem::Security::Policy
OpenSSL::X509::Certificate.new cert_pem
end
- verify chain, nil, digests, signatures
+ verify chain, nil, digests, signatures, spec.full_name
true
end
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index 78455c0732..bb1eae7cf2 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -29,7 +29,7 @@ class Gem::Security::Signer
# +chain+ containing X509 certificates, encoding certificates or paths to
# certificates.
- def initialize key, cert_chain
+ def initialize key, cert_chain, passphrase = nil
@cert_chain = cert_chain
@key = key
@@ -46,7 +46,7 @@ class Gem::Security::Signer
@digest_algorithm = Gem::Security::DIGEST_ALGORITHM
@digest_name = Gem::Security::DIGEST_NAME
- @key = OpenSSL::PKey::RSA.new File.read @key if
+ @key = OpenSSL::PKey::RSA.new File.read(@key), passphrase if
@key and not OpenSSL::PKey::RSA === @key
if @cert_chain then
@@ -63,6 +63,22 @@ class Gem::Security::Signer
end
##
+ # Extracts the full name of +cert+. If the certificate has a subjectAltName
+ # this value is preferred, otherwise the subject is used.
+
+ def extract_name cert # :nodoc:
+ subject_alt_name = cert.extensions.find { |e| 'subjectAltName' == e.oid }
+
+ if subject_alt_name then
+ /\Aemail:/ =~ subject_alt_name.value
+
+ $' || subject_alt_name.value
+ else
+ cert.subject
+ end
+ end
+
+ ##
# Loads any missing issuers in the cert chain from the trusted certificates.
#
# If the issuer does not exist it is ignored as it will be checked later.
@@ -89,7 +105,9 @@ class Gem::Security::Signer
re_sign_key
end
- Gem::Security::SigningPolicy.verify @cert_chain, @key
+ full_name = extract_name @cert_chain.last
+
+ Gem::Security::SigningPolicy.verify @cert_chain, @key, {}, {}, full_name
@key.sign @digest_algorithm.new, data
end
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
index dd582193ee..3ca588ae92 100644
--- a/lib/rubygems/server.rb
+++ b/lib/rubygems/server.rb
@@ -670,13 +670,13 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
# documentation for the particular gem, otherwise a list with results is
# shown.
#
- # === Additional trick - install documentation for ruby core
+ # === Additional trick - install documentation for Ruby core
#
# Note: please adjust paths accordingly use for example 'locate yaml.rb' and
# 'gem environment' to identify directories, that are specific for your
# local installation
#
- # 1. install ruby sources
+ # 1. install Ruby sources
# cd /usr/src
# sudo apt-get source ruby
#
@@ -702,7 +702,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; }
# name pattern was found.
#
# The search is based on the file system content, not on the gems metadata.
- # This allows additional documentation folders like 'core' for the ruby core
+ # This allows additional documentation folders like 'core' for the Ruby core
# documentation - just put it underneath the main doc folder.
def show_rdoc_for_pattern(pattern, res)
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index 96d57870e2..f0e2a597b9 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -25,14 +25,23 @@ class Gem::Source
end
def <=>(other)
- if !@uri
- return 0 unless other.uri
- return -1
- end
+ case other
+ when Gem::Source::Installed,
+ Gem::Source::Local,
+ Gem::Source::SpecificFile then
+ -1
+ when Gem::Source then
+ if !@uri
+ return 0 unless other.uri
+ return 1
+ end
- return 1 if !other.uri
+ return -1 if !other.uri
- @uri.to_s <=> other.uri.to_s
+ @uri.to_s <=> other.uri.to_s
+ else
+ nil
+ end
end
include Comparable
@@ -58,8 +67,7 @@ class Gem::Source
def cache_dir(uri)
# Correct for windows paths
escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
- root = File.join Gem.user_home, '.gem', 'specs'
- File.join root, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
+ File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
end
def update_cache?
@@ -141,4 +149,16 @@ class Gem::Source
fetcher = Gem::RemoteFetcher.fetcher
fetcher.download spec, @uri.to_s, dir
end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[Remote:', ']' do
+ q.breakable
+ q.text @uri.to_s
+ end
+ end
+
end
+
+require 'rubygems/source/installed'
+require 'rubygems/source/specific_file'
+require 'rubygems/source/local'
diff --git a/lib/rubygems/source_local.rb b/lib/rubygems/source_local.rb
index 44b170c4a4..0808f4694a 100644
--- a/lib/rubygems/source_local.rb
+++ b/lib/rubygems/source_local.rb
@@ -1,92 +1,5 @@
require 'rubygems/source'
+require 'rubygems/source_local'
-class Gem::Source::Local < Gem::Source
- def initialize
- @uri = nil
- end
+# TODO warn upon require, this file is deprecated.
- def load_specs(type)
- names = []
-
- @specs = {}
-
- Dir["*.gem"].each do |file|
- begin
- pkg = Gem::Package.new(file)
- rescue SystemCallError, Gem::Package::FormatError
- # ignore
- else
- tup = pkg.spec.name_tuple
- @specs[tup] = [File.expand_path(file), pkg]
-
- case type
- when :released
- unless pkg.spec.version.prerelease?
- names << pkg.spec.name_tuple
- end
- when :prerelease
- if pkg.spec.version.prerelease?
- names << pkg.spec.name_tuple
- end
- when :latest
- tup = pkg.spec.name_tuple
-
- cur = names.find { |x| x.name == tup.name }
- if !cur
- names << tup
- elsif cur.version < tup.version
- names.delete cur
- names << tup
- end
- else
- names << pkg.spec.name_tuple
- end
- end
- end
-
- names
- end
-
- def find_gem(gem_name, version=Gem::Requirement.default,
- prerelease=false)
- load_specs :complete
-
- found = []
-
- @specs.each do |n, data|
- if n.name == gem_name
- s = data[1].spec
-
- if version.satisfied_by?(s.version)
- if prerelease
- found << s
- elsif !s.version.prerelease?
- found << s
- end
- end
- end
- end
-
- found.sort_by { |s| s.version }.last
- end
-
- def fetch_spec(name)
- load_specs :complete
-
- if data = @specs[name]
- data.last.spec
- else
- raise Gem::Exception, "Unable to find spec for '#{name}'"
- end
- end
-
- def download(spec, cache_dir=nil)
- load_specs :complete
-
- @specs.each do |name, data|
- return data[0] if data[1].spec == spec
- end
-
- raise Gem::Exception, "Unable to find file for '#{spec.full_name}'"
- end
-end
diff --git a/lib/rubygems/source_specific_file.rb b/lib/rubygems/source_specific_file.rb
index d296e617cc..f785c2667c 100644
--- a/lib/rubygems/source_specific_file.rb
+++ b/lib/rubygems/source_specific_file.rb
@@ -1,28 +1,4 @@
-class Gem::Source::SpecificFile < Gem::Source
- def initialize(file)
- @uri = nil
- @path = ::File.expand_path(file)
+require 'rubygems/source/specific_file'
- @package = Gem::Package.new @path
- @spec = @package.spec
- @name = @spec.name_tuple
- end
+# TODO warn upon require, this file is deprecated.
- attr_reader :spec
-
- def load_specs(*a)
- [@name]
- end
-
- def fetch_spec(name)
- return @spec if name == @name
- raise Gem::Exception, "Unable to find '#{name}'"
- @spec
- end
-
- def download(spec, dir=nil)
- return @path if spec == @spec
- raise Gem::Exception, "Unable to download '#{spec.full_name}'"
- end
-
-end
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 3d484d1c13..2ed7d4286a 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -38,7 +38,6 @@ class Gem::SpecFetcher
end
def initialize
- @dir = File.join Gem.user_home, '.gem', 'specs'
@update_cache = File.stat(Gem.user_home).uid == Process.uid
@specs = {}
@@ -75,6 +74,12 @@ class Gem::SpecFetcher
list, errors = available_specs(type)
list.each do |source, specs|
+ if dependency.name.is_a?(String) && specs.respond_to?(:bsearch)
+ start_index = (0 ... specs.length).bsearch{ |i| specs[i].name >= dependency.name }
+ end_index = (0 ... specs.length).bsearch{ |i| specs[i].name > dependency.name }
+ specs = specs[start_index ... end_index] if start_index && end_index
+ end
+
found[source] = specs.select do |tup|
if dependency.match?(tup)
if matching_platform and !Gem::Platform.match(tup.platform)
@@ -195,8 +200,11 @@ class Gem::SpecFetcher
when :released
tuples_for source, :released
when :complete
- tuples_for(source, :prerelease, true) +
+ names =
+ tuples_for(source, :prerelease, true) +
tuples_for(source, :released)
+
+ names.sort
when :prerelease
tuples_for(source, :prerelease)
else
@@ -215,15 +223,15 @@ class Gem::SpecFetcher
def tuples_for(source, type, gracefully_ignore=false)
cache = @caches[type]
- if gracefully_ignore
+ tuples =
begin
cache[source.uri] ||= source.load_specs(type)
rescue Gem::RemoteFetcher::FetchError
+ raise unless gracefully_ignore
[]
end
- else
- cache[source.uri] ||= source.load_specs(type)
- end
+
+ tuples.sort_by { |tup| tup.name }
end
end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 87557cee5f..12943a3e24 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -5,10 +5,13 @@
# See LICENSE.txt for permissions.
#++
+
require 'rubygems/version'
require 'rubygems/requirement'
require 'rubygems/platform'
require 'rubygems/deprecate'
+require 'rubygems/basic_specification'
+require 'rubygems/stub_specification'
# :stopdoc:
# date.rb can't be loaded for `make install` due to miniruby
@@ -31,7 +34,7 @@ class Date; end
# s.homepage = 'https://rubygems.org/gems/example'
# end
#
-# Starting in RubyGems 1.9.0, a Specification can hold arbitrary
+# Starting in RubyGems 2.0, a Specification can hold arbitrary
# metadata. This metadata is accessed via Specification#metadata
# and has the following restrictions:
#
@@ -45,7 +48,7 @@ class Date; end
#
# s.metadata = { "bugtracker" => "http://somewhere.com/blah" }
-class Gem::Specification
+class Gem::Specification < Gem::BasicSpecification
# REFACTOR: Consider breaking out this version stuff into a separate
# module. There's enough special stuff around it that it may justify
@@ -107,6 +110,10 @@ class Gem::Specification
today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day)
+ LOAD_CACHE = {}
+
+ private_constant :LOAD_CACHE if defined? private_constant
+
# :startdoc:
##
@@ -156,6 +163,17 @@ class Gem::Specification
:version => nil,
}
+ Dupable = { }
+
+ @@default_value.each do |k,v|
+ case v
+ when Time, Numeric, Symbol, true, false, nil
+ Dupable[k] = false
+ else
+ Dupable[k] = true
+ end
+ end
+
@@attributes = @@default_value.keys.sort_by { |s| s.to_s }
@@array_attributes = @@default_value.reject { |k,v| v != [] }.keys
@@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition { |k|
@@ -440,7 +458,7 @@ class Gem::Specification
#
# For example, the rake gem has rake as an executable. You don’t specify the
# full path (as in bin/rake); all application-style files are expected to be
- # found in bindir. These files must be executable ruby files. Files that
+ # found in bindir. These files must be executable Ruby files. Files that
# use bash or other interpreters will not work.
#
# Usage:
@@ -599,17 +617,12 @@ class Gem::Specification
attr_writer :default_executable
##
- # Path this gemspec was loaded from. This attribute is not persisted.
-
- attr_reader :loaded_from
-
- ##
# Allows deinstallation of gems with legacy platforms.
attr_writer :original_platform # :nodoc:
##
- # The version of ruby required by this gem
+ # The version of Ruby required by this gem
attr_reader :required_ruby_version
@@ -633,58 +646,68 @@ class Gem::Specification
attr_accessor :specification_version
- class << self
- def default_specifications_dir
- File.join(Gem.default_dir, "specifications", "default")
+ def self._all # :nodoc:
+ unless defined?(@@all) && @@all then
+ @@all = stubs.map(&:to_spec)
+
+ # After a reset, make sure already loaded specs
+ # are still marked as activated.
+ specs = {}
+ Gem.loaded_specs.each_value{|s| specs[s] = true}
+ @@all.each{|s| s.activated = true if specs[s]}
+
+ _resort!(@@all)
end
+ @@all
+ end
- def each_spec(search_dirs) # :nodoc:
- search_dirs.each { |dir|
- Dir[File.join(dir, "*.gemspec")].each { |path|
- spec = Gem::Specification.load path.untaint
- # #load returns nil if the spec is bad, so we just ignore
- # it at this stage
- yield(spec) if spec
- }
- }
+ def self._clear_load_cache # :nodoc:
+ LOAD_CACHE.clear
+ end
+
+ # :nodoc:
+ def self.each_gemspec(dirs)
+ dirs.each do |dir|
+ Dir[File.join(dir, "*.gemspec")].each do |path|
+ yield path.untaint
+ end
end
+ end
- def each_default(&block) # :nodoc:
- each_spec([default_specifications_dir],
- &block)
+ # :nodoc:
+ def self.each_stub(dirs)
+ each_gemspec(dirs) do |path|
+ stub = Gem::StubSpecification.new(path)
+ yield stub if stub.valid?
end
+ end
- def each_normal(&block) # :nodoc:
- each_spec(dirs, &block)
+ # :nodoc:
+ def self.each_spec(dirs)
+ each_gemspec(dirs) do |path|
+ spec = self.load path
+ yield spec if spec
end
end
- def self._all # :nodoc:
- unless defined?(@@all) && @@all then
+ ##
+ # Returns a Gem::StubSpecification for every installed gem
- specs = {}
- each_default do |spec|
- specs[spec.full_name] ||= spec
+ def self.stubs
+ @@stubs ||= begin
+ stubs = {}
+ each_stub([default_specifications_dir] + dirs) do |stub|
+ stubs[stub.full_name] ||= stub
end
- each_normal do |spec|
- specs[spec.full_name] ||= spec
- end
-
- @@all = specs.values
- # After a reset, make sure already loaded specs
- # are still marked as activated.
- specs = {}
- Gem.loaded_specs.each_value{|s| specs[s] = true}
- @@all.each{|s| s.activated = true if specs[s]}
-
- _resort!
+ stubs = stubs.values
+ _resort!(stubs)
+ stubs
end
- @@all
end
- def self._resort! # :nodoc:
- @@all.sort! { |a, b|
+ def self._resort!(specs) # :nodoc:
+ specs.sort! { |a, b|
names = a.name <=> b.name
next names if names.nonzero?
b.version <=> a.version
@@ -695,7 +718,9 @@ class Gem::Specification
# Loads the default specifications. It should be called only once.
def self.load_defaults
- each_default do |spec|
+ each_spec([default_specifications_dir]) do |spec|
+ # #load returns nil if the spec is bad, so we just ignore
+ # it at this stage
Gem.register_default_spec(spec)
end
end
@@ -718,7 +743,9 @@ class Gem::Specification
return if _all.include? spec
_all << spec
- _resort!
+ stubs << spec
+ _resort!(_all)
+ _resort!(stubs)
end
##
@@ -759,7 +786,7 @@ class Gem::Specification
# -- wilsonb
def self.all= specs
- @@all = specs
+ @@all = @@stubs = specs
end
##
@@ -861,9 +888,10 @@ class Gem::Specification
# amongst the specs that are not activated.
def self.find_inactive_by_path path
- self.find { |spec|
- spec.contains_requirable_file? path unless spec.activated?
+ stub = stubs.find { |s|
+ s.contains_requirable_file? path unless s.activated?
}
+ stub && stub.to_spec
end
##
@@ -944,7 +972,7 @@ class Gem::Specification
result.map(&:last).map(&:values).flatten.reject { |spec|
minimum = native[spec.name]
minimum && spec.version < minimum
- }
+ }.sort_by{ |tup| tup.name }
end
##
@@ -955,6 +983,9 @@ class Gem::Specification
file = file.dup.untaint
return unless File.file?(file)
+ spec = LOAD_CACHE[file]
+ return spec if spec
+
code = if defined? Encoding
File.read file, :mode => 'r:UTF-8:-'
else
@@ -968,6 +999,7 @@ class Gem::Specification
if Gem::Specification === spec
spec.loaded_from = file.to_s
+ LOAD_CACHE[file] = spec
return spec
end
@@ -1002,25 +1034,43 @@ class Gem::Specification
end
##
- # Return a list of all outdated specifications. This method is HEAVY
+ # Return a list of all outdated local gem names. This method is HEAVY
# as it must go fetch specifications from the server.
+ #
+ # Use outdated_and_latest_version if you wish to retrieve the latest remote
+ # version as well.
def self.outdated
- outdateds = []
+ outdated_and_latest_version.map { |local, _| local.name }
+ end
+
+ ##
+ # Enumerates the outdated local gems yielding the local specification and
+ # the latest remote version.
+ #
+ # This method may take some time to return as it must check each local gem
+ # against the server's index.
+
+ def self.outdated_and_latest_version
+ return enum_for __method__ unless block_given?
# TODO: maybe we should switch to rubygems' version service?
fetcher = Gem::SpecFetcher.fetcher
- latest_specs(true).each do |local|
- dependency = Gem::Dependency.new local.name, ">= #{local.version}"
- remotes, _ = fetcher.search_for_dependency dependency
- remotes = remotes.map { |n, _| n.version }
- latest = remotes.sort.last
+ latest_specs(true).each do |local_spec|
+ dependency =
+ Gem::Dependency.new local_spec.name, ">= #{local_spec.version}"
- outdateds << local.name if latest and local.version < latest
+ remotes, = fetcher.search_for_dependency dependency
+ remotes = remotes.map { |n, _| n.version }
+
+ latest_remote = remotes.sort.last
+
+ yield [local_spec, latest_remote] if
+ latest_remote and local_spec.version < latest_remote
end
- outdateds
+ nil
end
##
@@ -1031,6 +1081,7 @@ class Gem::Specification
raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless
_all.include? spec
_all.delete spec
+ stubs.delete_if { |s| s.full_name == spec.full_name }
end
##
@@ -1055,6 +1106,8 @@ class Gem::Specification
@@dirs = nil
Gem.pre_reset_hooks.each { |hook| hook.call }
@@all = nil
+ @@stubs = nil
+ _clear_load_cache
unresolved = unresolved_deps
unless unresolved.empty? then
w = "W" + "ARN"
@@ -1301,20 +1354,6 @@ class Gem::Specification
end
##
- # Returns the full path to the base gem directory.
- #
- # eg: /usr/local/lib/ruby/gems/1.8
-
- def base_dir
- return Gem.dir unless loaded_from
- @base_dir ||= if default_gem? then
- File.dirname File.dirname File.dirname loaded_from
- else
- File.dirname File.dirname loaded_from
- end
- end
-
- ##
# Returns the full path to installed gem's bin directory.
#
# NOTE: do not confuse this with +bindir+, which is just 'bin', not
@@ -1388,19 +1427,6 @@ class Gem::Specification
end
##
- # Return true if this spec can require +file+.
-
- def contains_requirable_file? file
- root = full_gem_path
- suffixes = Gem.suffixes
-
- require_paths.any? do |lib|
- base = "#{root}/#{lib}/#{file}"
- suffixes.any? { |suf| File.file? "#{base}#{suf}" }
- end
- end
-
- ##
# The date this gem was created. Lazily defaults to the current UTC date.
#
# There is no need to set this in your gem specification.
@@ -1645,35 +1671,14 @@ class Gem::Specification
spec
end
- ##
- # The full path to the gem (install path + full name).
-
- def full_gem_path
- # TODO: This is a heavily used method by gems, so we'll need
- # to aleast just alias it to #gem_dir rather than remove it.
-
- # TODO: also, shouldn't it default to full_name if it hasn't been written?
- return @full_gem_path if defined?(@full_gem_path) && @full_gem_path
-
- @full_gem_path = File.expand_path File.join(gems_dir, full_name)
- @full_gem_path.untaint
-
- return @full_gem_path if File.directory? @full_gem_path
-
- @full_gem_path = File.expand_path File.join(gems_dir, original_name)
+ # :nodoc:
+ def find_full_gem_path
+ super || File.expand_path(File.join(gems_dir, original_name))
end
-
- ##
- # Returns the full name (name-version) of this Gem. Platform information
- # is included (name-version-platform) if it is specified and not the
- # default Ruby platform.
+ private :find_full_gem_path
def full_name
- @full_name ||= if platform == Gem::Platform::RUBY or platform.nil? then
- "#{@name}-#{@version}".untaint
- else
- "#{@name}-#{@version}-#{platform}".untaint
- end
+ @full_name ||= super
end
##
@@ -1685,15 +1690,6 @@ class Gem::Specification
end
##
- # Returns the full path to the gems directory containing this spec's
- # gem directory. eg: /usr/local/lib/ruby/1.8/gems
-
- def gems_dir
- # TODO: this logic seems terribly broken, but tests fail if just base_dir
- @gems_dir ||= File.join(loaded_from && base_dir || Gem.dir, "gems")
- end
-
- ##
# Deprecated and ignored, defaults to true.
#
# Formerly used to indicate this gem was RDoc-capable.
@@ -1725,9 +1721,7 @@ class Gem::Specification
# :startdoc:
def hash # :nodoc:
- @@attributes.inject(0) { |hash_code, (name, _)|
- hash_code ^ self.send(name).hash
- }
+ name.hash ^ version.hash
end
def init_with coder # :nodoc:
@@ -1742,7 +1736,7 @@ class Gem::Specification
def initialize name = nil, version = nil
@loaded = false
@activated = false
- @loaded_from = nil
+ self.loaded_from = nil
@original_platform = nil
@@nil_attributes.each do |key|
@@ -1751,11 +1745,7 @@ class Gem::Specification
@@non_nil_attributes.each do |key|
default = default_value(key)
- value = case default
- when Time, Numeric, Symbol, true, false, nil then default
- else default.dup
- end
-
+ value = Dupable[key] ? default.dup : default
instance_variable_set "@#{key}", value
end
@@ -1850,22 +1840,14 @@ class Gem::Specification
@licenses ||= []
end
- ##
- # Set the location a Specification was loaded from. +obj+ is converted
- # to a String.
-
- def loaded_from= path
- @loaded_from = path.to_s
+ def loaded_from= path # :nodoc:
+ super
- # reset everything @loaded_from depends upon
- @base_dir = nil
@bin_dir = nil
@cache_dir = nil
@cache_file = nil
@doc_dir = nil
- @full_gem_path = nil
@gem_dir = nil
- @gems_dir = nil
@ri_dir = nil
@spec_dir = nil
@spec_file = nil
@@ -2050,6 +2032,10 @@ class Gem::Specification
@requirements = Array req
end
+ def respond_to_missing? m, include_private = false # :nodoc:
+ false
+ end
+
##
# Returns the full path to this spec's ri directory.
@@ -2111,11 +2097,17 @@ class Gem::Specification
# Returns an object you can use to sort specifications in #sort_by.
def sort_obj
- # TODO: this is horrible. Deprecate it.
[@name, @version, @new_platform == Gem::Platform::RUBY ? -1 : 1]
end
##
+ # Used by Gem::DependencyResolver to order Gem::Specification objects
+
+ def source # :nodoc:
+ self
+ end
+
+ ##
# Returns the full path to the directory containing this spec's
# gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications
@@ -2194,6 +2186,7 @@ class Gem::Specification
mark_version
result = []
result << "# -*- encoding: utf-8 -*-"
+ result << "#{Gem::StubSpecification::PREFIX}#{name} #{version} #{platform} #{require_paths.join("\0")}"
result << nil
result << "Gem::Specification.new do |s|"
@@ -2281,6 +2274,13 @@ class Gem::Specification
"#<Gem::Specification name=#{@name} version=#{@version}>"
end
+ ##
+ # Returns self
+
+ def to_spec
+ self
+ end
+
def to_yaml(opts = {}) # :nodoc:
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? then
# Because the user can switch the YAML engine behind our
@@ -2581,11 +2581,6 @@ class Gem::Specification
end
end
- def default_gem?
- loaded_from &&
- File.dirname(loaded_from) == self.class.default_specifications_dir
- end
-
extend Gem::Deprecate
# TODO:
diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb
index 7d087afade..d6c1a36ad1 100644
--- a/lib/rubygems/test_case.rb
+++ b/lib/rubygems/test_case.rb
@@ -58,7 +58,7 @@ module Gem
end
##
- # Allows setting path to ruby. This method is available when requiring
+ # Allows setting path to Ruby. This method is available when requiring
# 'rubygems/test_case'
def self.ruby= ruby
@@ -84,6 +84,23 @@ end
class Gem::TestCase < MiniTest::Unit::TestCase
+ def assert_activate expected, *specs
+ specs.each do |spec|
+ case spec
+ when String then
+ Gem::Specification.find_by_name(spec).activate
+ when Gem::Specification then
+ spec.activate
+ else
+ flunk spec.inspect
+ end
+ end
+
+ loaded = Gem.loaded_specs.values.map(&:full_name)
+
+ assert_equal expected.sort, loaded.sort if expected
+ end
+
# TODO: move to minitest
def assert_path_exists path, msg = nil
msg = message(msg) { "Expected path '#{path}' to exist" }
@@ -206,10 +223,11 @@ class Gem::TestCase < MiniTest::Unit::TestCase
@gemhome = File.join @tempdir, 'gemhome'
@userhome = File.join @tempdir, 'userhome'
+ ENV["GEM_SPEC_CACHE"] = File.join @tempdir, 'spec_cache'
@orig_ruby = if ENV['RUBY'] then
- ruby = Gem.instance_variable_get :@ruby
- Gem.instance_variable_set :@ruby, ENV['RUBY']
+ ruby = Gem.ruby
+ Gem.ruby = ENV['RUBY']
ruby
end
@@ -227,6 +245,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase
FileUtils.mkdir_p @gemhome
FileUtils.mkdir_p @userhome
+ @orig_gem_private_key_passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
+ ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE
+
@default_dir = File.join @tempdir, 'default'
@default_spec_dir = File.join @default_dir, "specifications", "default"
Gem.instance_variable_set :@default_dir, @default_dir
@@ -272,39 +293,6 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
-
- # TODO: move to installer test cases
- Gem.post_build_hooks.clear
- Gem.post_install_hooks.clear
- Gem.done_installing_hooks.clear
- Gem.post_reset_hooks.clear
- Gem.post_uninstall_hooks.clear
- Gem.pre_install_hooks.clear
- Gem.pre_reset_hooks.clear
- Gem.pre_uninstall_hooks.clear
-
- # TODO: move to installer test cases
- Gem.post_build do |installer|
- @post_build_hook_arg = installer
- true
- end
-
- Gem.post_install do |installer|
- @post_install_hook_arg = installer
- end
-
- Gem.post_uninstall do |uninstaller|
- @post_uninstall_hook_arg = uninstaller
- end
-
- Gem.pre_install do |installer|
- @pre_install_hook_arg = installer
- true
- end
-
- Gem.pre_uninstall do |uninstaller|
- @pre_uninstall_hook_arg = uninstaller
- end
end
##
@@ -328,8 +316,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase
ENV['GEM_HOME'] = @orig_gem_home
ENV['GEM_PATH'] = @orig_gem_path
- _ = @orig_ruby
- Gem.instance_variable_set :@ruby, @orig_ruby if @orig_ruby
+ Gem.ruby = @orig_ruby if @orig_ruby
if @orig_ENV_HOME then
ENV['HOME'] = @orig_ENV_HOME
@@ -338,6 +325,47 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
Gem.instance_variable_set :@default_dir, nil
+
+ ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = @orig_gem_private_key_passphrase
+
+ Gem::Specification._clear_load_cache
+ end
+
+ def common_installer_setup
+ common_installer_teardown
+
+ Gem.post_build do |installer|
+ @post_build_hook_arg = installer
+ true
+ end
+
+ Gem.post_install do |installer|
+ @post_install_hook_arg = installer
+ end
+
+ Gem.post_uninstall do |uninstaller|
+ @post_uninstall_hook_arg = uninstaller
+ end
+
+ Gem.pre_install do |installer|
+ @pre_install_hook_arg = installer
+ true
+ end
+
+ Gem.pre_uninstall do |uninstaller|
+ @pre_uninstall_hook_arg = uninstaller
+ end
+ end
+
+ def common_installer_teardown
+ Gem.post_build_hooks.clear
+ Gem.post_install_hooks.clear
+ Gem.done_installing_hooks.clear
+ Gem.post_reset_hooks.clear
+ Gem.post_uninstall_hooks.clear
+ Gem.pre_install_hooks.clear
+ Gem.pre_reset_hooks.clear
+ Gem.pre_uninstall_hooks.clear
end
##
@@ -566,6 +594,21 @@ class Gem::TestCase < MiniTest::Unit::TestCase
end
end
+ def loaded_spec_names
+ Gem.loaded_specs.values.map(&:full_name).sort
+ end
+
+ def unresolved_names
+ Gem::Specification.unresolved_deps.values.map(&:to_s).sort
+ end
+
+ def save_loaded_features
+ old_loaded_features = $LOADED_FEATURES.dup
+ yield
+ ensure
+ $LOADED_FEATURES.replace old_loaded_features
+ end
+
##
# Create a new spec (or gem if passed an array of files) and set it
# up properly. Use this instead of util_spec and util_gem.
@@ -742,7 +785,7 @@ Also, a list:
@a_evil9 = quick_gem('a_evil', '9', &init)
@b2 = quick_gem('b', '2', &init)
@c1_2 = quick_gem('c', '1.2', &init)
- @x = quick_gem('x', '1', &init)
+ @x = quick_gem('x', '1', &init)
@dep_x = quick_gem('dep_x', '1') do |s|
s.files = %w[lib/code.rb]
s.require_paths = %w[lib]
@@ -762,14 +805,15 @@ Also, a list:
util_build_gem @a2_pre
end
- write_file File.join(*%W[gems #{@a1.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@a2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@b2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@x.original_name} lib code.rb])
- write_file File.join(*%W[gems #{@dep_x.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@a1.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@a2.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@a3a.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@a_evil9.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@b2.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@c1_2.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@pl1.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@x.original_name} lib code.rb])
+ write_file File.join(*%W[gems #{@dep_x.original_name} lib code.rb])
[@a1, @a2, @a3a, @a_evil9, @b2, @c1_2, @pl1, @x, @dep_x].each do |spec|
util_build_gem spec
@@ -972,7 +1016,7 @@ Also, a list:
end
##
- # Finds the path to the ruby executable
+ # Finds the path to the Ruby executable
def self.rubybin
ruby = ENV["RUBY"]
@@ -1012,6 +1056,24 @@ Also, a list:
end
##
+ # Constructs a Gem::DependencyResolver::DependencyRequest from a
+ # Gem::Dependency +dep+, a +from_name+ and +from_version+ requesting the
+ # dependency and a +parent+ DependencyRequest
+
+ def dependency_request dep, from_name, from_version, parent = nil
+ remote = Gem::Source.new @uri
+
+ parent ||= Gem::DependencyResolver::DependencyRequest.new \
+ dep, nil
+
+ spec = Gem::DependencyResolver::IndexSpecification.new \
+ nil, from_name, from_version, remote, Gem::Platform::RUBY
+ activation = Gem::DependencyResolver::ActivationRequest.new spec, parent
+
+ Gem::DependencyResolver::DependencyRequest.new dep, activation
+ end
+
+ ##
# Constructs a new Gem::Requirement.
def req *requirements
@@ -1035,7 +1097,11 @@ Also, a list:
class StaticSet
def initialize(specs)
- @specs = specs.sort_by { |s| s.full_name }
+ @specs = specs
+ end
+
+ def add spec
+ @specs << spec
end
def find_spec(dep)
@@ -1048,6 +1114,15 @@ Also, a list:
@specs.find_all { |s| dep.matches_spec? s }
end
+ def load_spec name, ver, platform, source
+ dep = Gem::Dependency.new name, ver
+ spec = find_spec dep
+
+ Gem::Specification.new spec.name, spec.version do |s|
+ s.platform = spec.platform
+ end
+ end
+
def prefetch(reqs)
end
end
@@ -1080,18 +1155,18 @@ Also, a list:
end
##
- # Loads an RSA private key named +key_name+ in <tt>test/rubygems/</tt>
+ # Loads an RSA private key named +key_name+ with +passphrase+ in <tt>test/rubygems/</tt>
- def self.load_key key_name
+ def self.load_key key_name, passphrase = nil
key_file = key_path key_name
key = File.read key_file
- OpenSSL::PKey::RSA.new key
+ OpenSSL::PKey::RSA.new key, passphrase
end
##
- # Returns the path tot he key named +key_name+ from <tt>test/rubygems</tt>
+ # Returns the path to the key named +key_name+ from <tt>test/rubygems</tt>
def self.key_path key_name
File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__
@@ -1100,17 +1175,24 @@ Also, a list:
# :stopdoc:
# only available in RubyGems tests
+ PRIVATE_KEY_PASSPHRASE = 'Foo bar'
+
begin
- PRIVATE_KEY = load_key 'private'
- PRIVATE_KEY_PATH = key_path 'private'
- PUBLIC_KEY = PRIVATE_KEY.public_key
+ PRIVATE_KEY = load_key 'private'
+ PRIVATE_KEY_PATH = key_path 'private'
- PUBLIC_CERT = load_cert 'public'
- PUBLIC_CERT_PATH = cert_path 'public'
+ # ENCRYPTED_PRIVATE_KEY is PRIVATE_KEY encrypted with PRIVATE_KEY_PASSPHRASE
+ ENCRYPTED_PRIVATE_KEY = load_key 'encrypted_private', PRIVATE_KEY_PASSPHRASE
+ ENCRYPTED_PRIVATE_KEY_PATH = key_path 'encrypted_private'
+
+ PUBLIC_KEY = PRIVATE_KEY.public_key
+
+ PUBLIC_CERT = load_cert 'public'
+ PUBLIC_CERT_PATH = cert_path 'public'
rescue Errno::ENOENT
PRIVATE_KEY = nil
PUBLIC_KEY = nil
PUBLIC_CERT = nil
- end
+ end if defined?(OpenSSL::SSL)
end
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index d672b9dec1..143ab6df26 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -43,14 +43,15 @@ class Gem::Uninstaller
def initialize(gem, options = {})
# TODO document the valid options
- @gem = gem
- @version = options[:version] || Gem::Requirement.default
- @gem_home = File.expand_path(options[:install_dir] || Gem.dir)
- @force_executables = options[:executables]
- @force_all = options[:all]
- @force_ignore = options[:ignore]
- @bin_dir = options[:bin_dir]
- @format_executable = options[:format_executable]
+ @gem = gem
+ @version = options[:version] || Gem::Requirement.default
+ @gem_home = File.expand_path(options[:install_dir] || Gem.dir)
+ @force_executables = options[:executables]
+ @force_all = options[:all]
+ @force_ignore = options[:ignore]
+ @bin_dir = options[:bin_dir]
+ @format_executable = options[:format_executable]
+ @abort_on_dependent = options[:abort_on_dependent]
# Indicate if development dependencies should be checked when
# uninstalling. (default: false)
@@ -143,7 +144,7 @@ class Gem::Uninstaller
@spec = spec
unless dependencies_ok? spec
- unless ask_if_ok(spec)
+ if abort_on_dependent? || !ask_if_ok(spec)
raise Gem::DependencyRemovalException,
"Uninstallation aborted due to dependent gem(s)"
end
@@ -290,6 +291,10 @@ class Gem::Uninstaller
deplist.ok_to_remove?(spec.full_name, @check_dev)
end
+ def abort_on_dependent?
+ @abort_on_dependent
+ end
+
def ask_if_ok(spec)
msg = ['']
msg << 'You have requested to uninstall the gem:'
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index bbf04f5403..2e546462d4 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -147,13 +147,16 @@ class Gem::Version
# FIX: These are only used once, in .correct?. Do they deserve to be
# constants?
- VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*' # :nodoc:
+ VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?' # :nodoc:
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
##
# A string representation of this Version.
- attr_reader :version
+ def version
+ @version.dup
+ end
+
alias to_s version
##
@@ -183,6 +186,12 @@ class Gem::Version
end
end
+ @@all = {}
+
+ def self.new version
+ @@all[version] ||= super
+ end
+
##
# Constructs a Version from the +version+ string. A version string is a
# series of digits or ASCII letters separated by dots.
@@ -191,7 +200,8 @@ class Gem::Version
raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version)
- @version = version.to_s.dup.strip
+ @version = version.to_s.strip.gsub("-",".pre.")
+ @segments = nil
end
##
diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb
index a3de4dc9e7..a0755d5020 100644
--- a/lib/rubygems/version_option.rb
+++ b/lib/rubygems/version_option.rb
@@ -42,6 +42,7 @@ module Gem::VersionOption
add_option("--[no-]prerelease",
"Allow prerelease versions of a gem", *wrap) do |value, options|
options[:prerelease] = value
+ options[:explicit_prerelease] = true
end
end
@@ -50,14 +51,19 @@ module Gem::VersionOption
def add_version_option(task = command, *wrap)
OptionParser.accept Gem::Requirement do |value|
- Gem::Requirement.new value
+ Gem::Requirement.new(*value.split(/\s*,\s*/))
end
add_option('-v', '--version VERSION', Gem::Requirement,
"Specify version of gem to #{task}", *wrap) do
|value, options|
options[:version] = value
- options[:prerelease] = true if value.prerelease?
+
+ explicit_prerelease_set = !options[:explicit_prerelease].nil?
+ options[:explicit_prerelease] = false unless explicit_prerelease_set
+
+ options[:prerelease] = value.prerelease? unless
+ options[:explicit_prerelease]
end
end