diff options
Diffstat (limited to 'lib/rubygems/commands')
28 files changed, 418 insertions, 228 deletions
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 0ebdec565b..2ec8324141 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,11 +1,13 @@ # 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" @@ -75,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 22864a9b29..72dcf1dd17 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -6,7 +6,7 @@ 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| diff --git a/lib/rubygems/commands/check_command.rb b/lib/rubygems/commands/check_command.rb index 6552258552..fb23dd9cb4 100644 --- a/lib/rubygems/commands/check_command.rb +++ b/lib/rubygems/commands/check_command.rb @@ -10,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', diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index 26beba48df..08fb598cea 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -8,8 +8,8 @@ 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| @@ -166,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 f3783468aa..807158d9c9 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -8,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 diff --git a/lib/rubygems/commands/dependency_command.rb b/lib/rubygems/commands/dependency_command.rb index 109bf03aff..9aaefae999 100644 --- a/lib/rubygems/commands/dependency_command.rb +++ b/lib/rubygems/commands/dependency_command.rb @@ -11,7 +11,7 @@ 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 diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb index 950d4fe30e..f7f5b62306 100644 --- a/lib/rubygems/commands/fetch_command.rb +++ b/lib/rubygems/commands/fetch_command.rb @@ -10,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 ce580cfaf9..13be92593b 100644 --- a/lib/rubygems/commands/generate_index_command.rb +++ b/lib/rubygems/commands/generate_index_command.rb @@ -1,86 +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` - -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 !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 043e7d3691..1619b152e7 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -59,7 +59,7 @@ multiple environments. The RubyGems implementation is designed to be compatible with Bundler's Gemfile format. You can see additional documentation on the format at: - http://bundler.io + https://bundler.io RubyGems automatically looks for these gem dependencies files: @@ -172,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 ================================== @@ -333,7 +333,7 @@ platform. @command_manager.command_names.each do |cmd_name| command = @command_manager[cmd_name] - next if command.deprecated? + next if command&.deprecated? summary = if command 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 b8dfc90111..2091634a29 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -23,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) @@ -136,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 @@ -162,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 @@ -171,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 diff --git a/lib/rubygems/commands/list_command.rb b/lib/rubygems/commands/list_command.rb index 522c771f90..fab4b73814 100644 --- a/lib/rubygems/commands/list_command.rb +++ b/lib/rubygems/commands/list_command.rb @@ -11,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 3a9512fe3f..f7fd5ada16 100644 --- a/lib/rubygems/commands/lock_command.rb +++ b/lib/rubygems/commands/lock_command.rb @@ -5,7 +5,7 @@ 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/open_command.rb b/lib/rubygems/commands/open_command.rb index 5a13074a1d..0fe90dc8b8 100644 --- a/lib/rubygems/commands/open_command.rb +++ b/lib/rubygems/commands/open_command.rb @@ -70,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/owner_command.rb b/lib/rubygems/commands/owner_command.rb index fce32aca3e..12bfe3a834 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -39,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 diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index fff59855f5..999c9fef0f 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -11,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", @@ -57,7 +57,7 @@ class Gem::Commands::PristineCommand < Gem::Command end add_option("-i", "--install-dir DIR", - "Gem repository to get binstubs and plugins installed") do |value, options| + "Gem repository to get gems restored") do |value, options| options[:install_dir] = File.expand_path(value) end @@ -103,25 +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 + specification_record.map # `--extensions` must be explicitly given to pristine only gems # with extensions. elsif options[:extensions_set] && options[:extensions] && options[:args].empty? - Gem::Specification.select do |spec| + specification_record.select do |spec| spec.extensions && !spec.extensions.empty? end elsif options[:only_missing_extensions] - Gem::Specification.select(&:missing_extensions?) + specification_record.select(&:missing_extensions?) else get_all_gem_names.sort.map do |gem_name| - Gem::Specification.find_all_by_name(gem_name, options[:version]).reverse + specification_record.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, @@ -144,7 +148,7 @@ extensions will be restored. end unless spec.extensions.empty? || options[:extensions] || options[:only_executables] || options[:only_plugins] - say "Skipped #{spec.full_name}, it needs to compile an extension" + say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end @@ -153,7 +157,7 @@ extensions will be restored. 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 @@ -176,15 +180,14 @@ extensions will be restored. end bin_dir = options[:bin_dir] if options[:bin_dir] - 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] @@ -198,7 +201,7 @@ extensions will be restored. 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 418b46acc5..591ddc3a80 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -30,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 => host + super "push", "Push a gem up to the gem server", host: host @user_defined_host = false diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index e7d21f341b..3b527974a3 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -20,8 +20,8 @@ class Gem::Commands::QueryCommand < Gem::Command 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 e318a52914..977c90b8c4 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -10,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", @@ -84,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 c7469e1fa8..50e161ac9b 100644 --- a/lib/rubygems/commands/search_command.rb +++ b/lib/rubygems/commands/search_command.rb @@ -8,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/setup_command.rb b/lib/rubygems/commands/setup_command.rb index c35d0f5ccc..9c633d6ef7 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -7,19 +7,19 @@ require_relative "../command" # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command - HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}.freeze - VERSION_MATCHER = %r{^#\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", @@ -265,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 @@ -287,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 @@ -369,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 => 0o755 + 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 @@ -388,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 => 0o755 - 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, @@ -418,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 @@ -430,8 +427,8 @@ By default, this RubyGems will install gem as: lib_dir, bin_dir = generate_default_dirs end - mkdir_p lib_dir, :mode => 0o755 - mkdir_p bin_dir, :mode => 0o755 + mkdir_p lib_dir, mode: 0o755 + mkdir_p bin_dir, mode: 0o755 [lib_dir, bin_dir] end @@ -576,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 @@ -588,6 +585,8 @@ abort "#{deprecation_message}" args = %w[--all --only-executables --silent] args << "--bindir=#{bindir}" + args << "--install-dir=#{default_dir}" + if options[:env_shebang] args << "--env-shebang" end @@ -639,10 +638,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 => 0o755 + mkdir_p dest_dir, mode: 0o755 end - install file, dest_file, :mode => options[:data_mode] || 0o644 + install file, dest_file, mode: options[:data_mode] || 0o644 end def remove_file_list(files, dir) diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index c9c6ee80ed..976f4a4ea2 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -59,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 @@ -81,7 +81,7 @@ 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.casecmp("http").zero? && uri.host.casecmp("rubygems.org").zero? diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index 938b9507f9..a21ed35be3 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -13,8 +13,8 @@ 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 diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb index c66bbe42e1..283bc96ce3 100644 --- a/lib/rubygems/commands/uninstall_command.rb +++ b/lib/rubygems/commands/uninstall_command.rb @@ -15,8 +15,8 @@ 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| @@ -168,10 +168,10 @@ that is a dependency of an existing gem. You can use the gems_to_uninstall = {} deps.each do |dep| - next if gems_to_uninstall[dep.name] - gems_to_uninstall[dep.name] = true - - unless original_gem_version[dep.name] == Gem::Requirement.default + if original_gem_version[dep.name] == Gem::Requirement.default + next if gems_to_uninstall[dep.name] + gems_to_uninstall[dep.name] = true + else options[:version] = dep.version end @@ -184,7 +184,7 @@ that is a dependency of an existing gem. You can use the 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}") + "\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 " \ diff --git a/lib/rubygems/commands/unpack_command.rb b/lib/rubygems/commands/unpack_command.rb index 73eefe153c..c2fc720297 100644 --- a/lib/rubygems/commands/unpack_command.rb +++ b/lib/rubygems/commands/unpack_command.rb @@ -21,8 +21,8 @@ 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| @@ -143,12 +143,6 @@ 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 /\.gem$/i.match?(dependency.name) diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index fb27e755bc..8e80d46856 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -21,7 +21,7 @@ class Gem::Commands::UpdateCommand < Gem::Command def initialize options = { - :force => false, + force: false, } options.merge!(install_update_options) @@ -197,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 @@ -244,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 @@ -282,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 @@ -294,9 +293,7 @@ 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 @@ -328,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 ec464d9672..5ed4d9d142 100644 --- a/lib/rubygems/commands/which_command.rb +++ b/lib/rubygems/commands/which_command.rb @@ -5,7 +5,7 @@ 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 |