summaryrefslogtreecommitdiff
path: root/lib/rubygems/commands
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/commands')
-rw-r--r--lib/rubygems/commands/build_command.rb91
-rw-r--r--lib/rubygems/commands/cert_command.rb213
-rw-r--r--lib/rubygems/commands/check_command.rb57
-rw-r--r--lib/rubygems/commands/cleanup_command.rb105
-rw-r--r--lib/rubygems/commands/contents_command.rb93
-rw-r--r--lib/rubygems/commands/dependency_command.rb118
-rw-r--r--lib/rubygems/commands/environment_command.rb54
-rw-r--r--lib/rubygems/commands/exec_command.rb259
-rw-r--r--lib/rubygems/commands/fetch_command.rb69
-rw-r--r--lib/rubygems/commands/generate_index_command.rb114
-rw-r--r--lib/rubygems/commands/help_command.rb50
-rw-r--r--lib/rubygems/commands/info_command.rb38
-rw-r--r--lib/rubygems/commands/install_command.rb165
-rw-r--r--lib/rubygems/commands/list_command.rb19
-rw-r--r--lib/rubygems/commands/lock_command.rb26
-rw-r--r--lib/rubygems/commands/mirror_command.rb8
-rw-r--r--lib/rubygems/commands/open_command.rb52
-rw-r--r--lib/rubygems/commands/outdated_command.rb12
-rw-r--r--lib/rubygems/commands/owner_command.rb80
-rw-r--r--lib/rubygems/commands/pristine_command.rb196
-rw-r--r--lib/rubygems/commands/push_command.rb169
-rw-r--r--lib/rubygems/commands/query_command.rb359
-rw-r--r--lib/rubygems/commands/rdoc_command.rb69
-rw-r--r--lib/rubygems/commands/rebuild_command.rb261
-rw-r--r--lib/rubygems/commands/search_command.rb18
-rw-r--r--lib/rubygems/commands/server_command.rb91
-rw-r--r--lib/rubygems/commands/setup_command.rb614
-rw-r--r--lib/rubygems/commands/signin_command.rb23
-rw-r--r--lib/rubygems/commands/signout_command.rb21
-rw-r--r--lib/rubygems/commands/sources_command.rb251
-rw-r--r--lib/rubygems/commands/specification_command.rb74
-rw-r--r--lib/rubygems/commands/stale_command.rb11
-rw-r--r--lib/rubygems/commands/uninstall_command.rb166
-rw-r--r--lib/rubygems/commands/unpack_command.rb91
-rw-r--r--lib/rubygems/commands/update_command.rb243
-rw-r--r--lib/rubygems/commands/which_command.rb29
-rw-r--r--lib/rubygems/commands/yank_command.rb49
37 files changed, 2506 insertions, 1852 deletions
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb
index 38c45e46f0..cfe1f8ec3c 100644
--- a/lib/rubygems/commands/build_command.rb
+++ b/lib/rubygems/commands/build_command.rb
@@ -1,15 +1,30 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/package'
+
+require_relative "../command"
+require_relative "../gemspec_helpers"
+require_relative "../package"
+require_relative "../version_option"
class Gem::Commands::BuildCommand < Gem::Command
+ include Gem::VersionOption
+ include Gem::GemspecHelpers
def initialize
- super 'build', 'Build a gem from a gemspec'
+ super "build", "Build a gem from a gemspec"
+
+ add_platform_option
- add_option '--force', 'skip validation of the spec' do |value, options|
+ add_option "--force", "skip validation of the spec" do |_value, options|
options[:force] = true
end
+
+ add_option "--strict", "consider warnings as errors when validating the spec" do |_value, options|
+ options[:strict] = true
+ end
+
+ add_option "-o", "--output FILE", "output gem with the given filename" do |value, options|
+ options[:output] = value
+ end
end
def arguments # :nodoc:
@@ -32,6 +47,11 @@ with gem spec:
$ cd my_gem-1.0
[edit gem contents]
$ gem build my_gem-1.0.gemspec
+
+Gems can be saved to a specified filename with the output option:
+
+ $ gem build my_gem-1.0.gemspec --output=release.gem
+
EOF
end
@@ -40,26 +60,61 @@ with gem spec:
end
def execute
- gemspec = get_one_gem_name
-
- unless File.exist? gemspec
- gemspec += '.gemspec' if File.exist? gemspec + '.gemspec'
+ if build_path = options[:build_path]
+ Dir.chdir(build_path) { build_gem }
+ return
end
- if File.exist? gemspec then
- spec = Gem::Specification.load gemspec
+ build_gem
+ end
+
+ private
+
+ def build_gem
+ gemspec = resolve_gem_name
+
+ if gemspec
+ build_package(gemspec)
+ else
+ alert_error error_message
+ terminate_interaction(1)
+ end
+ end
- if spec then
- Gem::Package.build spec, options[:force]
- else
- alert_error "Error loading gemspec. Aborting."
- terminate_interaction 1
- end
+ def build_package(gemspec)
+ spec = Gem::Specification.load(gemspec)
+ if spec
+ Gem::Package.build(
+ spec,
+ options[:force],
+ options[:strict],
+ options[:output]
+ )
else
- alert_error "Gemspec file not found: #{gemspec}"
+ alert_error "Error loading gemspec. Aborting."
terminate_interaction 1
end
end
-end
+ def resolve_gem_name
+ return find_gemspec unless gem_name
+
+ if File.exist?(gem_name)
+ gem_name
+ else
+ find_gemspec("#{gem_name}.gemspec") || find_gemspec(gem_name)
+ end
+ end
+ def error_message
+ if gem_name
+ "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}"
+ else
+ "Couldn't find a gemspec file in #{Dir.pwd}"
+ end
+ end
+
+ def gem_name
+ get_one_optional_argument
+ end
+end
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index 5542262a50..fe03841ddb 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -1,103 +1,113 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/security'
-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
+require_relative "../command"
+require_relative "../security"
+class Gem::Commands::CertCommand < Gem::Command
def initialize
- super 'cert', 'Manage RubyGems certificates and signing settings',
- :add => [], :remove => [], :list => [], :build => [], :sign => []
-
- OptionParser.accept OpenSSL::X509::Certificate do |certificate|
- begin
- OpenSSL::X509::Certificate.new File.read certificate
- rescue Errno::ENOENT
- raise OptionParser::InvalidArgument, "#{certificate}: does not exist"
- rescue OpenSSL::X509::CertificateError
- raise OptionParser::InvalidArgument,
- "#{certificate}: invalid X509 certificate"
- end
- end
-
- OptionParser.accept OpenSSL::PKey::RSA do |key_file|
- begin
- 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
- raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
- end
+ super "cert", "Manage RubyGems certificates and signing settings",
+ add: [], remove: [], list: [], build: [], sign: []
- raise OptionParser::InvalidArgument,
- "#{key_file}: private key not found" unless key.private?
-
- key
- end
-
- add_option('-a', '--add CERT', OpenSSL::X509::Certificate,
- 'Add a trusted certificate.') do |cert, options|
- options[:add] << cert
+ add_option("-a", "--add CERT",
+ "Add a trusted certificate.") do |cert_file, options|
+ options[:add] << open_cert(cert_file)
end
- add_option('-l', '--list [FILTER]',
- 'List trusted certificates where the',
- 'subject contains FILTER') do |filter, options|
- filter ||= ''
+ add_option("-l", "--list [FILTER]",
+ "List trusted certificates where the",
+ "subject contains FILTER") do |filter, options|
+ filter ||= ""
options[:list] << filter
end
- add_option('-r', '--remove FILTER',
- 'Remove trusted certificates where the',
- 'subject contains FILTER') do |filter, options|
+ add_option("-r", "--remove FILTER",
+ "Remove trusted certificates where the",
+ "subject contains FILTER") do |filter, options|
options[:remove] << filter
end
- add_option('-b', '--build EMAIL_ADDR',
- 'Build private key and self-signed',
- 'certificate for EMAIL_ADDR') do |email_address, options|
+ add_option("-b", "--build EMAIL_ADDR",
+ "Build private key and self-signed",
+ "certificate for EMAIL_ADDR") do |email_address, options|
options[:build] << email_address
end
- add_option('-C', '--certificate CERT', OpenSSL::X509::Certificate,
- 'Signing certificate for --sign') do |cert, options|
- options[:issuer_cert] = cert
+ add_option("-C", "--certificate CERT",
+ "Signing certificate for --sign") do |cert_file, options|
+ options[:issuer_cert] = open_cert(cert_file)
+ options[:issuer_cert_file] = cert_file
end
- add_option('-K', '--private-key KEY', OpenSSL::PKey::RSA,
- 'Key for --sign or --build') do |key, options|
- options[:key] = key
+ add_option("-K", "--private-key KEY",
+ "Key for --sign or --build") do |key_file, options|
+ options[:key] = open_private_key(key_file)
end
- add_option('-s', '--sign CERT',
- 'Signs CERT with the key from -K',
- 'and the certificate from -C') do |cert_file, options|
- raise OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
+ add_option("-A", "--key-algorithm ALGORITHM",
+ "Select which key algorithm to use for --build") do |algorithm, options|
+ options[:key_algorithm] = algorithm
+ end
+
+ add_option("-s", "--sign CERT",
+ "Signs CERT with the key from -K",
+ "and the certificate from -C") do |cert_file, options|
+ raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
File.file? cert_file
options[:sign] << cert_file
end
- add_option('-d', '--days NUMBER_OF_DAYS',
- 'Days before the certificate expires') do |days, options|
- options[:expiration_length_days] = days.to_i
+ add_option("-d", "--days NUMBER_OF_DAYS",
+ "Days before the certificate expires") do |days, options|
+ options[:expiration_length_days] = days.to_i
+ end
+
+ add_option("-R", "--re-sign",
+ "Re-signs the certificate from -C with the key from -K") do |resign, options|
+ options[:resign] = resign
end
end
- def add_certificate certificate # :nodoc:
+ def add_certificate(certificate) # :nodoc:
Gem::Security.trust_dir.trust_cert certificate
say "Added '#{certificate.subject}'"
end
+ def check_openssl
+ return if Gem::HAVE_OPENSSL
+
+ alert_error "OpenSSL library is required for the cert command"
+ terminate_interaction 1
+ end
+
+ def open_cert(certificate_file)
+ check_openssl
+ OpenSSL::X509::Certificate.new File.read certificate_file
+ rescue Errno::ENOENT
+ raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
+ rescue OpenSSL::X509::CertificateError
+ raise Gem::OptionParser::InvalidArgument,
+ "#{certificate_file}: invalid X509 certificate"
+ end
+
+ def open_private_key(key_file)
+ check_openssl
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
+ key = OpenSSL::PKey.read File.read(key_file), passphrase
+ raise Gem::OptionParser::InvalidArgument,
+ "#{key_file}: private key not found" unless key.private?
+ key
+ rescue Errno::ENOENT
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist"
+ rescue OpenSSL::PKey::PKeyError, ArgumentError
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
+ end
+
def execute
+ check_openssl
+
options[:add].each do |certificate|
add_certificate certificate
end
@@ -114,11 +124,19 @@ class Gem::Commands::CertCommand < Gem::Command
build email
end
+ if options[:resign]
+ re_sign_cert(
+ options[:issuer_cert],
+ options[:issuer_cert_file],
+ options[:key]
+ )
+ end
+
sign_certificates unless options[:sign].empty?
end
- def build email
- if !valid_email?(email)
+ def build(email)
+ unless valid_email?(email)
raise Gem::CommandLineError, "Invalid email address #{email}"
end
@@ -133,45 +151,46 @@ class Gem::Commands::CertCommand < Gem::Command
end
end
- def build_cert email, key # :nodoc:
- expiration_length_days = options[:expiration_length_days]
- age =
- if expiration_length_days.nil? || expiration_length_days == 0
- Gem::Security::ONE_YEAR
- else
- Gem::Security::ONE_DAY * expiration_length_days
- end
+ def build_cert(email, key) # :nodoc:
+ expiration_length_days = options[:expiration_length_days] ||
+ Gem.configuration.cert_expiration_length_days
+
+ cert = Gem::Security.create_cert_email(
+ email,
+ key,
+ Gem::Security::ONE_DAY * expiration_length_days
+ )
- cert = Gem::Security.create_cert_email email, key, age
Gem::Security.write cert, "gem-public_cert.pem"
end
def build_key # :nodoc:
return options[:key] if options[:key]
- passphrase = ask_for_password 'Passphrase for your Private Key:'
+ passphrase = ask_for_password "Passphrase for your Private Key:"
say "\n"
- passphrase_confirmation = ask_for_password 'Please repeat the passphrase for your Private Key:'
+ 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
+ algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM
+ key = Gem::Security.create_key(algorithm)
+ key_path = Gem::Security.write key, "gem-private_key.pem", 0o600, passphrase
- return key, key_path
+ [key, key_path]
end
- def certificates_matching filter
+ def certificates_matching(filter)
return enum_for __method__, filter unless block_given?
Gem::Security.trusted_certificates.select do |certificate, _|
subject = certificate.subject.to_s
subject.downcase.index filter
end.sort_by do |certificate, _|
- certificate.subject.to_a.map { |name, data,| [name, data] }
+ certificate.subject.to_a.map {|name, data,| [name, data] }
end.each do |certificate, path|
yield certificate, path
end
@@ -216,7 +235,7 @@ For further reading on signing gems see `ri Gem::Security`.
EOF
end
- def list_certificates_matching filter # :nodoc:
+ def list_certificates_matching(filter) # :nodoc:
certificates_matching filter do |certificate, _|
# this could probably be formatted more gracefully
say certificate.subject.to_s
@@ -242,14 +261,14 @@ 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
- passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- options[:key] = OpenSSL::PKey::RSA.new key, passphrase
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
+ options[:key] = OpenSSL::PKey.read key, passphrase
rescue Errno::ENOENT
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
terminate_interaction 1
- rescue OpenSSL::PKey::RSAError
+ rescue OpenSSL::PKey::PKeyError
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
@@ -261,18 +280,18 @@ For further reading on signing gems see `ri Gem::Security`.
load_default_key unless options[:key]
end
- def remove_certificates_matching filter # :nodoc:
+ 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
+ def sign(cert_file)
cert = File.read cert_file
cert = OpenSSL::X509::Certificate.new cert
- permissions = File.stat(cert_file).mode & 0777
+ permissions = File.stat(cert_file).mode & 0o777
issuer_cert = options[:issuer_cert]
issuer_key = options[:key]
@@ -290,13 +309,17 @@ For further reading on signing gems see `ri Gem::Security`.
end
end
+ def re_sign_cert(cert, cert_path, private_key)
+ Gem::Security::Signer.re_sign_cert(cert, cert_path, private_key) do |expired_cert_path, new_expired_cert_path|
+ alert("Your certificate #{expired_cert_path} has been re-signed")
+ alert("Your expired certificate will be located at: #{new_expired_cert_path}")
+ end
+ end
+
private
- def valid_email? email
+ def valid_email?(email)
# It's simple, but is all we need
email =~ /\A.+@.+\z/
end
-
-
-end if defined?(OpenSSL::SSL)
-
+end
diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb
index 818cb05f55..fb23dd9cb4 100644
--- a/lib/rubygems/commands/check_command.rb
+++ b/lib/rubygems/commands/check_command.rb
@@ -1,64 +1,68 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/validator'
-require 'rubygems/doctor'
-class Gem::Commands::CheckCommand < Gem::Command
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../validator"
+require_relative "../doctor"
+class Gem::Commands::CheckCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'check', 'Check a gem repository for added or missing files',
- :alien => true, :doctor => false, :dry_run => false, :gems => true
+ super "check", "Check a gem repository for added or missing files",
+ alien: true, doctor: false, dry_run: false, gems: true
- add_option('-a', '--[no-]alien',
+ add_option("-a", "--[no-]alien",
'Report "unmanaged" or rogue files in the',
- 'gem repository') do |value, options|
+ "gem repository") do |value, options|
options[:alien] = value
end
- add_option('--[no-]doctor',
- 'Clean up uninstalled gems and broken',
- 'specifications') do |value, options|
+ add_option("--[no-]doctor",
+ "Clean up uninstalled gems and broken",
+ "specifications") do |value, options|
options[:doctor] = value
end
- add_option('--[no-]dry-run',
- 'Do not remove files, only report what',
- 'would be removed') do |value, options|
+ add_option("--[no-]dry-run",
+ "Do not remove files, only report what",
+ "would be removed") do |value, options|
options[:dry_run] = value
end
- add_option('--[no-]gems',
- 'Check installed gems for problems') do |value, options|
+ add_option("--[no-]gems",
+ "Check installed gems for problems") do |value, options|
options[:gems] = value
end
- add_version_option 'check'
+ add_version_option "check"
end
def check_gems
- say 'Checking gems...'
+ say "Checking gems..."
say
- gems = get_all_gem_names rescue []
+ gems = begin
+ get_all_gem_names
+ rescue StandardError
+ []
+ end
Gem::Validator.new.alien(gems).sort.each do |key, val|
- unless val.empty? then
+ if val.empty?
+ say "#{key} is error-free" if Gem.configuration.verbose
+ else
say "#{key} has #{val.size} problems"
val.each do |error_entry|
say " #{error_entry.path}:"
say " #{error_entry.problem}"
end
- else
- say "#{key} is error-free" if Gem.configuration.verbose
end
say
end
end
def doctor
- say 'Checking for files from uninstalled gems...'
+ say "Checking for files from uninstalled gems..."
say
Gem.path.each do |gem_repo|
@@ -73,11 +77,11 @@ class Gem::Commands::CheckCommand < Gem::Command
end
def arguments # :nodoc:
- 'GEMNAME name of gem to check'
+ "GEMNAME name of gem to check"
end
def defaults_str # :nodoc:
- '--gems --alien'
+ "--gems --alien"
end
def description # :nodoc:
@@ -90,5 +94,4 @@ specifications and will clean up gems that have been partially uninstalled.
def usage # :nodoc:
"#{program_name} [OPTIONS] [GEMNAME ...]"
end
-
end
diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb
index db1bf3a794..c89a24eee9 100644
--- a/lib/rubygems/commands/cleanup_command.rb
+++ b/lib/rubygems/commands/cleanup_command.rb
@@ -1,26 +1,43 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/dependency_list'
-require 'rubygems/uninstaller'
-class Gem::Commands::CleanupCommand < Gem::Command
+require_relative "../command"
+require_relative "../dependency_list"
+require_relative "../uninstaller"
+class Gem::Commands::CleanupCommand < Gem::Command
def initialize
- super 'cleanup',
- 'Clean up old versions of installed gems',
- :force => false, :install_dir => Gem.dir
+ super "cleanup",
+ "Clean up old versions of installed gems",
+ force: false, install_dir: Gem.dir,
+ check_dev: true
+
+ add_option("-n", "-d", "--dry-run",
+ "Do not uninstall gems") do |_value, options|
+ options[:dryrun] = true
+ end
- add_option('-n', '-d', '--dryrun',
- 'Do not uninstall gems') do |value, options|
+ add_option(:Deprecated, "--dryrun",
+ "Do not uninstall gems") do |_value, options|
options[:dryrun] = true
end
+ deprecate_option("--dryrun", extra_msg: "Use --dry-run instead")
+
+ add_option("-D", "--[no-]check-development",
+ "Check development dependencies while uninstalling",
+ "(default: true)") do |value, options|
+ options[:check_dev] = value
+ end
+
+ add_option("--[no-]user-install",
+ "Cleanup in user's home directory instead",
+ "of GEM_HOME.") do |value, options|
+ options[:user_install] = value
+ end
@candidate_gems = nil
@default_gems = []
@full = nil
@gems_to_cleanup = nil
- @original_home = nil
- @original_path = nil
@primary_gems = nil
end
@@ -29,7 +46,7 @@ class Gem::Commands::CleanupCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--no-dryrun"
+ "--no-dry-run"
end
def description # :nodoc:
@@ -49,14 +66,14 @@ If no gems are named all gems in GEM_HOME are cleaned.
def execute
say "Cleaning up installed gems..."
- if options[:args].empty? then
+ if options[:args].empty?
done = false
last_set = nil
until done do
clean_gems
- this_set = @gems_to_cleanup.map { |spec| spec.full_name }.sort
+ this_set = @gems_to_cleanup.map(&:full_name).sort
done = this_set.empty? || last_set == this_set
@@ -69,16 +86,13 @@ If no gems are named all gems in GEM_HOME are cleaned.
say "Clean up complete"
verbose do
- skipped = @default_gems.map { |spec| spec.full_name }
+ skipped = @default_gems.map(&:full_name)
- "Skipped default gems: #{skipped.join ', '}"
+ "Skipped default gems: #{skipped.join ", "}"
end
end
def clean_gems
- @original_home = Gem.dir
- @original_path = Gem.path
-
get_primary_gems
get_candidate_gems
get_gems_to_cleanup
@@ -86,40 +100,37 @@ If no gems are named all gems in GEM_HOME are cleaned.
@full = Gem::DependencyList.from_specs
deplist = Gem::DependencyList.new
- @gems_to_cleanup.each do |spec| deplist.add spec end
+ @gems_to_cleanup.each {|spec| deplist.add spec }
deps = deplist.strongly_connected_components.flatten
deps.reverse_each do |spec|
uninstall_dep spec
end
-
- Gem::Specification.reset
end
def get_candidate_gems
- @candidate_gems = unless options[:args].empty? then
- options[:args].map do |gem_name|
- Gem::Specification.find_all_by_name gem_name
- end.flatten
- else
- Gem::Specification.to_a
- end
+ @candidate_gems = if options[:args].empty?
+ Gem::Specification.to_a
+ else
+ options[:args].flat_map do |gem_name|
+ Gem::Specification.find_all_by_name gem_name
+ end
+ end
end
def get_gems_to_cleanup
-
- gems_to_cleanup = @candidate_gems.select { |spec|
+ gems_to_cleanup = @candidate_gems.select do |spec|
@primary_gems[spec.name].version != spec.version
- }
+ end
- default_gems, gems_to_cleanup = gems_to_cleanup.partition { |spec|
- spec.default_gem?
- }
+ default_gems, gems_to_cleanup = gems_to_cleanup.partition(&:default_gem?)
- gems_to_cleanup = gems_to_cleanup.select { |spec|
- spec.base_dir == @original_home
- }
+ uninstall_from = options[:user_install] ? Gem.user_dir : Gem.dir
+
+ gems_to_cleanup = gems_to_cleanup.select do |spec|
+ spec.base_dir == uninstall_from
+ end
@default_gems += default_gems
@default_gems.uniq!
@@ -130,17 +141,17 @@ If no gems are named all gems in GEM_HOME are cleaned.
@primary_gems = {}
Gem::Specification.each do |spec|
- if @primary_gems[spec.name].nil? or
- @primary_gems[spec.name].version < spec.version then
+ if @primary_gems[spec.name].nil? ||
+ @primary_gems[spec.name].version < spec.version
@primary_gems[spec.name] = spec
end
end
end
- def uninstall_dep spec
- return unless @full.ok_to_remove?(spec.full_name)
+ def uninstall_dep(spec)
+ return unless @full.ok_to_remove?(spec.full_name, options[:check_dev])
- if options[:dryrun] then
+ if options[:dryrun]
say "Dry Run Mode: Would uninstall #{spec.full_name}"
return
end
@@ -148,8 +159,8 @@ If no gems are named all gems in GEM_HOME are cleaned.
say "Attempting to uninstall #{spec.full_name}"
uninstall_options = {
- :executables => false,
- :version => "= #{spec.version}",
+ executables: false,
+ version: "= #{spec.version}",
}
uninstall_options[:user_install] = Gem.user_dir == spec.base_dir
@@ -163,9 +174,5 @@ If no gems are named all gems in GEM_HOME are cleaned.
say "Unable to uninstall #{spec.full_name}:"
say "\t#{e.class}: #{e.message}"
end
- ensure
- # Restore path Gem::Uninstaller may have changed
- Gem.use_paths @original_home, *@original_path
end
-
end
diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb
index e0f2eedb5d..d4f9871868 100644
--- a/lib/rubygems/commands/contents_command.rb
+++ b/lib/rubygems/commands/contents_command.rb
@@ -1,41 +1,40 @@
# frozen_string_literal: true
-require 'English'
-require 'rubygems/command'
-require 'rubygems/version_option'
-class Gem::Commands::ContentsCommand < Gem::Command
+require_relative "../command"
+require_relative "../version_option"
+class Gem::Commands::ContentsCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'contents', 'Display the contents of the installed gems',
- :specdirs => [], :lib_only => false, :prefix => true,
- :show_install_dir => false
+ super "contents", "Display the contents of the installed gems",
+ specdirs: [], lib_only: false, prefix: true,
+ show_install_dir: false
add_version_option
- add_option( '--all',
+ add_option("--all",
"Contents for all gems") do |all, options|
options[:all] = all
end
- add_option('-s', '--spec-dir a,b,c', Array,
+ add_option("-s", "--spec-dir a,b,c", Array,
"Search for gems under specific paths") do |spec_dirs, options|
options[:specdirs] = spec_dirs
end
- add_option('-l', '--[no-]lib-only',
+ add_option("-l", "--[no-]lib-only",
"Only return files in the Gem's lib_dirs") do |lib_only, options|
options[:lib_only] = lib_only
end
- add_option( '--[no-]prefix',
+ add_option("--[no-]prefix",
"Don't include installed path prefix") do |prefix, options|
options[:prefix] = prefix
end
- add_option( '--[no-]show-install-dir',
- 'Show only the gem install dir') do |show, options|
+ add_option("--[no-]show-install-dir",
+ "Show only the gem install dir") do |show, options|
options[:show_install_dir] = show
end
@@ -73,49 +72,57 @@ prefix or only the files that are requireable.
names.each do |name|
found =
- if options[:show_install_dir] then
+ if options[:show_install_dir]
gem_install_dir name
else
gem_contents name
end
- terminate_interaction 1 unless found or names.length > 1
+ terminate_interaction 1 unless found || names.length > 1
end
end
- def files_in spec
- if spec.default_gem? then
+ def files_in(spec)
+ if spec.default_gem?
files_in_default_gem spec
else
files_in_gem spec
end
end
- def files_in_gem spec
+ def files_in_gem(spec)
gem_path = spec.full_gem_path
- extra = "/{#{spec.require_paths.join ','}}" if options[:lib_only]
+ extra = "/{#{spec.require_paths.join ","}}" if options[:lib_only]
glob = "#{gem_path}#{extra}/**/*"
- prefix_re = /#{Regexp.escape(gem_path)}\//
+ prefix_re = %r{#{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.map do |file|
- case file
- when /\A#{spec.bindir}\//
- [RbConfig::CONFIG['bindir'], $POSTMATCH]
- when /\.so\z/
- [RbConfig::CONFIG['archdir'], file]
+ def files_in_default_gem(spec)
+ spec.files.filter_map do |file|
+ if file.start_with?("#{spec.bindir}/")
+ [RbConfig::CONFIG["bindir"], file.delete_prefix("#{spec.bindir}/")]
else
- [RbConfig::CONFIG['rubylibdir'], file]
+ gem spec.name, spec.version
+
+ require_path = spec.require_paths.find do |path|
+ file.start_with?("#{path}/")
+ end
+
+ requirable_part = file.delete_prefix("#{require_path}/")
+
+ resolve = $LOAD_PATH.resolve_feature_path(requirable_part)&.last
+ next unless resolve
+
+ [resolve.delete_suffix(requirable_part), requirable_part]
end
end
end
- def gem_contents name
+ def gem_contents(name)
spec = spec_for name
return false unless spec
@@ -127,7 +134,7 @@ prefix or only the files that are requireable.
true
end
- def gem_install_dir name
+ def gem_install_dir(name)
spec = spec_for name
return false unless spec
@@ -138,27 +145,27 @@ prefix or only the files that are requireable.
end
def gem_names # :nodoc:
- if options[:all] then
+ if options[:all]
Gem::Specification.map(&:name)
else
get_all_gem_names
end
end
- def path_description spec_dirs # :nodoc:
- if spec_dirs.empty? then
+ def path_description(spec_dirs) # :nodoc:
+ if spec_dirs.empty?
"default gem paths"
else
"specified path"
end
end
- def show_files files
+ 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
+ if options[:prefix]
say absolute_path
else
say basename
@@ -166,26 +173,24 @@ prefix or only the files that are requireable.
end
end
- def spec_for name
- spec = Gem::Specification.find_all_by_name(name, @version).last
+ def spec_for(name)
+ spec = Gem::Specification.find_all_by_name(name, @version).first
return spec if spec
say "Unable to find gem '#{name}' in #{@path_kind}"
- if Gem.configuration.verbose then
+ if Gem.configuration.verbose
say "\nDirectories searched:"
- @spec_dirs.sort.each { |dir| say dir }
+ @spec_dirs.sort.each {|dir| say dir }
end
- return nil
+ nil
end
def specification_directories # :nodoc:
- options[:specdirs].map do |i|
+ options[:specdirs].flat_map do |i|
[i, File.join(i, "specifications")]
- end.flatten
+ end
end
-
end
-
diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb
index 97fd812ffa..9aaefae999 100644
--- a/lib/rubygems/commands/dependency_command.rb
+++ b/lib/rubygems/commands/dependency_command.rb
@@ -1,29 +1,28 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-class Gem::Commands::DependencyCommand < Gem::Command
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+class Gem::Commands::DependencyCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'dependency',
- 'Show the dependencies of an installed gem',
- :version => Gem::Requirement.default, :domain => :local
+ super "dependency",
+ "Show the dependencies of an installed gem",
+ version: Gem::Requirement.default, domain: :local
add_version_option
add_platform_option
add_prerelease_option
- add_option('-R', '--[no-]reverse-dependencies',
- 'Include reverse dependencies in the output') do
- |value, options|
+ add_option("-R", "--[no-]reverse-dependencies",
+ "Include reverse dependencies in the output") do |value, options|
options[:reverse_dependencies] = value
end
- add_option('-p', '--pipe',
+ add_option("-p", "--pipe",
"Pipe Format (name --version ver)") do |value, options|
options[:pipe_format] = value
end
@@ -54,57 +53,56 @@ use with other commands.
"#{program_name} REGEXP"
end
- def fetch_remote_specs dependency # :nodoc:
+ def fetch_remote_specs(name, requirement, prerelease) # :nodoc:
fetcher = Gem::SpecFetcher.fetcher
- ss, = fetcher.spec_for_dependency dependency
+ specs_type = prerelease ? :complete : :released
- ss.map { |spec, _| spec }
+ ss = if name.nil?
+ fetcher.detect(specs_type) { true }
+ else
+ fetcher.detect(specs_type) do |name_tuple|
+ name === name_tuple.name && requirement.satisfied_by?(name_tuple.version)
+ end
+ end
+
+ ss.map {|tuple, source| source.fetch_spec(tuple) }
end
- def fetch_specs name_pattern, dependency # :nodoc:
+ def fetch_specs(name_pattern, requirement, prerelease) # :nodoc:
specs = []
if local?
- specs.concat Gem::Specification.stubs.find_all { |spec|
- name_pattern =~ spec.name and
- dependency.requirement.satisfied_by? spec.version
+ specs.concat Gem::Specification.stubs.find_all {|spec|
+ name_matches = name_pattern ? name_pattern =~ spec.name : true
+ version_matches = requirement.satisfied_by?(spec.version)
+
+ name_matches && version_matches
}.map(&:to_spec)
end
- specs.concat fetch_remote_specs dependency if remote?
+ specs.concat fetch_remote_specs name_pattern, requirement, prerelease if remote?
ensure_specs specs
specs.uniq.sort
end
- def gem_dependency pattern, version, prerelease # :nodoc:
- dependency = Gem::Deprecate.skip_during {
- Gem::Dependency.new pattern, version
- }
-
- dependency.prerelease = prerelease
-
- dependency
- end
-
- def display_pipe specs # :nodoc:
+ 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
+ next if spec.dependencies.empty?
+ spec.dependencies.sort_by(&:name).each do |dep|
+ say "#{dep.name} --version '#{dep.requirement}'"
end
end
end
- def display_readable specs, reverse # :nodoc:
+ def display_readable(specs, reverse) # :nodoc:
response = String.new
specs.each do |spec|
response << print_dependencies(spec)
- unless reverse[spec.full_name].empty? then
+ unless reverse[spec.full_name].empty?
response << " Used by\n"
reverse[spec.full_name].each do |sp, dep|
response << " #{sp} (#{dep})\n"
@@ -120,15 +118,13 @@ use with other commands.
ensure_local_only_reverse_dependencies
pattern = name_pattern options[:args]
+ requirement = Gem::Requirement.new options[:version]
- dependency =
- gem_dependency pattern, options[:version], options[:prerelease]
-
- specs = fetch_specs pattern, dependency
+ specs = fetch_specs pattern, requirement, options[:prerelease]
reverse = reverse_dependencies specs
- if options[:pipe_format] then
+ if options[:pipe_format]
display_pipe specs
else
display_readable specs, reverse
@@ -136,16 +132,16 @@ use with other commands.
end
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.'
+ if options[:reverse_dependencies] && remote? && !local?
+ alert_error "Only reverse dependencies for local gems are supported."
terminate_interaction 1
end
end
- def ensure_specs specs # :nodoc:
+ def ensure_specs(specs) # :nodoc:
return unless specs.empty?
- patterns = options[:args].join ','
+ patterns = options[:args].join ","
say "No gems found matching #{patterns} (#{options[:version]})" if
Gem.configuration.verbose
@@ -154,25 +150,17 @@ use with other commands.
def print_dependencies(spec, level = 0) # :nodoc:
response = String.new
- response << ' ' * level + "Gem #{spec.full_name}\n"
- unless spec.dependencies.empty? then
- spec.dependencies.sort_by { |dep| dep.name }.each do |dep|
- response << ' ' * level + " #{dep}\n"
+ response << " " * level + "Gem #{spec.full_name}\n"
+ unless spec.dependencies.empty?
+ spec.dependencies.sort_by(&:name).each do |dep|
+ response << " " * level + " #{dep}\n"
end
end
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] = [] }
+ def reverse_dependencies(specs) # :nodoc:
+ reverse = Hash.new {|h, k| h[k] = [] }
return reverse unless options[:reverse_dependencies]
@@ -186,15 +174,15 @@ use with other commands.
##
# Returns an Array of [specification, dep] that are satisfied by +spec+.
- def find_reverse_dependencies spec # :nodoc:
+ def find_reverse_dependencies(spec) # :nodoc:
result = []
Gem::Specification.each do |sp|
sp.dependencies.each do |dep|
dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep
- if spec.name == dep.name and
- dep.requirement.satisfied_by?(spec.version) then
+ if spec.name == dep.name &&
+ dep.requirement.satisfied_by?(spec.version)
result << [sp.full_name, dep]
end
end
@@ -205,10 +193,10 @@ use with other commands.
private
- def name_pattern args
- args << '' if args.empty?
+ def name_pattern(args)
+ return if args.empty?
- if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m then
+ if args.length == 1 && args.first =~ /\A(.*)(i)?\z/m
flags = $2 ? Regexp::IGNORECASE : nil
Regexp.new $1, flags
else
diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb
index e825c761ad..a5eb521a53 100644
--- a/lib/rubygems/commands/environment_command.rb
+++ b/lib/rubygems/commands/environment_command.rb
@@ -1,23 +1,24 @@
# frozen_string_literal: true
-require 'rubygems/command'
-class Gem::Commands::EnvironmentCommand < Gem::Command
+require_relative "../command"
+class Gem::Commands::EnvironmentCommand < Gem::Command
def initialize
- super 'environment', 'Display information about the RubyGems environment'
+ super "environment", "Display information about the RubyGems environment"
end
def arguments # :nodoc:
args = <<-EOF
- packageversion display the package version
- gemdir display the path where gems are installed
- gempath display path used to search for gems
+ home display the path where gems are installed. Aliases: gemhome, gemdir, GEM_HOME
+ path display path used to search for gems. Aliases: gempath, GEM_PATH
+ user_gemhome display the path where gems are installed when `--user-install` is given. Aliases: user_gemdir
version display the gem format version
remotesources display the remote gem servers
platform display the supported gem platforms
+ credentials display the path where credentials are stored
<omitted> display everything
EOF
- return args.gsub(/^\s+/, '')
+ args.gsub(/^\s+/, "")
end
def description # :nodoc:
@@ -37,6 +38,7 @@ keys:
:verbose: Verbosity of the gem command. false, true, and :really are the
levels
:update_sources: Enable/disable automatic updating of repository metadata
+ :concurrent_downloads: The number of gem downloads to perform concurrently
:backtrace: Print backtrace when RubyGems encounters an error
:gempath: The paths in which to look for gems
:disable_default_gem_server: Force specification of gem server host on push
@@ -76,18 +78,20 @@ lib/rubygems/defaults/operating_system.rb
arg = options[:args][0]
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 /^user_gemdir/, /^user_gemhome/ then
+ Gem.user_dir
when /^remotesources/ then
Gem.sources.to_a.join("\n")
when /^platform/ then
Gem.platforms.join(File::PATH_SEPARATOR)
+ when /^credentials/, /^creds/ then
+ Gem.configuration.credentials_path
when nil then
show_environment
else
@@ -97,7 +101,7 @@ lib/rubygems/defaults/operating_system.rb
true
end
- def add_path out, path
+ def add_path(out, path)
path.each do |component|
out << " - #{component}\n"
end
@@ -108,18 +112,20 @@ lib/rubygems/defaults/operating_system.rb
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 << " - RUBY VERSION: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]\n"
out << " - INSTALLATION DIRECTORY: #{Gem.dir}\n"
out << " - USER INSTALLATION DIRECTORY: #{Gem.user_dir}\n"
+ out << " - CREDENTIALS FILE: #{Gem.configuration.credentials_path}\n"
+
out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil?
out << " - RUBY EXECUTABLE: #{Gem.ruby}\n"
+ out << " - GIT EXECUTABLE: #{git_path}\n"
+
out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n"
out << " - SPEC CACHE DIRECTORY: #{Gem.spec_cache_dir}\n"
@@ -128,7 +134,7 @@ lib/rubygems/defaults/operating_system.rb
out << " - RUBYGEMS PLATFORMS:\n"
Gem.platforms.each do |platform|
- out << " - #{platform}\n"
+ out << " - #{platform}\n"
end
out << " - GEM PATHS:\n"
@@ -140,7 +146,7 @@ lib/rubygems/defaults/operating_system.rb
out << " - GEM CONFIGURATION:\n"
Gem.configuration.each do |name, value|
- value = value.gsub(/./, '*') if name == 'gemcutter_key'
+ value = value.gsub(/./, "*") if name == "gemcutter_key"
out << " - #{name.inspect} => #{value.inspect}\n"
end
@@ -151,10 +157,26 @@ lib/rubygems/defaults/operating_system.rb
out << " - SHELL PATH:\n"
- shell_path = ENV['PATH'].split(File::PATH_SEPARATOR)
+ shell_path = ENV["PATH"].split(File::PATH_SEPARATOR)
add_path out, shell_path
out
end
+ private
+
+ ##
+ # Git binary path
+
+ def git_path
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
+ exts.each do |ext|
+ exe = File.join(path, "git#{ext}")
+ return exe if File.executable?(exe) && !File.directory?(exe)
+ end
+ end
+
+ nil
+ end
end
diff --git a/lib/rubygems/commands/exec_command.rb b/lib/rubygems/commands/exec_command.rb
new file mode 100644
index 0000000000..1feafbdd35
--- /dev/null
+++ b/lib/rubygems/commands/exec_command.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: true
+
+require_relative "../command"
+require_relative "../dependency_installer"
+require_relative "../gem_runner"
+require_relative "../package"
+require_relative "../version_option"
+
+class Gem::Commands::ExecCommand < Gem::Command
+ include Gem::VersionOption
+
+ def initialize
+ super "exec", "Run a command from a gem", {
+ version: Gem::Requirement.default,
+ }
+
+ add_version_option
+ add_prerelease_option "to be installed"
+
+ add_option "-g", "--gem GEM", "run the executable from the given gem" do |value, options|
+ options[:gem_name] = value
+ end
+
+ add_option(:"Install/Update", "--conservative",
+ "Prefer the most recent installed version, ",
+ "rather than the latest version overall") do |_value, options|
+ options[:conservative] = true
+ end
+ end
+
+ def arguments # :nodoc:
+ "COMMAND the executable command to run"
+ end
+
+ def defaults_str # :nodoc:
+ "--version '#{Gem::Requirement.default}'"
+ end
+
+ def description # :nodoc:
+ <<-EOF
+The exec command handles installing (if necessary) and running an executable
+from a gem, regardless of whether that gem is currently installed.
+
+The exec command can be thought of as a shortcut to running `gem install` and
+then the executable from the installed gem.
+
+For example, `gem exec rails new .` will run `rails new .` in the current
+directory, without having to manually run `gem install rails`.
+Additionally, the exec command ensures the most recent version of the gem
+is used (unless run with `--conservative`), and that the gem is not installed
+to the same gem path as user-installed gems.
+ EOF
+ end
+
+ def usage # :nodoc:
+ "#{program_name} [options --] COMMAND [args]"
+ end
+
+ def execute
+ check_executable
+
+ print_command
+ if options[:gem_name] == "gem" && options[:executable] == "gem"
+ set_gem_exec_install_paths
+ Gem::GemRunner.new.run options[:args]
+ return
+ elsif options[:conservative]
+ install_if_needed
+ else
+ install
+ activate!
+ end
+
+ load!
+ end
+
+ private
+
+ def handle_options(args)
+ args = add_extra_args(args)
+ check_deprecated_options(args)
+ @options = Marshal.load Marshal.dump @defaults # deep copy
+ parser.order!(args) do |v|
+ # put the non-option back at the front of the list of arguments
+ args.unshift(v)
+
+ # stop parsing once we hit the first non-option,
+ # so you can call `gem exec rails --version` and it prints the rails
+ # version rather than rubygem's
+ break
+ end
+ @options[:args] = args
+
+ options[:executable], gem_version = extract_gem_name_and_version(options[:args].shift)
+ options[:gem_name] ||= options[:executable]
+
+ if gem_version
+ if options[:version].none?
+ options[:version] = Gem::Requirement.new(gem_version)
+ else
+ options[:version].concat [gem_version]
+ end
+ end
+
+ if options[:prerelease] && !options[:version].prerelease?
+ if options[:version].none?
+ options[:version] = Gem::Requirement.default_prerelease
+ else
+ options[:version].concat [Gem::Requirement.default_prerelease]
+ end
+ end
+ end
+
+ def check_executable
+ if options[:executable].nil?
+ raise Gem::CommandLineError,
+ "Please specify an executable to run (e.g. #{program_name} COMMAND)"
+ end
+ end
+
+ def print_command
+ verbose "running #{program_name} with:\n"
+ opts = options.reject {|_, v| v.nil? || Array(v).empty? }
+ max_length = opts.map {|k, _| k.size }.max
+ opts.each do |k, v|
+ next if v.nil?
+ verbose "\t#{k.to_s.rjust(max_length)}: #{v}"
+ end
+ verbose ""
+ end
+
+ def install_if_needed
+ activate!
+ rescue Gem::MissingSpecError
+ verbose "#{Gem::Dependency.new(options[:gem_name], options[:version])} not available locally, installing from remote"
+ install
+ activate!
+ end
+
+ def set_gem_exec_install_paths
+ home = Gem.dir
+
+ ENV["GEM_PATH"] = ([home] + Gem.path).join(File::PATH_SEPARATOR)
+ ENV["GEM_HOME"] = home
+ Gem.clear_paths
+ end
+
+ def install
+ set_gem_exec_install_paths
+
+ gem_name = options[:gem_name]
+ gem_version = options[:version]
+
+ install_options = options.merge(
+ minimal_deps: false,
+ wrappers: true
+ )
+
+ suppress_always_install do
+ dep_installer = Gem::DependencyInstaller.new install_options
+
+ request_set = dep_installer.resolve_dependencies gem_name, gem_version
+
+ verbose "Gems to install:"
+ request_set.sorted_requests.each do |activation_request|
+ verbose "\t#{activation_request.full_name}"
+ end
+
+ request_set.install install_options
+ end
+
+ Gem::Specification.reset
+ rescue Gem::InstallError => e
+ alert_error "Error installing #{gem_name}:\n\t#{e.message}"
+ terminate_interaction 1
+ rescue Gem::DependencyResolutionError => e
+ alert_error "Error installing #{gem_name}:\n\t#{e.message}"
+ terminate_interaction 2
+ rescue Gem::GemNotFoundException => e
+ show_lookup_failure e.name, e.version, e.errors, false
+
+ terminate_interaction 2
+ rescue Gem::UnsatisfiableDependencyError => e
+ show_lookup_failure e.name, e.version, e.errors, false,
+ "'#{gem_name}' (#{gem_version})"
+
+ terminate_interaction 2
+ end
+
+ def activate!
+ gem(options[:gem_name], options[:version])
+ Gem.finish_resolve
+
+ verbose "activated #{options[:gem_name]} (#{Gem.loaded_specs[options[:gem_name]].version})"
+ end
+
+ def load!
+ argv = ARGV.clone
+ ARGV.replace options[:args]
+
+ executable = options[:executable]
+
+ contains_executable = Gem.loaded_specs.values.select do |spec|
+ spec.executables.include?(executable)
+ end
+
+ if contains_executable.any? {|s| s.name == executable }
+ contains_executable.select! {|s| s.name == executable }
+ end
+
+ if contains_executable.empty?
+ spec = Gem.loaded_specs[executable]
+
+ if spec.nil? || spec.executables.empty?
+ alert_error "Failed to load executable `#{executable}`," \
+ " are you sure the gem `#{options[:gem_name]}` contains it?"
+ terminate_interaction 1
+ end
+
+ if spec.executables.size > 1
+ alert_error "Ambiguous which executable from gem `#{executable}` should be run: " \
+ "the options are #{spec.executables.sort}, specify one via COMMAND, and use `-g` and `-v` to specify gem and version"
+ terminate_interaction 1
+ end
+
+ contains_executable << spec
+ executable = spec.executable
+ end
+
+ if contains_executable.size > 1
+ alert_error "Ambiguous which gem `#{executable}` should come from: " \
+ "the options are #{contains_executable.map(&:name)}, " \
+ "specify one via `-g`"
+ terminate_interaction 1
+ end
+
+ old_exe = $0
+ $0 = executable
+ load Gem.activate_bin_path(contains_executable.first.name, executable, ">= 0.a")
+ ensure
+ $0 = old_exe if old_exe
+ ARGV.replace argv
+ end
+
+ def suppress_always_install
+ name = :always_install
+ cls = ::Gem::Resolver::InstallerSet
+ method = cls.instance_method(name)
+ cls.remove_method(name)
+ cls.define_method(name) { [] }
+
+ begin
+ yield
+ ensure
+ cls.remove_method(name)
+ cls.define_method(name, method)
+ end
+ end
+end
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index 19559a7774..8e64a18cee 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -1,15 +1,20 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-class Gem::Commands::FetchCommand < Gem::Command
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+class Gem::Commands::FetchCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'fetch', 'Download a gem and place it in the current directory'
+ defaults = {
+ suggest_alternate: true,
+ version: Gem::Requirement.default,
+ }
+
+ super "fetch", "Download a gem and place it in the current directory", defaults
add_bulk_threshold_option
add_proxy_option
@@ -19,10 +24,14 @@ class Gem::Commands::FetchCommand < Gem::Command
add_version_option
add_platform_option
add_prerelease_option
+
+ add_option "--[no-]suggestions", "Suggest alternates when gems are not found" do |value, options|
+ options[:suggest_alternate] = value
+ end
end
def arguments # :nodoc:
- 'GEMNAME name of gem to download'
+ "GEMNAME name of gem to download"
end
def defaults_str # :nodoc:
@@ -43,36 +52,58 @@ then repackaging it.
"#{program_name} GEMNAME [GEMNAME ...]"
end
+ def check_version # :nodoc:
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
+ alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
+ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:>=2'`"
+ terminate_interaction 1
+ end
+ end
+
def execute
- version = options[:version] || Gem::Requirement.default
+ check_version
+
+ exit_code = fetch_gems
+
+ terminate_interaction exit_code
+ end
+
+ private
+
+ def fetch_gems
+ exit_code = 0
+
+ version = options[:version]
platform = Gem.platforms.last
- gem_names = get_all_gem_names
+ gem_names = get_all_gem_names_and_versions
- gem_names.each do |gem_name|
- dep = Gem::Dependency.new gem_name, version
+ gem_names.each do |gem_name, gem_version|
+ gem_version ||= version
+ dep = Gem::Dependency.new gem_name, gem_version
dep.prerelease = options[:prerelease]
+ suppress_suggestions = !options[:suggest_alternate]
specs_and_sources, errors =
Gem::SpecFetcher.fetcher.spec_for_dependency dep
- if platform then
- filtered = specs_and_sources.select { |s,| s.platform == platform }
+ if platform
+ filtered = specs_and_sources.select {|s,| s.platform == platform }
specs_and_sources = filtered unless filtered.empty?
end
- spec, source = specs_and_sources.max_by { |s,| s.version }
+ spec, source = specs_and_sources.max_by {|s,| s }
- if spec.nil? then
- show_lookup_failure gem_name, version, errors, options[:domain]
+ if spec.nil?
+ show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain]
+ exit_code |= 2
next
end
-
source.download spec
-
say "Downloaded #{spec.full_name}"
end
- end
+ exit_code
+ end
end
-
diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb
index 01f1f88405..13be92593b 100644
--- a/lib/rubygems/commands/generate_index_command.rb
+++ b/lib/rubygems/commands/generate_index_command.rb
@@ -1,85 +1,51 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/indexer'
-##
-# Generates a index files for use as a gem server.
-#
-# See `gem help generate_index`
+require_relative "../command"
-class Gem::Commands::GenerateIndexCommand < Gem::Command
-
- def initialize
- super 'generate_index',
- 'Generates the index files for a gem server directory',
- :directory => '.', :build_modern => true
+unless defined? Gem::Commands::GenerateIndexCommand
+ class Gem::Commands::GenerateIndexCommand < Gem::Command
+ module RubygemsTrampoline
+ def description # :nodoc:
+ <<~EOF
+ The generate_index command has been moved to the rubygems-generate_index gem.
+ EOF
+ end
- add_option '-d', '--directory=DIRNAME',
- 'repository base dir containing gems subdir' do |dir, options|
- options[:directory] = File.expand_path dir
- end
+ def execute
+ alert_error "Install the rubygems-generate_index gem for the generate_index command"
+ end
- add_option '--[no-]modern',
- 'Generate indexes for RubyGems',
- '(always true)' do |value, options|
- options[:build_modern] = value
+ def invoke_with_build_args(args, build_args)
+ name = "rubygems-generate_index"
+ spec = begin
+ Gem::Specification.find_by_name(name)
+ rescue Gem::LoadError
+ require "rubygems/dependency_installer"
+ Gem.install(name, Gem::Requirement.default, Gem::DependencyInstaller::DEFAULT_OPTIONS).find {|s| s.name == name }
+ end
+
+ # remove the methods defined in this file so that the methods defined in the gem are used instead,
+ # and without a method redefinition warning
+ %w[description execute invoke_with_build_args].each do |method|
+ RubygemsTrampoline.remove_method(method)
+ end
+ self.class.singleton_class.remove_method(:new)
+
+ spec.activate
+ Gem.load_plugin_files spec.matches_for_glob("rubygems_plugin#{Gem.suffix_pattern}")
+
+ self.class.new.invoke_with_build_args(args, build_args)
+ end
end
+ private_constant :RubygemsTrampoline
- add_option '--update',
- 'Update modern indexes with gems added',
- 'since the last update' do |value, options|
- options[:update] = value
+ # remove_method(:initialize) warns, but removing new does not warn
+ def self.new
+ command = allocate
+ command.send(:initialize, "generate_index", "Generates the index files for a gem server directory (requires rubygems-generate_index)")
+ command
end
- end
- def defaults_str # :nodoc:
- "--directory . --modern"
+ prepend(RubygemsTrampoline)
end
-
- def description # :nodoc:
- <<-EOF
-The generate_index command creates a set of indexes for serving gems
-statically. The command expects a 'gems' directory under the path given to
-the --directory option. The given directory will be the directory you serve
-as the gem repository.
-
-For `gem generate_index --directory /path/to/repo`, expose /path/to/repo via
-your HTTP server configuration (not /path/to/repo/gems).
-
-When done, it will generate a set of files like this:
-
- gems/*.gem # .gem files you want to
- # index
-
- specs.<version>.gz # specs index
- latest_specs.<version>.gz # latest specs index
- prerelease_specs.<version>.gz # prerelease specs index
- quick/Marshal.<version>/<gemname>.gemspec.rz # Marshal quick index file
-
-The .rz extension files are compressed with the inflate algorithm.
-The Marshal version number comes from ruby's Marshal::MAJOR_VERSION and
-Marshal::MINOR_VERSION constants. It is used to ensure compatibility.
- EOF
- end
-
- def execute
- # This is always true because it's the only way now.
- options[:build_modern] = true
-
- if not File.exist?(options[:directory]) or
- not File.directory?(options[:directory]) then
- alert_error "unknown directory name #{directory}."
- terminate_interaction 1
- else
- indexer = Gem::Indexer.new options.delete(:directory), options
-
- if options[:update] then
- indexer.update_index
- else
- indexer.generate_index
- end
- end
- end
-
end
-
diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb
index 7d02022369..664f400561 100644
--- a/lib/rubygems/commands/help_command.rb
+++ b/lib/rubygems/commands/help_command.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require 'rubygems/command'
-class Gem::Commands::HelpCommand < Gem::Command
+require_relative "../command"
+class Gem::Commands::HelpCommand < Gem::Command
# :stopdoc:
EXAMPLES = <<-EOF
Some examples of 'gem' usage.
@@ -38,7 +38,7 @@ Some examples of 'gem' usage.
* Create a gem:
- See http://guides.rubygems.org/make-your-own-gem/
+ See https://guides.rubygems.org/make-your-own-gem/
* See information about RubyGems:
@@ -59,7 +59,7 @@ multiple environments. The RubyGems implementation is designed to be
compatible with Bundler's Gemfile format. You can see additional
documentation on the format at:
- http://bundler.io
+ https://bundler.io
RubyGems automatically looks for these gem dependencies files:
@@ -90,7 +90,9 @@ Use #gem to declare which gems you directly depend upon:
To depend on a specific set of versions:
- gem 'rake', '~> 10.3', '>= 10.3.2'
+ gem 'rake', '>= 10.3.2'
+ # or for multiple version restrictions
+ gem 'rake', '>= 10.3.2', "< 13"
RubyGems will require the gem name when activating the gem using
the RUBYGEMS_GEMDEPS environment variable or Gem::use_gemdeps. Use the
@@ -172,7 +174,7 @@ and #platforms methods:
See the bundler Gemfile manual page for a list of platforms supported in a gem
dependencies file.:
- http://bundler.io/v1.6/man/gemfile.5.html
+ https://bundler.io/v2.5/man/gemfile.5.html
Ruby Version and Engine Dependency
==================================
@@ -269,7 +271,7 @@ Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's
platform.
EOF
- # NOTE when updating also update Gem::Command::HELP
+ # NOTE: when updating also update Gem::Command::HELP
SUBCOMMANDS = [
["commands", :show_commands],
@@ -277,11 +279,11 @@ platform.
["examples", EXAMPLES],
["gem_dependencies", GEM_DEPENDENCIES],
["platforms", PLATFORMS],
- ]
+ ].freeze
# :startdoc:
def initialize
- super 'help', "Provide help on the 'gem' command"
+ super "help", "Provide help on the 'gem' command"
@command_manager = Gem::CommandManager.instance
end
@@ -297,8 +299,8 @@ platform.
begins? command, arg
end
- if help then
- if Symbol === help then
+ if help
+ if Symbol === help
send help
else
say help
@@ -306,10 +308,10 @@ platform.
return
end
- if options[:help] then
+ if options[:help]
show_help
- elsif arg then
+ elsif arg
show_command_help arg
else
@@ -324,24 +326,26 @@ platform.
margin_width = 4
- desc_width = @command_manager.command_names.map { |n| n.size }.max + 4
+ desc_width = @command_manager.command_names.map(&:size).max + 4
summary_width = 80 - margin_width - desc_width
- wrap_indent = ' ' * (margin_width + desc_width)
- format = "#{' ' * margin_width}%-#{desc_width}s%s"
+ 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]
+ next if command&.deprecated?
+
summary =
- if command then
+ if command
command.summary
else
"[No command found for #{cmd_name}]"
end
summary = wrap(summary, summary_width).split "\n"
- out << sprintf(format, cmd_name, summary.shift)
+ out << format(format, cmd_name, summary.shift)
until summary.empty? do
out << "#{wrap_indent}#{summary.shift}"
end
@@ -356,20 +360,18 @@ platform.
say out.join("\n")
end
- def show_command_help command_name # :nodoc:
+ 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
+ if possibilities.size == 1
command = @command_manager[possibilities.first]
command.invoke("--help")
- elsif possibilities.size > 1 then
- alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})"
+ elsif possibilities.size > 1
+ alert_warning "Ambiguous command #{command_name} (#{possibilities.join(", ")})"
else
alert_warning "Unknown command #{command_name}. Try: gem help commands"
end
end
-
end
-
diff --git a/lib/rubygems/commands/info_command.rb b/lib/rubygems/commands/info_command.rb
new file mode 100644
index 0000000000..f65c639662
--- /dev/null
+++ b/lib/rubygems/commands/info_command.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require_relative "../command"
+require_relative "../query_utils"
+
+class Gem::Commands::InfoCommand < Gem::Command
+ include Gem::QueryUtils
+
+ def initialize
+ super "info", "Show information for the given gem",
+ name: //, domain: :local, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
+
+ add_query_options
+
+ remove_option("-d")
+
+ defaults[:details] = true
+ defaults[:exact] = true
+ end
+
+ def description # :nodoc:
+ "Info prints information about the gem such as name,"\
+ " description, website, license and installed paths"
+ end
+
+ def usage # :nodoc:
+ "#{program_name} GEMNAME"
+ end
+
+ def arguments # :nodoc:
+ "GEMNAME name of the gem to print information about"
+ end
+
+ def defaults_str
+ "--local"
+ end
+end
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 3a7d50517f..6d3beec0b4 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/install_update_options'
-require 'rubygems/dependency_installer'
-require 'rubygems/local_remote_options'
-require 'rubygems/validator'
-require 'rubygems/version_option'
+
+require_relative "../command"
+require_relative "../install_update_options"
+require_relative "../dependency_installer"
+require_relative "../local_remote_options"
+require_relative "../validator"
+require_relative "../version_option"
+require_relative "../update_suggestion"
##
# Gem installer command line tool
@@ -12,23 +14,25 @@ require 'rubygems/version_option'
# See `gem help install`
class Gem::Commands::InstallCommand < Gem::Command
-
attr_reader :installed_specs # :nodoc:
include Gem::VersionOption
include Gem::LocalRemoteOptions
include Gem::InstallUpdateOptions
+ include Gem::UpdateSuggestion
def initialize
defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
- :format_executable => false,
- :lock => true,
- :suggest_alternate => true,
- :version => Gem::Requirement.default,
- :without_groups => [],
+ format_executable: false,
+ lock: true,
+ suggest_alternate: true,
+ version: Gem::Requirement.default,
+ without_groups: [],
})
- super 'install', 'Install a gem into the local repository', defaults
+ defaults.merge!(install_update_options)
+
+ super "install", "Install a gem into the local repository", defaults
add_install_update_options
add_local_remote_options
@@ -44,8 +48,9 @@ class Gem::Commands::InstallCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--both --version '#{Gem::Requirement.default}' --document --no-force\n" +
- "--install-dir #{Gem.dir} --lock"
+ "--both --version '#{Gem::Requirement.default}' --no-force\n" \
+ "--install-dir #{Gem.dir} --lock\n" +
+ install_update_defaults_str
end
def description # :nodoc:
@@ -117,40 +122,39 @@ to write the specification by hand. For example:
some_extension_gem (1.0)
$
+Command Alias
+==========================
+
+You can use `i` command instead of `install`.
+
+ $ gem i GEMNAME
+
EOF
end
def usage # :nodoc:
- "#{program_name} GEMNAME [GEMNAME ...] [options] -- --build-flags"
- end
-
- 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
+ "#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags"
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."
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
+ alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
+ " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:>=2'`"
terminate_interaction 1
end
end
def execute
-
- if options.include? :gemdeps then
+ if options.include? :gemdeps
install_from_gemdeps
return # not reached
end
@installed_specs = []
- ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
+ ENV.delete "GEM_PATH" if options[:install_dir].nil?
- check_install_dir
check_version
load_hooks
@@ -159,11 +163,13 @@ to write the specification by hand. For example:
show_installed
+ say update_suggestion if eligible_for_update?
+
terminate_interaction exit_code
end
def install_from_gemdeps # :nodoc:
- require 'rubygems/request_set'
+ require_relative "../request_set"
rs = Gem::RequestSet.new
specs = rs.install_from_gemdeps options do |req, inst|
@@ -181,68 +187,27 @@ to write the specification by hand. For example:
terminate_interaction
end
- def install_gem name, version # :nodoc:
- return if options[:conservative] and
- not Gem::Dependency.new(name, version).matching_specs.empty?
+ def install_gem(name, version) # :nodoc:
+ return if options[:conservative] &&
+ !Gem::Dependency.new(name, version).matching_specs.empty?
req = Gem::Requirement.create(version)
- if options[:ignore_dependencies] then
- install_gem_without_dependencies name, req
- else
- inst = Gem::DependencyInstaller.new options
- request_set = inst.resolve_dependencies name, req
-
- if options[:explain]
- puts "Gems to install:"
-
- request_set.sorted_requests.each do |s|
- puts " #{s.full_name}"
- end
-
- return
- else
- @installed_specs.concat request_set.install options
- end
+ dinst = Gem::DependencyInstaller.new options
- show_install_errors inst.errors
- end
- end
+ request_set = dinst.resolve_dependencies name, req
- def install_gem_without_dependencies name, req # :nodoc:
- gem = nil
+ if options[:explain]
+ say "Gems to install:"
- if local? then
- if name =~ /\.gem$/ and File.file? name then
- source = Gem::Source::SpecificFile.new name
- spec = source.spec
- else
- source = Gem::Source::Local.new
- spec = source.find_gem name, req
+ request_set.sorted_requests.each do |activation_request|
+ say " #{activation_request.full_name}"
end
- gem = source.download spec if spec
- end
-
- if remote? and not gem then
- dependency = Gem::Dependency.new name, req
- dependency.prerelease = options[:prerelease]
-
- fetcher = Gem::RemoteFetcher.fetcher
- gem = fetcher.download_to_cache dependency
+ else
+ @installed_specs.concat request_set.install options
end
- inst = Gem::Installer.at gem, options
- inst.install
-
- require 'rubygems/dependency_installer'
- dinst = Gem::DependencyInstaller.new options
- dinst.installed_gems.replace [inst.spec]
-
- Gem.done_installing_hooks.each do |hook|
- hook.call dinst, [inst.spec]
- end unless Gem.done_installing_hooks.empty?
-
- @installed_specs.push(inst.spec)
+ show_install_errors dinst.errors
end
def install_gems # :nodoc:
@@ -250,16 +215,21 @@ to write the specification by hand. For example:
get_all_gem_names_and_versions.each do |gem_name, gem_version|
gem_version ||= options[:version]
+ domain = options[:domain]
+ domain = :local unless options[:suggest_alternate]
+ suppress_suggestions = (domain == :local)
begin
install_gem gem_name, gem_version
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
- rescue Gem::GemNotFoundException, Gem::UnsatisfiableDependencyError => e
- domain = options[:domain]
- domain = :local unless options[:suggest_alternate]
- show_lookup_failure e.name, e.version, e.errors, domain
+ rescue Gem::DependencyResolutionError => e
+ alert_error "Error installing #{gem_name}:\n\t#{e.message}"
+ exit_code |= 2
+ rescue Gem::UnsatisfiableDependencyError => e
+ show_lookup_failure e.name, e.version, e.errors, suppress_suggestions,
+ "'#{gem_name}' (#{gem_version})"
exit_code |= 2
end
@@ -272,21 +242,18 @@ to write the specification by hand. For example:
# 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'
+ require_relative "../install_message"
+ require_relative "../rdoc"
end
- def show_install_errors errors # :nodoc:
+ def show_install_errors(errors) # :nodoc:
return unless errors
errors.each do |x|
- return unless Gem::SourceFetchProblem === x
+ next unless Gem::SourceFetchProblem === x
- msg = "Unable to pull data from '#{x.source.uri}': #{x.error.message}"
+ require_relative "../uri"
+ msg = "Unable to pull data from '#{Gem::Uri.redact(x.source.uri)}': #{x.error.message}"
alert_warning msg
end
@@ -295,9 +262,7 @@ to write the specification by hand. For example:
def show_installed # :nodoc:
return if @installed_specs.empty?
- gems = @installed_specs.length == 1 ? 'gem' : 'gems'
+ 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 1acb49e5fb..fab4b73814 100644
--- a/lib/rubygems/commands/list_command.rb
+++ b/lib/rubygems/commands/list_command.rb
@@ -1,17 +1,20 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/commands/query_command'
+
+require_relative "../command"
+require_relative "../query_utils"
##
-# An alternate to Gem::Commands::QueryCommand that searches for gems starting
-# with the supplied argument.
+# Searches for gems starting with the supplied argument.
-class Gem::Commands::ListCommand < Gem::Commands::QueryCommand
+class Gem::Commands::ListCommand < Gem::Command
+ include Gem::QueryUtils
def initialize
- super 'list', 'Display local gems whose name matches REGEXP'
+ super "list", "Display local gems whose name matches REGEXP",
+ domain: :local, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
- remove_option('--name-matches')
+ add_query_options
end
def arguments # :nodoc:
@@ -36,6 +39,4 @@ To search for remote gems use the search command.
def usage # :nodoc:
"#{program_name} [REGEXP ...]"
end
-
end
-
diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb
index 3eebfadc05..f7fd5ada16 100644
--- a/lib/rubygems/commands/lock_command.rb
+++ b/lib/rubygems/commands/lock_command.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require 'rubygems/command'
-class Gem::Commands::LockCommand < Gem::Command
+require_relative "../command"
+class Gem::Commands::LockCommand < Gem::Command
def initialize
- super 'lock', 'Generate a lockdown list of gems',
- :strict => false
+ super "lock", "Generate a lockdown list of gems",
+ strict: false
- add_option '-s', '--[no-]strict',
- 'fail if unable to satisfy a dependency' do |strict, options|
+ add_option "-s", "--[no-]strict",
+ "fail if unable to satisfy a dependency" do |strict, options|
options[:strict] = strict
end
end
@@ -59,7 +59,7 @@ lock it down to the exact version.
end
def complain(message)
- if options[:strict] then
+ if options[:strict]
raise Gem::Exception, message
else
say "# #{message}"
@@ -78,7 +78,7 @@ lock it down to the exact version.
spec = Gem::Specification.load spec_path(full_name)
- if spec.nil? then
+ if spec.nil?
complain "Could not find gem #{full_name}, try using the full name"
next
end
@@ -90,7 +90,7 @@ lock it down to the exact version.
next if locked[dep.name]
candidates = dep.matching_specs
- if candidates.empty? then
+ if candidates.empty?
complain "Unable to satisfy '#{dep}' from currently installed gems"
else
pending << candidates.last.full_name
@@ -100,12 +100,10 @@ lock it down to the exact version.
end
def spec_path(gem_full_name)
- gemspecs = Gem.path.map { |path|
+ gemspecs = Gem.path.map do |path|
File.join path, "specifications", "#{gem_full_name}.gemspec"
- }
+ end
- gemspecs.find { |path| File.exist? path }
+ gemspecs.find {|path| File.exist? path }
end
-
end
-
diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb
index 801c9c8927..b91a8db12d 100644
--- a/lib/rubygems/commands/mirror_command.rb
+++ b/lib/rubygems/commands/mirror_command.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
unless defined? Gem::Commands::MirrorCommand
class Gem::Commands::MirrorCommand < Gem::Command
def initialize
- super('mirror', 'Mirror all gem files (requires rubygems-mirror)')
+ super("mirror", "Mirror all gem files (requires rubygems-mirror)")
begin
- Gem::Specification.find_by_name('rubygems-mirror').activate
+ Gem::Specification.find_by_name("rubygems-mirror").activate
rescue Gem::LoadError
# no-op
end
@@ -21,6 +22,5 @@ The mirror command has been moved to the rubygems-mirror gem.
def execute
alert_error "Install the rubygems-mirror gem for the mirror command"
end
-
end
end
diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb
index 059635e835..0fe90dc8b8 100644
--- a/lib/rubygems/commands/open_command.rb
+++ b/lib/rubygems/commands/open_command.rb
@@ -1,23 +1,21 @@
# frozen_string_literal: true
-require 'English'
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/util'
-class Gem::Commands::OpenCommand < Gem::Command
+require_relative "../command"
+require_relative "../version_option"
+class Gem::Commands::OpenCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'open', 'Open gem sources in editor'
+ super "open", "Open gem sources in editor"
- add_option('-e', '--editor EDITOR', String,
- "Opens gem sources in EDITOR") do |editor, options|
- options[:editor] = editor || get_env_editor
+ add_option("-e", "--editor COMMAND", String,
+ "Prepends COMMAND to gem path. Could be used to specify editor.") do |command, options|
+ options[:editor] = command || get_env_editor
end
- add_option('-v', '--version VERSION', String,
+ add_option("-v", "--version VERSION", String,
"Opens specific gem version") do |version|
- options[:version] = version
+ options[:version] = version
end
end
@@ -32,21 +30,21 @@ class Gem::Commands::OpenCommand < Gem::Command
def description # :nodoc:
<<-EOF
The open command opens gem in editor and changes current path
- to gem's source directory. Editor can be specified with -e option,
- otherwise rubygems will look for editor in $EDITOR, $VISUAL and
- $GEM_EDITOR variables.
+ to gem's source directory.
+ Editor command can be specified with -e option, otherwise rubygems
+ will look for editor in $EDITOR, $VISUAL and $GEM_EDITOR variables.
EOF
end
def usage # :nodoc:
- "#{program_name} GEMNAME [-e EDITOR]"
+ "#{program_name} [-e COMMAND] GEMNAME"
end
def get_env_editor
- ENV['GEM_EDITOR'] ||
- ENV['VISUAL'] ||
- ENV['EDITOR'] ||
- 'vi'
+ ENV["GEM_EDITOR"] ||
+ ENV["VISUAL"] ||
+ ENV["EDITOR"] ||
+ "vi"
end
def execute
@@ -58,20 +56,24 @@ class Gem::Commands::OpenCommand < Gem::Command
terminate_interaction 1 unless found
end
- def open_gem name
+ def open_gem(name)
spec = spec_for name
+
return false unless spec
+ if spec.default_gem?
+ say "'#{name}' is a default gem and can't be opened."
+ return false
+ end
+
open_editor(spec.full_gem_path)
end
- def open_editor path
- Dir.chdir(path) do
- system(*@editor.split(/\s+/) + [path])
- end
+ def open_editor(path)
+ system(*@editor.split(/\s+/) + [path], { chdir: path })
end
- def spec_for name
+ def spec_for(name)
spec = Gem::Specification.find_all_by_name(name, @version).first
return spec if spec
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index 70f6c02801..08a9221a26 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-class Gem::Commands::OutdatedCommand < Gem::Command
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../spec_fetcher"
+require_relative "../version_option"
+class Gem::Commands::OutdatedCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
- super 'outdated', 'Display all gems that need updates'
+ super "outdated", "Display all gems that need updates"
add_local_remote_options
add_platform_option
diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb
index 8e2271657a..675e866734 100644
--- a/lib/rubygems/commands/owner_command.rb
+++ b/lib/rubygems/commands/owner_command.rb
@@ -1,16 +1,24 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/gemcutter_utilities'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../gemcutter_utilities"
+require_relative "../text"
class Gem::Commands::OwnerCommand < Gem::Command
+ include Gem::Text
include Gem::LocalRemoteOptions
include Gem::GemcutterUtilities
def description # :nodoc:
<<-EOF
The owner command lets you add and remove owners of a gem on a push
-server (the default is https://rubygems.org).
+server (the default is https://rubygems.org). Multiple owners can be
+added or removed at the same time, if the flag is given multiple times.
+
+The supported user identifiers are dependent on the push server.
+For rubygems.org, both e-mail and handle are supported, even though the
+user identifier field is called "email".
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
@@ -27,22 +35,23 @@ permission to.
end
def initialize
- super 'owner', 'Manage gem owners of a gem on the push server'
+ super "owner", "Manage gem owners of a gem on the push server"
add_proxy_option
add_key_option
- defaults.merge! :add => [], :remove => []
+ add_otp_option
+ defaults.merge! add: [], remove: []
- add_option '-a', '--add EMAIL', 'Add an owner' do |value, options|
+ add_option "-a", "--add NEW_OWNER", "Add an owner by user identifier" do |value, options|
options[:add] << value
end
- add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options|
+ add_option "-r", "--remove OLD_OWNER", "Remove an owner by user identifier" do |value, options|
options[:remove] << value
end
- add_option '-h', '--host HOST',
- 'Use another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)' do |value, options|
+ add_option "-h", "--host HOST",
+ "Use another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)" do |value, options|
options[:host] = value
end
end
@@ -50,7 +59,7 @@ permission to.
def execute
@host = options[:host]
- sign_in
+ sign_in(scope: get_owner_scope)
name = get_one_gem_name
add_owners name, options[:add]
@@ -58,44 +67,59 @@ permission to.
show_owners name
end
- def show_owners name
+ def show_owners(name)
+ Gem.load_yaml
+
response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request|
request.add_field "Authorization", api_key
end
with_response response do |resp|
- owners = YAML.load resp.body
+ owners = Gem::SafeYAML.safe_load clean_text(resp.body)
say "Owners for gem: #{name}"
owners.each do |owner|
- say "- #{owner['email'] || owner['handle'] || owner['id']}"
+ identifier = owner["email"] || owner["handle"] || owner["id"]
+ say "- #{identifier} (#{owner["role"]})"
end
end
end
- def add_owners name, owners
+ def add_owners(name, owners)
manage_owners :post, name, owners
end
- def remove_owners name, owners
+ def remove_owners(name, owners)
manage_owners :delete, name, owners
end
- def manage_owners method, name, owners
+ def manage_owners(method, name, owners)
owners.each do |owner|
- begin
- response = rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request|
- request.set_form_data 'email' => owner
- request.add_field "Authorization", api_key
- end
+ response = send_owner_request(method, name, owner)
+ action = method == :delete ? "Removing" : "Adding"
+
+ with_response response, "#{action} #{owner}"
+ rescue Gem::WebauthnVerificationError => e
+ raise e
+ rescue StandardError
+ # ignore early exits to allow for completing the iteration of all owners
+ end
+ end
- action = method == :delete ? "Removing" : "Adding"
+ private
- with_response response, "#{action} #{owner}"
- rescue
- # ignore
- end
+ def send_owner_request(method, name, owner)
+ rubygems_api_request method, "api/v1/gems/#{name}/owners", scope: get_owner_scope(method: method) do |request|
+ request.set_form_data "email" => owner
+ request.add_field "Authorization", api_key
end
end
+ def get_owner_scope(method: nil)
+ if method == :post || options.any? && options[:add].any?
+ :add_owner
+ elsif method == :delete || options.any? && options[:remove].any?
+ :remove_owner
+ end
+ end
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index fafe35bec1..10978c2af7 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -1,51 +1,73 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/package'
-require 'rubygems/installer'
-require 'rubygems/version_option'
-class Gem::Commands::PristineCommand < Gem::Command
+require_relative "../command"
+require_relative "../package"
+require_relative "../installer"
+require_relative "../version_option"
+class Gem::Commands::PristineCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'pristine',
- 'Restores installed gems to pristine condition from files located in the gem cache',
- :version => Gem::Requirement.default,
- :extensions => true,
- :extensions_set => false,
- :all => false
-
- add_option('--all',
- 'Restore all installed gems to pristine',
- 'condition') do |value, options|
+ super "pristine",
+ "Restores installed gems to pristine condition from files located in the gem cache",
+ version: Gem::Requirement.default,
+ extensions: true,
+ extensions_set: false,
+ all: false
+
+ add_option("--all",
+ "Restore all installed gems to pristine",
+ "condition") do |value, options|
options[:all] = value
end
- add_option('--skip=gem_name',
- 'used on --all, skip if name == gem_name') do |value, options|
- options[:skip] = value
+ add_option("--skip=gem_name",
+ "used on --all, skip if name == gem_name") do |value, options|
+ options[:skip] ||= []
+ options[:skip] << value
end
- add_option('--[no-]extensions',
- 'Restore gems with extensions',
- 'in addition to regular gems') do |value, options|
+ add_option("--[no-]extensions",
+ "Restore gems with extensions",
+ "in addition to regular gems") do |value, options|
options[:extensions_set] = true
options[:extensions] = value
end
- add_option('--only-executables',
- 'Only restore executables') do |value, options|
+ add_option("--only-missing-extensions",
+ "Only restore gems with missing extensions") do |value, options|
+ options[:only_missing_extensions] = value
+ end
+
+ add_option("--only-executables",
+ "Only restore executables") do |value, options|
options[:only_executables] = value
end
- add_option('-E', '--[no-]env-shebang',
- 'Rewrite executables with a shebang',
- 'of /usr/bin/env') do |value, options|
+ add_option("--only-plugins",
+ "Only restore plugins") do |value, options|
+ options[:only_plugins] = 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')
+ add_option("-i", "--install-dir DIR",
+ "Gem repository to get gems restored") do |value, options|
+ options[:install_dir] = File.expand_path(value)
+ end
+
+ add_option("-n", "--bindir DIR",
+ "Directory where executables are",
+ "located") do |value, options|
+ options[:bin_dir] = File.expand_path(value)
+ end
+
+ add_version_option("restore to", "pristine condition")
end
def arguments # :nodoc:
@@ -53,7 +75,7 @@ class Gem::Commands::PristineCommand < Gem::Command
end
def defaults_str # :nodoc:
- '--extensions'
+ "--extensions"
end
def description # :nodoc:
@@ -66,6 +88,10 @@ If you have made modifications to an installed gem, the pristine command
will revert them. All extensions are rebuilt and all bin stubs for the gem
are regenerated after checking for modifications.
+Rebuilding extensions also refreshes C-extension gems against updated system
+libraries (for example after OS or package upgrades) to avoid mismatches like
+outdated library version warnings.
+
If the cached gem cannot be found it will be downloaded.
If --no-extensions is provided pristine will not attempt to restore a gem
@@ -81,61 +107,73 @@ extensions will be restored.
end
def execute
- specs = if options[:all] then
- Gem::Specification.map
-
- # `--extensions` must be explicitly given to pristine only gems
- # with extensions.
- elsif options[:extensions_set] and
- options[:extensions] and options[:args].empty? then
- Gem::Specification.select do |spec|
- spec.extensions and not spec.extensions.empty?
- end
- else
- get_all_gem_names.sort.map do |gem_name|
- Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse
- end.flatten
- end
-
- if specs.to_a.empty? then
- raise Gem::Exception,
- "Failed to find gems #{options[:args]} #{options[:version]}"
+ install_dir = options[:install_dir]
+
+ specification_record = install_dir ? Gem::SpecificationRecord.from_path(install_dir) : Gem::Specification.specification_record
+
+ specs = if options[:all]
+ specification_record.map
+
+ # `--extensions` must be explicitly given to pristine only gems
+ # with extensions.
+ elsif options[:extensions_set] &&
+ options[:extensions] && options[:args].empty?
+ specification_record.select do |spec|
+ spec.extensions && !spec.extensions.empty?
+ end
+ elsif options[:only_missing_extensions]
+ specification_record.select(&:missing_extensions?)
+ else
+ get_all_gem_names.sort.flat_map do |gem_name|
+ specification_record.find_all_by_name(gem_name, options[:version]).reverse
+ end
end
- install_dir = Gem.dir # TODO use installer option
+ specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY }
+
+ if specs.to_a.empty?
+ if options[:only_missing_extensions]
+ say "No gems with missing extensions to restore"
+ return
+ end
- raise Gem::FilePermissionError.new(install_dir) unless
- File.writable?(install_dir)
+ raise Gem::Exception,
+ "Failed to find gems #{options[:args]} #{options[:version]}"
+ end
say "Restoring gems to pristine condition..."
- specs.each do |spec|
- if spec.default_gem?
- say "Skipped #{spec.full_name}, it is a default gem"
- next
- end
+ specs.group_by(&:full_name_with_location).values.each do |grouped_specs|
+ spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first
- if spec.name == options[:skip]
- say "Skipped #{spec.full_name}, it was given through options"
- next
+ only_executables = options[:only_executables]
+ only_plugins = options[:only_plugins]
+
+ unless only_executables || only_plugins
+ # Default gemspecs include changes provided by ruby-core installer that
+ # can't currently be pristined (inclusion of compiled extension targets in
+ # the file list). So stick to resetting executables if it's a default gem.
+ only_executables = true if spec.default_gem?
end
- if spec.bundled_gem_in_old_ruby?
- say "Skipped #{spec.full_name}, it is bundled with old Ruby"
- next
+ if options.key? :skip
+ if options[:skip].include? spec.name
+ say "Skipped #{spec.full_name}, it was given through options"
+ next
+ end
end
- unless spec.extensions.empty? or options[:extensions] or options[:only_executables] then
- say "Skipped #{spec.full_name}, it needs to compile an extension"
+ unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins
+ say "Skipped #{spec.full_name_with_location}, it needs to compile an extension"
next
end
gem = spec.cache_file
- unless File.exist? gem or options[:only_executables] then
- require 'rubygems/remote_fetcher'
+ unless File.exist?(gem) || only_executables || only_plugins
+ require_relative "../remote_fetcher"
- say "Cached gem for #{spec.full_name} not found, attempting to fetch..."
+ say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..."
dep = Gem::Dependency.new spec.name, spec.version
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep
@@ -150,30 +188,36 @@ extensions will be restored.
end
env_shebang =
- if options.include? :env_shebang then
+ if options.include? :env_shebang
options[:env_shebang]
else
- install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install']
- install_defaults.to_s['--env-shebang']
+ install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS["install"]
+ install_defaults.to_s["--env-shebang"]
end
+ bin_dir = options[:bin_dir] if options[:bin_dir]
+
installer_options = {
- :wrappers => true,
- :force => true,
- :install_dir => spec.base_dir,
- :env_shebang => env_shebang,
- :build_args => spec.build_args,
+ wrappers: true,
+ force: true,
+ install_dir: install_dir || spec.base_dir,
+ env_shebang: env_shebang,
+ build_args: spec.build_args,
+ bin_dir: bin_dir,
}
- if options[:only_executables] then
+ if only_executables
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_bin
+ elsif only_plugins
+ installer = Gem::Installer.for_spec(spec, installer_options)
+ installer.generate_plugins
else
installer = Gem::Installer.at(gem, installer_options)
installer.install
end
- say "Restored #{spec.full_name}"
+ say "Restored #{spec.full_name_with_location}"
end
end
end
diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb
index d294cbc8df..02931b3025 100644
--- a/lib/rubygems/commands/push_command.rb
+++ b/lib/rubygems/commands/push_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/gemcutter_utilities'
-require 'rubygems/package'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../gemcutter_utilities"
+require_relative "../package"
class Gem::Commands::PushCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -13,8 +14,10 @@ class Gem::Commands::PushCommand < Gem::Command
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
+The gem can be removed from the index and deleted from the server using the yank
command. For further discussion see the help for the yank command.
+
+The push command will use ~/.gem/credentials to authenticate to a server, but you can use the RubyGems environment variable GEM_HOST_API_KEY to set the api key to authenticate.
EOF
end
@@ -27,78 +30,156 @@ command. For further discussion see the help for the yank command.
end
def initialize
- super 'push', 'Push a gem up to the gem server', :host => self.host
+ super "push", "Push a gem up to the gem server", host: host, attestations: []
+
+ @user_defined_host = false
add_proxy_option
add_key_option
+ add_otp_option
- add_option('--host HOST',
- 'Push to another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)') do |value, options|
+ add_option("--host HOST",
+ "Push to another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)") do |value, options|
options[:host] = value
+ @user_defined_host = true
+ end
+
+ add_option("--attestation FILE",
+ "Push with sigstore attestations") do |value, options|
+ options[:attestations] << value
end
@host = nil
end
def execute
- @host = options[:host]
+ gem_name = get_one_gem_name
+ default_gem_server, push_host = get_hosts_for(gem_name)
+
+ @host = if @user_defined_host
+ options[:host]
+ elsif default_gem_server
+ default_gem_server
+ elsif push_host
+ push_host
+ else
+ options[:host]
+ end
- sign_in @host
+ sign_in @host, scope: get_push_scope
- send_gem get_one_gem_name
+ send_gem(gem_name)
end
- def send_gem name
+ def send_gem(name)
args = [:post, "api/v1/gems"]
- latest_rubygems_version = Gem.latest_rubygems_version
+ _, push_host = get_hosts_for(name)
- if latest_rubygems_version < Gem.rubygems_version and
- Gem.rubygems_version.prerelease? and
- Gem::Version.new('2.0.0.rc.2') != Gem.rubygems_version then
- alert_error <<-ERROR
-You are using a beta release of RubyGems (#{Gem::VERSION}) which is not
-allowed to push gems. Please downgrade or upgrade to a release version.
+ @host ||= push_host
-The latest released RubyGems version is #{latest_rubygems_version}
+ # Always include @host, even if it's nil
+ args += [@host, push_host]
-You can upgrade or downgrade to the latest release version with:
+ say "Pushing gem to #{@host || Gem.host}..."
- gem update --system=#{latest_rubygems_version}
+ response = send_push_request(name, args)
- ERROR
- terminate_interaction 1
- end
+ with_response response
+ end
- gem_data = Gem::Package.new(name)
+ private
- unless @host then
- @host = gem_data.spec.metadata['default_gem_server']
+ def send_push_request(name, args)
+ # Always honor explicit --attestation option
+ # Auto-attestation is only supported on rubygems.org with GitHub Actions (not JRuby)
+ if options[:attestations].any? || (RUBY_ENGINE != "jruby" && attestation_supported_host? && ENV["GITHUB_ACTIONS"])
+ send_push_request_with_attestation(name, args)
+ else
+ send_push_request_without_attestation(name, args)
end
+ end
- push_host = nil
+ def send_push_request_without_attestation(name, args)
+ scope = get_push_scope
+ rubygems_api_request(*args, scope: scope) do |request|
+ body = Gem.read_binary name
+ request.body = body
+ request.add_field "Content-Type", "application/octet-stream"
+ request.add_field "Content-Length", request.body.size
+ request.add_field "Authorization", api_key
+ end
+ end
- if gem_data.spec.metadata.has_key?('allowed_push_host')
- push_host = gem_data.spec.metadata['allowed_push_host']
+ def send_push_request_with_attestation(name, args)
+ attestations = if options[:attestations].any?
+ options[:attestations].map do |attestation|
+ Gem.read_binary(attestation)
+ end
+ else
+ bundle_path = attest!(name)
+ begin
+ [Gem.read_binary(bundle_path)]
+ ensure
+ File.unlink(bundle_path) if bundle_path && File.exist?(bundle_path)
+ end
end
+ bundles = "[" + attestations.join(",") + "]"
+
+ rubygems_api_request(*args, scope: get_push_scope) do |request|
+ request.set_form([
+ ["gem", Gem.read_binary(name), { filename: name, content_type: "application/octet-stream" }],
+ ["attestations", bundles, { content_type: "application/json" }],
+ ], "multipart/form-data")
+ request.add_field "Authorization", api_key
+ end
+ rescue StandardError => e
+ message = "Failed to push with attestation, retrying without attestation.\n"
+ message += if Gem.configuration.really_verbose
+ e.full_message
+ else
+ e.message
+ end
+ alert_warning message
+ send_push_request_without_attestation(name, args)
+ end
- @host ||= push_host
+ def attest!(name)
+ require "open3"
+ require "tempfile"
- # Always include @host, even if it's nil
- args += [ @host, push_host ]
+ tempfile = Tempfile.new([File.basename(name, ".*"), ".sigstore.json"])
+ bundle = tempfile.path
+ tempfile.close(false)
- say "Pushing gem to #{@host || Gem.host}..."
+ env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h
+ out, st = Open3.capture2e(
+ env,
+ Gem.ruby, "-S", "gem", "exec", "--conservative",
+ "sigstore-cli", "sign", name, "--bundle", bundle,
+ unsetenv_others: true
+ )
+ raise Gem::Exception, "Failed to sign gem:\n\n#{out}" unless st.success?
- response = rubygems_api_request(*args) do |request|
- request.body = Gem.read_binary name
- request.add_field "Content-Length", request.body.size
- request.add_field "Content-Type", "application/octet-stream"
- request.add_field "Authorization", api_key
- end
+ bundle
+ end
- with_response response
+ def get_hosts_for(name)
+ gem_metadata = Gem::Package.new(name).spec.metadata
+
+ [
+ gem_metadata["default_gem_server"],
+ gem_metadata["allowed_push_host"],
+ ]
end
-end
+ def get_push_scope
+ :push_rubygem
+ end
+ def attestation_supported_host?
+ host = (@host || Gem.host).to_s.chomp("/")
+ host == Gem::DEFAULT_HOST
+ end
+end
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
deleted file mode 100644
index 4624e5a1e9..0000000000
--- a/lib/rubygems/commands/query_command.rb
+++ /dev/null
@@ -1,359 +0,0 @@
-# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-require 'rubygems/text'
-
-class Gem::Commands::QueryCommand < Gem::Command
-
- include Gem::Text
- include Gem::LocalRemoteOptions
- include Gem::VersionOption
-
- def initialize(name = 'query',
- summary = 'Query gem information in local or remote repositories')
- super name, summary,
- :name => //, :domain => :local, :details => false, :versions => true,
- :installed => nil, :version => Gem::Requirement.default
-
- add_option('-i', '--[no-]installed',
- 'Check for installed gem') do |value, options|
- options[:installed] = value
- end
-
- add_option('-I', 'Equivalent to --no-installed') do |value, options|
- options[:installed] = false
- end
-
- add_version_option command, "for use with --installed"
-
- add_option('-n', '--name-matches REGEXP',
- 'Name of gem(s) to query on matches the',
- 'provided REGEXP') do |value, options|
- options[:name] = /#{value}/i
- end
-
- add_option('-d', '--[no-]details',
- 'Display detailed information of gem(s)') do |value, options|
- options[:details] = value
- end
-
- add_option( '--[no-]versions',
- 'Display only gem names') do |value, options|
- options[:versions] = value
- options[:details] = false unless value
- end
-
- add_option('-a', '--all',
- 'Display all gem versions') do |value, options|
- options[:all] = value
- end
-
- add_option('-e', '--exact',
- 'Name of gem(s) to query on matches the',
- 'provided STRING') do |value, options|
- options[:exact] = value
- end
-
- add_option( '--[no-]prerelease',
- 'Display prerelease versions') do |value, options|
- options[:prerelease] = value
- end
-
- add_local_remote_options
- end
-
- def defaults_str # :nodoc:
- "--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
- if options[:args].to_a.empty? and options[:name].source.empty?
- name = options[:name]
- no_name = true
- elsif !options[:name].source.empty?
- name = Array(options[:name])
- else
- args = options[:args].to_a
- name = options[:exact] ? args.map{|arg| /\A#{Regexp.escape(arg)}\Z/ } : args.map{|arg| /#{arg}/i }
- end
-
- prerelease = options[:prerelease]
-
- unless options[:installed].nil? then
- if no_name then
- alert_error "You must specify a gem name"
- exit_code |= 4
- elsif name.count > 1
- alert_error "You must specify only ONE gem!"
- exit_code |= 4
- else
- installed = installed? name.first, options[:version]
- installed = !installed unless options[:installed]
-
- if installed then
- say "true"
- else
- say "false"
- exit_code |= 1
- end
- end
-
- terminate_interaction exit_code
- end
-
- names = Array(name)
- names.each { |n| show_gems n, prerelease }
- end
-
- private
-
- def display_header type
- if (ui.outs.tty? and Gem.configuration.verbose) or both? then
- say
- say "*** #{type} GEMS ***"
- say
- end
- end
-
- #Guts of original execute
- def show_gems name, prerelease
- req = Gem::Requirement.default
- # TODO: deprecate for real
- dep = Gem::Deprecate.skip_during { Gem::Dependency.new name, req }
- dep.prerelease = prerelease
-
- if local? then
- if prerelease and not both? then
- alert_warning "prereleases are always shown locally"
- end
-
- display_header 'LOCAL'
-
- specs = Gem::Specification.find_all { |s|
- s.name =~ name and req =~ s.version
- }
-
- spec_tuples = specs.map do |spec|
- [spec.name_tuple, spec]
- end
-
- output_query_results spec_tuples
- end
-
- if remote? then
- display_header 'REMOTE'
-
- fetcher = Gem::SpecFetcher.fetcher
-
- type = if options[:all]
- if options[:prerelease]
- :complete
- else
- :released
- end
- elsif options[:prerelease]
- :prerelease
- else
- :latest
- end
-
- if name.respond_to?(:source) && name.source.empty?
- spec_tuples = fetcher.detect(type) { true }
- else
- spec_tuples = fetcher.detect(type) do |name_tuple|
- name === name_tuple.name
- end
- end
-
- output_query_results spec_tuples
- end
- end
-
- ##
- # Check if gem +name+ version +version+ is installed.
-
- def installed?(name, req = Gem::Requirement.default)
- Gem::Specification.any? { |s| s.name =~ name and req =~ s.version }
- end
-
- def output_query_results(spec_tuples)
- output = []
- versions = Hash.new { |h,name| h[name] = [] }
-
- spec_tuples.each do |spec_tuple, source|
- versions[spec_tuple.name] << [spec_tuple, source]
- end
-
- versions = versions.sort_by do |(n,_),_|
- n.downcase
- end
-
- output_versions output, versions
-
- say output.join(options[:details] ? "\n\n" : "\n")
- end
-
- def output_versions output, versions
- versions.each do |gem_name, matching_tuples|
- matching_tuples = matching_tuples.sort_by { |n,_| n.version }.reverse
-
- platforms = Hash.new { |h,version| h[version] = [] }
-
- matching_tuples.each do |n, _|
- platforms[n.version] << n.platform if n.platform
- end
-
- seen = {}
-
- matching_tuples.delete_if do |n,_|
- if seen[n.version] then
- true
- else
- seen[n.version] = true
- false
- end
- end
-
- output << clean_text(make_entry(matching_tuples, platforms))
- end
- end
-
- def entry_details entry, detail_tuple, specs, platforms
- return unless options[:details]
-
- name_tuple, spec = detail_tuple
-
- spec = spec.fetch_spec name_tuple if spec.respond_to? :fetch_spec
-
- entry << "\n"
-
- spec_platforms entry, platforms
- spec_authors entry, spec
- spec_homepage entry, spec
- spec_license entry, spec
- spec_loaded_from entry, spec, specs
- spec_summary entry, spec
- end
-
- def entry_versions entry, name_tuples, platforms, specs
- return unless options[:versions]
-
- list =
- if platforms.empty? or options[:details] then
- name_tuples.map { |n| n.version }.uniq
- else
- platforms.sort.reverse.map do |version, pls|
- out = version.to_s
-
- if options[:domain] == :local
- default = specs.any? do |s|
- !s.is_a?(Gem::Source) && s.version == version && s.default_gem?
- end
- out = "default: #{out}" if default
- end
-
- if pls != [Gem::Platform::RUBY] then
- platform_list = [pls.delete(Gem::Platform::RUBY), *pls.sort].compact
- out = platform_list.unshift(out).join(' ')
- end
-
- out
- end
- end
-
- entry << " (#{list.join ', '})"
- end
-
- def make_entry entry_tuples, platforms
- detail_tuple = entry_tuples.first
-
- name_tuples, specs = entry_tuples.flatten.partition do |item|
- Gem::NameTuple === item
- end
-
- entry = [name_tuples.first.name]
-
- entry_versions entry, name_tuples, platforms, specs
- entry_details entry, detail_tuple, specs, platforms
-
- entry.join
- end
-
- def spec_authors entry, spec
- authors = "Author#{spec.authors.length > 1 ? 's' : ''}: ".dup
- authors << spec.authors.join(', ')
- entry << format_text(authors, 68, 4)
- end
-
- def spec_homepage entry, spec
- return if spec.homepage.nil? or spec.homepage.empty?
-
- entry << "\n" << format_text("Homepage: #{spec.homepage}", 68, 4)
- end
-
- def spec_license entry, spec
- return if spec.license.nil? or spec.license.empty?
-
- licenses = "License#{spec.licenses.length > 1 ? 's' : ''}: ".dup
- licenses << spec.licenses.join(', ')
- entry << "\n" << format_text(licenses, 68, 4)
- end
-
- def spec_loaded_from entry, spec, specs
- return unless spec.loaded_from
-
- if specs.length == 1 then
- default = spec.default_gem? ? ' (default)' : nil
- entry << "\n" << " Installed at#{default}: #{spec.base_dir}"
- else
- label = 'Installed at'
- specs.each do |s|
- version = s.version.to_s
- version << ', default' if s.default_gem?
- entry << "\n" << " #{label} (#{version}): #{s.base_dir}"
- label = ' ' * label.length
- end
- end
- end
-
- def spec_platforms entry, platforms
- non_ruby = platforms.any? do |_, pls|
- pls.any? { |pl| pl != Gem::Platform::RUBY }
- end
-
- return unless non_ruby
-
- if platforms.length == 1 then
- title = platforms.values.length == 1 ? 'Platform' : 'Platforms'
- entry << " #{title}: #{platforms.values.sort.join ', '}\n"
- else
- entry << " Platforms:\n"
- platforms.sort_by do |version,|
- version
- end.each do |version, pls|
- label = " #{version}: "
- data = format_text pls.sort.join(', '), 68, label.length
- data[0, label.length] = label
- entry << data << "\n"
- end
- end
- end
-
- def spec_summary entry, spec
- summary = truncate_text(spec.summary, "the summary for #{spec.full_name}")
- entry << "\n\n" << format_text(summary, 68, 4)
- end
-
-end
diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb
index 6992040dca..62c4bf8ce9 100644
--- a/lib/rubygems/commands/rdoc_command.rb
+++ b/lib/rubygems/commands/rdoc_command.rb
@@ -1,35 +1,36 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/rdoc'
-require 'fileutils'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../rdoc"
+require "fileutils"
class Gem::Commands::RdocCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'rdoc', 'Generates RDoc for pre-installed gems',
- :version => Gem::Requirement.default,
- :include_rdoc => false, :include_ri => true, :overwrite => false
+ super "rdoc", "Generates RDoc for pre-installed gems",
+ version: Gem::Requirement.default,
+ include_rdoc: false, include_ri: true, overwrite: false
- add_option('--all',
- 'Generate RDoc/RI documentation for all',
- 'installed gems') do |value, options|
+ add_option("--all",
+ "Generate RDoc/RI documentation for all",
+ "installed gems") do |value, options|
options[:all] = value
end
- add_option('--[no-]rdoc',
- 'Generate RDoc HTML') do |value, options|
+ add_option("--[no-]rdoc",
+ "Generate RDoc HTML") do |value, options|
options[:include_rdoc] = value
end
- add_option('--[no-]ri',
- 'Generate RI data') do |value, options|
+ add_option("--[no-]ri",
+ "Generate RI data") do |value, options|
options[:include_ri] = value
end
- add_option('--[no-]overwrite',
- 'Overwrite installed documents') do |value, options|
+ add_option("--[no-]overwrite",
+ "Overwrite installed documents") do |value, options|
options[:overwrite] = value
end
@@ -60,16 +61,16 @@ Use --overwrite to force rebuilding of documentation.
end
def execute
- specs = if options[:all] then
- Gem::Specification.to_a
- else
- get_all_gem_names.map do |name|
- Gem::Specification.find_by_name name, options[:version]
- end.flatten.uniq
- end
-
- if specs.empty? then
- alert_error 'No matching gems found'
+ specs = if options[:all]
+ Gem::Specification.to_a
+ else
+ get_all_gem_names.flat_map do |name|
+ Gem::Specification.find_by_name name, options[:version]
+ end.uniq
+ end
+
+ if specs.empty?
+ alert_error "No matching gems found"
terminate_interaction 1
end
@@ -78,20 +79,12 @@ Use --overwrite to force rebuilding of documentation.
doc.force = options[:overwrite]
- if options[:overwrite] then
- FileUtils.rm_rf File.join(spec.doc_dir, 'ri')
- FileUtils.rm_rf File.join(spec.doc_dir, 'rdoc')
+ if options[:overwrite]
+ FileUtils.rm_rf File.join(spec.doc_dir, "ri")
+ FileUtils.rm_rf File.join(spec.doc_dir, "rdoc")
end
- begin
- doc.generate
- rescue Errno::ENOENT => e
- e.message =~ / - /
- alert_error "Unable to document #{spec.full_name}, #{$'} is missing, skipping"
- terminate_interaction 1 if specs.length == 1
- end
+ doc.generate
end
end
-
end
-
diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb
new file mode 100644
index 0000000000..23b9d7b3ba
--- /dev/null
+++ b/lib/rubygems/commands/rebuild_command.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: true
+
+require "digest"
+require "fileutils"
+require "tmpdir"
+require_relative "../gemspec_helpers"
+require_relative "../package"
+
+class Gem::Commands::RebuildCommand < Gem::Command
+ include Gem::GemspecHelpers
+
+ def initialize
+ super "rebuild", "Attempt to reproduce a build of a gem."
+
+ add_option "--diff", "If the files don't match, compare them using diffoscope." do |_value, options|
+ options[:diff] = true
+ end
+
+ add_option "--force", "Skip validation of the spec." do |_value, options|
+ options[:force] = true
+ end
+
+ add_option "--strict", "Consider warnings as errors when validating the spec." do |_value, options|
+ options[:strict] = true
+ end
+
+ add_option "--source GEM_SOURCE", "Specify the source to download the gem from." do |value, options|
+ options[:source] = value
+ end
+
+ add_option "--original GEM_FILE", "Specify a local file to compare against (instead of downloading it)." do |value, options|
+ options[:original_gem_file] = value
+ end
+
+ add_option "--gemspec GEMSPEC_FILE", "Specify the name of the gemspec file." do |value, options|
+ options[:gemspec_file] = value
+ end
+
+ add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options|
+ options[:build_path] = value
+ end
+ end
+
+ def arguments # :nodoc:
+ "GEM_NAME gem name on gem server\n" \
+ "GEM_VERSION gem version you are attempting to rebuild"
+ end
+
+ def description # :nodoc:
+ <<-EOF
+The rebuild command allows you to (attempt to) reproduce a build of a gem
+from a ruby gemspec.
+
+This command assumes the gemspec can be built with the `gem build` command.
+If you use any of `gem build`, `rake build`, or`rake release` in the
+build/release process for a gem, it is a potential candidate.
+
+You will need to match the RubyGems version used, since this is included in
+the Gem metadata.
+
+If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will
+require more effort to reproduce a build. For example, it might require
+more precisely matched versions of Ruby and/or Bundler to be used.
+ EOF
+ end
+
+ def usage # :nodoc:
+ "#{program_name} GEM_NAME GEM_VERSION"
+ end
+
+ def execute
+ gem_name, gem_version = get_gem_name_and_version
+
+ old_dir, new_dir = prep_dirs
+
+ gem_filename = "#{gem_name}-#{gem_version}.gem"
+ old_file = File.join(old_dir, gem_filename)
+ new_file = File.join(new_dir, gem_filename)
+
+ if options[:original_gem_file]
+ FileUtils.copy_file(options[:original_gem_file], old_file)
+ else
+ download_gem(gem_name, gem_version, old_file)
+ end
+
+ rg_version = rubygems_version(old_file)
+ unless rg_version == Gem::VERSION
+ alert_error <<-EOF
+You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with.
+
+#{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}.
+Gem files include the version of RubyGems used to build them.
+This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}.
+
+You're using RubyGems v#{Gem::VERSION}.
+
+Please install RubyGems v#{rg_version} and try again.
+ EOF
+ terminate_interaction 1
+ end
+
+ source_date_epoch = get_timestamp(old_file).to_s
+
+ if build_path = options[:build_path]
+ Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) }
+ else
+ build_gem(gem_name, source_date_epoch, new_file)
+ end
+
+ compare(source_date_epoch, old_file, new_file)
+ end
+
+ private
+
+ def sha256(file)
+ Digest::SHA256.hexdigest(Gem.read_binary(file))
+ end
+
+ def get_timestamp(file)
+ mtime = nil
+ File.open(file, Gem.binary_mode) do |f|
+ Gem::Package::TarReader.new(f) do |tar|
+ mtime = tar.seek("metadata.gz") {|tf| tf.header.mtime }
+ end
+ end
+
+ mtime
+ end
+
+ def compare(source_date_epoch, old_file, new_file)
+ date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z")
+
+ old_hash = sha256(old_file)
+ new_hash = sha256(new_file)
+
+ say
+ say "Built at: #{date} (#{source_date_epoch})"
+ say "Original build saved to: #{old_file}"
+ say "Reproduced build saved to: #{new_file}"
+ say "Working directory: #{options[:build_path] || Dir.pwd}"
+ say
+ say "Hash comparison:"
+ say " #{old_hash}\t#{old_file}"
+ say " #{new_hash}\t#{new_file}"
+ say
+
+ if old_hash == new_hash
+ say "SUCCESS - original and rebuild hashes matched"
+ else
+ say "FAILURE - original and rebuild hashes did not match"
+ say
+
+ if options[:diff]
+ if system("diffoscope", old_file, new_file).nil?
+ alert_error "error: could not find `diffoscope` executable"
+ end
+ else
+ say "Pass --diff for more details (requires diffoscope to be installed)."
+ end
+
+ terminate_interaction 1
+ end
+ end
+
+ def prep_dirs
+ rebuild_dir = Dir.mktmpdir("gem_rebuild")
+ old_dir = File.join(rebuild_dir, "old")
+ new_dir = File.join(rebuild_dir, "new")
+
+ FileUtils.mkdir_p(old_dir)
+ FileUtils.mkdir_p(new_dir)
+
+ [old_dir, new_dir]
+ end
+
+ def get_gem_name_and_version
+ args = options[:args] || []
+ if args.length == 2
+ gem_name, gem_version = args
+ elsif args.length > 2
+ raise Gem::CommandLineError, "Too many arguments"
+ else
+ raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)"
+ end
+
+ [gem_name, gem_version]
+ end
+
+ def build_gem(gem_name, source_date_epoch, output_file)
+ gemspec = options[:gemspec_file] || find_gemspec("#{gem_name}.gemspec")
+
+ if gemspec
+ build_package(gemspec, source_date_epoch, output_file)
+ else
+ alert_error error_message(gem_name)
+ terminate_interaction(1)
+ end
+ end
+
+ def build_package(gemspec, source_date_epoch, output_file)
+ with_source_date_epoch(source_date_epoch) do
+ spec = Gem::Specification.load(gemspec)
+ if spec
+ Gem::Package.build(
+ spec,
+ options[:force],
+ options[:strict],
+ output_file
+ )
+ else
+ alert_error "Error loading gemspec. Aborting."
+ terminate_interaction 1
+ end
+ end
+ end
+
+ def with_source_date_epoch(source_date_epoch)
+ old_sde = ENV["SOURCE_DATE_EPOCH"]
+ ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s
+
+ yield
+ ensure
+ ENV["SOURCE_DATE_EPOCH"] = old_sde
+ end
+
+ def error_message(gem_name)
+ if gem_name
+ "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}"
+ else
+ "Couldn't find a gemspec file in #{Dir.pwd}"
+ end
+ end
+
+ def download_gem(gem_name, gem_version, old_file)
+ # This code was based loosely off the `gem fetch` command.
+ version = "= #{gem_version}"
+ dep = Gem::Dependency.new gem_name, version
+
+ specs_and_sources, errors =
+ Gem::SpecFetcher.fetcher.spec_for_dependency dep
+
+ # There should never be more than one item in specs_and_sources,
+ # since we search for an exact version.
+ spec, source = specs_and_sources[0]
+
+ if spec.nil?
+ show_lookup_failure gem_name, version, errors, options[:domain]
+ terminate_interaction 1
+ end
+
+ download_path = source.download spec
+
+ FileUtils.move(download_path, old_file)
+
+ say "Downloaded #{gem_name} version #{gem_version} as #{old_file}."
+ end
+
+ def rubygems_version(gem_file)
+ Gem::Package.new(gem_file).spec.rubygems_version
+ end
+end
diff --git a/lib/rubygems/commands/search_command.rb b/lib/rubygems/commands/search_command.rb
index 933436d84d..50e161ac9b 100644
--- a/lib/rubygems/commands/search_command.rb
+++ b/lib/rubygems/commands/search_command.rb
@@ -1,15 +1,17 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/commands/query_command'
-class Gem::Commands::SearchCommand < Gem::Commands::QueryCommand
+require_relative "../command"
+require_relative "../query_utils"
- def initialize
- super 'search', 'Display remote gems whose name matches REGEXP'
+class Gem::Commands::SearchCommand < Gem::Command
+ include Gem::QueryUtils
- remove_option '--name-matches'
+ def initialize
+ super "search", "Display remote gems whose name matches REGEXP",
+ domain: :remote, details: false, versions: true,
+ installed: nil, version: Gem::Requirement.default
- defaults[:domain] = :remote
+ add_query_options
end
def arguments # :nodoc:
@@ -36,6 +38,4 @@ To list local gems use the list command.
def usage # :nodoc:
"#{program_name} [REGEXP]"
end
-
end
-
diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb
index 245156d50d..f1dde4aa02 100644
--- a/lib/rubygems/commands/server_command.rb
+++ b/lib/rubygems/commands/server_command.rb
@@ -1,87 +1,26 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/server'
-class Gem::Commands::ServerCommand < Gem::Command
+require_relative "../command"
- def initialize
- super 'server', 'Documentation and gem repository HTTP server',
- :port => 8808, :gemdir => [], :daemon => false
-
- OptionParser.accept :Port do |port|
- if port =~ /\A\d+\z/ then
- port = Integer port
- raise OptionParser::InvalidArgument, "#{port}: not a port number" if
- port > 65535
-
- port
- else
- begin
- Socket.getservbyname port
- rescue SocketError
- raise OptionParser::InvalidArgument, "#{port}: no such named service"
- end
+unless defined? Gem::Commands::ServerCommand
+ class Gem::Commands::ServerCommand < Gem::Command
+ def initialize
+ super("server", "Starts up a web server that hosts the RDoc (requires rubygems-server)")
+ begin
+ Gem::Specification.find_by_name("rubygems-server").activate
+ rescue Gem::LoadError
+ # no-op
end
end
- add_option '-p', '--port=PORT', :Port,
- 'port to listen on' do |port, options|
- options[:port] = port
- end
-
- add_option '-d', '--dir=GEMDIR',
- 'directories from which to serve gems',
- 'multiple directories may be provided' do |gemdir, options|
- options[:gemdir] << File.expand_path(gemdir)
- end
-
- add_option '--[no-]daemon', 'run as a daemon' do |daemon, options|
- options[:daemon] = daemon
+ def description # :nodoc:
+ <<-EOF
+The server command has been moved to the rubygems-server gem.
+ EOF
end
- add_option '-b', '--bind=HOST,HOST',
- 'addresses to bind', Array do |address, options|
- options[:addresses] ||= []
- options[:addresses].push(*address)
- end
-
- add_option '-l', '--launch[=COMMAND]',
- 'launches a browser window',
- "COMMAND defaults to 'start' on Windows",
- "and 'open' on all other platforms" do |launch, options|
- launch ||= Gem.win_platform? ? 'start' : 'open'
- options[:launch] = launch
+ def execute
+ alert_error "Install the rubygems-server gem for the server command"
end
end
-
- def defaults_str # :nodoc:
- "--port 8808 --dir #{Gem.dir} --no-daemon"
- end
-
- def description # :nodoc:
- <<-EOF
-The server command starts up a web server that hosts the RDoc for your
-installed gems and can operate as a server for installation of gems on other
-machines.
-
-The cache files for installed gems must exist to use the server as a source
-for gem installation.
-
-To install gems from a running server, use `gem install GEMNAME --source
-http://gem_server_host:8808`
-
-You can set up a shortcut to gem server documentation using the URL:
-
- http://localhost:8808/rdoc?q=%s - Firefox
- http://localhost:8808/rdoc?q=* - LaunchBar
-
- EOF
- end
-
- def execute
- options[:gemdir] = Gem.path if options[:gemdir].empty?
- Gem::Server.run options
- end
-
end
-
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 93a46d4350..175599967c 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -1,104 +1,110 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
##
# Installs RubyGems itself. This command is ordinarily only available from a
# RubyGems checkout or tarball.
class Gem::Commands::SetupCommand < Gem::Command
- HISTORY_HEADER = /^===\s*[\d.]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
- VERSION_MATCHER = /^===\s*([\d.]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/
-
- def initialize
- require 'tmpdir'
+ HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
+ VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
- super 'setup', 'Install RubyGems',
- :format_executable => true, :document => %w[ri],
- :site_or_vendor => 'sitelibdir',
- :destdir => '', :prefix => '', :previous_version => '',
- :regenerate_binstubs => true
+ ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
- add_option '--previous-version=VERSION',
- 'Previous version of RubyGems',
- 'Used for changelog processing' do |version, options|
+ def initialize
+ super "setup", "Install RubyGems",
+ format_executable: false, document: %w[ri],
+ force: true,
+ site_or_vendor: "sitelibdir",
+ destdir: "", prefix: "", previous_version: "",
+ regenerate_binstubs: true,
+ regenerate_plugins: true
+
+ add_option "--previous-version=VERSION",
+ "Previous version of RubyGems",
+ "Used for changelog processing" do |version, options|
options[:previous_version] = version
end
- add_option '--prefix=PREFIX',
- 'Prefix path for installing RubyGems',
- 'Will not affect gem repository location' do |prefix, options|
+ add_option "--prefix=PREFIX",
+ "Prefix path for installing RubyGems",
+ "Will not affect gem repository location" do |prefix, options|
options[:prefix] = File.expand_path prefix
end
- add_option '--destdir=DESTDIR',
- 'Root directory to install RubyGems into',
- 'Mainly used for packaging RubyGems' do |destdir, options|
+ add_option "--destdir=DESTDIR",
+ "Root directory to install RubyGems into",
+ "Mainly used for packaging RubyGems" do |destdir, options|
options[:destdir] = File.expand_path destdir
end
- add_option '--[no-]vendor',
- 'Install into vendorlibdir not sitelibdir' do |vendor, options|
- options[:site_or_vendor] = vendor ? 'vendorlibdir' : 'sitelibdir'
+ add_option "--[no-]vendor",
+ "Install into vendorlibdir not sitelibdir" do |vendor, options|
+ options[:site_or_vendor] = vendor ? "vendorlibdir" : "sitelibdir"
end
- add_option '--[no-]format-executable',
- 'Makes `gem` match ruby',
- 'If Ruby is ruby18, gem will be gem18' do |value, options|
+ add_option "--[no-]format-executable",
+ "Makes `gem` match ruby",
+ "If Ruby is ruby18, gem will be gem18" do |value, options|
options[:format_executable] = value
end
- add_option '--[no-]document [TYPES]', Array,
- 'Generate documentation for RubyGems',
- 'List the documentation types you wish to',
- 'generate. For example: rdoc,ri' do |value, options|
+ add_option "--[no-]document [TYPES]", Array,
+ "Generate documentation for RubyGems",
+ "List the documentation types you wish to",
+ "generate. For example: rdoc,ri" do |value, options|
options[:document] = case value
when nil then %w[rdoc ri]
when false then []
- else value
- end
+ else value
+ end
end
- add_option '--[no-]rdoc',
- 'Generate RDoc documentation for RubyGems' do |value, options|
- if value then
- options[:document] << 'rdoc'
+ add_option "--[no-]rdoc",
+ "Generate RDoc documentation for RubyGems" do |value, options|
+ if value
+ options[:document] << "rdoc"
else
- options[:document].delete 'rdoc'
+ options[:document].delete "rdoc"
end
options[:document].uniq!
end
- add_option '--[no-]ri',
- 'Generate RI documentation for RubyGems' do |value, options|
- if value then
- options[:document] << 'ri'
+ add_option "--[no-]ri",
+ "Generate RI documentation for RubyGems" do |value, options|
+ if value
+ options[:document] << "ri"
else
- options[:document].delete 'ri'
+ options[:document].delete "ri"
end
options[:document].uniq!
end
- add_option '--[no-]regenerate-binstubs',
- 'Regenerate gem binstubs' do |value, options|
- if value then
- options[:regenerate_binstubs] = true
- else
- options.delete(:regenerate_binstubs)
- end
- end
+ add_option "--[no-]regenerate-binstubs",
+ "Regenerate gem binstubs" do |value, options|
+ options[:regenerate_binstubs] = value
+ end
- @verbose = nil
- end
+ add_option "--[no-]regenerate-plugins",
+ "Regenerate gem plugins" do |value, options|
+ options[:regenerate_plugins] = value
+ end
- def check_ruby_version
- required_version = Gem::Requirement.new '>= 1.8.7'
+ add_option "-f", "--[no-]force",
+ "Forcefully overwrite binstubs" do |value, options|
+ options[:force] = value
+ end
- unless required_version.satisfied_by? Gem.ruby_version then
- alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}"
- terminate_interaction 1
+ add_option("-E", "--[no-]env-shebang",
+ "Rewrite executables with a shebang",
+ "of /usr/bin/env") do |value, options|
+ options[:env_shebang] = value
end
+
+ @verbose = nil
end
def defaults_str # :nodoc:
@@ -119,30 +125,30 @@ prefix and suffix. If ruby was installed as `ruby18`, gem will be
installed as `gem18`.
By default, this RubyGems will install gem as:
- #{Gem.default_exec_format % 'gem'}
+ #{Gem.default_exec_format % "gem"}
EOF
end
- def execute
- @verbose = Gem.configuration.really_verbose
-
- install_destdir = options[:destdir]
-
- unless install_destdir.empty? then
- ENV['GEM_HOME'] ||= File.join(install_destdir,
- Gem.default_dir.gsub(/^[a-zA-Z]:/, ''))
+ module MakeDirs
+ def mkdir_p(path, **opts)
+ super
+ (@mkdirs ||= []) << path
end
+ end
- check_ruby_version
+ def execute
+ @verbose = Gem.configuration.really_verbose
- require 'fileutils'
- if Gem.configuration.really_verbose then
+ require "fileutils"
+ if Gem.configuration.really_verbose
extend FileUtils::Verbose
else
extend FileUtils
end
+ extend MakeDirs
- lib_dir, bin_dir = make_destination_dirs install_destdir
+ lib_dir, bin_dir = make_destination_dirs
+ man_dir = generate_default_man_dir
install_lib lib_dir
@@ -152,24 +158,33 @@ By default, this RubyGems will install gem as:
remove_old_lib_files lib_dir
- install_default_bundler_gem
+ # Can be removed one we drop support for bundler 2.2.3 (the last version installing man files to man_dir)
+ remove_old_man_files man_dir if man_dir && File.exist?(man_dir)
+
+ install_default_bundler_gem bin_dir
+
+ if mode = options[:dir_mode]
+ @mkdirs.uniq!
+ File.chmod(mode, @mkdirs)
+ end
say "RubyGems #{Gem::VERSION} installed"
- regenerate_binstubs
+ regenerate_binstubs(bin_dir) if options[:regenerate_binstubs]
+ regenerate_plugins(bin_dir) if options[:regenerate_plugins]
uninstall_old_gemcutter
documentation_success = install_rdoc
say
- if @verbose then
+ if @verbose
say "-" * 78
say
end
if options[:previous_version].empty?
- options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, '0')
+ options[:previous_version] = Gem::VERSION.sub(/[0-9]+$/, "0")
end
options[:previous_version] = Gem::Version.new(options[:previous_version])
@@ -181,17 +196,17 @@ By default, this RubyGems will install gem as:
say
say "RubyGems installed the following executables:"
- say @bin_file_names.map { |name| "\t#{name}\n" }
+ say bin_file_names.map {|name| "\t#{name}\n" }
say
- unless @bin_file_names.grep(/#{File::SEPARATOR}gem$/) then
+ unless bin_file_names.grep(/#{File::SEPARATOR}gem$/)
say "If `gem` was installed by a previous RubyGems installation, you may need"
say "to remove it by hand."
say
end
if documentation_success
- if options[:document].include? 'rdoc' then
+ if options[:document].include? "rdoc"
say "Rdoc documentation was installed. You may now invoke:"
say " gem server"
say "and then peruse beautifully formatted documentation for your gems"
@@ -202,7 +217,7 @@ By default, this RubyGems will install gem as:
say
end
- if options[:document].include? 'ri' then
+ if options[:document].include? "ri"
say "Ruby Interactive (ri) documentation was installed. ri is kind of like man "
say "pages for Ruby libraries. You may access it like this:"
say " ri Classname"
@@ -216,102 +231,81 @@ By default, this RubyGems will install gem as:
end
end
-
def install_executables(bin_dir)
- @bin_file_names = []
+ prog_mode = options[:prog_mode] || 0o755
- executables = { 'gem' => 'bin' }
- executables['bundler'] = 'bundler/exe' if Gem::USE_BUNDLER_FOR_GEMDEPS
+ executables = { "gem" => "exe" }
executables.each do |tool, path|
say "Installing #{tool} executable" if @verbose
Dir.chdir path do
- bin_files = Dir['*']
-
- bin_files -= %w[update_rubygems bundler bundle_ruby]
-
- bin_files.each do |bin_file|
- bin_file_formatted = if options[:format_executable] then
- Gem.default_exec_format % bin_file
- else
- bin_file
- end
+ bin_file = "gem"
- dest_file = File.join bin_dir, bin_file_formatted
- bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
+ require "tmpdir"
- begin
- bin = File.readlines bin_file
- bin[0] = "#!#{Gem.ruby}\n"
+ dest_file = target_bin_path(bin_dir, bin_file)
+ bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
- File.open bin_tmp_file, 'w' do |fp|
- fp.puts bin.join
- end
+ begin
+ bin = File.readlines bin_file
+ bin[0] = shebang
- install bin_tmp_file, dest_file, :mode => 0755
- @bin_file_names << dest_file
- ensure
- rm bin_tmp_file
+ File.open bin_tmp_file, "w" do |fp|
+ fp.puts bin.join
end
- next unless Gem.win_platform?
+ install bin_tmp_file, dest_file, mode: prog_mode
+ bin_file_names << dest_file
+ ensure
+ rm bin_tmp_file
+ end
+
+ next unless Gem.win_platform?
- begin
- bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
+ begin
+ bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
- File.open bin_cmd_file, 'w' do |file|
- file.puts <<-TEXT
+ File.open bin_cmd_file, "w" do |file|
+ file.puts <<-TEXT
@ECHO OFF
- IF NOT "%~f0" == "~f0" GOTO :WinNT
- @"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9
- GOTO :EOF
- :WinNT
- @"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
+ @"%~dp0#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
TEXT
- end
-
- install bin_cmd_file, "#{dest_file}.bat", :mode => 0755
- ensure
- rm bin_cmd_file
end
+
+ install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode
+ ensure
+ rm bin_cmd_file
end
end
end
end
- def install_file file, dest_dir
- dest_file = File.join dest_dir, file
- dest_dir = File.dirname dest_file
- mkdir_p dest_dir unless File.directory? dest_dir
-
- install file, dest_file, :mode => 0644
+ def shebang
+ if options[:env_shebang]
+ ruby_name = RbConfig::CONFIG["ruby_install_name"]
+ @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
+ "#!#{@env_path} #{ruby_name}\n"
+ else
+ "#!#{Gem.ruby}\n"
+ end
end
def install_lib(lib_dir)
- libs = { 'RubyGems' => 'lib' }
- libs['Bundler'] = 'bundler/lib' if Gem::USE_BUNDLER_FOR_GEMDEPS
+ libs = { "RubyGems" => "lib" }
+ libs["Bundler"] = "bundler/lib"
libs.each do |tool, path|
say "Installing #{tool}" if @verbose
- lib_files = rb_files_in path
- lib_files.concat(template_files) if tool == 'Bundler'
-
- pem_files = pem_files_in path
+ lib_files = files_in path
Dir.chdir path do
- lib_files.each do |lib_file|
- install_file lib_file, lib_dir
- end
-
- pem_files.each do |pem_file|
- install_file pem_file, lib_dir
- end
+ install_file_list(lib_files, lib_dir)
end
end
end
def install_rdoc
- gem_doc_dir = File.join Gem.dir, 'doc'
+ gem_doc_dir = File.join Gem.dir, "doc"
rubygems_name = "rubygems-#{Gem::VERSION}"
rubygems_doc_dir = File.join gem_doc_dir, rubygems_name
@@ -321,148 +315,164 @@ By default, this RubyGems will install gem as:
# ignore
end
- if File.writable? gem_doc_dir and
- (not File.exist? rubygems_doc_dir or
- File.writable? rubygems_doc_dir) then
+ if File.writable?(gem_doc_dir) &&
+ (!File.exist?(rubygems_doc_dir) ||
+ File.writable?(rubygems_doc_dir))
say "Removing old RubyGems RDoc and ri" if @verbose
- Dir[File.join(Gem.dir, 'doc', 'rubygems-[0-9]*')].each do |dir|
+ Dir[File.join(Gem.dir, "doc", "rubygems-[0-9]*")].each do |dir|
rm_rf dir
end
- require 'rubygems/rdoc'
+ require_relative "../rdoc"
- fake_spec = Gem::Specification.new 'rubygems', Gem::VERSION
+ return false unless defined?(Gem::RDoc)
+
+ fake_spec = Gem::Specification.new "rubygems", Gem::VERSION
def fake_spec.full_gem_path
- File.expand_path '../../../..', __FILE__
+ File.expand_path "../../..", __dir__
end
- generate_ri = options[:document].include? 'ri'
- generate_rdoc = options[:document].include? 'rdoc'
+ generate_ri = options[:document].include? "ri"
+ generate_rdoc = options[:document].include? "rdoc"
rdoc = Gem::RDoc.new fake_spec, generate_rdoc, generate_ri
rdoc.generate
return true
- elsif @verbose then
+ elsif @verbose
say "Skipping RDoc generation, #{gem_doc_dir} not writable"
say "Set the GEM_HOME environment variable if you want RDoc generated"
end
- return false
+ false
end
- def install_default_bundler_gem
- return unless Gem::USE_BUNDLER_FOR_GEMDEPS
+ def install_default_bundler_gem(bin_dir)
+ current_default_spec = Gem::Specification.default_stubs.find {|s| s.name == "bundler" }
+ specs_dir = if current_default_spec && default_dir == Gem.default_dir
+ all_specs_current_version = Gem::Specification.stubs.select {|s| s.full_name == current_default_spec.full_name }
- mkdir_p Gem::Specification.default_specifications_dir
+ Gem::Specification.remove_spec current_default_spec
+ loaded_from = current_default_spec.loaded_from
+ File.delete(loaded_from)
- # Workaround for non-git environment.
- gemspec = File.read('bundler/bundler.gemspec').gsub(/`git ls-files -z`/, "''")
- File.open('bundler/bundler.gemspec', 'w'){|f| f.write gemspec }
+ # Remove previous default gem executables if they were not shadowed by a regular gem
+ FileUtils.rm_rf current_default_spec.full_gem_path if all_specs_current_version.size == 1
- bundler_spec = Gem::Specification.load("bundler/bundler.gemspec")
- bundler_spec.files = Dir.chdir("bundler") { Dir["{*.md,{lib,exe,man}/**/*}"] }
- bundler_spec.executables -= %w[bundler bundle_ruby]
- Dir.entries(Gem::Specification.default_specifications_dir).
- select {|gs| gs.start_with?("bundler-") }.
- each {|gs| File.delete(File.join(Gem::Specification.default_specifications_dir, gs)) }
+ File.dirname(loaded_from)
+ else
+ target_specs_dir = File.join(default_dir, "specifications", "default")
+ mkdir_p target_specs_dir, mode: 0o755
+ target_specs_dir
+ end
+
+ new_bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") }
+ full_name = new_bundler_spec.full_name
+ gemspec_path = "#{full_name}.gemspec"
- default_spec_path = File.join(Gem::Specification.default_specifications_dir, "#{bundler_spec.full_name}.gemspec")
- Gem.write_binary(default_spec_path, bundler_spec.to_ruby)
+ default_spec_path = File.join(specs_dir, gemspec_path)
+ Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby)
bundler_spec = Gem::Specification.load(default_spec_path)
- Dir.entries(bundler_spec.gems_dir).
- select {|default_gem| default_gem.start_with?("bundler-") }.
- each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) }
+ # Remove gemspec that was same version of vendored bundler.
+ normal_gemspec = File.join(default_dir, "specifications", gemspec_path)
+ if File.file? normal_gemspec
+ File.delete normal_gemspec
+ end
+
+ # Remove gem files that were same version of vendored bundler.
+ if File.directory? bundler_spec.gems_dir
+ Dir.entries(bundler_spec.gems_dir).
+ select {|default_gem| File.basename(default_gem) == full_name }.
+ each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) }
+ end
+
+ require_relative "../installer"
+
+ Dir.chdir("bundler") do
+ built_gem = Gem::Package.build(new_bundler_spec)
+ begin
+ installer = Gem::Installer.at(
+ built_gem,
+ env_shebang: options[:env_shebang],
+ format_executable: options[:format_executable],
+ force: options[:force],
+ bin_dir: bin_dir,
+ install_dir: default_dir,
+ wrappers: true
+ )
+ # We need to install only executable and default spec files.
+ # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory.
+ installer.extract_bin
+ installer.generate_bin
+ installer.write_default_spec
+ ensure
+ FileUtils.rm_f built_gem
+ end
+ end
- mkdir_p bundler_spec.bin_dir
- bundler_spec.executables.each {|e| cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_spec.bin_dir, e) }
+ new_bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) }
- say "Bundler #{bundler_spec.version} installed"
+ say "Bundler #{new_bundler_spec.version} installed"
end
- def make_destination_dirs(install_destdir)
+ def make_destination_dirs
lib_dir, bin_dir = Gem.default_rubygems_dirs
unless lib_dir
- lib_dir, bin_dir = generate_default_dirs(install_destdir)
+ lib_dir, bin_dir = generate_default_dirs
end
- mkdir_p lib_dir
- mkdir_p bin_dir
+ mkdir_p lib_dir, mode: 0o755
+ mkdir_p bin_dir, mode: 0o755
- return lib_dir, bin_dir
+ [lib_dir, bin_dir]
end
- def generate_default_dirs(install_destdir)
+ def generate_default_man_dir
prefix = options[:prefix]
- site_or_vendor = options[:site_or_vendor]
- if prefix.empty? then
- lib_dir = RbConfig::CONFIG[site_or_vendor]
- bin_dir = RbConfig::CONFIG['bindir']
+ if prefix.empty?
+ man_dir = RbConfig::CONFIG["mandir"]
+ return unless man_dir
else
- # Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets
- # confused about installation location, so switch back to
- # sitelibdir/vendorlibdir.
- if defined?(APPLE_GEM_HOME) and
- # just in case Apple and RubyGems don't get this patched up proper.
- (prefix == RbConfig::CONFIG['libdir'] or
- # this one is important
- prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) then
- lib_dir = RbConfig::CONFIG[site_or_vendor]
- bin_dir = RbConfig::CONFIG['bindir']
- else
- lib_dir = File.join prefix, 'lib'
- bin_dir = File.join prefix, 'bin'
- end
- end
-
- unless install_destdir.empty? then
- lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '')
- bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '')
+ man_dir = File.join prefix, "man"
end
- [lib_dir, bin_dir]
+ prepend_destdir_if_present(man_dir)
end
- def pem_files_in dir
- Dir.chdir dir do
- Dir[File.join('**', '*pem')]
- end
- end
+ def generate_default_dirs
+ prefix = options[:prefix]
+ site_or_vendor = options[:site_or_vendor]
- def rb_files_in dir
- Dir.chdir dir do
- Dir[File.join('**', '*rb')]
+ if prefix.empty?
+ lib_dir = RbConfig::CONFIG[site_or_vendor]
+ bin_dir = RbConfig::CONFIG["bindir"]
+ else
+ lib_dir = File.join prefix, "lib"
+ bin_dir = File.join prefix, "bin"
end
- end
- # for installation of bundler as default gems
- def template_files
- Dir.chdir "bundler/lib" do
- (Dir[File.join('bundler', 'templates', '**', '*')] + Dir[File.join('bundler', 'templates', '**', '.*')]).
- select{|f| !File.directory?(f)}
- end
+ [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)]
end
- # for cleanup old bundler files
- def template_files_in dir
+ def files_in(dir)
Dir.chdir dir do
- (Dir[File.join('templates', '**', '*')] + Dir[File.join('templates', '**', '.*')]).
- select{|f| !File.directory?(f)}
+ Dir.glob(File.join("**", "*"), File::FNM_DOTMATCH).
+ select {|f| !File.directory?(f) }
end
end
def remove_old_bin_files(bin_dir)
old_bin_files = {
- 'gem_mirror' => 'gem mirror',
- 'gem_server' => 'gem server',
- 'gemlock' => 'gem lock',
- 'gemri' => 'ri',
- 'gemwhich' => 'gem which',
- 'index_gem_repository.rb' => 'gem generate_index',
+ "gem_mirror" => "gem mirror",
+ "gem_server" => "gem server",
+ "gemlock" => "gem lock",
+ "gemri" => "ri",
+ "gemwhich" => "gem which",
+ "index_gem_repository.rb" => "gem generate_index",
}
old_bin_files.each do |old_bin_file, new_name|
@@ -471,7 +481,7 @@ By default, this RubyGems will install gem as:
deprecation_message = "`#{old_bin_file}` has been deprecated. Use `#{new_name}` instead."
- File.open old_bin_path, 'w' do |fp|
+ File.open old_bin_path, "w" do |fp|
fp.write <<-EOF
#!#{Gem.ruby}
@@ -481,50 +491,59 @@ abort "#{deprecation_message}"
next unless Gem.win_platform?
- File.open "#{old_bin_path}.bat", 'w' do |fp|
- fp.puts %{@ECHO.#{deprecation_message}}
+ File.open "#{old_bin_path}.bat", "w" do |fp|
+ fp.puts %(@ECHO.#{deprecation_message})
end
end
end
- def remove_old_lib_files lib_dir
- lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' }
- lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler' if Gem::USE_BUNDLER_FOR_GEMDEPS
+ def remove_old_lib_files(lib_dir)
+ lib_dirs = { File.join(lib_dir, "rubygems") => "lib/rubygems" }
+ lib_dirs[File.join(lib_dir, "bundler")] = "bundler/lib/bundler"
lib_dirs.each do |old_lib_dir, new_lib_dir|
- lib_files = rb_files_in(new_lib_dir)
- lib_files.concat(template_files_in(new_lib_dir)) if new_lib_dir =~ /bundler/
+ lib_files = files_in(new_lib_dir)
- old_lib_files = rb_files_in(old_lib_dir)
- old_lib_files.concat(template_files_in(old_lib_dir)) if old_lib_dir =~ /bundler/
+ old_lib_files = files_in(old_lib_dir)
to_remove = old_lib_files - lib_files
+ gauntlet_rubygems = File.join(lib_dir, "gauntlet_rubygems.rb")
+ to_remove << gauntlet_rubygems if File.exist? gauntlet_rubygems
+
to_remove.delete_if do |file|
- file.start_with? 'defaults'
+ file.start_with? "defaults"
end
- Dir.chdir old_lib_dir do
- to_remove.each do |file|
- FileUtils.rm_f file
+ remove_file_list(to_remove, old_lib_dir)
+ end
+ end
- warn "unable to remove old file #{file} please remove it by hand" if
- File.exist? file
- end
- end
+ def remove_old_man_files(old_man_dir)
+ old_man1_dir = "#{old_man_dir}/man1"
+
+ if File.exist?(old_man1_dir)
+ man1_to_remove = Dir.chdir(old_man1_dir) { Dir["bundle*.1{,.txt,.ronn}"] }
+
+ remove_file_list(man1_to_remove, old_man1_dir)
+ end
+
+ old_man5_dir = "#{old_man_dir}/man5"
+
+ if File.exist?(old_man5_dir)
+ man5_to_remove = Dir.chdir(old_man5_dir) { Dir["gemfile.5{,.txt,.ronn}"] }
+
+ remove_file_list(man5_to_remove, old_man5_dir)
end
end
def show_release_notes
- release_notes = File.join Dir.pwd, 'History.txt'
+ release_notes = File.join Dir.pwd, "CHANGELOG.md"
release_notes =
- if File.exist? release_notes then
+ if File.exist? release_notes
history = File.read release_notes
- history.force_encoding Encoding::UTF_8 if
- Object.const_defined? :Encoding
-
- history = history.sub(/^# coding:.*?(?=^=)/m, '')
+ history.force_encoding Encoding::UTF_8
text = history.split(HISTORY_HEADER)
text.shift # correct an off-by-one generated by split
@@ -535,8 +554,8 @@ abort "#{deprecation_message}"
history_string = ""
- until versions.length == 0 or
- versions.shift < options[:previous_version] do
+ until versions.length == 0 ||
+ versions.shift <= options[:previous_version] do
history_string += version_lines.shift + text.shift
end
@@ -549,19 +568,100 @@ abort "#{deprecation_message}"
end
def uninstall_old_gemcutter
- require 'rubygems/uninstaller'
+ require_relative "../uninstaller"
- ui = Gem::Uninstaller.new('gemcutter', :all => true, :ignore => true,
- :version => '< 0.4')
+ ui = Gem::Uninstaller.new("gemcutter", all: true, ignore: true,
+ version: "< 0.4")
ui.uninstall
rescue Gem::InstallError
end
- def regenerate_binstubs
- require "rubygems/commands/pristine_command"
+ def regenerate_binstubs(bindir)
+ require_relative "pristine_command"
say "Regenerating binstubs"
+
+ args = %w[--all --only-executables --silent]
+ args << "--bindir=#{bindir}"
+ args << "--install-dir=#{default_dir}"
+
+ if options[:env_shebang]
+ args << "--env-shebang"
+ end
+
command = Gem::Commands::PristineCommand.new
- command.invoke(*%w[--all --only-executables --silent])
+ command.invoke(*args)
+ end
+
+ def regenerate_plugins(bindir)
+ require_relative "pristine_command"
+ say "Regenerating plugins"
+
+ args = %w[--all --only-plugins --silent]
+ args << "--bindir=#{bindir}"
+ args << "--install-dir=#{default_dir}"
+
+ command = Gem::Commands::PristineCommand.new
+ command.invoke(*args)
+ end
+
+ private
+
+ def default_dir
+ prefix = options[:prefix]
+
+ if prefix.empty?
+ dir = Gem.default_dir
+ else
+ dir = prefix
+ end
+
+ prepend_destdir_if_present(dir)
+ end
+
+ def prepend_destdir_if_present(path)
+ destdir = options[:destdir]
+ return path if destdir.empty?
+
+ File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, ""))
+ end
+
+ def install_file_list(files, dest_dir)
+ files.each do |file|
+ install_file file, dest_dir
+ end
+ end
+
+ def install_file(file, dest_dir)
+ dest_file = File.join dest_dir, file
+ dest_dir = File.dirname dest_file
+ unless File.directory? dest_dir
+ mkdir_p dest_dir, mode: 0o755
+ end
+
+ install file, dest_file, mode: options[:data_mode] || 0o644
end
+ def remove_file_list(files, dir)
+ Dir.chdir dir do
+ files.each do |file|
+ FileUtils.rm_f file
+
+ warn "unable to remove old file #{file} please remove it by hand" if
+ File.exist? file
+ end
+ end
+ end
+
+ def target_bin_path(bin_dir, bin_file)
+ bin_file_formatted = if options[:format_executable]
+ Gem.default_exec_format % bin_file
+ else
+ bin_file
+ end
+ File.join bin_dir, bin_file_formatted
+ end
+
+ def bin_file_names
+ @bin_file_names ||= []
+ end
end
diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb
index 6556db5a89..0f77908c5b 100644
--- a/lib/rubygems/commands/signin_command.rb
+++ b/lib/rubygems/commands/signin_command.rb
@@ -1,25 +1,27 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/gemcutter_utilities'
+
+require_relative "../command"
+require_relative "../gemcutter_utilities"
class Gem::Commands::SigninCommand < Gem::Command
include Gem::GemcutterUtilities
def initialize
- super 'signin', 'Sign in to any gemcutter-compatible host. '\
- 'It defaults to https://rubygems.org'
+ super "signin", "Sign in to any gemcutter-compatible host. "\
+ "It defaults to https://rubygems.org"
- add_option('--host HOST', 'Push to another gemcutter-compatible host') do |value, options|
- options[:host] = value
+ add_option("--host HOST", "Push to another gemcutter-compatible host") do |value, options|
+ options[:host] = value
end
+ add_otp_option
end
def description # :nodoc:
- 'The signin command executes host sign in for a push server (the default is'\
- ' https://rubygems.org). The host can be provided with the host flag or can'\
- ' be inferred from the provided gem. Host resolution matches the resolution'\
- ' strategy for the push command.'
+ "The signin command executes host sign in for a push server (the default is"\
+ " https://rubygems.org). The host can be provided with the host flag or can"\
+ " be inferred from the provided gem. Host resolution matches the resolution"\
+ " strategy for the push command."
end
def usage # :nodoc:
@@ -29,5 +31,4 @@ class Gem::Commands::SigninCommand < Gem::Command
def execute
sign_in options[:host]
end
-
end
diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb
index 2452e8cae1..bdd01e4393 100644
--- a/lib/rubygems/commands/signout_command.rb
+++ b/lib/rubygems/commands/signout_command.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require 'rubygems/command'
-class Gem::Commands::SignoutCommand < Gem::Command
+require_relative "../command"
+class Gem::Commands::SignoutCommand < Gem::Command
def initialize
- super 'signout', 'Sign out from all the current sessions.'
+ super "signout", "Sign out from all the current sessions."
end
def description # :nodoc:
- 'The `signout` command is used to sign out from all current sessions,'\
- ' allowing you to sign in using a different set of credentials.'
+ "The `signout` command is used to sign out from all current sessions,"\
+ " allowing you to sign in using a different set of credentials."
end
def usage # :nodoc:
@@ -19,15 +19,14 @@ class Gem::Commands::SignoutCommand < Gem::Command
def execute
credentials_path = Gem.configuration.credentials_path
- if !File.exist?(credentials_path) then
- alert_error 'You are not currently signed in.'
- elsif !File.writable?(credentials_path) then
+ if !File.exist?(credentials_path)
+ alert_error "You are not currently signed in."
+ elsif !File.writable?(credentials_path)
alert_error "File '#{Gem.configuration.credentials_path}' is read-only."\
- ' Please make sure it is writable.'
+ " Please make sure it is writable."
else
Gem.configuration.unset_api_key!
- say 'You have successfully signed out from all sessions.'
+ say "You have successfully signed out from all sessions."
end
end
-
end
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index 7e46963a4c..b399af2bd3 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -1,50 +1,60 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/remote_fetcher'
-require 'rubygems/spec_fetcher'
-require 'rubygems/local_remote_options'
-class Gem::Commands::SourcesCommand < Gem::Command
+require_relative "../command"
+require_relative "../remote_fetcher"
+require_relative "../spec_fetcher"
+require_relative "../local_remote_options"
+class Gem::Commands::SourcesCommand < Gem::Command
include Gem::LocalRemoteOptions
def initialize
- require 'fileutils'
+ require "fileutils"
- super 'sources',
- 'Manage the sources and cache file RubyGems uses to search for gems'
+ super "sources",
+ "Manage the sources and cache file RubyGems uses to search for gems"
- add_option '-a', '--add SOURCE_URI', 'Add source' do |value, options|
+ add_option "-a", "--add SOURCE_URI", "Add source" do |value, options|
options[:add] = value
end
- add_option '-l', '--list', 'List sources' do |value, options|
+ add_option "--append SOURCE_URI", "Append source (can be used multiple times)" do |value, options|
+ options[:append] = value
+ end
+
+ add_option "-p", "--prepend SOURCE_URI", "Prepend source (can be used multiple times)" do |value, options|
+ options[:prepend] = value
+ end
+
+ add_option "-l", "--list", "List sources" do |value, options|
options[:list] = value
end
- add_option '-r', '--remove SOURCE_URI', 'Remove source' do |value, options|
+ add_option "-r", "--remove SOURCE_URI", "Remove source" do |value, options|
options[:remove] = value
end
- add_option '-c', '--clear-all',
- 'Remove all sources (clear the cache)' do |value, options|
+ add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options|
options[:clear_all] = value
end
- add_option '-u', '--update', 'Update source cache' do |value, options|
+ add_option "-u", "--update", "Update source cache" do |value, options|
options[:update] = value
end
+ add_option "-f", "--[no-]force", "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options|
+ options[:force] = value
+ end
+
add_proxy_option
end
- def add_source source_uri # :nodoc:
- check_rubygems_https source_uri
-
- source = Gem::Source.new source_uri
+ def add_source(source_uri) # :nodoc:
+ source = build_new_source(source_uri)
+ source_uri = source.uri.to_s
begin
- if Gem.sources.include? source then
+ if Gem.sources.include? source
say "source #{source_uri} already present in the cache"
else
source.load_specs :released
@@ -53,27 +63,100 @@ class Gem::Commands::SourcesCommand < Gem::Command
say "#{source_uri} added to sources"
end
- rescue URI::Error, ArgumentError
+ rescue Gem::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}"
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
terminate_interaction 1
end
end
- def check_rubygems_https source_uri # :nodoc:
- uri = URI source_uri
+ def append_source(source_uri) # :nodoc:
+ source = build_new_source(source_uri)
+ source_uri = source.uri.to_s
+
+ begin
+ source.load_specs :released
+ was_present = Gem.sources.include?(source)
+ Gem.sources.append source
+ Gem.configuration.write
+
+ if was_present
+ say "#{source_uri} moved to end of sources"
+ else
+ say "#{source_uri} added to sources"
+ end
+ rescue Gem::URI::Error, ArgumentError
+ say "#{source_uri} is not a URI"
+ terminate_interaction 1
+ rescue Gem::RemoteFetcher::FetchError => e
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
+ terminate_interaction 1
+ end
+ end
+
+ def prepend_source(source_uri) # :nodoc:
+ source = build_new_source(source_uri)
+ source_uri = source.uri.to_s
+
+ begin
+ source.load_specs :released
+ was_present = Gem.sources.include?(source)
+ Gem.sources.prepend source
+ Gem.configuration.write
+
+ if was_present
+ say "#{source_uri} moved to top of sources"
+ else
+ say "#{source_uri} added to sources"
+ end
+ rescue Gem::URI::Error, ArgumentError
+ say "#{source_uri} is not a URI"
+ terminate_interaction 1
+ rescue Gem::RemoteFetcher::FetchError => e
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
+ terminate_interaction 1
+ end
+ end
+
+ def check_typo_squatting(source)
+ if source.typo_squatting?("rubygems.org")
+ question = <<-QUESTION.chomp
+#{source.uri} is too similar to https://rubygems.org
+
+Do you want to add this source?
+ QUESTION
+
+ terminate_interaction 1 unless options[:force] || ask_yes_no(question)
+ end
+ end
+
+ def normalize_source_uri(source_uri) # :nodoc:
+ # Ensure the source URI has a trailing slash for proper RFC 2396 path merging
+ # Without a trailing slash, the last path segment is treated as a file and removed
+ # during relative path resolution (e.g., "/blish" + "gems/foo.gem" = "/gems/foo.gem")
+ # With a trailing slash, it's treated as a directory (e.g., "/blish/" + "gems/foo.gem" = "/blish/gems/foo.gem")
+ uri = Gem::URI.parse(source_uri)
+ uri.path = uri.path.gsub(%r{/+\z}, "") + "/" if uri.path && !uri.path.empty?
+ uri.to_s
+ rescue Gem::URI::Error
+ # If parsing fails, return the original URI and let later validation handle it
+ source_uri
+ end
+
+ def check_rubygems_https(source_uri) # :nodoc:
+ uri = Gem::URI source_uri
- if uri.scheme and uri.scheme.downcase == 'http' and
- uri.host.downcase == 'rubygems.org' then
+ if uri.scheme && uri.scheme.casecmp("http").zero? &&
+ uri.host.casecmp("rubygems.org").zero?
question = <<-QUESTION.chomp
https://rubygems.org is recommended for security over #{uri}
Do you want to add this insecure source?
QUESTION
- terminate_interaction 1 unless ask_yes_no question
+ terminate_interaction 1 unless options[:force] || ask_yes_no(question)
end
end
@@ -81,21 +164,21 @@ Do you want to add this insecure source?
path = Gem.spec_cache_dir
FileUtils.rm_rf path
- unless File.exist? path then
- say "*** Removed specs cache ***"
- else
- unless File.writable? path then
- say "*** Unable to remove source cache (write protected) ***"
- else
+ if File.exist? path
+ if File.writable? path
say "*** Unable to remove source cache ***"
+ else
+ say "*** Unable to remove source cache (write protected) ***"
end
terminate_interaction 1
+ else
+ say "*** Removed specs cache ***"
end
end
def defaults_str # :nodoc:
- '--list'
+ "--list"
end
def description # :nodoc:
@@ -110,7 +193,7 @@ yourself to use your own gem server.
Without any arguments the sources lists your currently configured sources:
$ gem sources
- *** CURRENT SOURCES ***
+ *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
https://rubygems.org
@@ -121,41 +204,57 @@ do not recognize you should remove them.
RubyGems has been configured to serve gems via the following URLs through
its history:
-* http://gems.rubyforge.org (RubyGems 1.3.6 and earlier)
-* http://rubygems.org (RubyGems 1.3.7 through 1.8.25)
+* http://gems.rubyforge.org (RubyGems 1.3.5 and earlier)
+* http://rubygems.org (RubyGems 1.3.6 through 1.8.30, and 2.0.0)
* 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:
+To add a private gem source use the --prepend argument to insert it before
+the default source. This is usually the best place for private gem sources:
- $ gem sources --add https://rubygems.org
- https://rubygems.org added to sources
+ $ gem sources --prepend https://my.private.source
+ https://my.private.source added to sources
RubyGems will check to see if gems can be installed from the source given
before it is added.
+To add or move a source after all other sources, use --append:
+
+ $ gem sources --append https://rubygems.org
+ https://rubygems.org moved to end of sources
+
To remove a source use the --remove argument:
- $ gem sources --remove http://rubygems.org
- http://rubygems.org removed from sources
+ $ gem sources --remove https://my.private.source/
+ https://my.private.source/ removed from sources
EOF
end
def list # :nodoc:
- say "*** CURRENT SOURCES ***"
+ if configured_sources
+ header = "*** CURRENT SOURCES ***"
+ list = configured_sources
+ else
+ header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***"
+ list = Gem.sources
+ end
+
+ say header
say
- Gem.sources.each do |src|
+ list.each do |src|
say src
end
end
def list? # :nodoc:
!(options[:add] ||
+ options[:prepend] ||
+ options[:append] ||
options[:clear_all] ||
options[:remove] ||
options[:update])
@@ -164,25 +263,36 @@ To remove a source use the --remove argument:
def execute
clear_all if options[:clear_all]
- source_uri = options[:add]
- add_source source_uri if source_uri
+ add_source options[:add] if options[:add]
- source_uri = options[:remove]
- remove_source source_uri if source_uri
+ prepend_source options[:prepend] if options[:prepend]
+
+ append_source options[:append] if options[:append]
+
+ remove_source options[:remove] if options[:remove]
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
+ def remove_source(source_uri) # :nodoc:
+ source = build_source(source_uri)
+ source_uri = source.uri.to_s
+
+ if configured_sources&.include? source
+ Gem.sources.delete source
Gem.configuration.write
- say "#{source_uri} removed from sources"
+ if default_sources.include?(source) && configured_sources.one?
+ alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source."
+ else
+ say "#{source_uri} removed from sources"
+ end
+ elsif configured_sources
+ say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}"
+ else
+ say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}"
end
end
@@ -195,17 +305,44 @@ To remove a source use the --remove argument:
say "source cache successfully updated"
end
- def remove_cache_file desc, path # :nodoc:
+ def remove_cache_file(desc, path) # :nodoc:
FileUtils.rm_rf path
- if not File.exist?(path) then
+ if !File.exist?(path)
say "*** Removed #{desc} source cache ***"
- elsif not File.writable?(path) then
+ elsif !File.writable?(path)
say "*** Unable to remove #{desc} source cache (write protected) ***"
else
say "*** Unable to remove #{desc} source cache ***"
end
end
-end
+ private
+
+ def default_sources
+ Gem::SourceList.from(Gem.default_sources)
+ end
+ def configured_sources
+ return @configured_sources if defined?(@configured_sources)
+
+ configuration_sources = Gem.configuration.sources
+ @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources
+ end
+
+ def config_file_name
+ Gem.configuration.config_file_name
+ end
+
+ def build_source(source_uri)
+ source_uri = normalize_source_uri(source_uri)
+ Gem::Source.new(source_uri)
+ end
+
+ def build_new_source(source_uri)
+ source = build_source(source_uri)
+ check_rubygems_https(source.uri.to_s)
+ check_typo_squatting(source)
+ source
+ end
+end
diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb
index ad8840adc2..15e543f1a6 100644
--- a/lib/rubygems/commands/specification_command.rb
+++ b/lib/rubygems/commands/specification_command.rb
@@ -1,39 +1,39 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-require 'rubygems/package'
-class Gem::Commands::SpecificationCommand < Gem::Command
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+require_relative "../package"
+class Gem::Commands::SpecificationCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
def initialize
Gem.load_yaml
- super 'specification', 'Display gem specification (in yaml)',
- :domain => :local, :version => Gem::Requirement.default,
- :format => :yaml
+ super "specification", "Display gem specification (in yaml)",
+ domain: :local, version: Gem::Requirement.default,
+ format: :yaml
- add_version_option('examine')
+ add_version_option("examine")
add_platform_option
add_prerelease_option
- add_option('--all', 'Output specifications for all versions of',
- 'the gem') do |value, options|
+ add_option("--all", "Output specifications for all versions of",
+ "the gem") do |_value, options|
options[:all] = true
end
- add_option('--ruby', 'Output ruby format') do |value, options|
+ add_option("--ruby", "Output ruby format") do |_value, options|
options[:format] = :ruby
end
- add_option('--yaml', 'Output YAML format') do |value, options|
+ add_option("--yaml", "Output YAML format") do |_value, options|
options[:format] = :yaml
end
- add_option('--marshal', 'Output Marshal format') do |value, options|
+ add_option("--marshal", "Output Marshal format") do |_value, options|
options[:format] = :marshal
end
@@ -42,7 +42,7 @@ class Gem::Commands::SpecificationCommand < Gem::Command
def arguments # :nodoc:
<<-ARGS
-GEMFILE name of gem to show the gemspec for
+GEM_OR_FILE gem name or a .gem file to show the gemspec for
FIELD name of gemspec field to show
ARGS
end
@@ -68,16 +68,16 @@ Specific fields in the specification can be extracted in YAML format:
end
def usage # :nodoc:
- "#{program_name} [GEMFILE] [FIELD]"
+ "#{program_name} [GEM_OR_FILE] [FIELD]"
end
def execute
specs = []
gem = options[:args].shift
- unless gem then
+ unless gem
raise Gem::CommandLineError,
- "Please specify a gem name or file on the command line"
+ "Please specify a gem name or a .gem file on the command line"
end
case v = options[:version]
@@ -89,7 +89,7 @@ Specific fields in the specification can be extracted in YAML format:
raise Gem::CommandLineError, "Unsupported version type: '#{v}'"
end
- if !req.none? and options[:all]
+ if !req.none? && options[:all]
alert_error "Specify --all or -v, not both"
terminate_interaction 1
end
@@ -103,32 +103,42 @@ Specific fields in the specification can be extracted in YAML format:
field = get_one_optional_argument
raise Gem::CommandLineError, "--ruby and FIELD are mutually exclusive" if
- field and options[:format] == :ruby
-
- if local? then
- if File.exist? gem then
- specs << Gem::Package.new(gem).spec rescue nil
+ field && options[:format] == :ruby
+
+ if local?
+ if File.exist? gem
+ begin
+ specs << Gem::Package.new(gem).spec
+ rescue StandardError
+ nil
+ end
end
- if specs.empty? then
+ if specs.empty?
specs.push(*dep.matching_specs)
end
end
- if remote? then
+ if remote?
dep.prerelease = options[:prerelease]
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dep
- specs.push(*found.map { |spec,| spec })
+ specs.push(*found.map {|spec,| spec })
end
- if specs.empty? then
+ if specs.empty?
alert_error "No gem matching '#{dep}' found"
terminate_interaction 1
end
- unless options[:all] then
- specs = [specs.max_by { |s| s.version }]
+ platform = get_platform_from_requirements(options)
+
+ if platform
+ specs = specs.select {|s| s.platform.to_s == platform }
+ end
+
+ unless options[:all]
+ specs = [specs.max_by(&:version)]
end
specs.each do |s|
@@ -137,8 +147,8 @@ Specific fields in the specification can be extracted in YAML format:
say case options[:format]
when :ruby then s.to_ruby
when :marshal then Marshal.dump s
- else s.to_yaml
- end
+ else Gem.use_psych? ? s.to_yaml : Gem::YAMLSerializer.dump(s)
+ end
say "\n"
end
diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb
index 0524ee1e2b..0be2b85159 100644
--- a/lib/rubygems/commands/stale_command.rb
+++ b/lib/rubygems/commands/stale_command.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::StaleCommand < Gem::Command
def initialize
- super('stale', 'List gems along with access times')
+ super("stale", "List gems along with access times")
end
def description # :nodoc:
@@ -17,7 +18,7 @@ longer using.
end
def usage # :nodoc:
- "#{program_name}"
+ program_name.to_s
end
def execute
@@ -32,8 +33,8 @@ longer using.
end
end
- gem_to_atime.sort_by { |_, atime| atime }.each do |name, atime|
- say "#{name} at #{atime.strftime '%c'}"
+ gem_to_atime.sort_by {|_, atime| atime }.each do |name, atime|
+ say "#{name} at #{atime.strftime "%c"}"
end
end
end
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index 20b3a7a1e4..3c26074f93 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/version_option'
-require 'rubygems/uninstaller'
-require 'fileutils'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../uninstaller"
+require "fileutils"
##
# Gem uninstaller command line tool
@@ -10,82 +11,80 @@ require 'fileutils'
# See `gem help uninstall`
class Gem::Commands::UninstallCommand < Gem::Command
-
include Gem::VersionOption
def initialize
- super 'uninstall', 'Uninstall gems from the local repository',
- :version => Gem::Requirement.default, :user_install => true,
- :check_dev => false, :vendor => false
+ super "uninstall", "Uninstall gems from the local repository",
+ version: Gem::Requirement.default, user_install: true,
+ check_dev: false, vendor: false
- add_option('-a', '--[no-]all',
- 'Uninstall all matching versions'
- ) do |value, options|
+ add_option("-a", "--[no-]all",
+ "Uninstall all matching versions") do |value, options|
options[:all] = value
end
- add_option('-I', '--[no-]ignore-dependencies',
- 'Ignore dependency requirements while',
- 'uninstalling') do |value, options|
+ add_option("-I", "--[no-]ignore-dependencies",
+ "Ignore dependency requirements while",
+ "uninstalling") do |value, options|
options[:ignore] = value
end
- add_option('-D', '--[no-]check-development',
- 'Check development dependencies while uninstalling',
- '(default: false)') do |value, options|
+ add_option("-D", "--[no-]check-development",
+ "Check development dependencies while uninstalling",
+ "(default: false)") do |value, options|
options[:check_dev] = value
end
- add_option('-x', '--[no-]executables',
- 'Uninstall applicable executables without',
- 'confirmation') do |value, options|
+ add_option("-x", "--[no-]executables",
+ "Uninstall applicable executables without",
+ "confirmation") do |value, options|
options[:executables] = value
end
- add_option('-i', '--install-dir DIR',
- 'Directory to uninstall gem from') do |value, options|
+ add_option("-i", "--install-dir DIR",
+ "Directory to uninstall gem from") do |value, options|
options[:install_dir] = File.expand_path(value)
end
- add_option('-n', '--bindir DIR',
- 'Directory to remove binaries from') do |value, options|
+ add_option("-n", "--bindir DIR",
+ "Directory to remove executables from") do |value, options|
options[:bin_dir] = File.expand_path(value)
end
- add_option('--[no-]user-install',
- 'Uninstall from user\'s home directory',
- 'in addition to GEM_HOME.') do |value, options|
+ add_option("--[no-]user-install",
+ "Uninstall from user's home directory",
+ "in addition to GEM_HOME.") do |value, options|
options[:user_install] = value
end
- add_option('--[no-]format-executable',
- 'Assume executable names match Ruby\'s prefix and suffix.') do |value, options|
+ add_option("--[no-]format-executable",
+ "Assume executable names match Ruby's prefix and suffix.") do |value, options|
options[:format_executable] = value
end
- add_option('--[no-]force',
- 'Uninstall all versions of the named gems',
- 'ignoring dependencies') do |value, options|
+ add_option("--[no-]force",
+ "Uninstall all versions of the named gems",
+ "ignoring dependencies") do |value, options|
options[:force] = value
end
- add_option('--[no-]abort-on-dependent',
- 'Prevent uninstalling gems that are',
- 'depended on by other gems.') do |value, options|
+ 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
- add_option('--vendor',
- 'Uninstall gem from the vendor directory.',
- 'Only for use by gem repackagers.') do |value, options|
- unless Gem.vendor_dir then
- raise OptionParser::InvalidOption.new 'your platform is not supported'
+ add_option("--vendor",
+ "Uninstall gem from the vendor directory.",
+ "Only for use by gem repackagers.") do |_value, options|
+ unless Gem.vendor_dir
+ raise Gem::OptionParser::InvalidOption.new "your platform is not supported"
end
- alert_warning 'Use your OS package manager to uninstall vendor gems'
+ alert_warning "Use your OS package manager to uninstall vendor gems"
options[:vendor] = true
options[:install_dir] = Gem.vendor_dir
end
@@ -96,8 +95,8 @@ class Gem::Commands::UninstallCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--version '#{Gem::Requirement.default}' --no-force " +
- "--user-install"
+ "--version '#{Gem::Requirement.default}' --no-force " \
+ "--user-install"
end
def description # :nodoc:
@@ -114,10 +113,24 @@ that is a dependency of an existing gem. You can use the
"#{program_name} GEMNAME [GEMNAME ...]"
end
+ def check_version # :nodoc:
+ if options[:version] != Gem::Requirement.default &&
+ get_all_gem_names.size > 1
+ alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
+ " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:>=2'`"
+ terminate_interaction 1
+ end
+ end
+
def execute
- if options[:all] and not options[:args].empty? then
+ check_version
+
+ # Consider only gem specifications installed at `--install-dir`
+ Gem::Specification.dirs = options[:install_dir] if options[:install_dir]
+
+ if options[:all] && !options[:args].empty?
uninstall_specific
- elsif options[:all] then
+ elsif options[:all]
uninstall_all
else
uninstall_specific
@@ -125,42 +138,67 @@ that is a dependency of an existing gem. You can use the
end
def uninstall_all
- specs = Gem::Specification.reject { |spec| spec.default_gem? }
+ specs = Gem::Specification.reject(&:default_gem?)
specs.each do |spec|
options[:version] = spec.version
-
- begin
- Gem::Uninstaller.new(spec.name, options).uninstall
- rescue Gem::InstallError
- end
+ uninstall_gem spec.name
end
- alert "Uninstalled all gems in #{options[:install_dir]}"
+ alert "Uninstalled all gems in #{options[:install_dir] || Gem.dir}"
end
def uninstall_specific
deplist = Gem::DependencyList.new
+ original_gem_version = {}
- get_all_gem_names.uniq.each do |name|
- gem_specs = Gem::Specification.find_all_by_name(name)
- say("Gem '#{name}' is not installed") if gem_specs.empty?
- gem_specs.each do |spec|
- deplist.add spec
+ get_all_gem_names_and_versions.each do |name, version|
+ original_gem_version[name] = version || options[:version]
+
+ gem_specs = Gem::Specification.find_all_by_name(name, original_gem_version[name])
+
+ if gem_specs.empty?
+ say("Gem '#{name}' is not installed")
+ else
+ gem_specs.reject!(&:default_gem?) if gem_specs.size > 1
+
+ gem_specs.each do |spec|
+ deplist.add spec
+ end
end
end
deps = deplist.strongly_connected_components.flatten.reverse
- deps.map(&:name).uniq.each do |gem_name|
- begin
- Gem::Uninstaller.new(gem_name, options).uninstall
- rescue Gem::GemNotInHomeException => e
- spec = e.spec
- alert("In order to remove #{spec.name}, please execute:\n" +
- "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}")
+ gems_to_uninstall = {}
+
+ deps.each do |dep|
+ if original_gem_version[dep.name] == Gem::Requirement.default
+ next if gems_to_uninstall[dep.name]
+ gems_to_uninstall[dep.name] = true
+ else
+ options[:version] = dep.version
end
+
+ uninstall_gem(dep.name)
end
end
+ def uninstall_gem(gem_name)
+ uninstall(gem_name)
+ rescue Gem::GemNotInHomeException => e
+ spec = e.spec
+ alert("In order to remove #{spec.name}, please execute:\n" \
+ "\tgem uninstall #{spec.name} --install-dir=#{spec.base_dir}")
+ rescue Gem::UninstallError => e
+ spec = e.spec
+ alert_error("Error: unable to successfully uninstall '#{spec.name}' which is " \
+ "located at '#{spec.full_gem_path}'. This is most likely because" \
+ "the current user does not have the appropriate permissions")
+ terminate_interaction 1
+ end
+
+ def uninstall(gem_name)
+ Gem::Uninstaller.new(gem_name, options).uninstall
+ end
end
diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb
index eb7f550673..c2fc720297 100644
--- a/lib/rubygems/commands/unpack_command.rb
+++ b/lib/rubygems/commands/unpack_command.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/installer'
-require 'rubygems/version_option'
-require 'rubygems/security_option'
-require 'rubygems/remote_fetcher'
+
+require_relative "../command"
+require_relative "../version_option"
+require_relative "../security_option"
+require_relative "../remote_fetcher"
+require_relative "../package"
# forward-declare
@@ -13,23 +14,22 @@ module Gem::Security # :nodoc:
end
class Gem::Commands::UnpackCommand < Gem::Command
-
include Gem::VersionOption
include Gem::SecurityOption
def initialize
- require 'fileutils'
+ require "fileutils"
- super 'unpack', 'Unpack an installed gem to the current directory',
- :version => Gem::Requirement.default,
- :target => Dir.pwd
+ super "unpack", "Unpack an installed gem to the current directory",
+ version: Gem::Requirement.default,
+ target: Dir.pwd
- add_option('--target=DIR',
- 'target directory for unpacking') do |value, options|
+ add_option("--target=DIR",
+ "target directory for unpacking") do |value, options|
options[:target] = value
end
- add_option('--spec', 'unpack the gem specification') do |value, options|
+ add_option("--spec", "unpack the gem specification") do |_value, options|
options[:spec] = true
end
@@ -79,26 +79,34 @@ command help for an example.
dependency = Gem::Dependency.new name, options[:version]
path = get_path dependency
- unless path then
+ unless path
alert_error "Gem '#{name}' not installed nor fetchable."
next
end
- if @options[:spec] then
- spec, metadata = get_metadata path, security_policy
+ if @options[:spec]
+ spec, metadata = Gem::Package.raw_spec(path, security_policy)
- if metadata.nil? then
+ if metadata.nil?
alert_error "--spec is unsupported on '#{name}' (old format gem)"
next
end
spec_file = File.basename spec.spec_file
- open spec_file, 'w' do |io|
+ FileUtils.mkdir_p @options[:target] if @options[:target]
+
+ destination = if @options[:target]
+ File.join @options[:target], spec_file
+ else
+ spec_file
+ end
+
+ File.open destination, "w" do |io|
io.write metadata
end
else
- basename = File.basename path, '.gem'
+ basename = File.basename path, ".gem"
target_dir = File.expand_path basename, options[:target]
package = Gem::Package.new path, security_policy
@@ -122,7 +130,7 @@ command help for an example.
return this_path if File.exist? this_path
end
- return nil
+ nil
end
##
@@ -135,24 +143,18 @@ command help for an example.
# get_path 'rake', '< 0.1' # nil
# get_path 'rak' # nil (exact name required)
#--
- # TODO: This should be refactored so that it's a general service. I don't
- # think any of our existing classes are the right place though. Just maybe
- # 'Cache'?
- #
- # TODO: It just uses Gem.dir for now. What's an easy way to get the list of
- # source directories?
- def get_path dependency
- return dependency.name if dependency.name =~ /\.gem$/i
+ def get_path(dependency)
+ return dependency.name if /\.gem$/i.match?(dependency.name)
specs = dependency.matching_specs
- selected = specs.max_by { |s| s.version }
+ selected = specs.max_by(&:version)
return Gem::RemoteFetcher.fetcher.download_to_cache(dependency) unless
selected
- return unless dependency.name =~ /^#{selected.name}$/i
+ return unless /^#{selected.name}$/i.match?(dependency.name)
# We expect to find (basename).gem in the 'cache' directory. Furthermore,
# the name match must be exact (ignoring case).
@@ -163,33 +165,4 @@ command help for an example.
path
end
-
- ##
- # Extracts the Gem::Specification and raw metadata from the .gem file at
- # +path+.
- #--
- # TODO move to Gem::Package as #raw_spec or something
-
- def get_metadata path, security_policy = nil
- format = Gem::Package.new path, security_policy
- spec = format.spec
-
- metadata = nil
-
- open path, Gem.binary_mode do |io|
- tar = Gem::Package::TarReader.new io
- tar.each_entry do |entry|
- case entry.full_name
- when 'metadata' then
- metadata = entry.read
- when 'metadata.gz' then
- metadata = Gem.gunzip entry.read
- end
- end
- end
-
- return spec, metadata
- end
-
end
-
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index 93ee60e1ab..d9740d814a 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/command_manager'
-require 'rubygems/dependency_installer'
-require 'rubygems/install_update_options'
-require 'rubygems/local_remote_options'
-require 'rubygems/spec_fetcher'
-require 'rubygems/version_option'
-require 'rubygems/install_message' # must come before rdoc for messaging
-require 'rubygems/rdoc'
-class Gem::Commands::UpdateCommand < Gem::Command
+require_relative "../command"
+require_relative "../command_manager"
+require_relative "../dependency_installer"
+require_relative "../install_update_options"
+require_relative "../local_remote_options"
+require_relative "../spec_fetcher"
+require_relative "../version_option"
+require_relative "../install_message" # must come before rdoc for messaging
+require_relative "../rdoc"
+class Gem::Commands::UpdateCommand < Gem::Command
include Gem::InstallUpdateOptions
include Gem::LocalRemoteOptions
include Gem::VersionOption
@@ -20,23 +20,27 @@ class Gem::Commands::UpdateCommand < Gem::Command
attr_reader :updated # :nodoc:
def initialize
- super 'update', 'Update installed gems to the latest version',
- :document => %w[rdoc ri],
- :force => false
+ options = {
+ force: false,
+ }
+
+ options.merge!(install_update_options)
+
+ super "update", "Update installed gems to the latest version", options
add_install_update_options
- OptionParser.accept Gem::Version do |value|
+ Gem::OptionParser.accept Gem::Version do |value|
Gem::Version.new value
value
end
- add_option('--system [VERSION]', Gem::Version,
- 'Update the RubyGems system software') do |value, options|
- value = true unless value
+ add_option("--system [VERSION]", Gem::Version,
+ "Update the RubyGems system software") do |value, opts|
+ value ||= true
- options[:system] = value
+ opts[:system] = value
end
add_local_remote_options
@@ -52,7 +56,8 @@ class Gem::Commands::UpdateCommand < Gem::Command
end
def defaults_str # :nodoc:
- "--document --no-force --install-dir #{Gem.dir}"
+ "--no-force --install-dir #{Gem.dir}\n" +
+ install_update_defaults_str
end
def description # :nodoc:
@@ -68,49 +73,68 @@ command to remove old versions.
"#{program_name} GEMNAME [GEMNAME ...]"
end
- def check_latest_rubygems version # :nodoc:
- if Gem.rubygems_version == version then
+ def check_latest_rubygems(version) # :nodoc:
+ if Gem.rubygems_version == version
say "Latest version already installed. Done."
terminate_interaction
end
+ end
- options[:user_install] = false
+ def check_oldest_rubygems(version) # :nodoc:
+ if oldest_supported_version > version
+ alert_error "rubygems #{version} is not supported on #{RUBY_VERSION}. The oldest version supported by this ruby is #{oldest_supported_version}"
+ terminate_interaction 1
+ end
end
def check_update_arguments # :nodoc:
- unless options[:args].empty? then
+ unless options[:args].empty?
alert_error "Gem names are not allowed with the --system option"
terminate_interaction 1
end
end
def execute
-
- if options[:system] then
+ if options[:system]
update_rubygems
return
end
- say "Updating installed gems"
+ gems_to_update = which_to_update(
+ highest_installed_gems,
+ options[:args].uniq
+ )
+
+ if options[:explain]
+ say "Gems to update:"
- hig = highest_installed_gems
+ gems_to_update.each do |name_tuple|
+ say " #{name_tuple.full_name}"
+ end
+
+ return
+ end
- gems_to_update = which_to_update hig, options[:args].uniq
+ say "Updating installed gems"
updated = update_gems gems_to_update
- updated_names = updated.map { |spec| spec.name }
+ installed_names = highest_installed_gems.keys
+ updated_names = updated.map(&:name)
not_updated_names = options[:args].uniq - updated_names
+ not_installed_names = not_updated_names - installed_names
+ up_to_date_names = not_updated_names - not_installed_names
- if updated.empty? then
+ if updated.empty?
say "Nothing to update"
else
- say "Gems updated: #{updated_names.join(' ')}"
- say "Gems already up-to-date: #{not_updated_names.join(' ')}" unless not_updated_names.empty?
+ say "Gems updated: #{updated_names.join(" ")}"
end
+ say "Gems already up-to-date: #{up_to_date_names.join(" ")}" unless up_to_date_names.empty?
+ say "Gems not currently installed: #{not_installed_names.join(" ")}" unless not_installed_names.empty?
end
- def fetch_remote_gems spec # :nodoc:
+ def fetch_remote_gems(spec) # :nodoc:
dependency = Gem::Dependency.new spec.name, "> #{spec.version}"
dependency.prerelease = options[:prerelease]
@@ -118,7 +142,7 @@ command to remove old versions.
spec_tuples, errors = fetcher.search_for_dependency dependency
- error = errors.find { |e| e.respond_to? :exception }
+ error = errors.find {|e| e.respond_to? :exception }
raise error if error
@@ -128,8 +152,11 @@ command to remove old versions.
def highest_installed_gems # :nodoc:
hig = {} # highest installed gems
+ # Get only gem specifications installed as --user-install
+ Gem::Specification.dirs = Gem.user_dir if options[:user_install]
+
Gem::Specification.each do |spec|
- if hig[spec.name].nil? or hig[spec.name].version < spec.version then
+ if hig[spec.name].nil? || hig[spec.name].version < spec.version
hig[spec.name] = spec
end
end
@@ -137,34 +164,50 @@ command to remove old versions.
hig
end
- def highest_remote_version spec # :nodoc:
+ def highest_remote_name_tuple(spec) # :nodoc:
spec_tuples = fetch_remote_gems spec
- matching_gems = spec_tuples.select do |g,_|
- g.name == spec.name and g.match_platform?
- end
-
- highest_remote_gem = matching_gems.max_by { |g,_| g.version }
-
- highest_remote_gem ||= [Gem::NameTuple.null]
+ highest_remote_gem = spec_tuples.max
+ return unless highest_remote_gem
- highest_remote_gem.first.version
+ highest_remote_gem.first
end
- def install_rubygems version # :nodoc:
+ def install_rubygems(spec) # :nodoc:
args = update_rubygems_arguments
+ version = spec.version
- update_dir = File.join Gem.dir, 'gems', "rubygems-update-#{version}"
+ update_dir = File.join spec.base_dir, "gems", "rubygems-update-#{version}"
Dir.chdir update_dir do
- say "Installing RubyGems #{version}"
-
- # 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
+ say "Installing RubyGems #{version}" unless options[:silent]
+
+ installed = preparing_gem_layout_for(version) do
+ system Gem.ruby, "--disable-gems", "setup.rb", *args
+ end
+
+ unless options[:silent]
+ say "RubyGems system software updated" if installed
+ end
+ end
+ end
+
+ def preparing_gem_layout_for(version)
+ if Gem::Version.new(version) >= Gem::Version.new("3.2.a")
+ yield
+ else
+ require "tmpdir"
+ Dir.mktmpdir("gem_update") do |tmpdir|
+ FileUtils.mv Gem.plugindir, tmpdir
+
+ status = yield
+
+ unless status
+ FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir
+ end
+
+ status
+ end
end
end
@@ -172,43 +215,35 @@ command to remove old versions.
version = options[:system]
update_latest = version == true
- if update_latest then
- version = Gem::Version.new Gem::VERSION
- requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
- else
+ unless update_latest
version = Gem::Version.new version
requirement = Gem::Requirement.new version
+
+ return version, requirement
end
+ version = Gem::Version.new Gem::VERSION
+ requirement = Gem::Requirement.new ">= #{Gem::VERSION}"
+
rubygems_update = Gem::Specification.new
- rubygems_update.name = 'rubygems-update'
+ rubygems_update.name = "rubygems-update"
rubygems_update.version = version
- hig = {
- 'rubygems-update' => rubygems_update
- }
-
- gems_to_update = which_to_update hig, options[:args], :system
- _, up_ver = gems_to_update.first
-
- target = if update_latest then
- up_ver
- else
- version
- end
+ highest_remote_tup = highest_remote_name_tuple(rubygems_update)
+ target = highest_remote_tup ? highest_remote_tup.version : version
- return target, requirement
+ [target, requirement]
end
- def update_gem name, version = Gem::Requirement.default
- return if @updated.any? { |spec| spec.name == name }
+ def update_gem(name, version = Gem::Requirement.default)
+ return if @updated.any? {|spec| spec.name == name }
update_options = options.dup
update_options[:prerelease] = version.prerelease?
@installer = Gem::DependencyInstaller.new update_options
- say "Updating #{name}"
+ say "Updating #{name}" unless options[:system]
begin
@installer.install name, Gem::Requirement.new(version)
rescue Gem::InstallError, Gem::DependencyError => e
@@ -220,9 +255,9 @@ command to remove old versions.
end
end
- def update_gems gems_to_update
- gems_to_update.uniq.sort.each do |(name, version)|
- update_gem name, version
+ def update_gems(gems_to_update)
+ gems_to_update.uniq.sort.each do |name_tuple|
+ update_gem name_tuple.name, name_tuple.version
end
@updated
@@ -232,48 +267,60 @@ command to remove old versions.
# Update RubyGems software to the latest version.
def update_rubygems
+ if Gem.disable_system_update_message
+ alert_error Gem.disable_system_update_message
+ terminate_interaction 1
+ end
+
check_update_arguments
version, requirement = rubygems_target_version
check_latest_rubygems version
- update_gem 'rubygems-update', version
+ check_oldest_rubygems version
- installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
- version = installed_gems.first.version
+ installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement
+ installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version
+ return if installed_gems.empty?
- install_rubygems version
+ install_rubygems installed_gems.first
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]
- args << '--previous-version' << Gem::VERSION if
- options[:system] == true or
- Gem::Version.new(options[:system]) >= Gem::Version.new(2)
+ args << "--silent" if options[:silent]
+ args << "--prefix" << Gem.prefix if Gem.prefix
+ args << "--no-document" unless options[:document].include?("rdoc") || options[:document].include?("ri")
+ args << "--no-format-executable" if options[:no_format_executable]
+ args << "--previous-version" << Gem::VERSION
args
end
- def which_to_update highest_installed_gems, gem_names, system = false
+ def which_to_update(highest_installed_gems, gem_names)
result = []
- highest_installed_gems.each do |l_name, l_spec|
- next if not gem_names.empty? and
- gem_names.none? { |name| name == l_spec.name }
+ highest_installed_gems.each do |_l_name, l_spec|
+ next if !gem_names.empty? &&
+ gem_names.none? {|name| name == l_spec.name }
- highest_remote_ver = highest_remote_version l_spec
+ highest_remote_tup = highest_remote_name_tuple l_spec
+ next unless highest_remote_tup
- if system or (l_spec.version < highest_remote_ver) then
- result << [l_spec.name, [l_spec.version, highest_remote_ver].max]
- end
+ result << highest_remote_tup
end
result
end
+ private
+
+ #
+ # Oldest version we support downgrading to. This is the version that
+ # originally ships with the oldest supported patch version of ruby.
+ #
+ def oldest_supported_version
+ @oldest_supported_version ||=
+ Gem::Version.new("3.3.3")
+ end
end
diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb
index 704d79fc60..5ed4d9d142 100644
--- a/lib/rubygems/commands/which_command.rb
+++ b/lib/rubygems/commands/which_command.rb
@@ -1,17 +1,18 @@
# frozen_string_literal: true
-require 'rubygems/command'
+
+require_relative "../command"
class Gem::Commands::WhichCommand < Gem::Command
def initialize
- super 'which', 'Find the location of a library file you can require',
- :search_gems_first => false, :show_all => false
+ super "which", "Find the location of a library file you can require",
+ search_gems_first: false, show_all: false
- add_option '-a', '--[no-]all', 'show all matching files' do |show_all, options|
+ add_option "-a", "--[no-]all", "show all matching files" do |show_all, options|
options[:show_all] = show_all
end
- add_option '-g', '--[no-]gems-first',
- 'search gems before non-gems' do |gems_first, options|
+ add_option "-g", "--[no-]gems-first",
+ "search gems before non-gems" do |gems_first, options|
options[:search_gems_first] = gems_first
end
end
@@ -39,26 +40,24 @@ requiring to see why it does not behave as you expect.
found = true
options[:args].each do |arg|
- arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, '')
+ arg = arg.sub(/#{Regexp.union(*Gem.suffixes)}$/, "")
dirs = $LOAD_PATH
spec = Gem::Specification.find_by_path arg
- if spec then
- if options[:search_gems_first] then
+ if spec
+ if options[:search_gems_first]
dirs = spec.full_require_paths + $LOAD_PATH
else
dirs = $LOAD_PATH + spec.full_require_paths
end
end
- # TODO: this is totally redundant and stupid
paths = find_paths arg, dirs
- if paths.empty? then
+ if paths.empty?
alert_error "Can't find Ruby library file or shared library #{arg}"
-
- found &&= false
+ found = false
else
say paths
end
@@ -73,7 +72,7 @@ requiring to see why it does not behave as you expect.
dirs.each do |dir|
Gem.suffixes.each do |ext|
full_path = File.join dir, "#{package_name}#{ext}"
- if File.exist? full_path and not File.directory? full_path then
+ if File.exist?(full_path) && !File.directory?(full_path)
result << full_path
return result unless options[:show_all]
end
@@ -86,6 +85,4 @@ requiring to see why it does not behave as you expect.
def usage # :nodoc:
"#{program_name} FILE [FILE ...]"
end
-
end
-
diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb
index ebf24e5c77..fbdc262549 100644
--- a/lib/rubygems/commands/yank_command.rb
+++ b/lib/rubygems/commands/yank_command.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-require 'rubygems/command'
-require 'rubygems/local_remote_options'
-require 'rubygems/version_option'
-require 'rubygems/gemcutter_utilities'
+
+require_relative "../command"
+require_relative "../local_remote_options"
+require_relative "../version_option"
+require_relative "../gemcutter_utilities"
class Gem::Commands::YankCommand < Gem::Command
include Gem::LocalRemoteOptions
@@ -24,18 +25,19 @@ data you will need to change them immediately and yank your gem.
end
def usage # :nodoc:
- "#{program_name} GEM -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST]"
+ "#{program_name} -v VERSION [-p PLATFORM] [--key KEY_NAME] [--host HOST] GEM"
end
def initialize
- super 'yank', 'Remove a pushed gem from the index'
+ super "yank", "Remove a pushed gem from the index"
add_version_option("remove")
add_platform_option("remove")
+ add_otp_option
- add_option('--host HOST',
- 'Yank from another gemcutter-compatible host',
- ' (e.g. https://rubygems.org)') do |value, options|
+ add_option("--host HOST",
+ "Yank from another gemcutter-compatible host",
+ " (e.g. https://rubygems.org)") do |value, options|
options[:host] = value
end
@@ -46,12 +48,12 @@ data you will need to change them immediately and yank your gem.
def execute
@host = options[:host]
- sign_in @host
+ sign_in @host, scope: get_yank_scope
version = get_version_from_requirements(options[:version])
platform = get_platform_from_requirements(options)
- if version then
+ if version
yank_gem(version, platform)
else
say "A version argument is required: #{usage}"
@@ -60,37 +62,38 @@ data you will need to change them immediately and yank your gem.
end
def yank_gem(version, platform)
- say "Yanking gem from #{self.host}..."
- yank_api_request(:delete, version, platform, "api/v1/gems/yank")
+ say "Yanking gem from #{host}..."
+ args = [:delete, version, platform, "api/v1/gems/yank"]
+ response = yank_api_request(*args)
+
+ say response.body
end
private
def yank_api_request(method, version, platform, api)
name = get_one_gem_name
- response = rubygems_api_request(method, api, host) do |request|
+ response = rubygems_api_request(method, api, host, scope: get_yank_scope) do |request|
request.add_field("Authorization", api_key)
data = {
- 'gem_name' => name,
- 'version' => version,
+ "gem_name" => name,
+ "version" => version,
}
- data['platform'] = platform if platform
+ data["platform"] = platform if platform
request.set_form_data data
end
- say response.body
+ response
end
def get_version_from_requirements(requirements)
requirements.requirements.first[1].version
- rescue
+ rescue StandardError
nil
end
- def get_platform_from_requirements(requirements)
- Gem.platforms[1].to_s if requirements.key? :added_platform
+ def get_yank_scope
+ :yank_rubygem
end
-
end
-