diff options
Diffstat (limited to 'lib/rubygems/commands')
37 files changed, 818 insertions, 342 deletions
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 5d6152d3b9..2ec8324141 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,21 +1,24 @@ # frozen_string_literal: true + 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" 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 @@ -74,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 17b1d11b19..72dcf1dd17 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true + 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 => [] + add: [], remove: [], list: [], build: [], sign: [] add_option("-a", "--add CERT", "Add a trusted certificate.") do |cert_file, options| @@ -135,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 @@ -177,9 +178,9 @@ class Gem::Commands::CertCommand < Gem::Command 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", 0600, passphrase + key_path = Gem::Security.write key, "gem-private_key.pem", 0o600, passphrase - return key, key_path + [key, key_path] end def certificates_matching(filter) @@ -262,7 +263,6 @@ For further reading on signing gems see `ri Gem::Security`. key = File.read key_file 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" @@ -291,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 4d1f8782b1..fb23dd9cb4 100644 --- a/lib/rubygems/commands/check_command.rb +++ b/lib/rubygems/commands/check_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../validator" @@ -9,7 +10,7 @@ class Gem::Commands::CheckCommand < Gem::Command def initialize super "check", "Check a gem repository for added or missing files", - :alien => true, :doctor => false, :dry_run => false, :gems => true + alien: true, doctor: false, dry_run: false, gems: true add_option("-a", "--[no-]alien", 'Report "unmanaged" or rogue files in the', @@ -40,17 +41,21 @@ class Gem::Commands::CheckCommand < Gem::Command def check_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 diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 1ae84924c1..08fb598cea 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../dependency_list" require_relative "../uninstaller" @@ -7,16 +8,16 @@ 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 + force: false, install_dir: Gem.dir, + check_dev: true add_option("-n", "-d", "--dry-run", - "Do not uninstall gems") do |value, options| + "Do not uninstall gems") do |_value, options| options[:dryrun] = true end add_option(:Deprecated, "--dryrun", - "Do not uninstall gems") do |value, options| + "Do not uninstall gems") do |_value, options| options[:dryrun] = true end deprecate_option("--dryrun", extra_msg: "Use --dry-run instead") @@ -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,12 +117,12 @@ If no gems are named all gems in GEM_HOME are cleaned. end def get_candidate_gems - @candidate_gems = unless options[:args].empty? + @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 - else - Gem::Specification.to_a end end @@ -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 @@ -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 c5fdfca31e..807158d9c9 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" @@ -7,8 +8,8 @@ class Gem::Commands::ContentsCommand < Gem::Command def initialize super "contents", "Display the contents of the installed gems", - :specdirs => [], :lib_only => false, :prefix => true, - :show_install_dir => false + specdirs: [], lib_only: false, prefix: true, + show_install_dir: false add_version_option @@ -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,7 +104,7 @@ 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"], $'] when /\.so\z/ @@ -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 3f69a95e83..9aaefae999 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" @@ -10,15 +11,14 @@ class Gem::Commands::DependencyCommand < Gem::Command def initialize super "dependency", "Show the dependencies of an installed gem", - :version => Gem::Requirement.default, :domain => :local + 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| + "Include reverse dependencies in the output") do |value, options| options[:reverse_dependencies] = value end @@ -90,10 +90,9 @@ use with other commands. 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 @@ -153,7 +152,7 @@ use with other commands. response = String.new response << " " * level + "Gem #{spec.full_name}\n" unless spec.dependencies.empty? - spec.dependencies.sort_by {|dep| dep.name }.each do |dep| + spec.dependencies.sort_by(&:name).each do |dep| response << " " * level + " #{dep}\n" end end diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index d95e1d0dbb..8ed0996069 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::EnvironmentCommand < Gem::Command @@ -16,7 +17,7 @@ class Gem::Commands::EnvironmentCommand < Gem::Command platform display the supported gem platforms <omitted> display everything EOF - return args.gsub(/^\s+/, "") + args.gsub(/^\s+/, "") end def description # :nodoc: @@ -107,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" @@ -172,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..d588804290 --- /dev/null +++ b/lib/rubygems/commands/exec_command.rb @@ -0,0 +1,249 @@ +# 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 + gem_paths = { "GEM_HOME" => Gem.paths.home, "GEM_PATH" => Gem.paths.path.join(File::PATH_SEPARATOR), "GEM_SPEC_CACHE" => Gem.paths.spec_cache_dir }.compact + + 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! + ensure + ENV.update(gem_paths) if gem_paths + Gem.clear_paths + 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 = File.join(Gem.dir, "gem_exec") + + 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 5eb45d259c..f7f5b62306 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" @@ -9,8 +10,8 @@ class Gem::Commands::FetchCommand < Gem::Command def initialize defaults = { - :suggest_alternate => true, - :version => Gem::Requirement.default, + suggest_alternate: true, + version: Gem::Requirement.default, } super "fetch", "Download a gem and place it in the current directory", defaults diff --git a/lib/rubygems/commands/generate_index_command.rb b/lib/rubygems/commands/generate_index_command.rb index bc71e60ff0..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_relative "../command" -require_relative "../indexer" -## -# Generates a index files for use as a gem server. -# -# See `gem help generate_index` +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 -class Gem::Commands::GenerateIndexCommand < Gem::Command - def initialize - super "generate_index", - "Generates the index files for a gem server directory", - :directory => ".", :build_modern => true + def execute + alert_error "Install the rubygems-generate_index gem for the generate_index command" + end - add_option "-d", "--directory=DIRNAME", - "repository base dir containing gems subdir" do |dir, options| - options[:directory] = File.expand_path dir + def invoke_with_build_args(args, build_args) + name = "rubygems-generate_index" + spec = begin + Gem::Specification.find_by_name(name) + rescue Gem::LoadError + require "rubygems/dependency_installer" + Gem.install(name, Gem::Requirement.default, Gem::DependencyInstaller::DEFAULT_OPTIONS).find {|s| s.name == name } + end + + # remove the methods defined in this file so that the methods defined in the gem are used instead, + # and without a method redefinition warning + %w[description execute invoke_with_build_args].each do |method| + RubygemsTrampoline.remove_method(method) + end + self.class.singleton_class.remove_method(:new) + + spec.activate + Gem.load_plugin_files spec.matches_for_glob("rubygems_plugin#{Gem.suffix_pattern}") + + self.class.new.invoke_with_build_args(args, build_args) + end end + private_constant :RubygemsTrampoline - add_option "--[no-]modern", - "Generate indexes for RubyGems", - "(always true)" do |value, options| - options[:build_modern] = 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 - 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 - 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 !File.exist?(options[:directory]) || - !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 bf4ffefbb7..1619b152e7 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::HelpCommand < Gem::Command @@ -58,7 +59,7 @@ multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional documentation on the format at: - http://bundler.io + https://bundler.io RubyGems automatically looks for these gem dependencies files: @@ -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 ================================== @@ -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], @@ -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" + 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 ced7751ff5..f65c639662 100644 --- a/lib/rubygems/commands/info_command.rb +++ b/lib/rubygems/commands/info_command.rb @@ -8,8 +8,8 @@ class Gem::Commands::InfoCommand < Gem::Command 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 diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index c04c01f258..2091634a29 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../install_update_options" require_relative "../dependency_installer" @@ -22,11 +23,11 @@ class Gem::Commands::InstallCommand < Gem::Command def initialize defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ - :format_executable => false, - :lock => true, - :suggest_alternate => true, - :version => Gem::Requirement.default, - :without_groups => [], + format_executable: false, + lock: true, + suggest_alternate: true, + version: Gem::Requirement.default, + without_groups: [], }) defaults.merge!(install_update_options) @@ -47,7 +48,7 @@ class Gem::Commands::InstallCommand < Gem::Command end def defaults_str # :nodoc: - "--both --version '#{Gem::Requirement.default}' --no-force\n" + + "--both --version '#{Gem::Requirement.default}' --no-force\n" \ "--install-dir #{Gem.dir} --lock\n" + install_update_defaults_str end @@ -135,13 +136,6 @@ 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] && 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 && get_all_gem_names.size > 1 @@ -161,7 +155,6 @@ You can use `i` command instead of `install`. ENV.delete "GEM_PATH" if options[:install_dir].nil? - check_install_dir check_version load_hooks @@ -170,7 +163,7 @@ You can use `i` command instead of `install`. show_installed - say update_suggestion if eglible_for_update? + say update_suggestion if eligible_for_update? terminate_interaction exit_code end @@ -262,7 +255,7 @@ You can use `i` command instead of `install`. return unless errors errors.each do |x| - return unless Gem::SourceFetchProblem === x + next unless Gem::SourceFetchProblem === x require_relative "../uri" msg = "Unable to pull data from '#{Gem::Uri.redact(x.source.uri)}': #{x.error.message}" diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index 011873b99c..fab4b73814 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" @@ -10,8 +11,8 @@ class Gem::Commands::ListCommand < Gem::Command def initialize super "list", "Display local gems whose name matches REGEXP", - :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_query_options end diff --git a/lib/rubygems/commands/lock_command.rb b/lib/rubygems/commands/lock_command.rb index da636492c9..f7fd5ada16 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::LockCommand < Gem::Command def initialize super "lock", "Generate a lockdown list of gems", - :strict => false + strict: false add_option "-s", "--[no-]strict", "fail if unable to satisfy a dependency" do |strict, options| diff --git a/lib/rubygems/commands/mirror_command.rb b/lib/rubygems/commands/mirror_command.rb index b633cd3d81..b91a8db12d 100644 --- a/lib/rubygems/commands/mirror_command.rb +++ b/lib/rubygems/commands/mirror_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" unless defined? Gem::Commands::MirrorCommand diff --git a/lib/rubygems/commands/open_command.rb b/lib/rubygems/commands/open_command.rb index d5283f72dd..0fe90dc8b8 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" @@ -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 1785194389..08a9221a26 100644 --- a/lib/rubygems/commands/outdated_command.rb +++ b/lib/rubygems/commands/outdated_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../spec_fetcher" diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 959a6186dc..12bfe3a834 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" @@ -38,7 +39,7 @@ permission to. add_proxy_option add_key_option add_otp_option - defaults.merge! :add => [], :remove => [] + defaults.merge! add: [], remove: [] add_option "-a", "--add NEW_OWNER", "Add an owner by user identifier" do |value, options| options[:add] << value @@ -78,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 @@ -93,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 diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 72db53ef37..456d897df2 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../package" require_relative "../installer" @@ -10,10 +11,10 @@ class Gem::Commands::PristineCommand < Gem::Command 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 + version: Gem::Requirement.default, + extensions: true, + extensions_set: false, + all: false add_option("--all", "Restore all installed gems to pristine", @@ -34,6 +35,11 @@ class Gem::Commands::PristineCommand < Gem::Command options[:extensions] = value end + 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 @@ -107,13 +113,15 @@ extensions will be restored. Gem::Specification.select do |spec| spec.extensions && !spec.extensions.empty? end + elsif options[:only_missing_extensions] + Gem::Specification.select(&:missing_extensions?) 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 } + 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, @@ -128,7 +136,7 @@ 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 @@ -171,12 +179,12 @@ extensions will be restored. install_dir = options[:install_dir] if options[:install_dir] installer_options = { - :wrappers => true, - :force => true, - :install_dir => 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] diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 46b65f4e15..591ddc3a80 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../gemcutter_utilities" @@ -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 @@ -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 c6315acf8c..3b527974a3 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" require_relative "../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,11 +18,10 @@ 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, - :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", diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index a998a9704c..977c90b8c4 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../rdoc" @@ -9,8 +10,8 @@ class Gem::Commands::RdocCommand < Gem::Command def initialize super "rdoc", "Generates RDoc for pre-installed gems", - :version => Gem::Requirement.default, - :include_rdoc => false, :include_ri => true, :overwrite => false + version: Gem::Requirement.default, + include_rdoc: false, include_ri: true, overwrite: false add_option("--all", "Generate RDoc/RI documentation for all", @@ -83,14 +84,7 @@ Use --overwrite to force rebuilding of documentation. FileUtils.rm_rf File.join(spec.doc_dir, "rdoc") end - begin - doc.generate - rescue Errno::ENOENT => e - match = / - /.match(e.message) - alert_error "Unable to document #{spec.full_name}, " \ - " #{match.post_match} 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 3f8f7e13f2..50e161ac9b 100644 --- a/lib/rubygems/commands/search_command.rb +++ b/lib/rubygems/commands/search_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../query_utils" @@ -7,8 +8,8 @@ class Gem::Commands::SearchCommand < Gem::Command def initialize super "search", "Display remote gems whose name matches REGEXP", - :domain => :remote, :details => false, :versions => true, - :installed => nil, :version => Gem::Requirement.default + 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 56be07c79d..f1dde4aa02 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" unless defined? Gem::Commands::ServerCommand diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index c779b7c244..3f38074280 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" ## @@ -6,19 +7,19 @@ require_relative "../command" # 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 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 + 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", @@ -54,9 +55,9 @@ class Gem::Commands::SetupCommand < Gem::Command "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 + when nil then %w[rdoc ri] + when false then [] + else value end end @@ -133,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 @@ -242,9 +243,9 @@ 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 @@ -264,7 +265,7 @@ By default, this RubyGems will install gem as: fp.puts bin.join end - install bin_tmp_file, dest_file, :mode => prog_mode + install bin_tmp_file, dest_file, mode: prog_mode bin_file_names << dest_file ensure rm bin_tmp_file @@ -286,7 +287,7 @@ By default, this RubyGems will install gem as: TEXT end - install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode + install bin_cmd_file, "#{dest_file}.bat", mode: prog_mode ensure rm bin_cmd_file end @@ -356,7 +357,7 @@ 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) @@ -368,18 +369,21 @@ By default, this RubyGems will install gem as: File.dirname(loaded_from) else target_specs_dir = File.join(default_dir, "specifications", "default") - mkdir_p target_specs_dir, :mode => 0755 + mkdir_p target_specs_dir, mode: 0o755 target_specs_dir end - bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") } - default_spec_path = File.join(specs_dir, "#{bundler_spec.full_name}.gemspec") - Gem.write_binary(default_spec_path, bundler_spec.to_ruby) + 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, 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(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 @@ -387,20 +391,14 @@ 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 - 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_relative "../installer" Dir.chdir("bundler") do - built_gem = Gem::Package.build(bundler_spec) + built_gem = Gem::Package.build(new_bundler_spec) begin Gem::Installer.at( built_gem, @@ -417,9 +415,9 @@ By default, this RubyGems will install gem as: 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 @@ -429,10 +427,10 @@ By default, this RubyGems will install gem as: 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 @@ -575,8 +573,8 @@ abort "#{deprecation_message}" def uninstall_old_gemcutter 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 @@ -638,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) diff --git a/lib/rubygems/commands/signin_command.rb b/lib/rubygems/commands/signin_command.rb index 2660eee4f3..0f77908c5b 100644 --- a/lib/rubygems/commands/signin_command.rb +++ b/lib/rubygems/commands/signin_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../gemcutter_utilities" diff --git a/lib/rubygems/commands/signout_command.rb b/lib/rubygems/commands/signout_command.rb index fa688ea3f8..bdd01e4393 100644 --- a/lib/rubygems/commands/signout_command.rb +++ b/lib/rubygems/commands/signout_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::SignoutCommand < Gem::Command diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 5a8f5af9c3..976f4a4ea2 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../remote_fetcher" require_relative "../spec_fetcher" @@ -58,7 +59,7 @@ 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 @@ -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 && uri.scheme.downcase == "http" && - 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,16 +99,16 @@ 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 @@ -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 diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 12004a6d56..a21ed35be3 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" @@ -12,27 +13,27 @@ class Gem::Commands::SpecificationCommand < Gem::Command Gem.load_yaml super "specification", "Display gem specification (in yaml)", - :domain => :local, :version => Gem::Requirement.default, - :format => :yaml + domain: :local, version: Gem::Requirement.default, + format: :yaml add_version_option("examine") add_platform_option add_prerelease_option add_option("--all", "Output specifications for all versions of", - "the gem") do |value, options| + "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 @@ -106,7 +107,11 @@ Specific fields in the specification can be extracted in YAML format: 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? @@ -133,16 +138,16 @@ Specific fields in the specification can be extracted in YAML format: end unless options[:all] - specs = [specs.max_by {|s| s.version }] + specs = [specs.max_by(&:version)] end specs.each do |s| s = s.send field if field say case options[:format] - when :ruby then s.to_ruby - when :marshal then Marshal.dump s - else s.to_yaml + when :ruby then s.to_ruby + when :marshal then Marshal.dump s + else s.to_yaml end say "\n" diff --git a/lib/rubygems/commands/stale_command.rb b/lib/rubygems/commands/stale_command.rb index 0246f42e3e..0be2b85159 100644 --- a/lib/rubygems/commands/stale_command.rb +++ b/lib/rubygems/commands/stale_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" class Gem::Commands::StaleCommand < Gem::Command @@ -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 3c520826e5..2a77ec72cf 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../uninstaller" @@ -14,12 +15,11 @@ class Gem::Commands::UninstallCommand < Gem::Command def initialize super "uninstall", "Uninstall gems from the local repository", - :version => Gem::Requirement.default, :user_install => true, - :check_dev => false, :vendor => false + version: Gem::Requirement.default, user_install: true, + check_dev: false, vendor: false add_option("-a", "--[no-]all", - "Uninstall all matching versions" - ) do |value, options| + "Uninstall all matching versions") do |value, options| options[:all] = value end @@ -79,7 +79,7 @@ class Gem::Commands::UninstallCommand < Gem::Command add_option("--vendor", "Uninstall gem from the vendor directory.", - "Only for use by gem repackagers.") do |value, options| + "Only for use by gem repackagers.") do |_value, options| unless Gem.vendor_dir raise Gem::OptionParser::InvalidOption.new "your platform is not supported" end @@ -95,7 +95,7 @@ class Gem::Commands::UninstallCommand < Gem::Command end def defaults_str # :nodoc: - "--version '#{Gem::Requirement.default}' --no-force " + + "--version '#{Gem::Requirement.default}' --no-force " \ "--user-install" end @@ -125,6 +125,9 @@ that is a dependency of an existing gem. You can use the def execute check_version + # Consider only gem specifications installed at `--install-dir` + Gem::Specification.dirs = options[:install_dir] if options[:install_dir] + if options[:all] && !options[:args].empty? uninstall_specific elsif options[:all] @@ -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 @@ -165,15 +168,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 +183,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" + + alert("In order to remove #{spec.name}, please execute:\n" \ "\tgem uninstall #{spec.name} --install-dir=#{spec.installation_path}") 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 b1f939b0bc..c2fc720297 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../version_option" require_relative "../security_option" @@ -20,15 +21,15 @@ class Gem::Commands::UnpackCommand < Gem::Command require "fileutils" super "unpack", "Unpack an installed gem to the current directory", - :version => Gem::Requirement.default, - :target => Dir.pwd + version: Gem::Requirement.default, + target: Dir.pwd 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,12 +96,10 @@ 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| @@ -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 5c90981645..8e80d46856 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../command_manager" require_relative "../dependency_installer" @@ -20,7 +21,7 @@ class Gem::Commands::UpdateCommand < Gem::Command def initialize options = { - :force => false, + force: false, } options.merge!(install_update_options) @@ -36,10 +37,10 @@ class Gem::Commands::UpdateCommand < Gem::Command end add_option("--system [VERSION]", Gem::Version, - "Update the RubyGems system software") do |value, options| - value = true unless value + "Update the RubyGems system software") do |value, opts| + value ||= true - options[:system] = value + opts[:system] = value end add_local_remote_options @@ -119,7 +120,7 @@ command to remove old versions. updated = update_gems gems_to_update installed_names = highest_installed_gems.keys - updated_names = updated.map {|spec| spec.name } + 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 @@ -127,10 +128,10 @@ command to remove old versions. if updated.empty? say "Nothing to update" else - say "Gems updated: #{updated_names.join(' ')}" + 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? + 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: @@ -185,7 +186,9 @@ command to remove old versions. 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 @@ -230,7 +232,7 @@ command to remove old versions. 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) @@ -241,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 @@ -279,7 +281,7 @@ command to remove old versions. check_oldest_rubygems version installed_gems = Gem::Specification.find_all_by_name "rubygems-update", requirement - installed_gems = update_gem("rubygems-update", version) if installed_gems.empty? || installed_gems.first.version != version + installed_gems = update_gem("rubygems-update", requirement) if installed_gems.empty? || installed_gems.first.version != version return if installed_gems.empty? install_rubygems installed_gems.first @@ -291,16 +293,14 @@ command to remove old versions. 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 || - Gem::Version.new(options[:system]) >= Gem::Version.new(2) + args << "--previous-version" << Gem::VERSION args end def which_to_update(highest_installed_gems, gem_names) result = [] - highest_installed_gems.each do |l_name, l_spec| + highest_installed_gems.each do |_l_name, l_spec| next if !gem_names.empty? && gem_names.none? {|name| name == l_spec.name } @@ -325,12 +325,8 @@ command to remove old versions. @oldest_supported_version ||= if Gem.ruby_version > Gem::Version.new("3.1.a") Gem::Version.new("3.3.3") - elsif 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") else - Gem::Version.new("3.0.1") + 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 5b9a79b734..5ed4d9d142 100644 --- a/lib/rubygems/commands/which_command.rb +++ b/lib/rubygems/commands/which_command.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true + 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 + search_gems_first: false, show_all: false add_option "-a", "--[no-]all", "show all matching files" do |show_all, options| options[:show_all] = show_all diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb index 1499f72f5d..fbdc262549 100644 --- a/lib/rubygems/commands/yank_command.rb +++ b/lib/rubygems/commands/yank_command.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative "../command" require_relative "../local_remote_options" require_relative "../version_option" @@ -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) @@ -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 |