diff options
Diffstat (limited to 'lib/rubygems/commands')
37 files changed, 1536 insertions, 1088 deletions
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 842ec1855a..2ec8324141 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,31 +1,37 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/package' -require 'rubygems/version_option' + +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| + 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| + add_option "-o", "--output FILE", "output gem with the given filename" do |value, options| options[:output] = value end - add_option '-C PATH', 'Run as if gem build was started in <PATH> instead of the current working directory.' do |value, options| + 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 + deprecate_option "-C", + version: "4.0", + extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead" end def arguments # :nodoc: @@ -71,17 +77,6 @@ Gems can be saved to a specified filename with the output option: private - def find_gemspec(glob = "*.gemspec") - gemspecs = Dir.glob(glob).sort - - if gemspecs.size > 1 - alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" - terminate_interaction(1) - end - - gemspecs.first - end - def build_gem gemspec = resolve_gem_name diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 3fc0daea7d..72dcf1dd17 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -1,64 +1,70 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/security' + +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 => [] + super "cert", "Manage RubyGems certificates and signing settings", + add: [], remove: [], list: [], build: [], sign: [] - add_option('-a', '--add CERT', - 'Add a trusted certificate.') do |cert_file, options| + 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', - 'Signing certificate for --sign') do |cert_file, options| + 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', - 'Key for --sign or --build') do |key_file, options| + 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| + 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| + add_option("-R", "--re-sign", + "Re-signs the certificate from -C with the key from -K") do |resign, options| options[:resign] = resign end end @@ -80,23 +86,23 @@ class Gem::Commands::CertCommand < Gem::Command check_openssl OpenSSL::X509::Certificate.new File.read certificate_file rescue Errno::ENOENT - raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist" + raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist" rescue OpenSSL::X509::CertificateError - raise OptionParser::InvalidArgument, + 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::RSA.new File.read(key_file), passphrase - raise OptionParser::InvalidArgument, + 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 OptionParser::InvalidArgument, "#{key_file}: does not exist" - rescue OpenSSL::PKey::RSAError - raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key" + 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 @@ -130,7 +136,7 @@ class Gem::Commands::CertCommand < Gem::Command end def build(email) - if !valid_email?(email) + unless valid_email?(email) raise Gem::CommandLineError, "Invalid email address #{email}" end @@ -147,7 +153,7 @@ class Gem::Commands::CertCommand < Gem::Command def build_cert(email, key) # :nodoc: expiration_length_days = options[:expiration_length_days] || - Gem.configuration.cert_expiration_length_days + Gem.configuration.cert_expiration_length_days cert = Gem::Security.create_cert_email( email, @@ -161,19 +167,20 @@ class Gem::Commands::CertCommand < Gem::Command 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) @@ -254,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" @@ -284,7 +291,7 @@ For further reading on signing gems see `ri Gem::Security`. 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] diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb index 8b8eda53cf..fb23dd9cb4 100644 --- a/lib/rubygems/commands/check_command.rb +++ b/lib/rubygems/commands/check_command.rb @@ -1,63 +1,68 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' -require 'rubygems/validator' -require 'rubygems/doctor' + +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? + 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| @@ -72,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: diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 662badce33..08fb598cea 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -1,35 +1,36 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/dependency_list' -require 'rubygems/uninstaller' + +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, - :check_dev => true + 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| + add_option("-n", "-d", "--dry-run", + "Do not uninstall gems") do |_value, options| options[:dryrun] = true end - add_option(:Deprecated, '--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') + 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| + 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| + add_option("--[no-]user-install", + "Cleanup in user's home directory instead", + "of GEM_HOME.") do |value, options| options[:user_install] = value end @@ -74,7 +75,7 @@ If no gems are named all gems in GEM_HOME are cleaned. 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 @@ -87,9 +88,9 @@ 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 @@ -116,13 +117,13 @@ If no gems are named all gems in GEM_HOME are cleaned. end def get_candidate_gems - @candidate_gems = unless options[:args].empty? - 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].map do |gem_name| + Gem::Specification.find_all_by_name gem_name + end.flatten + end end def get_gems_to_cleanup @@ -130,9 +131,7 @@ If no gems are named all gems in GEM_HOME are cleaned. @primary_gems[spec.name].version != spec.version end - default_gems, gems_to_cleanup = gems_to_cleanup.partition do |spec| - spec.default_gem? - end + default_gems, gems_to_cleanup = gems_to_cleanup.partition(&:default_gem?) uninstall_from = options[:user_install] ? Gem.user_dir : @original_home @@ -149,7 +148,7 @@ 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 + if @primary_gems[spec.name].nil? || @primary_gems[spec.name].version < spec.version @primary_gems[spec.name] = spec end @@ -167,8 +166,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 diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb index f17aed64db..807158d9c9 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -1,39 +1,40 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' + +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 @@ -77,7 +78,7 @@ prefix or only the files that are requireable. gem_contents name end - terminate_interaction 1 unless found or names.length > 1 + terminate_interaction 1 unless found || names.length > 1 end end @@ -91,9 +92,9 @@ prefix or only the files that are requireable. 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, "")] @@ -103,13 +104,13 @@ prefix or only the files that are requireable. def files_in_default_gem(spec) spec.files.map do |file| case file - when /\A#{spec.bindir}\// + when %r{\A#{spec.bindir}/} # $' is POSTMATCH - [RbConfig::CONFIG['bindir'], $'] + [RbConfig::CONFIG["bindir"], $'] when /\.so\z/ - [RbConfig::CONFIG['archdir'], file] + [RbConfig::CONFIG["archdir"], file] else - [RbConfig::CONFIG['rubylibdir'], file] + [RbConfig::CONFIG["rubylibdir"], file] end end end @@ -177,7 +178,7 @@ prefix or only the files that are requireable. @spec_dirs.sort.each {|dir| say dir } end - return nil + nil end def specification_directories # :nodoc: diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index e472d8fa8d..9aaefae999 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -1,28 +1,28 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' + +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 @@ -53,47 +53,46 @@ 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 = 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 {|spec, _| spec } + 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 + 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 do - Gem::Dependency.new pattern, version - end - - dependency.prerelease = prerelease - - dependency - end - def display_pipe(specs) # :nodoc: specs.each do |spec| - unless spec.dependencies.empty? - spec.dependencies.sort_by {|dep| dep.name }.each do |dep| - say "#{dep.name} --version '#{dep.requirement}'" - end + next if spec.dependencies.empty? + spec.dependencies.sort_by(&:name).each do |dep| + say "#{dep.name} --version '#{dep.requirement}'" end end end @@ -119,11 +118,9 @@ 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 @@ -135,8 +132,8 @@ use with other commands. end def ensure_local_only_reverse_dependencies # :nodoc: - if options[:reverse_dependencies] and remote? and not local? - 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 @@ -144,7 +141,7 @@ use with other commands. 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 @@ -153,23 +150,15 @@ use with other commands. def print_dependencies(spec, level = 0) # :nodoc: response = String.new - response << ' ' * level + "Gem #{spec.full_name}\n" + response << " " * level + "Gem #{spec.full_name}\n" unless spec.dependencies.empty? - spec.dependencies.sort_by {|dep| dep.name }.each do |dep| - response << ' ' * level + " #{dep}\n" + 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] = [] } @@ -192,7 +181,7 @@ use with other commands. sp.dependencies.each do |dep| dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep - if spec.name == dep.name and + if spec.name == dep.name && dep.requirement.satisfied_by?(spec.version) result << [sp.full_name, dep] end @@ -205,9 +194,9 @@ use with other commands. private def name_pattern(args) - args << '' if args.empty? + return if args.empty? - if args.length == 1 and args.first =~ /\A(.*)(i)?\z/m + 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 37429fb836..8ed0996069 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -1,21 +1,23 @@ # frozen_string_literal: true -require 'rubygems/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 - 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 <omitted> display everything EOF - return args.gsub(/^\s+/, '') + args.gsub(/^\s+/, "") end def description # :nodoc: @@ -80,6 +82,8 @@ lib/rubygems/defaults/operating_system.rb 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 @@ -104,9 +108,7 @@ 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" @@ -138,7 +140,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 @@ -149,7 +151,7 @@ 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 @@ -169,6 +171,6 @@ lib/rubygems/defaults/operating_system.rb end end - return nil + 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..7985b0fda6 --- /dev/null +++ b/lib/rubygems/commands/exec_command.rb @@ -0,0 +1,244 @@ +# 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::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] + + exe = 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? + if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable) + contains_executable << spec + else + alert_error "Failed to load executable `#{executable}`," \ + " are you sure the gem `#{options[:gem_name]}` contains it?" + terminate_interaction 1 + end + 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 + + load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a") + ensure + 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 6a1b346dd3..c524cf7922 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -1,14 +1,20 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' + +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 @@ -18,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: @@ -42,15 +52,38 @@ 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.0.0'`" + 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 @@ -60,16 +93,17 @@ then repackaging it. 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? - show_lookup_failure gem_name, version, errors, options[:domain] + 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 + + exit_code end end diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb index 93e25ef5e4..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 - deprecate_option('--modern', version: '4.0', extra_msg: 'Modern indexes (specs, latest_specs, and prerelease_specs) are always generated, so this option is not needed.') - deprecate_option('--no-modern', version: '4.0', extra_msg: 'The `--no-modern` option is currently ignored. Modern indexes (specs, latest_specs, and prerelease_specs) are always generated.') - - 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" - 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]) - alert_error "unknown directory name #{options[:directory]}." - terminate_interaction 1 - else - indexer = Gem::Indexer.new options.delete(:directory), options - - if options[:update] - indexer.update_index - else - indexer.generate_index - end - end + prepend(RubygemsTrampoline) end end diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index 4e8d7600fb..1619b152e7 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -require 'rubygems/command' + +require_relative "../command" class Gem::Commands::HelpCommand < Gem::Command # :stopdoc: - EXAMPLES = <<-EOF.freeze + EXAMPLES = <<-EOF Some examples of 'gem' usage. * Install 'rake', either from local directory or remote server: @@ -52,13 +53,13 @@ Some examples of 'gem' usage. gem update --system EOF - GEM_DEPENDENCIES = <<-EOF.freeze + GEM_DEPENDENCIES = <<-EOF A gem dependencies file allows installation of a consistent set of gems across multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional documentation on the format at: - http://bundler.io + https://bundler.io RubyGems automatically looks for these gem dependencies files: @@ -171,7 +172,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 ================================== @@ -229,7 +230,7 @@ default. This may be overridden with the :development_group option: EOF - PLATFORMS = <<-'EOF'.freeze + PLATFORMS = <<-'EOF' RubyGems platforms are composed of three parts, a CPU, an OS, and a version. These values are taken from values in rbconfig.rb. You can view your current platform by running `gem environment`. @@ -268,7 +269,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], @@ -280,7 +281,7 @@ platform. # :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 @@ -323,16 +324,16 @@ 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? + next if command&.deprecated? summary = if command @@ -342,7 +343,7 @@ platform. 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 @@ -366,7 +367,7 @@ platform. command = @command_manager[possibilities.first] command.invoke("--help") elsif possibilities.size > 1 - alert_warning "Ambiguous command #{command_name} (#{possibilities.join(', ')})" + alert_warning "Ambiguous command #{command_name} (#{possibilities.join(", ")})" else alert_warning "Unknown command #{command_name}. Try: gem help commands" end diff --git a/lib/rubygems/commands/info_command.rb b/lib/rubygems/commands/info_command.rb index 9ca6ae364f..f65c639662 100644 --- a/lib/rubygems/commands/info_command.rb +++ b/lib/rubygems/commands/info_command.rb @@ -1,19 +1,19 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' +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 + name: //, domain: :local, details: false, versions: true, + installed: nil, version: Gem::Requirement.default add_query_options - remove_option('-d') + remove_option("-d") defaults[:details] = true defaults[:exact] = true diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 4d36c69d51..2888b6c55a 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 @@ -17,19 +19,20 @@ class Gem::Commands::InstallCommand < Gem::Command 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: [], }) defaults.merge!(install_update_options) - super 'install', 'Install a gem into the local repository', defaults + super "install", "Install a gem into the local repository", defaults add_install_update_options add_local_remote_options @@ -45,9 +48,9 @@ class Gem::Commands::InstallCommand < Gem::Command end def defaults_str # :nodoc: - "--both --version '#{Gem::Requirement.default}' --no-force\n" + - "--install-dir #{Gem.dir} --lock\n" + - install_update_defaults_str + "--both --version '#{Gem::Requirement.default}' --no-force\n" \ + "--install-dir #{Gem.dir} --lock\n" + + install_update_defaults_str end def description # :nodoc: @@ -133,16 +136,9 @@ You can use `i` command instead of `install`. "#{program_name} [options] GEMNAME [GEMNAME ...] -- --build-flags" end - def check_install_dir # :nodoc: - if options[:install_dir] and options[:user_install] - alert_error "Use --install-dir or --user-install but not both" - terminate_interaction 1 - end - end - def check_version # :nodoc: - if options[:version] != Gem::Requirement.default and - get_all_gem_names.size > 1 + 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.0.0'`" terminate_interaction 1 @@ -157,9 +153,8 @@ You can use `i` command instead of `install`. @installed_specs = [] - ENV.delete 'GEM_PATH' if options[:install_dir].nil? + ENV.delete "GEM_PATH" if options[:install_dir].nil? - check_install_dir check_version load_hooks @@ -168,11 +163,13 @@ You can use `i` command instead of `install`. 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| @@ -191,8 +188,8 @@ You can use `i` command instead of `install`. end def install_gem(name, version) # :nodoc: - return if options[:conservative] and - not Gem::Dependency.new(name, version).matching_specs.empty? + return if options[:conservative] && + !Gem::Dependency.new(name, version).matching_specs.empty? req = Gem::Requirement.create(version) @@ -227,10 +224,6 @@ You can use `i` command instead of `install`. rescue Gem::InstallError => e alert_error "Error installing #{gem_name}:\n\t#{e.message}" exit_code |= 1 - rescue Gem::GemNotFoundException => e - show_lookup_failure e.name, e.version, e.errors, suppress_suggestions - - exit_code |= 2 rescue Gem::UnsatisfiableDependencyError => e show_lookup_failure e.name, e.version, e.errors, suppress_suggestions, "'#{gem_name}' (#{gem_version})" @@ -247,20 +240,21 @@ You can use `i` command instead of `install`. def load_hooks # :nodoc: if options[:install_as_default] - require 'rubygems/install_default_message' + require_relative "../install_default_message" else - require 'rubygems/install_message' + require_relative "../install_message" end - require 'rubygems/rdoc' + require_relative "../rdoc" end 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 @@ -269,7 +263,7 @@ You can use `i` command instead of `install`. 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 5c99d3d73d..fab4b73814 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' + +require_relative "../command" +require_relative "../query_utils" ## # Searches for gems starting with the supplied argument. @@ -9,9 +10,9 @@ class Gem::Commands::ListCommand < Gem::Command include Gem::QueryUtils def initialize - super 'list', 'Display local gems whose name matches REGEXP', - :name => //, :domain => :local, :details => false, :versions => true, - :installed => nil, :version => Gem::Requirement.default + super "list", "Display local gems whose name matches REGEXP", + domain: :local, details: false, versions: true, + installed: nil, version: Gem::Requirement.default add_query_options end diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb index f1dc1ac586..f7fd5ada16 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true -require 'rubygems/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 diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb index 86671a93b2..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 diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index 8012a9a0e1..0fe90dc8b8 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -1,18 +1,19 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/version_option' + +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 COMMAND', String, + 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 end @@ -40,10 +41,10 @@ class Gem::Commands::OpenCommand < Gem::Command end def get_env_editor - ENV['GEM_EDITOR'] || - ENV['VISUAL'] || - ENV['EDITOR'] || - 'vi' + ENV["GEM_EDITOR"] || + ENV["VISUAL"] || + ENV["EDITOR"] || + "vi" end def execute @@ -69,9 +70,7 @@ class Gem::Commands::OpenCommand < Gem::Command end def open_editor(path) - Dir.chdir(path) do - system(*@editor.split(/\s+/) + [path]) - end + system(*@editor.split(/\s+/) + [path], { chdir: path }) end def spec_for(name) diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb index 3579bfc3ba..08a9221a26 100644 --- a/lib/rubygems/commands/outdated_command.rb +++ b/lib/rubygems/commands/outdated_command.rb @@ -1,15 +1,16 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/spec_fetcher' -require 'rubygems/version_option' + +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 dd49027469..12bfe3a834 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/gemcutter_utilities' -require 'rubygems/text' + +require_relative "../command" +require_relative "../local_remote_options" +require_relative "../gemcutter_utilities" +require_relative "../text" class Gem::Commands::OwnerCommand < Gem::Command include Gem::Text @@ -12,7 +13,12 @@ class Gem::Commands::OwnerCommand < Gem::Command 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 @@ -29,23 +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 add_otp_option - defaults.merge! :add => [], :remove => [] + 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 @@ -73,7 +79,7 @@ permission to. say "Owners for gem: #{name}" owners.each do |owner| - say "- #{owner['email'] || owner['handle'] || owner['id']}" + say "- #{owner["email"] || owner["handle"] || owner["id"]}" end end end @@ -88,14 +94,14 @@ permission to. def manage_owners(method, name, owners) owners.each do |owner| - begin - response = send_owner_request(method, name, owner) - action = method == :delete ? "Removing" : "Adding" - - with_response response, "#{action} #{owner}" - rescue - # ignore - 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 @@ -103,7 +109,7 @@ permission to. 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.set_form_data "email" => owner request.add_field "Authorization", api_key end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 143105981e..999c9fef0f 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -1,62 +1,73 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/package' -require 'rubygems/installer' -require 'rubygems/version_option' + +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| + 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('--only-plugins', - 'Only restore plugins') 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| + add_option("-E", "--[no-]env-shebang", + "Rewrite executables with a shebang", + "of /usr/bin/env") do |value, options| options[:env_shebang] = value end - add_option('-n', '--bindir DIR', - 'Directory where executables are', - 'located') do |value, options| + 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') + add_version_option("restore to", "pristine condition") end def arguments # :nodoc: @@ -64,7 +75,7 @@ class Gem::Commands::PristineCommand < Gem::Command end def defaults_str # :nodoc: - '--extensions' + "--extensions" end def description # :nodoc: @@ -92,23 +103,29 @@ extensions will be restored. end def execute + install_dir = options[:install_dir] + + specification_record = install_dir ? Gem::SpecificationRecord.from_path(install_dir) : Gem::Specification.specification_record + specs = if options[:all] - 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? - 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 - - specs = specs.select{|spec| RUBY_ENGINE == spec.platform || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY } + 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.map do |gem_name| + specification_record.find_all_by_name(gem_name, options[:version]).reverse + end.flatten + end + + specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY } if specs.to_a.empty? raise Gem::Exception, @@ -123,24 +140,24 @@ extensions will be restored. next end - if options.has_key? :skip + 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] or options[:only_plugins] - say "Skipped #{spec.full_name}, it needs to compile an extension" + unless spec.extensions.empty? || options[:extensions] || options[:only_executables] || options[: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] or options[:only_plugins] - require 'rubygems/remote_fetcher' + unless File.exist?(gem) || options[:only_executables] || options[: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 @@ -158,33 +175,33 @@ extensions will be restored. 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, - :bin_dir => bin_dir, + 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] installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin elsif options[:only_plugins] - installer = Gem::Installer.for_spec(spec) + 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 1a9a1932f8..591ddc3a80 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 @@ -29,7 +30,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo 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 @user_defined_host = false @@ -37,9 +38,9 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo 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 @@ -52,14 +53,14 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo 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 + options[:host] + elsif default_gem_server + default_gem_server + elsif push_host + push_host + else + options[:host] + end sign_in @host, scope: get_push_scope @@ -74,7 +75,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo @host ||= push_host # Always include @host, even if it's nil - args += [ @host, push_host ] + args += [@host, push_host] say "Pushing gem to #{@host || Gem.host}..." diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 789afd6509..3b527974a3 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' -require 'rubygems/deprecate' + +require_relative "../command" +require_relative "../query_utils" +require_relative "../deprecate" class Gem::Commands::QueryCommand < Gem::Command extend Gem::Deprecate @@ -9,7 +10,7 @@ class Gem::Commands::QueryCommand < Gem::Command include Gem::QueryUtils - alias warning_without_suggested_alternatives deprecation_warning + alias_method :warning_without_suggested_alternatives, :deprecation_warning def deprecation_warning warning_without_suggested_alternatives @@ -17,15 +18,14 @@ class Gem::Commands::QueryCommand < Gem::Command alert_warning message unless Gem::Deprecate.skip end - def initialize(name = 'query', - summary = 'Query gem information in local or remote repositories') + 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 + domain: :local, details: false, versions: true, + installed: nil, version: Gem::Requirement.default - add_option('-n', '--name-matches REGEXP', - 'Name of gem(s) to query on matches the', - 'provided REGEXP') do |value, options| + 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 diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index e8c9e84b29..977c90b8c4 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 @@ -61,15 +62,15 @@ Use --overwrite to force rebuilding of documentation. def execute specs = if options[:all] - Gem::Specification.to_a - else - get_all_gem_names.map do |name| - Gem::Specification.find_by_name name, options[:version] - end.flatten.uniq - end + 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? - alert_error 'No matching gems found' + alert_error "No matching gems found" terminate_interaction 1 end @@ -79,17 +80,11 @@ Use --overwrite to force rebuilding of documentation. doc.force = options[:overwrite] if options[:overwrite] - FileUtils.rm_rf File.join(spec.doc_dir, 'ri') - FileUtils.rm_rf File.join(spec.doc_dir, 'rdoc') + 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..77a474ef1d --- /dev/null +++ b/lib/rubygems/commands/rebuild_command.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "date" +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 aeb2119235..50e161ac9b 100644 --- a/lib/rubygems/commands/search_command.rb +++ b/lib/rubygems/commands/search_command.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/query_utils' + +require_relative "../command" +require_relative "../query_utils" class Gem::Commands::SearchCommand < Gem::Command include Gem::QueryUtils def initialize - super 'search', 'Display remote gems whose name matches REGEXP', - :name => //, :domain => :remote, :details => false, :versions => true, - :installed => nil, :version => Gem::Requirement.default + super "search", "Display remote gems whose name matches REGEXP", + domain: :remote, details: false, versions: true, + installed: nil, version: Gem::Requirement.default add_query_options end diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb index 594cf77f66..f1dde4aa02 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,88 +1,26 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/server' -require 'rubygems/deprecate' -class Gem::Commands::ServerCommand < Gem::Command - extend Gem::Deprecate - rubygems_deprecate_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/ - 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 - end - - add_option '-b', '--bind=HOST,HOST', - 'addresses to bind', Array do |address, options| - options[:addresses] ||= [] - options[:addresses].push(*address) + def description # :nodoc: + <<-EOF +The server command has been moved to the rubygems-server gem. + EOF 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 47e215c149..bb2246ca31 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -1,107 +1,106 @@ # 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.a-zA-Z]+\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze - VERSION_MATCHER = /^#\s*([\d.a-zA-Z]+)\s*\/\s*\d{4}-\d{2}-\d{2}\s*$/.freeze + 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*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze def initialize - require 'tmpdir' - - 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| + 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| + add_option "--[no-]rdoc", + "Generate RDoc documentation for RubyGems" do |value, options| if value - options[:document] << 'rdoc' + 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| + add_option "--[no-]ri", + "Generate RI documentation for RubyGems" do |value, options| if value - options[:document] << 'ri' + 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| + add_option "--[no-]regenerate-binstubs", + "Regenerate gem binstubs" do |value, options| options[:regenerate_binstubs] = value end - add_option '--[no-]regenerate-plugins', - 'Regenerate gem plugins' do |value, options| + add_option "--[no-]regenerate-plugins", + "Regenerate gem plugins" do |value, options| options[:regenerate_plugins] = value end - add_option '-f', '--[no-]force', - 'Forcefully overwrite binstubs' do |value, options| + add_option "-f", "--[no-]force", + "Forcefully overwrite binstubs" do |value, options| options[:force] = value end - add_option('-E', '--[no-]env-shebang', - 'Rewrite executables with a shebang', - 'of /usr/bin/env') do |value, options| + add_option("-E", "--[no-]env-shebang", + "Rewrite executables with a shebang", + "of /usr/bin/env") do |value, options| options[:env_shebang] = value end @@ -109,7 +108,7 @@ class Gem::Commands::SetupCommand < Gem::Command end def check_ruby_version - required_version = Gem::Requirement.new '>= 2.3.0' + required_version = Gem::Requirement.new ">= 2.6.0" unless required_version.satisfied_by? Gem.ruby_version alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" @@ -135,7 +134,7 @@ 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 @@ -149,16 +148,9 @@ By default, this RubyGems will install gem as: def execute @verbose = Gem.configuration.really_verbose - install_destdir = options[:destdir] - - unless install_destdir.empty? - ENV['GEM_HOME'] ||= File.join(install_destdir, - Gem.default_dir.gsub(/^[a-zA-Z]:/, '')) - end - check_ruby_version - require 'fileutils' + require "fileutils" if Gem.configuration.really_verbose extend FileUtils::Verbose else @@ -166,8 +158,8 @@ By default, this RubyGems will install gem as: end extend MakeDirs - lib_dir, bin_dir = make_destination_dirs install_destdir - man_dir = generate_default_man_dir install_destdir + lib_dir, bin_dir = make_destination_dirs + man_dir = generate_default_man_dir install_lib lib_dir @@ -189,8 +181,8 @@ By default, this RubyGems will install gem as: say "RubyGems #{Gem::VERSION} installed" - regenerate_binstubs if options[:regenerate_binstubs] - regenerate_plugins if options[:regenerate_plugins] + regenerate_binstubs(bin_dir) if options[:regenerate_binstubs] + regenerate_plugins(bin_dir) if options[:regenerate_plugins] uninstall_old_gemcutter @@ -203,7 +195,7 @@ By default, this RubyGems will install gem as: 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]) @@ -225,7 +217,7 @@ By default, this RubyGems will install gem as: end if documentation_success - if options[:document].include? 'rdoc' + 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" @@ -236,7 +228,7 @@ By default, this RubyGems will install gem as: say end - if options[:document].include? 'ri' + 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" @@ -251,55 +243,49 @@ By default, this RubyGems will install gem as: end def install_executables(bin_dir) - prog_mode = options[:prog_mode] || 0755 + prog_mode = options[:prog_mode] || 0o755 - executables = { 'gem' => 'bin' } + executables = { "gem" => "exe" } executables.each do |tool, path| say "Installing #{tool} executable" if @verbose Dir.chdir path do - bin_files = Dir['*'] + bin_file = "gem" - bin_files -= %w[update_rubygems] + require "tmpdir" - bin_files.each do |bin_file| - dest_file = target_bin_path(bin_dir, bin_file) - bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" + dest_file = target_bin_path(bin_dir, bin_file) + bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}" - begin - bin = File.readlines bin_file - bin[0] = shebang + begin + bin = File.readlines bin_file + bin[0] = shebang - File.open bin_tmp_file, 'w' do |fp| - fp.puts bin.join - end - - install bin_tmp_file, dest_file, :mode => prog_mode - 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 => prog_mode - ensure - rm bin_cmd_file end + + install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode + ensure + rm bin_cmd_file end end end @@ -307,7 +293,7 @@ By default, this RubyGems will install gem as: def shebang if options[:env_shebang] - ruby_name = RbConfig::CONFIG['ruby_install_name'] + ruby_name = RbConfig::CONFIG["ruby_install_name"] @env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path } "#!#{@env_path} #{ruby_name}\n" else @@ -316,8 +302,8 @@ By default, this RubyGems will install gem as: end def install_lib(lib_dir) - libs = { 'RubyGems' => 'lib' } - libs['Bundler'] = 'bundler/lib' + libs = { "RubyGems" => "lib" } + libs["Bundler"] = "bundler/lib" libs.each do |tool, path| say "Installing #{tool}" if @verbose @@ -330,7 +316,7 @@ By default, this RubyGems will install gem as: 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 @@ -340,23 +326,25 @@ 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) + 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 @@ -367,28 +355,33 @@ By default, this RubyGems will install gem as: say "Set the GEM_HOME environment variable if you want RDoc generated" end - return false + false end def install_default_bundler_gem(bin_dir) - specs_dir = Gem.default_specifications_dir - specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform? - mkdir_p specs_dir, :mode => 0755 - - bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") } + current_default_spec = Gem::Specification.default_stubs.find {|s| s.name == "bundler" } + specs_dir = if current_default_spec && default_dir == Gem.default_dir + Gem::Specification.remove_spec current_default_spec + loaded_from = current_default_spec.loaded_from + File.delete(loaded_from) + 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 - # Remove bundler-*.gemspec in default specification directory. - Dir.entries(specs_dir). - select {|gs| gs.start_with?("bundler-") }. - each {|gs| File.delete(File.join(specs_dir, gs)) } + 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(specs_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) # Remove gemspec that was same version of vendored bundler. - normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec") + normal_gemspec = File.join(default_dir, "specifications", gemspec_path) if File.file? normal_gemspec File.delete normal_gemspec end @@ -396,111 +389,91 @@ By default, this RubyGems will install gem as: # 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) == "bundler-#{bundler_spec.version}" }. + select {|default_gem| File.basename(default_gem) == full_name }. each {|default_gem| rm_r File.join(bundler_spec.gems_dir, default_gem) } end - bundler_bin_dir = bundler_spec.bin_dir - bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform? - mkdir_p bundler_bin_dir, :mode => 0755 - bundler_spec.executables.each do |e| - cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e) - end - - require 'rubygems/installer' + require_relative "../installer" Dir.chdir("bundler") do - built_gem = Gem::Package.build(bundler_spec) + 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], install_as_default: true, bin_dir: bin_dir, wrappers: true) - installer.install + Gem::Installer.at( + built_gem, + env_shebang: options[:env_shebang], + format_executable: options[:format_executable], + force: options[:force], + install_as_default: true, + bin_dir: bin_dir, + install_dir: default_dir, + wrappers: true + ).install ensure FileUtils.rm_f built_gem end end - bundler_spec.executables.each {|executable| bin_file_names << target_bin_path(bin_dir, executable) } + 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, :mode => 0755 - mkdir_p bin_dir, :mode => 0755 + 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_man_dir(install_destdir) + def generate_default_man_dir prefix = options[:prefix] if prefix.empty? - man_dir = RbConfig::CONFIG['mandir'] + man_dir = RbConfig::CONFIG["mandir"] return unless man_dir else - man_dir = File.join prefix, 'man' - end - - unless install_destdir.empty? - man_dir = File.join install_destdir, man_dir.gsub(/^[a-zA-Z]:/, '') + man_dir = File.join prefix, "man" end - man_dir + prepend_destdir_if_present(man_dir) end - def generate_default_dirs(install_destdir) + def generate_default_dirs prefix = options[:prefix] site_or_vendor = options[:site_or_vendor] if prefix.empty? lib_dir = RbConfig::CONFIG[site_or_vendor] - bin_dir = RbConfig::CONFIG['bindir'] + bin_dir = RbConfig::CONFIG["bindir"] else - # Apple installed RubyGems into libdir, and RubyGems <= 1.1.0 gets - # confused about installation location, so switch back to - # sitelibdir/vendorlibdir. - if defined?(APPLE_GEM_HOME) and - # just in case Apple and RubyGems don't get this patched up proper. - (prefix == RbConfig::CONFIG['libdir'] or - # this one is important - prefix == File.join(RbConfig::CONFIG['libdir'], 'ruby')) - 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 + lib_dir = File.join prefix, "lib" + bin_dir = File.join prefix, "bin" end - unless install_destdir.empty? - lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '') - bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '') - end - - [lib_dir, bin_dir] + [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)] end def files_in(dir) Dir.chdir dir do - Dir.glob(File.join('**', '*'), File::FNM_DOTMATCH). - 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| @@ -509,7 +482,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} @@ -519,15 +492,15 @@ abort "#{deprecation_message}" next unless Gem.win_platform? - File.open "#{old_bin_path}.bat", 'w' do |fp| + 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' + 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 = files_in(new_lib_dir) @@ -535,11 +508,11 @@ abort "#{deprecation_message}" to_remove = old_lib_files - lib_files - gauntlet_rubygems = File.join(lib_dir, 'gauntlet_rubygems.rb') + 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 remove_file_list(to_remove, old_lib_dir) @@ -565,7 +538,7 @@ abort "#{deprecation_message}" end def show_release_notes - release_notes = File.join Dir.pwd, 'CHANGELOG.md' + release_notes = File.join Dir.pwd, "CHANGELOG.md" release_notes = if File.exist? release_notes @@ -582,7 +555,7 @@ abort "#{deprecation_message}" history_string = "" - until versions.length == 0 or + until versions.length == 0 || versions.shift <= options[:previous_version] do history_string += version_lines.shift + text.shift end @@ -596,19 +569,22 @@ 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 @@ -617,11 +593,13 @@ abort "#{deprecation_message}" command.invoke(*args) end - def regenerate_plugins - require "rubygems/commands/pristine_command" + 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) @@ -629,6 +607,25 @@ abort "#{deprecation_message}" 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 @@ -639,10 +636,10 @@ abort "#{deprecation_message}" dest_file = File.join dest_dir, file dest_dir = File.dirname dest_file unless File.directory? dest_dir - mkdir_p dest_dir, :mode => 0755 + mkdir_p dest_dir, mode: 0o755 end - install file, dest_file, :mode => options[:data_mode] || 0644 + install file, dest_file, mode: options[:data_mode] || 0o644 end def remove_file_list(files, dir) @@ -658,10 +655,10 @@ abort "#{deprecation_message}" 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 + Gem.default_exec_format % bin_file + else + bin_file + end File.join bin_dir, bin_file_formatted end diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 2e19c8333c..0f77908c5b 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb @@ -1,15 +1,16 @@ # 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| + add_option("--host HOST", "Push to another gemcutter-compatible host") do |value, options| options[:host] = value end @@ -17,10 +18,10 @@ class Gem::Commands::SigninCommand < Gem::Command 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: diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb index ebbe746cb4..bdd01e4393 100644 --- a/lib/rubygems/commands/signout_command.rb +++ b/lib/rubygems/commands/signout_command.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true -require 'rubygems/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,13 +20,13 @@ class Gem::Commands::SignoutCommand < Gem::Command credentials_path = Gem.configuration.credentials_path if !File.exist?(credentials_path) - alert_error 'You are not currently signed in.' + 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 f74fb12e42..976f4a4ea2 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -1,40 +1,41 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/remote_fetcher' -require 'rubygems/spec_fetcher' -require 'rubygems/local_remote_options' + +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 "-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| + 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 @@ -58,11 +59,11 @@ 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 @@ -70,7 +71,7 @@ class Gem::Commands::SourcesCommand < Gem::Command def check_typo_squatting(source) if source.typo_squatting?("rubygems.org") question = <<-QUESTION.chomp -#{source.uri.to_s} is too similar to https://rubygems.org +#{source.uri} is too similar to https://rubygems.org Do you want to add this source? QUESTION @@ -80,10 +81,10 @@ Do you want to add this source? end def check_rubygems_https(source_uri) # :nodoc: - uri = URI source_uri + uri = Gem::URI source_uri - if uri.scheme and uri.scheme.downcase == 'http' and - uri.host.downcase == 'rubygems.org' + 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} @@ -98,21 +99,21 @@ Do you want to add this insecure source? path = Gem.spec_cache_dir FileUtils.rm_rf path - unless File.exist? path - say "*** Removed specs cache ***" - else - unless File.writable? path - 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: @@ -138,8 +139,8 @@ 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) -* https://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 @@ -193,13 +194,13 @@ To remove a source use the --remove argument: end def remove_source(source_uri) # :nodoc: - unless Gem.sources.include? source_uri - say "source #{source_uri} not present in cache" - else + if Gem.sources.include? source_uri Gem.sources.delete source_uri Gem.configuration.write say "#{source_uri} removed from sources" + else + say "source #{source_uri} not present in cache" end end @@ -215,9 +216,9 @@ To remove a source use the --remove argument: def remove_cache_file(desc, path) # :nodoc: FileUtils.rm_rf path - if not File.exist?(path) + if !File.exist?(path) say "*** Removed #{desc} source cache ***" - elsif not File.writable?(path) + elsif !File.writable?(path) say "*** Unable to remove #{desc} source cache (write protected) ***" else say "*** Unable to remove #{desc} source cache ***" diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 3fddaaaf30..a21ed35be3 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -require 'rubygems/command' -require 'rubygems/local_remote_options' -require 'rubygems/version_option' -require 'rubygems/package' + +require_relative "../command" +require_relative "../local_remote_options" +require_relative "../version_option" +require_relative "../package" class Gem::Commands::SpecificationCommand < Gem::Command include Gem::LocalRemoteOptions @@ -11,28 +12,28 @@ class Gem::Commands::SpecificationCommand < Gem::Command 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 @@ -88,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 @@ -102,11 +103,15 @@ 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 + field && options[:format] == :ruby if local? if File.exist? gem - specs << Gem::Package.new(gem).spec rescue nil + begin + specs << Gem::Package.new(gem).spec + rescue StandardError + nil + end end if specs.empty? @@ -129,11 +134,11 @@ Specific fields in the specification can be extracted in YAML format: platform = get_platform_from_requirements(options) if platform - specs = specs.select{|s| s.platform.to_s == platform } + specs = specs.select {|s| s.platform.to_s == platform } end unless options[:all] - specs = [specs.max_by {|s| s.version }] + specs = [specs.max_by(&:version)] end specs.each do |s| @@ -143,7 +148,7 @@ Specific fields in the specification can be extracted in YAML format: when :ruby then s.to_ruby when :marshal then Marshal.dump s else s.to_yaml - end + end say "\n" end diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb index badc9905c1..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 @@ -33,7 +34,7 @@ longer using. end gem_to_atime.sort_by {|_, atime| atime }.each do |name, atime| - say "#{name} at #{atime.strftime '%c'}" + 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 1540b2f0fb..3d6e41e49e 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 @@ -13,78 +14,77 @@ 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 executables 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| + add_option("--vendor", + "Uninstall gem from the vendor directory.", + "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir - raise OptionParser::InvalidOption.new 'your platform is not supported' + 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 @@ -95,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,8 +114,8 @@ that is a dependency of an existing gem. You can use the end def check_version # :nodoc: - if options[:version] != Gem::Requirement.default and - get_all_gem_names.size > 1 + 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.0.0'`" terminate_interaction 1 @@ -125,7 +125,10 @@ that is a dependency of an existing gem. You can use the def execute check_version - if options[:all] and not options[:args].empty? + # 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] uninstall_all @@ -135,7 +138,7 @@ 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 @@ -154,9 +157,14 @@ that is a dependency of an existing gem. You can use the gem_specs = Gem::Specification.find_all_by_name(name, original_gem_version[name]) - say("Gem '#{name}' is not installed") if gem_specs.empty? - gem_specs.each do |spec| - deplist.add spec + 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 @@ -165,15 +173,14 @@ that is a dependency of an existing gem. You can use the gems_to_uninstall = {} deps.each do |dep| - unless gems_to_uninstall[dep.name] + if original_gem_version[dep.name] == Gem::Requirement.default + next if gems_to_uninstall[dep.name] gems_to_uninstall[dep.name] = true - - unless original_gem_version[dep.name] == Gem::Requirement.default - options[:version] = dep.version - end - - uninstall_gem(dep.name) + else + options[:version] = dep.version end + + uninstall_gem(dep.name) end end @@ -181,12 +188,12 @@ that is a dependency of an existing gem. You can use the 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.installation_path}") + 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" + + 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 diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index 8d90d08eb4..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/version_option' -require 'rubygems/security_option' -require 'rubygems/remote_fetcher' -require 'rubygems/package' + +require_relative "../command" +require_relative "../version_option" +require_relative "../security_option" +require_relative "../remote_fetcher" +require_relative "../package" # forward-declare @@ -17,18 +18,18 @@ class Gem::Commands::UnpackCommand < Gem::Command 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 @@ -95,19 +96,17 @@ command help for an example. FileUtils.mkdir_p @options[:target] if @options[:target] - destination = begin - if @options[:target] - File.join @options[:target], spec_file - else - spec_file - end + destination = if @options[:target] + File.join @options[:target], spec_file + else + spec_file end - File.open destination, 'w' do |io| + 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 @@ -131,7 +130,7 @@ command help for an example. return this_path if File.exist? this_path end - return nil + nil end ## @@ -144,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 + 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). diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index 91d93e398c..8e80d46856 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -1,13 +1,14 @@ # 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' + +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 @@ -20,26 +21,26 @@ class Gem::Commands::UpdateCommand < Gem::Command def initialize options = { - :force => false, + force: false, } options.merge!(install_update_options) - super 'update', 'Update installed gems to the latest version', 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 @@ -56,7 +57,7 @@ class Gem::Commands::UpdateCommand < Gem::Command def defaults_str # :nodoc: "--no-force --install-dir #{Gem.dir}\n" + - install_update_defaults_str + install_update_defaults_str end def description # :nodoc: @@ -118,15 +119,19 @@ command to remove old versions. 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? 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: @@ -151,7 +156,7 @@ command to remove old versions. 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 + if hig[spec.name].nil? || hig[spec.name].version < spec.version hig[spec.name] = spec end end @@ -162,30 +167,28 @@ command to remove old versions. 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 - - highest_remote_gem ||= [Gem::NameTuple.null] + highest_remote_gem = spec_tuples.max + return unless highest_remote_gem 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}" unless options[:silent] installed = preparing_gem_layout_for(version) do - system Gem.ruby, '--disable-gems', 'setup.rb', *args + system Gem.ruby, "--disable-gems", "setup.rb", *args end - say "RubyGems system software updated" if installed unless options[:silent] + unless options[:silent] + say "RubyGems system software updated" if installed + end end end @@ -194,18 +197,17 @@ command to remove old versions. yield else require "tmpdir" - tmpdir = Dir.mktmpdir - FileUtils.mv Gem.plugindir, tmpdir + Dir.mktmpdir("gem_update") do |tmpdir| + FileUtils.mv Gem.plugindir, tmpdir - status = yield + status = yield - if status - FileUtils.rm_rf tmpdir - else - FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir - end + unless status + FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir + end - status + status + end end end @@ -213,32 +215,24 @@ command to remove old versions. version = options[:system] update_latest = version == true - if update_latest - 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.version - - target = if update_latest - 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) @@ -249,7 +243,7 @@ command to remove old versions. @installer = Gem::DependencyInstaller.new update_options - say "Updating #{name}" unless options[:system] && options[:silent] + say "Updating #{name}" unless options[:system] begin @installer.install name, Gem::Requirement.new(version) rescue Gem::InstallError, Gem::DependencyError => e @@ -286,40 +280,34 @@ command to remove old versions. check_oldest_rubygems version - update_gem 'rubygems-update', 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? - installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement - version = installed_gems.first.version - - install_rubygems version + install_rubygems installed_gems.first end def update_rubygems_arguments # :nodoc: args = [] - 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 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 + highest_installed_gems.each do |_l_name, l_spec| + next if !gem_names.empty? && gem_names.none? {|name| name == l_spec.name } highest_remote_tup = highest_remote_name_tuple l_spec - highest_remote_ver = highest_remote_tup.version - highest_installed_ver = l_spec.version + next unless highest_remote_tup - if system or (highest_installed_ver < highest_remote_ver) - result << Gem::NameTuple.new(l_spec.name, [highest_installed_ver, highest_remote_ver].max, highest_remote_tup.platform) - end + result << highest_remote_tup end result @@ -335,18 +323,10 @@ command to remove old versions. # def oldest_supported_version @oldest_supported_version ||= - if Gem.ruby_version > Gem::Version.new("3.0.a") - Gem::Version.new("3.2.3") - elsif Gem.ruby_version > Gem::Version.new("2.7.a") - Gem::Version.new("3.1.2") - elsif Gem.ruby_version > Gem::Version.new("2.6.a") - Gem::Version.new("3.0.1") - elsif Gem.ruby_version > Gem::Version.new("2.5.a") - Gem::Version.new("2.7.3") - elsif Gem.ruby_version > Gem::Version.new("2.4.a") - Gem::Version.new("2.6.8") + if Gem.ruby_version > Gem::Version.new("3.1.a") + Gem::Version.new("3.3.3") else - Gem::Version.new("2.5.2") + Gem::Version.new("3.2.3") end end end diff --git a/lib/rubygems/commands/which_command.rb b/lib/rubygems/commands/which_command.rb index d42ab18395..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,7 +40,7 @@ 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 @@ -71,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 + if File.exist?(full_path) && !File.directory?(full_path) result << full_path return result unless options[:show_all] end diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb index a7930253d6..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 @@ -28,15 +29,15 @@ data you will need to change them immediately and yank your 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 @@ -61,7 +62,7 @@ data you will need to change them immediately and yank your gem. end def yank_gem(version, platform) - say "Yanking gem from #{self.host}..." + say "Yanking gem from #{host}..." args = [:delete, version, platform, "api/v1/gems/yank"] response = yank_api_request(*args) @@ -76,10 +77,10 @@ data you will need to change them immediately and yank your gem. 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 @@ -88,7 +89,7 @@ data you will need to change them immediately and yank your gem. def get_version_from_requirements(requirements) requirements.requirements.first[1].version - rescue + rescue StandardError nil end |