From 4aca77edde91f826aa243e268bf1ef5214530583 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 8 Dec 2020 16:33:39 +0900 Subject: [PATCH] Merge prepare version of RubyGems 3.2.0 --- lib/rubygems.rb | 8 +- lib/rubygems/available_set.rb | 2 +- lib/rubygems/command_manager.rb | 4 +- lib/rubygems/commands/build_command.rb | 57 +- lib/rubygems/commands/cert_command.rb | 2 +- lib/rubygems/commands/help_command.rb | 2 + lib/rubygems/commands/owner_command.rb | 12 +- lib/rubygems/commands/pristine_command.rb | 2 +- lib/rubygems/commands/push_command.rb | 10 +- lib/rubygems/commands/query_command.rb | 17 + lib/rubygems/commands/server_command.rb | 4 + lib/rubygems/commands/setup_command.rb | 70 +- lib/rubygems/commands/sources_command.rb | 8 +- .../commands/specification_command.rb | 6 + lib/rubygems/commands/yank_command.rb | 8 +- lib/rubygems/core_ext/kernel_require.rb | 3 + lib/rubygems/core_ext/kernel_warn.rb | 13 +- lib/rubygems/defaults.rb | 4 +- lib/rubygems/dependency.rb | 2 +- lib/rubygems/dependency_installer.rb | 7 +- lib/rubygems/ext/builder.rb | 43 +- lib/rubygems/ext/cmake_builder.rb | 8 +- lib/rubygems/ext/configure_builder.rb | 8 +- lib/rubygems/ext/ext_conf_builder.rb | 33 +- lib/rubygems/ext/rake_builder.rb | 6 +- lib/rubygems/gemcutter_utilities.rb | 104 ++- lib/rubygems/indexer.rb | 1 - lib/rubygems/install_update_options.rb | 4 +- lib/rubygems/installer.rb | 21 +- lib/rubygems/installer_test_case.rb | 13 +- lib/rubygems/name_tuple.rb | 2 +- lib/rubygems/openssl.rb | 8 +- lib/rubygems/package.rb | 5 +- lib/rubygems/package/tar_header.rb | 2 +- lib/rubygems/package/tar_test_case.rb | 2 +- lib/rubygems/platform.rb | 25 +- lib/rubygems/query_utils.rb | 9 - lib/rubygems/remote_fetcher.rb | 3 +- lib/rubygems/request.rb | 7 +- .../request_set/gem_dependency_api.rb | 8 +- lib/rubygems/requirement.rb | 2 +- lib/rubygems/resolver.rb | 2 +- lib/rubygems/resolver/activation_request.rb | 10 +- lib/rubygems/resolver/api_specification.rb | 6 +- lib/rubygems/resolver/conflict.rb | 2 +- lib/rubygems/resolver/dependency_request.rb | 2 +- lib/rubygems/resolver/index_specification.rb | 11 + lib/rubygems/resolver/installer_set.rb | 3 +- lib/rubygems/resolver/lock_set.rb | 2 +- .../resolver/molinillo/lib/molinillo.rb | 11 +- .../molinillo/delegates/resolution_state.rb | 7 + .../delegates/specification_provider.rb | 1 + .../lib/molinillo/dependency_graph.rb | 44 +- .../lib/molinillo/dependency_graph/action.rb | 1 + .../dependency_graph/add_edge_no_circular.rb | 3 +- .../molinillo/dependency_graph/add_vertex.rb | 3 +- .../molinillo/dependency_graph/delete_edge.rb | 3 +- .../dependency_graph/detach_vertex_named.rb | 3 +- .../lib/molinillo/dependency_graph/log.rb | 13 +- .../molinillo/dependency_graph/set_payload.rb | 3 +- .../lib/molinillo/dependency_graph/tag.rb | 7 +- .../lib/molinillo/dependency_graph/vertex.rb | 53 +- .../molinillo/lib/molinillo/errors.rb | 82 ++- .../molinillo/lib/molinillo/gem_metadata.rb | 3 +- .../modules/specification_provider.rb | 1 + .../molinillo/lib/molinillo/modules/ui.rb | 4 +- .../molinillo/lib/molinillo/resolution.rb | 671 +++++++++++++----- .../molinillo/lib/molinillo/resolver.rb | 5 +- .../resolver/molinillo/lib/molinillo/state.rb | 12 +- lib/rubygems/resolver/specification.rb | 2 +- lib/rubygems/s3_uri_signer.rb | 2 +- lib/rubygems/security.rb | 3 +- lib/rubygems/security/policy.rb | 2 +- lib/rubygems/security/signer.rb | 2 +- lib/rubygems/server.rb | 2 +- lib/rubygems/source.rb | 14 +- lib/rubygems/spec_fetcher.rb | 2 +- lib/rubygems/specification.rb | 16 +- .../DigiCertHighAssuranceEVRootCA.pem | 23 - .../GlobalSignRootCA.pem | 0 lib/rubygems/stub_specification.rb | 2 +- lib/rubygems/test_case.rb | 39 +- lib/rubygems/uri_formatter.rb | 3 +- lib/rubygems/version_option.rb | 6 + test/rubygems/test_bundled_ca.rb | 14 +- test/rubygems/test_gem.rb | 6 +- .../test_gem_commands_build_command.rb | 202 +++++- .../test_gem_commands_cert_command.rb | 4 +- .../test_gem_commands_cleanup_command.rb | 2 +- .../test_gem_commands_contents_command.rb | 4 +- .../test_gem_commands_help_command.rb | 17 +- .../test_gem_commands_install_command.rb | 2 +- .../test_gem_commands_owner_command.rb | 50 +- .../test_gem_commands_pristine_command.rb | 4 +- .../test_gem_commands_push_command.rb | 36 +- .../test_gem_commands_query_command.rb | 6 +- .../test_gem_commands_setup_command.rb | 31 +- .../test_gem_commands_signin_command.rb | 30 +- .../test_gem_commands_sources_command.rb | 60 ++ ...test_gem_commands_specification_command.rb | 28 + .../test_gem_commands_update_command.rb | 4 +- .../test_gem_commands_yank_command.rb | 32 +- .../rubygems/test_gem_dependency_installer.rb | 38 +- test/rubygems/test_gem_dependency_list.rb | 4 +- test/rubygems/test_gem_ext_builder.rb | 24 +- test/rubygems/test_gem_ext_cmake_builder.rb | 12 +- .../test_gem_ext_configure_builder.rb | 12 +- .../rubygems/test_gem_ext_ext_conf_builder.rb | 35 +- test/rubygems/test_gem_ext_rake_builder.rb | 18 +- test/rubygems/test_gem_gem_runner.rb | 9 +- test/rubygems/test_gem_gemcutter_utilities.rb | 6 +- .../test_gem_install_update_options.rb | 18 +- test/rubygems/test_gem_installer.rb | 85 ++- test/rubygems/test_gem_package.rb | 16 +- test/rubygems/test_gem_package_old.rb | 8 +- test/rubygems/test_gem_package_tar_writer.rb | 2 +- test/rubygems/test_gem_platform.rb | 67 +- test/rubygems/test_gem_remote_fetcher.rb | 32 +- test/rubygems/test_gem_request.rb | 4 +- .../rubygems/test_gem_request_set_lockfile.rb | 8 +- test/rubygems/test_gem_resolver_api_set.rb | 16 +- test/rubygems/test_gem_resolver_conflict.rb | 2 +- test/rubygems/test_gem_resolver_vendor_set.rb | 2 +- test/rubygems/test_gem_security.rb | 4 +- test/rubygems/test_gem_security_policy.rb | 4 +- test/rubygems/test_gem_security_signer.rb | 4 +- test/rubygems/test_gem_security_trust_dir.rb | 4 +- test/rubygems/test_gem_source.rb | 5 + .../test_gem_source_subpath_problem.rb | 49 ++ test/rubygems/test_gem_specification.rb | 9 +- test/rubygems/test_gem_uninstaller.rb | 14 +- test/rubygems/test_gem_validator.rb | 2 +- test/rubygems/test_gem_version_option.rb | 2 +- test/rubygems/test_require.rb | 59 +- 134 files changed, 2002 insertions(+), 745 deletions(-) delete mode 100644 lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem rename lib/rubygems/ssl_certs/{index.rubygems.org => rubygems.org}/GlobalSignRootCA.pem (100%) create mode 100644 test/rubygems/test_gem_source_subpath_problem.rb diff --git a/lib/rubygems.rb b/lib/rubygems.rb index eabe1c45dd..e6a3c63c60 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -8,7 +8,7 @@ require 'rbconfig' module Gem - VERSION = "3.2.0.rc.2".freeze + VERSION = "3.2.0".freeze end # Must be first since it unloads the prelude from 1.9.2 @@ -119,6 +119,10 @@ module Gem # to avoid deprecation warnings in Ruby 2.7. UNTAINT = RUBY_VERSION < '2.7' ? :untaint.to_sym : proc{} + # When https://bugs.ruby-lang.org/issues/17259 is available, there is no need to override Kernel#warn + KERNEL_WARN_IGNORES_INTERNAL_ENTRIES = RUBY_ENGINE == "truffleruby" || + (RUBY_ENGINE == "ruby" && RUBY_VERSION >= '3.0') + ## # An Array of Regexps that match windows Ruby platforms. @@ -975,7 +979,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} val = RbConfig::CONFIG[key] next unless val and not val.empty? ".#{val}" - end + end, ].compact.uniq end diff --git a/lib/rubygems/available_set.rb b/lib/rubygems/available_set.rb index 80ef29df64..499483d9e9 100644 --- a/lib/rubygems/available_set.rb +++ b/lib/rubygems/available_set.rb @@ -73,7 +73,7 @@ class Gem::AvailableSet end def match_platform! - @set.reject! {|t| !Gem::Platform.match(t.spec.platform) } + @set.reject! {|t| !Gem::Platform.match_spec?(t.spec) } @sorted = nil self end diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 1dcb577f7e..97e52544ca 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -73,7 +73,7 @@ class Gem::CommandManager ].freeze ALIAS_COMMANDS = { - 'i' => 'install' + 'i' => 'install', }.freeze ## @@ -174,8 +174,8 @@ class Gem::CommandManager else cmd_name = args.shift.downcase cmd = find_command cmd_name - cmd.invoke_with_build_args args, build_args cmd.deprecation_warning if cmd.deprecated? + cmd.invoke_with_build_args args, build_args end end diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index eaf8573d8f..fff5f7c76f 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -61,14 +61,18 @@ Gems can be saved to a specified filename with the output option: end def execute - gem_name = get_one_optional_argument || find_gemspec - build_gem(gem_name) + if build_path = options[:build_path] + Dir.chdir(build_path) { build_gem } + return + end + + build_gem end private - def find_gemspec - gemspecs = Dir.glob("*.gemspec").sort + def find_gemspec(glob = "*.gemspec") + gemspecs = Dir.glob(glob).sort if gemspecs.size > 1 alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" @@ -78,28 +82,19 @@ Gems can be saved to a specified filename with the output option: gemspecs.first end - def build_gem(gem_name) - gemspec = File.exist?(gem_name) ? gem_name : "#{gem_name}.gemspec" - - if File.exist?(gemspec) - spec = Gem::Specification.load(gemspec) - - if options[:build_path] - Dir.chdir(File.dirname(gemspec)) do - spec = Gem::Specification.load(File.basename(gemspec)) - build_package(spec) - end - else - build_package(spec) - end + def build_gem + gemspec = resolve_gem_name + if gemspec + build_package(gemspec) else - alert_error "Gemspec file not found: #{gemspec}" + alert_error error_message terminate_interaction(1) end end - def build_package(spec) + def build_package(gemspec) + spec = Gem::Specification.load(gemspec) if spec Gem::Package.build( spec, @@ -112,4 +107,26 @@ Gems can be saved to a specified filename with the output option: terminate_interaction 1 end end + + def resolve_gem_name + return find_gemspec unless gem_name + + if File.exist?(gem_name) + gem_name + else + find_gemspec("#{gem_name}.gemspec") || find_gemspec(gem_name) + end + end + + def error_message + if gem_name + "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" + else + "Couldn't find a gemspec file in #{Dir.pwd}" + end + end + + def gem_name + get_one_optional_argument + end end diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index e5355d3652..998df0621b 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -311,4 +311,4 @@ For further reading on signing gems see `ri Gem::Security`. # It's simple, but is all we need email =~ /\A.+@.+\z/ end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index 9ba8bf1293..4e8d7600fb 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -332,6 +332,8 @@ platform. @command_manager.command_names.each do |cmd_name| command = @command_manager[cmd_name] + next if command.deprecated? + summary = if command command.summary diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 0f01823967..46172854cf 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -53,7 +53,7 @@ permission to. def execute @host = options[:host] - sign_in + sign_in(scope: get_owner_scope) name = get_one_gem_name add_owners name, options[:add] @@ -102,10 +102,18 @@ permission to. private def send_owner_request(method, name, owner) - rubygems_api_request method, "api/v1/gems/#{name}/owners" do |request| + rubygems_api_request method, "api/v1/gems/#{name}/owners", scope: get_owner_scope(method: method) do |request| request.set_form_data 'email' => owner request.add_field "Authorization", api_key request.add_field "OTP", options[:otp] if options[:otp] end end + + def get_owner_scope(method: nil) + if method == :post || options.any? && options[:add].any? + :add_owner + elsif method == :delete || options.any? && options[:remove].any? + :remove_owner + end + end end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index db8136c9a6..143105981e 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -170,7 +170,7 @@ extensions will be restored. :install_dir => spec.base_dir, :env_shebang => env_shebang, :build_args => spec.build_args, - :bin_dir => bin_dir + :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 003b2dacc7..8885269ecb 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -61,7 +61,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo options[:host] end - sign_in @host + sign_in @host, scope: get_push_scope send_gem(gem_name) end @@ -86,7 +86,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo private def send_push_request(name, args) - rubygems_api_request(*args) do |request| + rubygems_api_request(*args, scope: get_push_scope) do |request| request.body = Gem.read_binary name request.add_field "Content-Length", request.body.size request.add_field "Content-Type", "application/octet-stream" @@ -100,7 +100,11 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo [ gem_metadata["default_gem_server"], - gem_metadata["allowed_push_host"] + gem_metadata["allowed_push_host"], ] end + + def get_push_scope + :push_rubygem + end end diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb index 406a34e549..789afd6509 100644 --- a/lib/rubygems/commands/query_command.rb +++ b/lib/rubygems/commands/query_command.rb @@ -9,6 +9,14 @@ class Gem::Commands::QueryCommand < Gem::Command include Gem::QueryUtils + alias warning_without_suggested_alternatives deprecation_warning + def deprecation_warning + warning_without_suggested_alternatives + + message = "It is recommended that you use `gem search` or `gem list` instead.\n" + alert_warning message unless Gem::Deprecate.skip + end + def initialize(name = 'query', summary = 'Query gem information in local or remote repositories') super name, summary, @@ -23,4 +31,13 @@ class Gem::Commands::QueryCommand < Gem::Command add_query_options end + + def description # :nodoc: + <<-EOF +The query command is the basis for the list and search commands. + +You should really use the list and search commands instead. This command +is too hard to use. + EOF + end end diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb index 91d5e267f8..594cf77f66 100644 --- a/lib/rubygems/commands/server_command.rb +++ b/lib/rubygems/commands/server_command.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true require 'rubygems/command' require 'rubygems/server' +require 'rubygems/deprecate' class Gem::Commands::ServerCommand < Gem::Command + extend Gem::Deprecate + rubygems_deprecate_command + def initialize super 'server', 'Documentation and gem repository HTTP server', :port => 8808, :gemdir => [], :daemon => false diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index b63920ab8d..22b1371a1f 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -322,13 +322,10 @@ By default, this RubyGems will install gem as: libs.each do |tool, path| say "Installing #{tool}" if @verbose - lib_files = rb_files_in path - lib_files.concat(bundler_template_files) if tool == 'Bundler' - - pem_files = pem_files_in path + lib_files = files_in path Dir.chdir path do - install_file_list(lib_files + pem_files, lib_dir) + install_file_list(lib_files, lib_dir) end end end @@ -394,10 +391,6 @@ By default, this RubyGems will install gem as: specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform? mkdir_p specs_dir, :mode => 0755 - # Workaround for non-git environment. - gemspec = File.open('bundler/bundler.gemspec', 'rb'){|f| f.read.gsub(/`git ls-files -z`/, "''") } - File.open('bundler/bundler.gemspec', 'w'){|f| f.write gemspec } - bundler_spec = Gem::Specification.load("bundler/bundler.gemspec") bundler_spec.files = Dir.chdir("bundler") { Dir["{*.md,{lib,exe,man}/**/*}"] } bundler_spec.executables -= %w[bundler bundle_ruby] @@ -518,44 +511,24 @@ By default, this RubyGems will install gem as: [lib_dir, bin_dir] end - def pem_files_in(dir) + def files_in(dir) Dir.chdir dir do - Dir[File.join('**', '*pem')] - end - end - - def rb_files_in(dir) - Dir.chdir dir do - Dir[File.join('**', '*rb')] + Dir.glob(File.join('**', '*'), File::FNM_DOTMATCH). + select{|f| !File.directory?(f) } end end # for installation of bundler as default gems def bundler_man1_files_in(dir) Dir.chdir dir do - Dir['bundle*.1{,.txt,.ronn}'] + Dir['bundle*.1'] end end # for installation of bundler as default gems def bundler_man5_files_in(dir) Dir.chdir dir do - Dir['gemfile.5{,.txt,.ronn}'] - end - end - - def bundler_template_files - Dir.chdir "bundler/lib" do - Dir.glob(File.join('bundler', 'templates', '**', '*'), File::FNM_DOTMATCH). - select{|f| !File.directory?(f) } - end - end - - # for cleanup old bundler files - def template_files_in(dir) - Dir.chdir dir do - Dir.glob(File.join('templates', '**', '*'), File::FNM_DOTMATCH). - select{|f| !File.directory?(f) } + Dir['gemfile.5'] end end @@ -595,11 +568,9 @@ abort "#{deprecation_message}" lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' } lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler' lib_dirs.each do |old_lib_dir, new_lib_dir| - lib_files = rb_files_in(new_lib_dir) - lib_files.concat(template_files_in(new_lib_dir)) if new_lib_dir =~ /bundler/ + lib_files = files_in(new_lib_dir) - old_lib_files = rb_files_in(old_lib_dir) - old_lib_files.concat(template_files_in(old_lib_dir)) if old_lib_dir =~ /bundler/ + old_lib_files = files_in(old_lib_dir) to_remove = old_lib_files - lib_files @@ -617,16 +588,25 @@ abort "#{deprecation_message}" def remove_old_man_files(man_dir) man_dirs = { man_dir => "bundler/man" } man_dirs.each do |old_man_dir, new_man_dir| - ["1", "5"].each do |section| - man_files = send(:"bundler_man#{section}_files_in", new_man_dir) + man1_files = bundler_man1_files_in(new_man_dir) - old_man_dir_with_section = "#{old_man_dir}/man#{section}" - old_man_files = send(:"bundler_man#{section}_files_in", old_man_dir_with_section) + old_man1_dir = "#{old_man_dir}/man1" + old_man1_files = bundler_man1_files_in(old_man1_dir) + old_man1_files += Dir.chdir(old_man1_dir) { Dir["bundle*.1.{txt,ronn}"] } - man_to_remove = old_man_files - man_files + man1_to_remove = old_man1_files - man1_files - remove_file_list(man_to_remove, old_man_dir_with_section) - end + remove_file_list(man1_to_remove, old_man1_dir) + + man5_files = bundler_man5_files_in(new_man_dir) + + old_man5_dir = "#{old_man_dir}/man5" + old_man5_files = bundler_man5_files_in(old_man5_dir) + old_man5_files += Dir.chdir(old_man5_dir) { Dir["gemfile.5.{txt,ronn}"] } + + man5_to_remove = old_man5_files - man5_files + + remove_file_list(man5_to_remove, old_man5_dir) end end diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 3be3a5dc79..f74fb12e42 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -34,6 +34,10 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:update] = value end + add_option '-f', '--[no-]force', "Do not show any confirmation prompts and behave as if 'yes' was always answered" do |value, options| + options[:force] = value + end + add_proxy_option end @@ -71,7 +75,7 @@ class Gem::Commands::SourcesCommand < Gem::Command Do you want to add this source? QUESTION - terminate_interaction 1 unless ask_yes_no question + terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end @@ -86,7 +90,7 @@ https://rubygems.org is recommended for security over #{uri} Do you want to add this insecure source? QUESTION - terminate_interaction 1 unless ask_yes_no question + terminate_interaction 1 unless options[:force] || ask_yes_no(question) end end diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index e4a8afab21..3fddaaaf30 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -126,6 +126,12 @@ Specific fields in the specification can be extracted in YAML format: terminate_interaction 1 end + platform = get_platform_from_requirements(options) + + if platform + specs = specs.select{|s| s.platform.to_s == platform } + end + unless options[:all] specs = [specs.max_by {|s| s.version }] end diff --git a/lib/rubygems/commands/yank_command.rb b/lib/rubygems/commands/yank_command.rb index 6ad74de96b..0e08bbfd5d 100644 --- a/lib/rubygems/commands/yank_command.rb +++ b/lib/rubygems/commands/yank_command.rb @@ -47,7 +47,7 @@ data you will need to change them immediately and yank your gem. def execute @host = options[:host] - sign_in @host + sign_in @host, scope: get_yank_scope version = get_version_from_requirements(options[:version]) platform = get_platform_from_requirements(options) @@ -72,7 +72,7 @@ data you will need to change them immediately and yank your gem. def yank_api_request(method, version, platform, api) name = get_one_gem_name - response = rubygems_api_request(method, api, host) do |request| + response = rubygems_api_request(method, api, host, scope: get_yank_scope) do |request| request.add_field("Authorization", api_key) request.add_field("OTP", options[:otp]) if options[:otp] @@ -93,7 +93,7 @@ data you will need to change them immediately and yank your gem. nil end - def get_platform_from_requirements(requirements) - Gem.platforms[1].to_s if requirements.key? :added_platform + def get_yank_scope + :yank_rubygem end end diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index edf046651e..4b867c55e9 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -17,6 +17,8 @@ module Kernel private :gem_original_require end + file = Gem::KERNEL_WARN_IGNORES_INTERNAL_ENTRIES ? "" : __FILE__ + module_eval <<'RUBY', file, __LINE__ + 1 ## # When RubyGems is required, Kernel#require is replaced with our own which # is capable of loading gems on demand. @@ -166,6 +168,7 @@ module Kernel end end end +RUBY private :require diff --git a/lib/rubygems/core_ext/kernel_warn.rb b/lib/rubygems/core_ext/kernel_warn.rb index e030ef815c..3373cfdd3b 100644 --- a/lib/rubygems/core_ext/kernel_warn.rb +++ b/lib/rubygems/core_ext/kernel_warn.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true # `uplevel` keyword argument of Kernel#warn is available since ruby 2.5. -if RUBY_VERSION >= "2.5" +if RUBY_VERSION >= "2.5" && !Gem::KERNEL_WARN_IGNORES_INTERNAL_ENTRIES module Kernel rubygems_path = "#{__dir__}/" # Frames to be skipped start with this path. - original_warn = method(:warn) + original_warn = instance_method(:warn) remove_method :warn @@ -17,9 +17,9 @@ if RUBY_VERSION >= "2.5" module_function define_method(:warn) {|*messages, **kw| unless uplevel = kw[:uplevel] if Gem.java_platform? - return original_warn.call(*messages) + return original_warn.bind(self).call(*messages) else - return original_warn.call(*messages, **kw) + return original_warn.bind(self).call(*messages, **kw) end end @@ -45,11 +45,10 @@ if RUBY_VERSION >= "2.5" end end end - uplevel = start + kw[:uplevel] = start end - kw[:uplevel] = uplevel - original_warn.call(*messages, **kw) + original_warn.bind(self).call(*messages, **kw) } end end diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 2d0b077fdc..8aae67cd6b 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -38,13 +38,13 @@ module Gem [ File.dirname(RbConfig::CONFIG['sitedir']), 'Gems', - RbConfig::CONFIG['ruby_version'] + RbConfig::CONFIG['ruby_version'], ] else [ RbConfig::CONFIG['rubylibprefix'], 'gems', - RbConfig::CONFIG['ruby_version'] + RbConfig::CONFIG['ruby_version'], ] end diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index 8634d71a72..68f3e3d991 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -281,7 +281,7 @@ class Gem::Dependency if platform_only matches.reject! do |spec| - spec.nil? || !Gem::Platform.match(spec.platform) + spec.nil? || !Gem::Platform.match_spec?(spec) end end diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index 1afdc4b4c2..fb555a46d4 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -27,7 +27,7 @@ class Gem::DependencyInstaller :wrappers => true, :build_args => nil, :build_docs_in_background => false, - :install_as_default => false + :install_as_default => false, }.freeze ## @@ -283,10 +283,9 @@ class Gem::DependencyInstaller request_set.development_shallow = @dev_shallow request_set.soft_missing = @force request_set.prerelease = @prerelease - request_set.remote = false unless consider_remote? installer_set = Gem::Resolver::InstallerSet.new @domain - installer_set.ignore_installed = @only_install_dir + installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir if consider_local? if dep_or_name =~ /\.gem$/ and File.file? dep_or_name @@ -307,6 +306,7 @@ class Gem::DependencyInstaller dependency = if spec = installer_set.local?(dep_or_name) + installer_set.remote = nil if spec.dependencies.none? Gem::Dependency.new spec.name, version elsif String === dep_or_name Gem::Dependency.new dep_or_name, version @@ -321,6 +321,7 @@ class Gem::DependencyInstaller installer_set.add_always_install dependency request_set.always_install = installer_set.always_install + request_set.remote = installer_set.consider_remote? if @ignore_dependencies installer_set.ignore_dependencies = true diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index afc8cb0ee4..f6de6a50d7 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -10,14 +10,6 @@ require_relative '../user_interaction' class Gem::Ext::Builder include Gem::UserInteraction - ## - # The builder shells-out to run various commands after changing the - # directory. This means multiple installations cannot be allowed to build - # extensions in parallel as they may change each other's directories leading - # to broken extensions or failed installations. - - CHDIR_MUTEX = Mutex.new # :nodoc: - attr_accessor :build_args # :nodoc: def self.class_name @@ -25,8 +17,8 @@ class Gem::Ext::Builder $1.downcase end - def self.make(dest_path, results) - unless File.exist? 'Makefile' + def self.make(dest_path, results, make_dir = Dir.pwd) + unless File.exist? File.join(make_dir, 'Makefile') raise Gem::InstallError, 'Makefile not found' end @@ -44,32 +36,32 @@ class Gem::Ext::Builder cmd = [ make_program, destdir, - target + target, ].join(' ').rstrip begin - run(cmd, results, "make #{target}".rstrip) + run(cmd, results, "make #{target}".rstrip, make_dir) rescue Gem::InstallError raise unless target == 'clean' # ignore clean failure end end end - def self.run(command, results, command_name = nil) + def self.run(command, results, command_name = nil, dir = Dir.pwd) verbose = Gem.configuration.really_verbose begin rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil if verbose - puts("current directory: #{Dir.pwd}") + puts("current directory: #{dir}") p(command) end - results << "current directory: #{Dir.pwd}" + results << "current directory: #{dir}" results << (command.respond_to?(:shelljoin) ? command.shelljoin : command) require "open3" # Set $SOURCE_DATE_EPOCH for the subprocess. env = {'SOURCE_DATE_EPOCH' => Gem.source_date_epoch_string} - output, status = Open3.capture2e(env, *command) + output, status = Open3.capture2e(env, *command, :chdir => dir) if verbose puts output else @@ -161,22 +153,10 @@ EOF begin FileUtils.mkdir_p dest_path - CHDIR_MUTEX.synchronize do - pwd = Dir.getwd - Dir.chdir extension_dir - begin - results = builder.build(extension, dest_path, - results, @build_args, lib_dir) + results = builder.build(extension, dest_path, + results, @build_args, lib_dir, extension_dir) - verbose { results.join("\n") } - ensure - begin - Dir.chdir pwd - rescue SystemCallError - Dir.chdir dest_path - end - end - end + verbose { results.join("\n") } write_gem_make_out results.join "\n" rescue => e @@ -201,6 +181,7 @@ EOF dest_path = @spec.extension_dir + require "fileutils" FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 519372e742..2efec91f15 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -2,15 +2,15 @@ require_relative '../command' class Gem::Ext::CmakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil) - unless File.exist?('Makefile') + def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd) + unless File.exist?(File.join(cmake_dir, 'Makefile')) cmd = "cmake . -DCMAKE_INSTALL_PREFIX=#{dest_path}" cmd << " #{Gem::Command.build_args.join ' '}" unless Gem::Command.build_args.empty? - run cmd, results + run cmd, results, class_name, cmake_dir end - make dest_path, results + make dest_path, results, cmake_dir results end diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb index 209e75fe8e..36a758989b 100644 --- a/lib/rubygems/ext/configure_builder.rb +++ b/lib/rubygems/ext/configure_builder.rb @@ -6,15 +6,15 @@ #++ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil) - unless File.exist?('Makefile') + def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd) + unless File.exist?(File.join(configure_dir, 'Makefile')) cmd = "sh ./configure --prefix=#{dest_path}" cmd << " #{args.join ' '}" unless args.empty? - run cmd, results + run cmd, results, class_name, configure_dir end - make dest_path, results + make dest_path, results, configure_dir results end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index 305e1dcfb1..fede270417 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -8,11 +8,11 @@ require 'shellwords' class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil) + def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) require 'fileutils' require 'tempfile' - tmp_dest = Dir.mktmpdir(".gem.", ".") + tmp_dest = Dir.mktmpdir(".gem.", extension_dir) # Some versions of `mktmpdir` return absolute paths, which will break make # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative @@ -23,9 +23,9 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder # spaces do not work. # # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940 - tmp_dest = get_relative_path(tmp_dest) + tmp_dest = get_relative_path(tmp_dest, extension_dir) - Tempfile.open %w[siteconf .rb], "." do |siteconf| + Tempfile.open %w[siteconf .rb], extension_dir do |siteconf| siteconf.puts "require 'rbconfig'" siteconf.puts "dest_path = #{tmp_dest.dump}" %w[sitearchdir sitelibdir].each do |dir| @@ -38,19 +38,22 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder destdir = ENV["DESTDIR"] begin + # workaround for https://github.com/oracle/truffleruby/issues/2115 + siteconf_path = RUBY_ENGINE == "truffleruby" ? siteconf.path.dup : siteconf.path cmd = Gem.ruby.shellsplit << "-I" << File.expand_path("../../..", __FILE__) << - "-r" << get_relative_path(siteconf.path) << File.basename(extension) + "-r" << get_relative_path(siteconf_path, extension_dir) << File.basename(extension) cmd.push(*args) begin - run(cmd, results) do |s, r| - if File.exist? 'mkmf.log' + run(cmd, results, class_name, extension_dir) do |s, r| + mkmf_log = File.join(extension_dir, 'mkmf.log') + if File.exist? mkmf_log unless s.success? r << "To see why this extension failed to compile, please check" \ " the mkmf.log which can be found here:\n" r << " " + File.join(dest_path, 'mkmf.log') + "\n" end - FileUtils.mv 'mkmf.log', dest_path + FileUtils.mv mkmf_log, dest_path end end siteconf.unlink @@ -58,18 +61,20 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder ENV["DESTDIR"] = nil - make dest_path, results + make dest_path, results, extension_dir if tmp_dest + full_tmp_dest = File.join(extension_dir, tmp_dest) + # TODO remove in RubyGems 3 if Gem.install_extension_in_lib and lib_dir FileUtils.mkdir_p lib_dir - entries = Dir.entries(tmp_dest) - %w[. ..] - entries = entries.map {|entry| File.join tmp_dest, entry } + entries = Dir.entries(full_tmp_dest) - %w[. ..] + entries = entries.map {|entry| File.join full_tmp_dest, entry } FileUtils.cp_r entries, lib_dir, :remove_destination => true end - FileUtils::Entry_.new(tmp_dest).traverse do |ent| + FileUtils::Entry_.new(full_tmp_dest).traverse do |ent| destent = ent.class.new(dest_path, ent.rel) destent.exist? or FileUtils.mv(ent.path, destent.path) end @@ -87,8 +92,8 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder private - def self.get_relative_path(path) - path[0..Dir.pwd.length - 1] = '.' if path.start_with?(Dir.pwd) + def self.get_relative_path(path, base) + path[0..base.length - 1] = '.' if path.start_with?(base) path end end diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 53507090fe..34c3922f2f 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -8,9 +8,9 @@ require "shellwords" class Gem::Ext::RakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil) + def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd) if File.basename(extension) =~ /mkrf_conf/i - run([Gem.ruby, File.basename(extension), *args], results) + run([Gem.ruby, File.basename(extension), *args], results, class_name, extension_dir) end rake = ENV['rake'] @@ -26,7 +26,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder end rake_args = ["RUBYARCHDIR=#{dest_path}", "RUBYLIBDIR=#{dest_path}", *args] - run(rake + rake_args, results) + run(rake + rake_args, results, class_name, extension_dir) results end diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index bba9280691..d021f47e24 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -8,10 +8,12 @@ require 'rubygems/text' module Gem::GemcutterUtilities ERROR_CODE = 1 + API_SCOPES = %i[index_rubygems push_rubygem yank_rubygem add_owner remove_owner access_webhooks show_dashboard].freeze include Gem::Text attr_writer :host + attr_writer :scope ## # Add the --key option @@ -72,7 +74,7 @@ module Gem::GemcutterUtilities # # If +allowed_push_host+ metadata is present, then it will only allow that host. - def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &block) + def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, &block) require 'net/http' self.host = host if host @@ -95,11 +97,19 @@ module Gem::GemcutterUtilities request_method = Net::HTTP.const_get method.to_s.capitalize response = Gem::RemoteFetcher.fetcher.request(uri, request_method, &block) - return response unless mfa_unauthorized?(response) - Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req| - req.add_field "OTP", get_otp - block.call(req) + if mfa_unauthorized?(response) + response = Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req| + req.add_field "OTP", get_otp + block.call(req) + end + end + + if api_key_forbidden?(response) + update_scope(scope) + Gem::RemoteFetcher.fetcher.request(uri, request_method, &block) + else + response end end @@ -112,19 +122,37 @@ module Gem::GemcutterUtilities ask 'Code: ' end + def update_scope(scope) + sign_in_host = self.host + pretty_host = pretty_host(sign_in_host) + update_scope_params = { scope => true } + + say "The existing key doesn't have access of #{scope} on #{pretty_host}. Please sign in to update access." + + email = ask " Email: " + password = ask_for_password "Password: " + + response = rubygems_api_request(:put, "api/v1/api_key", + sign_in_host, scope: scope) do |request| + request.basic_auth email, password + request.add_field "OTP", options[:otp] if options[:otp] + request.body = URI.encode_www_form({:api_key => api_key }.merge(update_scope_params)) + end + + with_response response do |resp| + say "Added #{scope} scope to the existing API key" + end + end + ## # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API # key. - def sign_in(sign_in_host = nil) + def sign_in(sign_in_host = nil, scope: nil) sign_in_host ||= self.host return if api_key - pretty_host = if Gem::DEFAULT_HOST == sign_in_host - 'RubyGems.org' - else - sign_in_host - end + pretty_host = pretty_host(sign_in_host) say "Enter your #{pretty_host} credentials." say "Don't have an account yet? " + @@ -134,14 +162,18 @@ module Gem::GemcutterUtilities password = ask_for_password "Password: " say "\n" - response = rubygems_api_request(:get, "api/v1/api_key", - sign_in_host) do |request| + key_name = get_key_name(scope) + scope_params = get_scope_params(scope) + + response = rubygems_api_request(:post, "api/v1/api_key", + sign_in_host, scope: scope) do |request| request.basic_auth email, password request.add_field "OTP", options[:otp] if options[:otp] + request.body = URI.encode_www_form({ name: key_name }.merge(scope_params)) end with_response response do |resp| - say "Signed in." + say "Signed in with API key: #{key_name}." set_api_key host, resp.body end end @@ -195,4 +227,48 @@ module Gem::GemcutterUtilities end end + private + + def pretty_host(host) + if Gem::DEFAULT_HOST == host + 'RubyGems.org' + else + host + end + end + + def get_scope_params(scope) + scope_params = {} + + if scope + scope_params = { scope => true } + else + say "Please select scopes you want to enable for the API key (y/n)" + API_SCOPES.each do |scope| + selected = ask "#{scope} [y/N]: " + scope_params[scope] = true if selected =~ /^[yY](es)?$/ + end + say "\n" + end + + scope_params + end + + def get_key_name(scope) + hostname = Socket.gethostname || "unkown-host" + user = ENV["USER"] || ENV["USERNAME"] || "unkown-user" + ts = Time.now.strftime("%Y%m%d%H%M%S") + default_key_name = "#{hostname}-#{user}-#{ts}" + + key_name = ask "API Key name [#{default_key_name}]: " unless scope + if key_name.nil? || key_name.empty? + default_key_name + else + key_name + end + end + + def api_key_forbidden?(response) + response.kind_of?(Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access") + end end diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index 9f4bd12c46..31285ca962 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'rubygems' require 'rubygems/package' -require 'time' require 'tmpdir' ## diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index b46b53e76b..ef1ad1edcb 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -122,10 +122,10 @@ module Gem::InstallUpdateOptions options[:minimal_deps] = true end - add_option(:"Install/Update", "--minimal-deps", + add_option(:"Install/Update", "--[no-]minimal-deps", "Don't upgrade any dependencies that already", "meet version requirements") do |value, options| - options[:minimal_deps] = true + options[:minimal_deps] = value end add_option(:"Install/Update", "--[no-]post-install-message", diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 33171a8eb9..2c583743b9 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -12,7 +12,6 @@ require 'rubygems/deprecate' require 'rubygems/package' require 'rubygems/ext' require 'rubygems/user_interaction' -require 'fileutils' ## # The installer installs the files contained in the .gem into the Gem.home. @@ -433,7 +432,7 @@ class Gem::Installer # def default_spec_file - File.join Gem.default_specifications_dir, "#{spec.full_name}.gemspec" + File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" end ## @@ -492,7 +491,11 @@ class Gem::Installer mode = File.stat(bin_path).mode dir_mode = options[:prog_mode] || (mode | 0111) - FileUtils.chmod dir_mode, bin_path unless dir_mode == mode + + unless dir_mode == mode + require 'fileutils' + FileUtils.chmod dir_mode, bin_path + end check_executable_overwrite filename @@ -527,6 +530,7 @@ class Gem::Installer def generate_bin_script(filename, bindir) bin_script_path = File.join bindir, formatted_program_filename(filename) + require 'fileutils' FileUtils.rm_f bin_script_path # prior install may have been --no-wrappers File.open bin_script_path, 'wb', 0755 do |file| @@ -670,7 +674,7 @@ class Gem::Installer :env_shebang => false, :force => false, :only_install_dir => false, - :post_install_message => true + :post_install_message => true, }.merge options @env_shebang = options[:env_shebang] @@ -693,11 +697,10 @@ class Gem::Installer @build_args = options[:build_args] || Gem::Command.build_args unless @build_root.nil? - require 'pathname' - @build_root = Pathname.new(@build_root).expand_path - @bin_dir = File.join(@build_root, options[:bin_dir] || Gem.bindir(@gem_home)) - @gem_home = File.join(@build_root, @gem_home) - alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}" + @bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, '')) + @gem_home = File.join(@build_root, @gem_home.gsub(/^[a-zA-Z]:/, '')) + @plugins_dir = File.join(@build_root, @plugins_dir.gsub(/^[a-zA-Z]:/, '')) + alert_warning "You build with buildroot.\n Build root: #{@build_root}\n Bin dir: #{@bin_dir}\n Gem home: #{@gem_home}\n Plugins dir: #{@plugins_dir}" end end diff --git a/lib/rubygems/installer_test_case.rb b/lib/rubygems/installer_test_case.rb index d78b6a4712..416dac7be6 100644 --- a/lib/rubygems/installer_test_case.rb +++ b/lib/rubygems/installer_test_case.rb @@ -108,9 +108,9 @@ class Gem::InstallerTestCase < Gem::TestCase # # And returns a Gem::Installer for the @spec that installs into @gemhome - def setup_base_installer + def setup_base_installer(force = true) @gem = setup_base_gem - util_installer @spec, @gemhome + util_installer @spec, @gemhome, false, force end ## @@ -182,7 +182,7 @@ class Gem::InstallerTestCase < Gem::TestCase # lib/code.rb # ext/a/mkrf_conf.rb - def util_setup_gem(ui = @ui) + def util_setup_gem(ui = @ui, force = true) @spec.files << File.join('lib', 'code.rb') @spec.extensions << File.join('ext', 'a', 'mkrf_conf.rb') @@ -214,17 +214,18 @@ class Gem::InstallerTestCase < Gem::TestCase end end - Gem::Installer.at @gem + Gem::Installer.at @gem, :force => force end ## # Creates an installer for +spec+ that will install into +gem_home+. If # +user+ is true a user-install will be performed. - def util_installer(spec, gem_home, user=false) + def util_installer(spec, gem_home, user=false, force=true) Gem::Installer.at(spec.cache_file, :install_dir => gem_home, - :user_install => user) + :user_install => user, + :force => force) end @@symlink_supported = nil diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb index cb5604e8dd..3d0afa3094 100644 --- a/lib/rubygems/name_tuple.rb +++ b/lib/rubygems/name_tuple.rb @@ -59,7 +59,7 @@ class Gem::NameTuple # Indicate if this NameTuple matches the current platform. def match_platform? - Gem::Platform.match @platform + Gem::Platform.match_gem? @platform, @name end ## diff --git a/lib/rubygems/openssl.rb b/lib/rubygems/openssl.rb index 39ef91e888..c44f619c4c 100644 --- a/lib/rubygems/openssl.rb +++ b/lib/rubygems/openssl.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -begin - require "openssl" -rescue LoadError => e - raise unless e.path == 'openssl' +autoload :OpenSSL, "openssl" + +module Gem + HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 9b53cd4a7b..0587cd212b 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -44,7 +44,6 @@ require "rubygems" require 'rubygems/security' require 'rubygems/user_interaction' -require 'zlib' class Gem::Package include Gem::UserInteraction @@ -186,6 +185,8 @@ class Gem::Package # Creates a new package that will read or write to the file +gem+. def initialize(gem, security_policy) # :notnew: + require 'zlib' + @gem = gem @build_time = Gem.source_date_epoch @@ -297,7 +298,7 @@ class Gem::Package setup_signer( signer_options: { - expiration_length_days: Gem.configuration.cert_expiration_length_days + expiration_length_days: Gem.configuration.cert_expiration_length_days, } ) diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index f19aea549d..ce9b49e3eb 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -229,7 +229,7 @@ class Gem::Package::TarHeader gname, oct(devmajor, 7), oct(devminor, 7), - prefix + prefix, ] header = header.pack PACK_FORMAT diff --git a/lib/rubygems/package/tar_test_case.rb b/lib/rubygems/package/tar_test_case.rb index 5fc34d2e8c..1161d0a5a8 100644 --- a/lib/rubygems/package/tar_test_case.rb +++ b/lib/rubygems/package/tar_test_case.rb @@ -90,7 +90,7 @@ class Gem::Package::TarTestCase < Gem::TestCase ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null - ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled) + ASCIIZ(dname, 155), # char prefix[155]; ASCII + (Z unless filled) ] h = arr.join diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 34306fcf83..a500fd24c8 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -9,11 +9,7 @@ require "rubygems/deprecate" class Gem::Platform @local = nil - attr_accessor :cpu - - attr_accessor :os - - attr_accessor :version + attr_accessor :cpu, :os, :version def self.local arch = RbConfig::CONFIG['arch'] @@ -22,18 +18,33 @@ class Gem::Platform end def self.match(platform) - Gem.platforms.any? do |local_platform| + match_platforms?(platform, Gem.platforms) + end + + def self.match_platforms?(platform, platforms) + platforms.any? do |local_platform| platform.nil? or local_platform == platform or (local_platform != Gem::Platform::RUBY and local_platform =~ platform) end end + private_class_method :match_platforms? + + def self.match_spec?(spec) + match_gem?(spec.platform, spec.name) + end + + def self.match_gem?(platform, gem_name) + # Note: this method might be redefined by Ruby implementations to + # customize behavior per RUBY_ENGINE, gem_name or other criteria. + match_platforms?(platform, Gem.platforms) + end def self.installable?(spec) if spec.respond_to? :installable_platform? spec.installable_platform? else - match spec.platform + match_spec? spec end end diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb index 4e9b6efee6..ea0f260ab4 100644 --- a/lib/rubygems/query_utils.rb +++ b/lib/rubygems/query_utils.rb @@ -57,15 +57,6 @@ module Gem::QueryUtils "--local --name-matches // --no-details --versions --no-installed" end - def description # :nodoc: - <<-EOF -The query command is the basis for the list and search commands. - -You should really use the list and search commands instead. This command -is too hard to use. - EOF - end - def execute gem_names = Array(options[:name]) diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 40ac0e95c0..8ebe6acc70 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -78,7 +78,6 @@ class Gem::RemoteFetcher def initialize(proxy=nil, dns=nil, headers={}) require 'net/http' require 'stringio' - require 'time' require 'uri' Socket.do_not_reverse_lookup = true @@ -263,7 +262,7 @@ class Gem::RemoteFetcher rescue Timeout::Error raise UnknownHostError.new('timed out', uri) rescue IOError, SocketError, SystemCallError, - *(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e + *(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e if e.message =~ /getaddrinfo/ raise UnknownHostError.new('no such name', uri) else diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 75f9e9979a..1ed0fbcb99 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require 'net/http' -require 'time' require 'rubygems/user_interaction' class Gem::Request @@ -45,7 +44,8 @@ class Gem::Request end def self.configure_connection_for_https(connection, cert_files) - require 'openssl' + raise Gem::Exception.new('OpenSSl is not available. Install OpenSSL and rebuild Ruby (preferred) or use non-HTTPS sources') unless Gem::HAVE_OPENSSL + connection.use_ssl = true connection.verify_mode = Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER @@ -125,7 +125,7 @@ class Gem::Request def connection_for(uri) @connection_pool.checkout - rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN, + rescue Gem::HAVE_OPENSSL ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN, Errno::EHOSTDOWN => e raise Gem::RemoteFetcher::FetchError.new(e.message, uri) end @@ -143,6 +143,7 @@ class Gem::Request request.add_field 'Keep-Alive', '30' if @last_modified + require 'time' request.add_field 'If-Modified-Since', @last_modified.httpdate end diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 9fbe3a1e44..7188b07346 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -88,7 +88,7 @@ class Gem::RequestSet::GemDependencyAPI :truffleruby => Gem::Platform::RUBY, :x64_mingw => x64_mingw, :x64_mingw_20 => x64_mingw, - :x64_mingw_21 => x64_mingw + :x64_mingw_21 => x64_mingw, }.freeze gt_eq_0 = Gem::Requirement.new '>= 0' @@ -379,7 +379,7 @@ class Gem::RequestSet::GemDependencyAPI Gem::Requirement.create requirements end - return unless gem_platforms options + return unless gem_platforms name, options groups = gem_group name, options @@ -532,7 +532,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta # Handles the platforms: option from +options+. Returns true if the # platform matches the current platform. - def gem_platforms(options) # :nodoc: + def gem_platforms(name, options) # :nodoc: platform_names = Array(options.delete :platform) platform_names.concat Array(options.delete :platforms) platform_names.concat @current_platforms if @current_platforms @@ -543,7 +543,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta raise ArgumentError, "unknown platform #{platform_name.inspect}" unless platform = PLATFORM_MAP[platform_name] - next false unless Gem::Platform.match platform + next false unless Gem::Platform.match_gem? platform, name if engines = ENGINE_MAP[platform_name] next false unless engines.include? Gem.ruby_engine diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index a2a5c7bca1..430060e2ff 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -16,7 +16,7 @@ class Gem::Requirement "<" => lambda {|v, r| v < r }, ">=" => lambda {|v, r| v >= r }, "<=" => lambda {|v, r| v <= r }, - "~>" => lambda {|v, r| v >= r && v.release < r.bump } + "~>" => lambda {|v, r| v >= r && v.release < r.bump }, }.freeze SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc: diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index fa5f5e6bb2..e1c0d2dd0a 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -281,7 +281,7 @@ class Gem::Resolver amount_constrained(dependency), conflicts[name] ? 0 : 1, activated.vertex_named(name).payload ? 0 : search_for(dependency).count, - i # for stable sort + i, # for stable sort ] end end diff --git a/lib/rubygems/resolver/activation_request.rb b/lib/rubygems/resolver/activation_request.rb index 293df1efe9..ae35681db9 100644 --- a/lib/rubygems/resolver/activation_request.rb +++ b/lib/rubygems/resolver/activation_request.rb @@ -28,12 +28,20 @@ class Gem::Resolver::ActivationRequest when Gem::Specification @spec == other when Gem::Resolver::ActivationRequest - @spec == other.spec && @request == other.request + @spec == other.spec else false end end + def eql?(other) + self == other + end + + def hash + @spec.hash + end + ## # Is this activation request for a development dependency? diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index 232c2b041b..589ea1ba61 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -46,6 +46,10 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification @dependencies == other.dependencies end + def hash + @set.hash ^ @name.hash ^ @version.hash ^ @platform.hash ^ @dependencies.hash + end + def fetch_development_dependencies # :nodoc: spec = source.fetch_spec Gem::NameTuple.new @name, @version, @platform @@ -53,7 +57,7 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification end def installable_platform? # :nodoc: - Gem::Platform.match @platform + Gem::Platform.match_gem? @platform, @name end def pretty_print(q) # :nodoc: diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb index 2ce63feef2..4c4588d7e8 100644 --- a/lib/rubygems/resolver/conflict.rb +++ b/lib/rubygems/resolver/conflict.rb @@ -85,7 +85,7 @@ class Gem::Resolver::Conflict activated, requirement, request_path(@activated).reverse.join(", depends on\n "), request_path(@failed_dep).reverse.join(", depends on\n "), - matching, + matching ] end diff --git a/lib/rubygems/resolver/dependency_request.rb b/lib/rubygems/resolver/dependency_request.rb index 77539c340f..356aadb3b2 100644 --- a/lib/rubygems/resolver/dependency_request.rb +++ b/lib/rubygems/resolver/dependency_request.rb @@ -28,7 +28,7 @@ class Gem::Resolver::DependencyRequest when Gem::Dependency @dependency == other when Gem::Resolver::DependencyRequest - @dependency == other.dependency && @requester == other.requester + @dependency == other.dependency else false end diff --git a/lib/rubygems/resolver/index_specification.rb b/lib/rubygems/resolver/index_specification.rb index d80f121189..3b75b719b0 100644 --- a/lib/rubygems/resolver/index_specification.rb +++ b/lib/rubygems/resolver/index_specification.rb @@ -33,6 +33,17 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification spec.dependencies end + def ==(other) + self.class === other && + @name == other.name && + @version == other.version && + @platform == other.platform + end + + def hash + @name.hash ^ @version.hash ^ @platform.hash + end + def inspect # :nodoc: '#<%s %s source %s>' % [self.class, full_name, @source] end diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb index eaa7e207b2..b0e4ce5a37 100644 --- a/lib/rubygems/resolver/installer_set.rb +++ b/lib/rubygems/resolver/installer_set.rb @@ -32,7 +32,6 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set super() @domain = domain - @remote = consider_remote? @f = Gem::SpecFetcher.fetcher @@ -170,7 +169,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set always_install = @always_install.map {|s| s.full_name } '#<%s domain: %s specs: %p always install: %p>' % [ - self.class, @domain, @specs.keys, always_install, + self.class, @domain, @specs.keys, always_install ] end diff --git a/lib/rubygems/resolver/lock_set.rb b/lib/rubygems/resolver/lock_set.rb index 1ab03e753b..eabf217aba 100644 --- a/lib/rubygems/resolver/lock_set.rb +++ b/lib/rubygems/resolver/lock_set.rb @@ -28,7 +28,7 @@ class Gem::Resolver::LockSet < Gem::Resolver::Set def add(name, version, platform) # :nodoc: version = Gem::Version.new version specs = [ - Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform) + Gem::Resolver::LockSpecification.new(self, name, version, @sources, platform), ] @specs.concat specs diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo.rb b/lib/rubygems/resolver/molinillo/lib/molinillo.rb index 0ae4b6a912..f67badbde7 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/gem_metadata' -require 'rubygems/resolver/molinillo/lib/molinillo/errors' -require 'rubygems/resolver/molinillo/lib/molinillo/resolver' -require 'rubygems/resolver/molinillo/lib/molinillo/modules/ui' -require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' + +require_relative 'molinillo/gem_metadata' +require_relative 'molinillo/errors' +require_relative 'molinillo/resolver' +require_relative 'molinillo/modules/ui' +require_relative 'molinillo/modules/specification_provider' # Gem::Resolver::Molinillo is a generic dependency resolution algorithm. module Gem::Resolver::Molinillo diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb index 1bbc72c1f6..d540d3baff 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # @!visibility private module Delegates @@ -45,6 +46,12 @@ module Gem::Resolver::Molinillo current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty current_state.conflicts end + + # (see Gem::Resolver::Molinillo::ResolutionState#unused_unwind_options) + def unused_unwind_options + current_state = state || Gem::Resolver::Molinillo::ResolutionState.empty + current_state.unused_unwind_options + end end end end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb index 71903c7e86..2ddb0ac426 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo module Delegates # Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb index b413e3ab6a..773bb3417f 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true + require 'set' require 'tsort' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex' +require_relative 'dependency_graph/log' +require_relative 'dependency_graph/vertex' module Gem::Resolver::Molinillo # A directed acyclic graph that is tuned to hold named dependencies @@ -123,6 +124,7 @@ module Gem::Resolver::Molinillo dot.join("\n") end + # @param [DependencyGraph] other # @return [Boolean] whether the two dependency graphs are equal, determined # by a recursive traversal of each {#root_vertices} and its # {Vertex#successors} @@ -147,8 +149,8 @@ module Gem::Resolver::Molinillo vertex = add_vertex(name, payload, root) vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| - parent_node = vertex_named(parent_name) - add_edge(parent_node, vertex, requirement) + parent_vertex = vertex_named(parent_name) + add_edge(parent_vertex, vertex, requirement) end vertex end @@ -189,7 +191,7 @@ module Gem::Resolver::Molinillo # @return [Edge] the added edge def add_edge(origin, destination, requirement) if destination.path_to?(origin) - raise CircularDependencyError.new([origin, destination]) + raise CircularDependencyError.new(path(destination, origin)) end add_edge_no_circular(origin, destination, requirement) end @@ -218,5 +220,37 @@ module Gem::Resolver::Molinillo def add_edge_no_circular(origin, destination, requirement) log.add_edge_no_circular(self, origin.name, destination.name, requirement) end + + # Returns the path between two vertices + # @raise [ArgumentError] if there is no path between the vertices + # @param [Vertex] from + # @param [Vertex] to + # @return [Array] the shortest path from `from` to `to` + def path(from, to) + distances = Hash.new(vertices.size + 1) + distances[from.name] = 0 + predecessors = {} + each do |vertex| + vertex.successors.each do |successor| + if distances[successor.name] > distances[vertex.name] + 1 + distances[successor.name] = distances[vertex.name] + 1 + predecessors[successor] = vertex + end + end + end + + path = [to] + while before = predecessors[to] + path << before + to = before + break if to == from + end + + unless path.last.equal?(from) + raise ArgumentError, "There is no path from #{from.name} to #{to.name}" + end + + path.reverse + end end end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb index eeedabb069..cc140031b3 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo class DependencyGraph # An action that modifies a {DependencyGraph} that is reversible. diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb index e994e59d05..5570483253 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb index 6cde933080..f1411d5efa 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb index d44aaf1f06..3b48d77a50 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb index fa03e2d365..92f60d5be8 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb index 5cdd84b5c1..7aeb8847ec 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload' -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag' + +require_relative 'add_edge_no_circular' +require_relative 'add_vertex' +require_relative 'delete_edge' +require_relative 'detach_vertex_named' +require_relative 'set_payload' +require_relative 'tag' module Gem::Resolver::Molinillo class DependencyGraph diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb index 02cfba64a7..726292a2c3 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb index 0cb08075ca..bfe6fd31f8 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action' + +require_relative 'action' module Gem::Resolver::Molinillo class DependencyGraph # @!visibility private @@ -13,11 +14,11 @@ module Gem::Resolver::Molinillo end # (see Action#up) - def up(_graph) + def up(graph) end # (see Action#down) - def down(_graph) + def down(graph) end # @!group Tag diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb index cebd9cafdd..88b6580a52 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo class DependencyGraph # A vertex in a {DependencyGraph} that encapsulates a {#name} and a @@ -32,7 +33,7 @@ module Gem::Resolver::Molinillo # @return [Array] all of the requirements that required # this vertex def requirements - incoming_edges.map(&:requirement) + explicit_requirements + (incoming_edges.map(&:requirement) + explicit_requirements).uniq end # @return [Array] the edges of {#graph} that have `self` as their @@ -49,14 +50,25 @@ module Gem::Resolver::Molinillo incoming_edges.map(&:origin) end - # @return [Array] the vertices of {#graph} where `self` is a + # @return [Set] the vertices of {#graph} where `self` is a # {#descendent?} def recursive_predecessors - vertices = predecessors - vertices += vertices.map(&:recursive_predecessors).flatten(1) - vertices.uniq! + _recursive_predecessors + end + + # @param [Set] vertices the set to add the predecessors to + # @return [Set] the vertices of {#graph} where `self` is a + # {#descendent?} + def _recursive_predecessors(vertices = Set.new) + incoming_edges.each do |edge| + vertex = edge.origin + next unless vertices.add?(vertex) + vertex._recursive_predecessors(vertices) + end + vertices end + protected :_recursive_predecessors # @return [Array] the vertices of {#graph} that have an edge with # `self` as their {Edge#origin} @@ -64,14 +76,25 @@ module Gem::Resolver::Molinillo outgoing_edges.map(&:destination) end - # @return [Array] the vertices of {#graph} where `self` is an + # @return [Set] the vertices of {#graph} where `self` is an # {#ancestor?} def recursive_successors - vertices = successors - vertices += vertices.map(&:recursive_successors).flatten(1) - vertices.uniq! + _recursive_successors + end + + # @param [Set] vertices the set to add the successors to + # @return [Set] the vertices of {#graph} where `self` is an + # {#ancestor?} + def _recursive_successors(vertices = Set.new) + outgoing_edges.each do |edge| + vertex = edge.destination + next unless vertices.add?(vertex) + vertex._recursive_successors(vertices) + end + vertices end + protected :_recursive_successors # @return [String] a string suitable for debugging def inspect @@ -107,11 +130,21 @@ module Gem::Resolver::Molinillo # dependency graph? # @return true iff there is a path following edges within this {#graph} def path_to?(other) - equal?(other) || successors.any? { |v| v.path_to?(other) } + _path_to?(other) end alias descendent? path_to? + # @param [Vertex] other the vertex to check if there's a path to + # @param [Set] visited the vertices of {#graph} that have been visited + # @return [Boolean] whether there is a path to `other` from `self` + def _path_to?(other, visited = Set.new) + return false unless visited.add?(self) + return true if equal?(other) + successors.any? { |v| v._path_to?(other, visited) } + end + protected :_path_to? + # Is there a path from `other` to `self` following edges in the # dependency graph? # @return true iff there is a path following edges within this {#graph} diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb index 129246bf4a..2ec6b068ac 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # An error that occurred during the resolution process class ResolverError < StandardError; end @@ -17,7 +18,7 @@ module Gem::Resolver::Molinillo # @param [Array] required_by @see {#required_by} def initialize(dependency, required_by = []) @dependency = dependency - @required_by = required_by + @required_by = required_by.uniq super() end @@ -41,11 +42,11 @@ module Gem::Resolver::Molinillo attr_reader :dependencies # Initializes a new error with the given circular vertices. - # @param [Array] nodes the nodes in the dependency + # @param [Array] vertices the vertices in the dependency # that caused the error - def initialize(nodes) - super "There is a circular dependency between #{nodes.map(&:name).join(' and ')}" - @dependencies = nodes.map(&:payload).to_set + def initialize(vertices) + super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" + @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set end end @@ -55,11 +56,16 @@ module Gem::Resolver::Molinillo # resolution to fail attr_reader :conflicts + # @return [SpecificationProvider] the specification provider used during + # resolution + attr_reader :specification_provider + # Initializes a new error with the given version conflicts. # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} - def initialize(conflicts) + # @param [SpecificationProvider] specification_provider see {#specification_provider} + def initialize(conflicts, specification_provider) pairs = [] - conflicts.values.flatten.map(&:requirements).flatten.each do |conflicting| + conflicts.values.flat_map(&:requirements).each do |conflicting| conflicting.each do |source, conflict_requirements| conflict_requirements.each do |c| pairs << [c, source] @@ -69,7 +75,69 @@ module Gem::Resolver::Molinillo super "Unable to satisfy the following requirements:\n\n" \ "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" + @conflicts = conflicts + @specification_provider = specification_provider + end + + require_relative 'delegates/specification_provider' + include Delegates::SpecificationProvider + + # @return [String] An error message that includes requirement trees, + # which is much more detailed & customizable than the default message + # @param [Hash] opts the options to create a message with. + # @option opts [String] :solver_name The user-facing name of the solver + # @option opts [String] :possibility_type The generic name of a possibility + # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees + # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements + # @option opts [Proc] :additional_message_for_conflict A proc that appends additional + # messages for each conflict + # @option opts [Proc] :version_for_spec A proc that returns the version number for a + # possibility + def message_with_trees(opts = {}) + solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } + possibility_type = opts.delete(:possibility_type) { 'possibility named' } + reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } + printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } + additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } + version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } + incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do + proc do |name, _conflict| + %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) + end + end + + conflicts.sort.reduce(''.dup) do |o, (name, conflict)| + o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n" + if conflict.locked_requirement + o << %( In snapshot (#{name_for_locking_dependency_source}):\n) + o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) + o << %(\n) + end + o << %( In #{name_for_explicit_dependency_source}:\n) + trees = reduce_trees.call(conflict.requirement_trees) + + o << trees.map do |tree| + t = ''.dup + depth = 2 + tree.each do |req| + t << ' ' * depth << req.to_s + unless tree.last == req + if spec = conflict.activated_by_name[name_for(req)] + t << %( was resolved to #{version_for_spec.call(spec)}, which) + end + t << %( depends on) + end + t << %(\n) + depth += 1 + end + t + end.join("\n") + + additional_message_for_conflict.call(o, name, conflict) + + o + end.strip end end end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb index c5b5bd729f..6b5ada7ade 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # The version of Gem::Resolver::Molinillo. - VERSION = '0.5.7'.freeze + VERSION = '0.7.0'.freeze end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb index 916345b12a..a44b9c0d5d 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # Provides information about specifcations and dependencies to the resolver, # allowing the {Resolver} class to remain generic while still providing power diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb index dbc4e000e4..a810fd519c 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # Conveys information about the resolution process to a user. module UI @@ -48,7 +49,8 @@ module Gem::Resolver::Molinillo if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) - output.puts debug_info.split("\n").map { |s| ' ' * depth + s } + debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } + output.puts debug_info end end diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb index 73a4242157..f1c60ec544 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo class Resolver # A specific resolution from a given {Resolver} @@ -8,22 +9,125 @@ module Gem::Resolver::Molinillo # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict # @attr [Object, nil] existing the existing spec that was in conflict with # the {#possibility} - # @attr [Object] possibility the spec that was unable to be activated due - # to a conflict + # @attr [Object] possibility_set the set of specs that was unable to be + # activated due to a conflict. # @attr [Object] locked_requirement the relevant locking requirement. # @attr [Array>] requirement_trees the different requirement # trees that led to every requirement for the conflicting name. # @attr [{String=>Object}] activated_by_name the already-activated specs. + # @attr [Object] underlying_error an error that has occurred during resolution, and + # will be raised at the end of it if no resolution is found. Conflict = Struct.new( :requirement, :requirements, :existing, - :possibility, + :possibility_set, :locked_requirement, :requirement_trees, - :activated_by_name + :activated_by_name, + :underlying_error ) + class Conflict + # @return [Object] a spec that was unable to be activated due to a conflict + def possibility + possibility_set && possibility_set.latest_version + end + end + + # A collection of possibility states that share the same dependencies + # @attr [Array] dependencies the dependencies for this set of possibilities + # @attr [Array] possibilities the possibilities + PossibilitySet = Struct.new(:dependencies, :possibilities) + + class PossibilitySet + # String representation of the possibility set, for debugging + def to_s + "[#{possibilities.join(', ')}]" + end + + # @return [Object] most up-to-date dependency in the possibility set + def latest_version + possibilities.last + end + end + + # Details of the state to unwind to when a conflict occurs, and the cause of the unwind + # @attr [Integer] state_index the index of the state to unwind to + # @attr [Object] state_requirement the requirement of the state we're unwinding to + # @attr [Array] requirement_tree for the requirement we're relaxing + # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict + # @attr [Array] requirement_trees for the conflict + # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind + UnwindDetails = Struct.new( + :state_index, + :state_requirement, + :requirement_tree, + :conflicting_requirements, + :requirement_trees, + :requirements_unwound_to_instead + ) + + class UnwindDetails + include Comparable + + # We compare UnwindDetails when choosing which state to unwind to. If + # two options have the same state_index we prefer the one most + # removed from a requirement that caused the conflict. Both options + # would unwind to the same state, but a `grandparent` option will + # filter out fewer of its possibilities after doing so - where a state + # is both a `parent` and a `grandparent` to requirements that have + # caused a conflict this is the correct behaviour. + # @param [UnwindDetail] other UnwindDetail to be compared + # @return [Integer] integer specifying ordering + def <=>(other) + if state_index > other.state_index + 1 + elsif state_index == other.state_index + reversed_requirement_tree_index <=> other.reversed_requirement_tree_index + else + -1 + end + end + + # @return [Integer] index of state requirement in reversed requirement tree + # (the conflicting requirement itself will be at position 0) + def reversed_requirement_tree_index + @reversed_requirement_tree_index ||= + if state_requirement + requirement_tree.reverse.index(state_requirement) + else + 999_999 + end + end + + # @return [Boolean] where the requirement of the state we're unwinding + # to directly caused the conflict. Note: in this case, it is + # impossible for the state we're unwinding to to be a parent of + # any of the other conflicting requirements (or we would have + # circularity) + def unwinding_to_primary_requirement? + requirement_tree.last == state_requirement + end + + # @return [Array] array of sub-dependencies to avoid when choosing a + # new possibility for the state we've unwound to. Only relevant for + # non-primary unwinds + def sub_dependencies_to_avoid + @requirements_to_avoid ||= + requirement_trees.map do |tree| + index = tree.index(state_requirement) + tree[index + 1] if index + end.compact + end + + # @return [Array] array of all the requirements that led to the need for + # this unwind + def all_requirements + @all_requirements ||= requirement_trees.flatten(1) + end + end + # @return [SpecificationProvider] the provider that knows about # dependencies, requirements, specifications, versions, etc. attr_reader :specification_provider @@ -64,7 +168,7 @@ module Gem::Resolver::Molinillo start_resolution while state - break unless state.requirements.any? || state.requirement + break if !state.requirement && state.requirements.empty? indicate_progress if state.respond_to?(:pop_possibility_state) # DependencyState debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } @@ -78,7 +182,7 @@ module Gem::Resolver::Molinillo process_topmost_state end - activated.freeze + resolve_activated_specs ensure end_resolution end @@ -103,12 +207,25 @@ module Gem::Resolver::Molinillo def start_resolution @started_at = Time.now - handle_missing_or_push_dependency_state(initial_state) + push_initial_state debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end + def resolve_activated_specs + activated.vertices.each do |_, vertex| + next unless vertex.payload + + latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| + vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } + end + + activated.set_payload(vertex.name, latest_version) + end + activated.freeze + end + # Ends the resolution process # @return [void] def end_resolution @@ -121,11 +238,11 @@ module Gem::Resolver::Molinillo debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state end - require 'rubygems/resolver/molinillo/lib/molinillo/state' - require 'rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider' + require_relative 'state' + require_relative 'modules/specification_provider' - require 'rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state' - require 'rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider' + require_relative 'delegates/resolution_state' + require_relative 'delegates/specification_provider' include Gem::Resolver::Molinillo::Delegates::ResolutionState include Gem::Resolver::Molinillo::Delegates::SpecificationProvider @@ -136,9 +253,12 @@ module Gem::Resolver::Molinillo if possibility attempt_to_activate else - create_conflict if state.is_a? PossibilityState - unwind_for_conflict until possibility && state.is_a?(DependencyState) + create_conflict + unwind_for_conflict end + rescue CircularDependencyError => underlying_error + create_conflict(underlying_error) + unwind_for_conflict end # @return [Object] the current possibility that the resolution is trying @@ -153,63 +273,292 @@ module Gem::Resolver::Molinillo states.last end - # Creates the initial state for the resolution, based upon the + # Creates and pushes the initial state for the resolution, based upon the # {#requested} dependencies - # @return [DependencyState] the initial state for the resolution - def initial_state + # @return [void] + def push_initial_state graph = DependencyGraph.new.tap do |dg| - original_requested.each { |r| dg.add_vertex(name_for(r), nil, true).tap { |v| v.explicit_requirements << r } } + original_requested.each do |requested| + vertex = dg.add_vertex(name_for(requested), nil, true) + vertex.explicit_requirements << requested + end dg.tag(:initial_state) end - requirements = sort_dependencies(original_requested, graph, {}) - initial_requirement = requirements.shift - DependencyState.new( - initial_requirement && name_for(initial_requirement), - requirements, - graph, - initial_requirement, - initial_requirement && search_for(initial_requirement), - 0, - {} - ) + push_state_for_requirements(original_requested, true, graph) end # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict - debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" } + details_for_unwind = build_details_for_unwind + unwind_options = unused_unwind_options + debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } conflicts.tap do |c| - sliced_states = states.slice!((state_index_for_unwind + 1)..-1) - raise VersionConflict.new(c) unless state + sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) + raise_error_unless_state(c) activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c + state.unused_unwind_options = unwind_options + filter_possibilities_after_unwind(details_for_unwind) index = states.size - 1 @parents_of.each { |_, a| a.reject! { |i| i >= index } } + state.unused_unwind_options.reject! { |uw| uw.state_index >= index } end end - # @return [Integer] The index to which the resolution should unwind in the - # case of conflict. - def state_index_for_unwind - current_requirement = requirement - existing_requirement = requirement_for_existing_name(name) - index = -1 - [current_requirement, existing_requirement].each do |r| - until r.nil? - current_state = find_state_for(r) - if state_any?(current_state) - current_index = states.index(current_state) - index = current_index if current_index > index - break + # Raises a VersionConflict error, or any underlying error, if there is no + # current state + # @return [void] + def raise_error_unless_state(conflicts) + return if state + + error = conflicts.values.map(&:underlying_error).compact.first + raise error || VersionConflict.new(conflicts, specification_provider) + end + + # @return [UnwindDetails] Details of the nearest index to which we could unwind + def build_details_for_unwind + # Get the possible unwinds for the current conflict + current_conflict = conflicts[name] + binding_requirements = binding_requirements_for_conflict(current_conflict) + unwind_details = unwind_options_for_requirements(binding_requirements) + + last_detail_for_current_unwind = unwind_details.sort.last + current_detail = last_detail_for_current_unwind + + # Look for past conflicts that could be unwound to affect the + # requirement tree for the current conflict + relevant_unused_unwinds = unused_unwind_options.select do |alternative| + intersecting_requirements = + last_detail_for_current_unwind.all_requirements & + alternative.requirements_unwound_to_instead + next if intersecting_requirements.empty? + # Find the highest index unwind whilst looping through + current_detail = alternative if alternative > current_detail + alternative + end + + # Add the current unwind options to the `unused_unwind_options` array. + # The "used" option will be filtered out during `unwind_for_conflict`. + state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } + + # Update the requirements_unwound_to_instead on any relevant unused unwinds + relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement } + unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement } + + current_detail + end + + # @param [Array] binding_requirements array of requirements that combine to create a conflict + # @return [Array] array of UnwindDetails that have a chance + # of resolving the passed requirements + def unwind_options_for_requirements(binding_requirements) + unwind_details = [] + + trees = [] + binding_requirements.reverse_each do |r| + partial_tree = [r] + trees << partial_tree + unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) + + # If this requirement has alternative possibilities, check if any would + # satisfy the other requirements that created this conflict + requirement_state = find_state_for(r) + if conflict_fixing_possibilities?(requirement_state, binding_requirements) + unwind_details << UnwindDetails.new( + states.index(requirement_state), + r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Next, look at the parent of this requirement, and check if the requirement + # could have been avoided if an alternative PossibilitySet had been chosen + parent_r = parent_of(r) + next if parent_r.nil? + partial_tree.unshift(parent_r) + requirement_state = find_state_for(parent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + parent_r, + partial_tree, + binding_requirements, + trees, + [] + ) + end + + # Finally, look at the grandparent and up of this requirement, looking + # for any possibilities that wouldn't create their parent requirement + grandparent_r = parent_of(parent_r) + until grandparent_r.nil? + partial_tree.unshift(grandparent_r) + requirement_state = find_state_for(grandparent_r) + if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } + unwind_details << UnwindDetails.new( + states.index(requirement_state), + grandparent_r, + partial_tree, + binding_requirements, + trees, + [] + ) end - r = parent_of(r) + parent_r = grandparent_r + grandparent_r = parent_of(parent_r) end end - index + unwind_details end + # @param [DependencyState] state + # @param [Array] binding_requirements array of requirements + # @return [Boolean] whether or not the given state has any possibilities + # that could satisfy the given requirements + def conflict_fixing_possibilities?(state, binding_requirements) + return false unless state + + state.possibilities.any? do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, binding_requirements) + end + end + end + + # Filter's a state's possibilities to remove any that would not fix the + # conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just + # unwound from + # @return [void] + def filter_possibilities_after_unwind(unwind_details) + return unless state && !state.possibilities.empty? + + if unwind_details.unwinding_to_primary_requirement? + filter_possibilities_for_primary_unwind(unwind_details) + else + filter_possibilities_for_parent_unwind(unwind_details) + end + end + + # Filter's a state's possibilities to remove any that would not satisfy + # the requirements in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_primary_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) + + state.possibilities.reject! do |possibility_set| + possibility_set.possibilities.none? do |poss| + unwind_requirement_sets.any? do |requirements| + possibility_satisfies_requirements?(poss, requirements) + end + end + end + end + + # @param [Object] possibility a single possibility + # @param [Array] requirements an array of requirements + # @return [Boolean] whether the possibility satisfies all of the + # given requirements + def possibility_satisfies_requirements?(possibility, requirements) + name = name_for(possibility) + + activated.tag(:swap) + activated.set_payload(name, possibility) if activated.vertex_named(name) + satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } + activated.rewind_to(:swap) + + satisfied + end + + # Filter's a state's possibilities to remove any that would (eventually) + # create a requirement in the conflict we've just rewound from + # @param [UnwindDetails] unwind_details details of the conflict just unwound from + # @return [void] + def filter_possibilities_for_parent_unwind(unwind_details) + unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } + unwinds_to_state << unwind_details + + primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq + parent_unwinds = unwinds_to_state.uniq - primary_unwinds + + allowed_possibility_sets = primary_unwinds.flat_map do |unwind| + states[unwind.state_index].possibilities.select do |possibility_set| + possibility_set.possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) + end + end + end + + requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) + + state.possibilities.reject! do |possibility_set| + !allowed_possibility_sets.include?(possibility_set) && + (requirements_to_avoid - possibility_set.dependencies).empty? + end + end + + # @param [Conflict] conflict + # @return [Array] minimal array of requirements that would cause the passed + # conflict to occur. + def binding_requirements_for_conflict(conflict) + return [conflict.requirement] if conflict.possibility.nil? + + possible_binding_requirements = conflict.requirements.values.flatten(1).uniq + + # When there's a `CircularDependency` error the conflicting requirement + # (the one causing the circular) won't be `conflict.requirement` + # (which won't be for the right state, because we won't have created it, + # because it's circular). + # We need to make sure we have that requirement in the conflict's list, + # otherwise we won't be able to unwind properly, so we just return all + # the requirements for the conflict. + return possible_binding_requirements if conflict.underlying_error + + possibilities = search_for(conflict.requirement) + + # If all the requirements together don't filter out all possibilities, + # then the only two requirements we need to consider are the initial one + # (where the dependency's version was first chosen) and the last + if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) + return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact + end + + # Loop through the possible binding requirements, removing each one + # that doesn't bind. Use a `reverse_each` as we want the earliest set of + # binding requirements, and don't use `reject!` as we wish to refine the + # array *on each iteration*. + binding_requirements = possible_binding_requirements.dup + possible_binding_requirements.reverse_each do |req| + next if req == conflict.requirement + unless binding_requirement_in_set?(req, binding_requirements, possibilities) + binding_requirements -= [req] + end + end + + binding_requirements + end + + # @param [Object] requirement we wish to check + # @param [Array] possible_binding_requirements array of requirements + # @param [Array] possibilities array of possibilities the requirements will be used to filter + # @return [Boolean] whether or not the given requirement is required to filter + # out all elements of the array of possibilities. + def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) + possibilities.any? do |poss| + possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) + end + end + + # @param [Object] requirement # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) @@ -219,29 +568,27 @@ module Gem::Resolver::Molinillo parent_state.requirement end + # @param [String] name # @return [Object] the requirement that led to a version of a possibility # with the given name being activated. def requirement_for_existing_name(name) - return nil unless activated.vertex_named(name).payload + return nil unless vertex = activated.vertex_named(name) + return nil unless vertex.payload states.find { |s| s.name == name }.requirement end + # @param [Object] requirement # @return [ResolutionState] the state whose `requirement` is the given # `requirement`. def find_state_for(requirement) return nil unless requirement - states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) } - end - - # @return [Boolean] whether or not the given state has any possibilities - # left. - def state_any?(state) - state && state.possibilities.any? + states.find { |i| requirement == i.requirement } end + # @param [Object] underlying_error # @return [Conflict] a {Conflict} that reflects the failure to activate # the {#possibility} in conjunction with the current {#state} - def create_conflict + def create_conflict(underlying_error = nil) vertex = activated.vertex_named(name) locked_requirement = locked_requirement_named(name) @@ -250,18 +597,21 @@ module Gem::Resolver::Molinillo requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements end requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement - vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) } + vertex.incoming_edges.each do |edge| + (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) + end activated_by_name = {} - activated.each { |v| activated_by_name[v.name] = v.payload if v.payload } + activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } conflicts[name] = Conflict.new( requirement, requirements, - vertex.payload, + vertex.payload && vertex.payload.latest_version, possibility, locked_requirement, requirement_trees, - activated_by_name + activated_by_name, + underlying_error ) end @@ -272,6 +622,7 @@ module Gem::Resolver::Molinillo vertex.requirements.map { |r| requirement_tree_for(r) } end + # @param [Object] requirement # @return [Array] the list of requirements that led to # `requirement` being required. def requirement_tree_for(requirement) @@ -311,116 +662,47 @@ module Gem::Resolver::Molinillo # @return [void] def attempt_to_activate debug(depth) { 'Attempting to activate ' + possibility.to_s } - existing_node = activated.vertex_named(name) - if existing_node.payload - debug(depth) { "Found existing spec (#{existing_node.payload})" } - attempt_to_activate_existing_spec(existing_node) + existing_vertex = activated.vertex_named(name) + if existing_vertex.payload + debug(depth) { "Found existing spec (#{existing_vertex.payload})" } + attempt_to_filter_existing_spec(existing_vertex) else - attempt_to_activate_new_spec + latest = possibility.latest_version + possibility.possibilities.select! do |possibility| + requirement_satisfied_by?(requirement, activated, possibility) + end + if possibility.latest_version.nil? + # ensure there's a possibility for better error messages + possibility.possibilities << latest if latest + create_conflict + unwind_for_conflict + else + activate_new_spec + end end end - # Attempts to activate the current {#possibility} (given that it has - # already been activated) + # Attempts to update the existing vertex's `PossibilitySet` with a filtered version # @return [void] - def attempt_to_activate_existing_spec(existing_node) - existing_spec = existing_node.payload - if requirement_satisfied_by?(requirement, activated, existing_spec) + def attempt_to_filter_existing_spec(vertex) + filtered_set = filtered_possibility_set(vertex) + if !filtered_set.possibilities.empty? + activated.set_payload(name, filtered_set) new_requirements = requirements.dup push_state_for_requirements(new_requirements, false) else - return if attempt_to_swap_possibility create_conflict - debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" } + debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } unwind_for_conflict end end - # Attempts to swp the current {#possibility} with the already-activated - # spec with the given name - # @return [Boolean] Whether the possibility was swapped into {#activated} - def attempt_to_swap_possibility - activated.tag(:swap) - vertex = activated.vertex_named(name) - activated.set_payload(name, possibility) - if !vertex.requirements. - all? { |r| requirement_satisfied_by?(r, activated, possibility) } || - !new_spec_satisfied? - activated.rewind_to(:swap) - return - end - fixup_swapped_children(vertex) - activate_spec - end - - # Ensures there are no orphaned successors to the given {vertex}. - # @param [DependencyGraph::Vertex] vertex the vertex to fix up. - # @return [void] - def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity - payload = vertex.payload - deps = dependencies_for(payload).group_by(&method(:name_for)) - vertex.outgoing_edges.each do |outgoing_edge| - requirement = outgoing_edge.requirement - parent_index = @parents_of[requirement].last - succ = outgoing_edge.destination - matching_deps = Array(deps[succ.name]) - dep_matched = matching_deps.include?(requirement) - - # only push the current index when it was originally required by the - # same named spec - if parent_index && states[parent_index].name == name - @parents_of[requirement].push(states.size - 1) - end - - if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex] - debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" } - succ.requirements.each { |r| @parents_of.delete(r) } - - removed_names = activated.detach_vertex_named(succ.name).map(&:name) - requirements.delete_if do |r| - # the only removed vertices are those with no other requirements, - # so it's safe to delete only based upon name here - removed_names.include?(name_for(r)) - end - elsif !dep_matched - debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" } - # also reset if we're removing the edge, but only if its parent has - # already been fixed up - @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty? - - activated.delete_edge(outgoing_edge) - requirements.delete(requirement) - end - end - end - - # Attempts to activate the current {#possibility} (given that it hasn't - # already been activated) - # @return [void] - def attempt_to_activate_new_spec - if new_spec_satisfied? - activate_spec - else - create_conflict - unwind_for_conflict - end - end - - # @return [Boolean] whether the current spec is satisfied as a new - # possibility. - def new_spec_satisfied? - unless requirement_satisfied_by?(requirement, activated, possibility) - debug(depth) { 'Unsatisfied by requested spec' } - return false - end - - locked_requirement = locked_requirement_named(name) - - locked_spec_satisfied = !locked_requirement || - requirement_satisfied_by?(locked_requirement, activated, possibility) - debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied - - locked_spec_satisfied + # Generates a filtered version of the existing vertex's `PossibilitySet` using the + # current state's `requirement` + # @param [Object] vertex existing vertex + # @return [PossibilitySet] filtered possibility set + def filtered_possibility_set(vertex) + PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) end # @param [String] requirement_name the spec name to search for @@ -434,7 +716,7 @@ module Gem::Resolver::Molinillo # Add the current {#possibility} to the dependency graph of the current # {#state} # @return [void] - def activate_spec + def activate_new_spec conflicts.delete(name) debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) @@ -442,14 +724,14 @@ module Gem::Resolver::Molinillo end # Requires the dependencies that the recently activated spec has - # @param [Object] activated_spec the specification that has just been + # @param [Object] possibility_set the PossibilitySet that has just been # activated # @return [void] - def require_nested_dependencies_for(activated_spec) - nested_dependencies = dependencies_for(activated_spec) + def require_nested_dependencies_for(possibility_set) + nested_dependencies = dependencies_for(possibility_set.latest_version) debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| - activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d) + activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) parent_index = states.size - 1 parents = @parents_of[d] parents << parent_index if parents.empty? @@ -461,23 +743,82 @@ module Gem::Resolver::Molinillo # Pushes a new {DependencyState} that encapsulates both existing and new # requirements # @param [Array] new_requirements + # @param [Boolean] requires_sort + # @param [Object] new_activated # @return [void] def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort - new_requirement = new_requirements.shift + new_requirement = nil + loop do + new_requirement = new_requirements.shift + break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } + end new_name = new_requirement ? name_for(new_requirement) : ''.freeze - possibilities = new_requirement ? search_for(new_requirement) : [] + possibilities = possibilities_for_requirement(new_requirement) handle_missing_or_push_dependency_state DependencyState.new( new_name, new_requirements, new_activated, - new_requirement, possibilities, depth, conflicts.dup + new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup ) end + # Checks a proposed requirement with any existing locked requirement + # before generating an array of possibilities for it. + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibilities + def possibilities_for_requirement(requirement, activated = self.activated) + return [] unless requirement + if locked_requirement_named(name_for(requirement)) + return locked_requirement_possibility_set(requirement, activated) + end + + group_possibilities(search_for(requirement)) + end + + # @param [Object] requirement the proposed requirement + # @param [Object] activated + # @return [Array] possibility set containing only the locked requirement, if any + def locked_requirement_possibility_set(requirement, activated = self.activated) + all_possibilities = search_for(requirement) + locked_requirement = locked_requirement_named(name_for(requirement)) + + # Longwinded way to build a possibilities array with either the locked + # requirement or nothing in it. Required, since the API for + # locked_requirement isn't guaranteed. + locked_possibilities = all_possibilities.select do |possibility| + requirement_satisfied_by?(locked_requirement, activated, possibility) + end + + group_possibilities(locked_possibilities) + end + + # Build an array of PossibilitySets, with each element representing a group of + # dependency versions that all have the same sub-dependency version constraints + # and are contiguous. + # @param [Array] possibilities an array of possibilities + # @return [Array] an array of possibility sets + def group_possibilities(possibilities) + possibility_sets = [] + current_possibility_set = nil + + possibilities.reverse_each do |possibility| + dependencies = dependencies_for(possibility) + if current_possibility_set && current_possibility_set.dependencies == dependencies + current_possibility_set.possibilities.unshift(possibility) + else + possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) + current_possibility_set = possibility_sets.first + end + end + + possibility_sets + end + # Pushes a new {DependencyState}. # If the {#specification_provider} says to # {SpecificationProvider#allow_missing?} that particular requirement, and # there are no possibilities for that requirement, then `state` is not - # pushed, and the node in {#activated} is removed, and we continue + # pushed, and the vertex in {#activated} is removed, and we continue # resolving the remaining requirements. # @param [DependencyState] state # @return [void] diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb index 5c59a45c3d..d43121f8ca 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph' + +require_relative 'dependency_graph' module Gem::Resolver::Molinillo # This class encapsulates a dependency resolver. @@ -8,7 +9,7 @@ module Gem::Resolver::Molinillo # # class Resolver - require 'rubygems/resolver/molinillo/lib/molinillo/resolution' + require_relative 'resolution' # @return [SpecificationProvider] the specification provider used # in the resolution process diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb index c20de98854..6e7c715fce 100644 --- a/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb +++ b/lib/rubygems/resolver/molinillo/lib/molinillo/state.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Gem::Resolver::Molinillo # A state that a {Resolution} can be in # @attr [String] name the name of the current requirement @@ -7,7 +8,8 @@ module Gem::Resolver::Molinillo # @attr [Object] requirement the current requirement # @attr [Object] possibilities the possibilities to satisfy the current requirement # @attr [Integer] depth the depth of the resolution - # @attr [Set] conflicts unresolved conflicts + # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name + # @attr [Array] unused_unwind_options unwinds for previous conflicts that weren't explored ResolutionState = Struct.new( :name, :requirements, @@ -15,14 +17,15 @@ module Gem::Resolver::Molinillo :requirement, :possibilities, :depth, - :conflicts + :conflicts, + :unused_unwind_options ) class ResolutionState # Returns an empty resolution state # @return [ResolutionState] an empty state def self.empty - new(nil, [], DependencyGraph.new, nil, nil, 0, Set.new) + new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) end end @@ -40,7 +43,8 @@ module Gem::Resolver::Molinillo requirement, [possibilities.pop], depth + 1, - conflicts.dup + conflicts.dup, + unused_unwind_options.dup ).tap do |state| state.activated.tag(state) end diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb index 7fe2afd3bd..5ae5f15813 100644 --- a/lib/rubygems/resolver/specification.rb +++ b/lib/rubygems/resolver/specification.rb @@ -104,7 +104,7 @@ class Gem::Resolver::Specification # Returns true if this specification is installable on this platform. def installable_platform? - Gem::Platform.match spec.platform + Gem::Platform.match_spec? spec end def local? # :nodoc: diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index c0b88842a0..f1f9229ca5 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -88,7 +88,7 @@ class Gem::S3URISigner "AWS4-HMAC-SHA256", date_time, credential_info, - Digest::SHA256.hexdigest(canonical_request) + Digest::SHA256.hexdigest(canonical_request), ].join("\n") end diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb index bd6d6ff8b9..c80639af6d 100644 --- a/lib/rubygems/security.rb +++ b/lib/rubygems/security.rb @@ -6,7 +6,6 @@ #++ require 'rubygems/exceptions' -require 'fileutils' require_relative 'openssl' ## @@ -592,7 +591,7 @@ module Gem::Security end -if defined?(OpenSSL::SSL) +if Gem::HAVE_OPENSSL require 'rubygems/security/policy' require 'rubygems/security/policies' require 'rubygems/security/trust_dir' diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 43abdb6d91..7629d64796 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -194,7 +194,7 @@ class Gem::Security::Policy ("[Policy: %s - data: %p signer: %p chain: %p root: %p " + "signed-only: %p trusted-only: %p]") % [ @name, @verify_chain, @verify_data, @verify_root, @verify_signer, - @only_signed, @only_trusted, + @only_signed, @only_trusted ] end diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 89200f9e38..6c85ab08d2 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -34,7 +34,7 @@ class Gem::Security::Signer attr_reader :options DEFAULT_OPTIONS = { - expiration_length_days: 365 + expiration_length_days: 365, }.freeze ## diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb index 70ae44609a..a9529f7923 100644 --- a/lib/rubygems/server.rb +++ b/lib/rubygems/server.rb @@ -771,7 +771,7 @@ div.method-source-code pre { color: #ffdead; overflow: hidden; } doc_items << { :name => base_name, :url => doc_root(new_path), - :summary => '' + :summary => '', } end diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index ef232ff35d..891cc3e644 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -79,7 +79,7 @@ class Gem::Source def dependency_resolver_set # :nodoc: return Gem::Resolver::IndexSet.new self if 'file' == uri.scheme - bundler_api_uri = uri + './api/v1/dependencies' + bundler_api_uri = enforce_trailing_slash(uri) + './api/v1/dependencies' begin fetcher = Gem::RemoteFetcher.fetcher @@ -130,7 +130,7 @@ class Gem::Source spec_file_name = name_tuple.spec_name - source_uri = uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}" + source_uri = enforce_trailing_slash(uri) + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}" cache_dir = cache_dir source_uri @@ -174,7 +174,7 @@ class Gem::Source file = FILES[type] fetcher = Gem::RemoteFetcher.fetcher file_name = "#{file}.#{Gem.marshal_version}" - spec_path = uri + "#{file_name}.gz" + spec_path = enforce_trailing_slash(uri) + "#{file_name}.gz" cache_dir = cache_dir spec_path local_file = File.join(cache_dir, file_name) retried = false @@ -223,7 +223,13 @@ class Gem::Source def typo_squatting?(host, distance_threshold=4) return if @uri.host.nil? - levenshtein_distance(@uri.host, host) <= distance_threshold + levenshtein_distance(@uri.host, host).between? 1, distance_threshold + end + + private + + def enforce_trailing_slash(uri) + uri.merge(uri.path.gsub(/\/+$/, '') + '/') end end diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index f748b090cc..b2bcadc49c 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -98,7 +98,7 @@ class Gem::SpecFetcher found[source] = specs.select do |tup| if dependency.match?(tup) - if matching_platform and !Gem::Platform.match(tup.platform) + if matching_platform and !Gem::Platform.match_gem?(tup.platform, tup.name) pm = ( rejected_specs[dependency] ||= \ Gem::PlatformMismatch.new(tup.name, tup.version)) diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index cb7ec2b76d..d59f57c49f 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -77,18 +77,18 @@ class Gem::Specification < Gem::BasicSpecification -1 => ['(RubyGems versions up to and including 0.7 did not have versioned specifications)'], 1 => [ 'Deprecated "test_suite_file" in favor of the new, but equivalent, "test_files"', - '"test_file=x" is a shortcut for "test_files=[x]"' + '"test_file=x" is a shortcut for "test_files=[x]"', ], 2 => [ 'Added "required_rubygems_version"', 'Now forward-compatible with future versions', ], 3 => [ - 'Added Fixnum validation to the specification_version' + 'Added Fixnum validation to the specification_version', ], 4 => [ - 'Added sandboxed freeform metadata to the specification version.' - ] + 'Added sandboxed freeform metadata to the specification version.', + ], }.freeze MARSHAL_FIELDS = { # :nodoc: @@ -804,7 +804,7 @@ class Gem::Specification < Gem::BasicSpecification stubs = stubs.uniq {|stub| stub.full_name } _resort!(stubs) - @@stubs_by_name = stubs.select {|s| Gem::Platform.match s.platform }.group_by(&:name) + @@stubs_by_name = stubs.select {|s| Gem::Platform.match_spec? s }.group_by(&:name) stubs end end @@ -831,7 +831,7 @@ class Gem::Specification < Gem::BasicSpecification @@stubs_by_name[name] else pattern = "#{name}-*.gemspec" - stubs = installed_stubs(dirs, pattern).select {|s| Gem::Platform.match s.platform } + default_stubs(pattern) + stubs = installed_stubs(dirs, pattern).select {|s| Gem::Platform.match_spec? s } + default_stubs(pattern) stubs = stubs.uniq {|stub| stub.full_name }.group_by(&:name) stubs.each_value {|v| _resort!(v) } @@ -1344,7 +1344,7 @@ class Gem::Specification < Gem::BasicSpecification true, # has_rdoc @new_platform, @licenses, - @metadata + @metadata, ] end @@ -2450,7 +2450,7 @@ class Gem::Specification < Gem::BasicSpecification :version, :has_rdoc, :default_executable, - :metadata + :metadata, ] @@attributes.each do |attr_name| diff --git a/lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem b/lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem deleted file mode 100644 index 9e6810ab70..0000000000 --- a/lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- diff --git a/lib/rubygems/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem similarity index 100% rename from lib/rubygems/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem rename to lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb index 5d4d761953..4246f9de86 100644 --- a/lib/rubygems/stub_specification.rb +++ b/lib/rubygems/stub_specification.rb @@ -29,7 +29,7 @@ class Gem::StubSpecification < Gem::BasicSpecification # in their require paths, so lets take advantage of that by pre-allocating # a require path list for that case. REQUIRE_PATH_LIST = { # :nodoc: - 'lib' => ['lib'].freeze + 'lib' => ['lib'].freeze, }.freeze def initialize(data, extensions) diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index 09b91b6ac6..a8261ef5c2 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -252,14 +252,14 @@ class Gem::TestCase < Minitest::Test msg = message(msg) do 'Expected output containing make command "%s": %s' % [ ('%s %s' % [make_command, target]).rstrip, - output.inspect + output.inspect, ] end else msg = message(msg) do 'Expected make command "%s": %s' % [ ('%s %s' % [make_command, target]).rstrip, - output.inspect + output.inspect, ] end end @@ -335,6 +335,7 @@ class Gem::TestCase < Minitest::Test @git = ENV['GIT'] || (win_platform? ? 'git.exe' : 'git') Gem.ensure_gem_subdirectories @gemhome + Gem.ensure_default_gem_subdirectories @gemhome @orig_LOAD_PATH = $LOAD_PATH.dup $LOAD_PATH.map! do |s| @@ -360,26 +361,23 @@ class Gem::TestCase < Minitest::Test Gem.send :remove_instance_variable, :@ruby_version if Gem.instance_variables.include? :@ruby_version - FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @userhome ENV['GEM_PRIVATE_KEY_PASSPHRASE'] = PRIVATE_KEY_PASSPHRASE - @default_dir = File.join @tempdir, 'default' - @default_spec_dir = File.join @default_dir, "specifications", "default" if Gem.java_platform? @orig_default_gem_home = RbConfig::CONFIG['default_gem_home'] - RbConfig::CONFIG['default_gem_home'] = @default_dir + RbConfig::CONFIG['default_gem_home'] = @gemhome else - Gem.instance_variable_set(:@default_dir, @default_dir) + Gem.instance_variable_set(:@default_dir, @gemhome) end - FileUtils.mkdir_p @default_spec_dir + + @orig_bindir = RbConfig::CONFIG["bindir"] + RbConfig::CONFIG["bindir"] = File.join @gemhome, "bin" Gem::Specification.unresolved_deps.clear Gem.use_paths(@gemhome) - Gem::Security.reset - Gem.loaded_specs.clear Gem.instance_variable_set(:@activated_gem_paths, 0) Gem.clear_default_specs @@ -448,6 +446,8 @@ class Gem::TestCase < Minitest::Test Gem.ruby = @orig_ruby if @orig_ruby + RbConfig::CONFIG['bindir'] = @orig_bindir + if Gem.java_platform? RbConfig::CONFIG['default_gem_home'] = @orig_default_gem_home else @@ -741,7 +741,7 @@ class Gem::TestCase < Minitest::Test def install_specs(*specs) specs.each do |spec| - Gem::Installer.for_spec(spec).install + Gem::Installer.for_spec(spec, :force => true).install end Gem.searcher = nil @@ -751,19 +751,6 @@ class Gem::TestCase < Minitest::Test # Installs the provided default specs including writing the spec file def install_default_gems(*specs) - install_default_specs(*specs) - - specs.each do |spec| - File.open spec.loaded_from, 'w' do |io| - io.write spec.to_ruby_for_cache - end - end - end - - ## - # Install the provided default specs - - def install_default_specs(*specs) specs.each do |spec| installer = Gem::Installer.for_spec(spec, :install_as_default => true) installer.install @@ -792,7 +779,7 @@ class Gem::TestCase < Minitest::Test def new_default_spec(name, version, deps = nil, *files) spec = util_spec name, version, deps - spec.loaded_from = File.join(@default_spec_dir, spec.spec_name) + spec.loaded_from = File.join(@gemhome, "specifications", "default", spec.spec_name) spec.files = files lib_dir = File.join(@tempdir, "default_gems", "lib") @@ -1517,7 +1504,7 @@ Also, a list: PRIVATE_KEY = nil PUBLIC_KEY = nil PUBLIC_CERT = nil - end if defined?(OpenSSL::SSL) + end if Gem::HAVE_OPENSSL end require 'rubygems/test_utilities' diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb index ab5cc78e67..3bda896875 100644 --- a/lib/rubygems/uri_formatter.rb +++ b/lib/rubygems/uri_formatter.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'cgi' ## # The UriFormatter handles URIs from user-input and escaping. @@ -18,6 +17,8 @@ class Gem::UriFormatter # Creates a new URI formatter for +uri+. def initialize(uri) + require 'cgi' + @uri = uri end diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb index 458a7a6601..be71ef409b 100644 --- a/lib/rubygems/version_option.rb +++ b/lib/rubygems/version_option.rb @@ -73,4 +73,10 @@ module Gem::VersionOption end end + ## + # Extract platform given on the command line + + def get_platform_from_requirements(requirements) + Gem.platforms[1].to_s if requirements.key? :added_platform + end end diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb index 557298d8d5..6973758c4c 100644 --- a/test/rubygems/test_bundled_ca.rb +++ b/test/rubygems/test_bundled_ca.rb @@ -3,7 +3,7 @@ require 'rubygems/test_case' require 'net/http' require 'rubygems/openssl' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping bundled certificates tests. openssl not found.' end @@ -46,11 +46,15 @@ class TestBundledCA < Gem::TestCase assert_https('rubygems.org') end - def test_accessing_fastly - assert_https('rubygems.global.ssl.fastly.net') + def test_accessing_www_rubygems + assert_https('www.rubygems.org') + end + + def test_accessing_staging + assert_https('staging.rubygems.org') end def test_accessing_new_index - assert_https('fastly.rubygems.org') + assert_https('index.rubygems.org') end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index cf5c9720b4..344b03be9b 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -164,7 +164,7 @@ class TestGem < Gem::TestCase :prog_mode => win_platform? ? 0410 : 0510, :data_mode => 0640, :wrappers => true, - :format_executable => format_executable + :format_executable => format_executable, } Dir.chdir @tempdir do Dir.mkdir 'bin' @@ -765,7 +765,7 @@ class TestGem < Gem::TestCase expected = [ File.expand_path('test/rubygems/sff/discover.rb', PROJECT_DIR), - File.join(foo1.full_gem_path, discover_path) + File.join(foo1.full_gem_path, discover_path), ].sort assert_equal expected, Gem.find_files('sff/discover').sort @@ -1532,7 +1532,7 @@ class TestGem < Gem::TestCase tests = [ [:dir0, [ Gem.dir, Gem.user_dir], m0], - [:dir1, [ Gem.user_dir, Gem.dir], m1] + [:dir1, [ Gem.user_dir, Gem.dir], m1], ] tests.each do |_name, _paths, expected| diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb index 24c60473f2..fe537780be 100644 --- a/test/rubygems/test_gem_commands_build_command.rb +++ b/test/rubygems/test_gem_commands_build_command.rb @@ -231,7 +231,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end assert_equal '', @ui.output - assert_equal "ERROR: Gemspec file not found: some_gem.gemspec\n", @ui.error + assert_equal "ERROR: Couldn't find a gemspec file matching 'some_gem' in #{@tempdir}\n", @ui.error end def test_execute_outside_dir @@ -272,8 +272,200 @@ class TestGemCommandsBuildCommand < Gem::TestCase assert_equal "this is a summary", spec.summary end + def test_execute_outside_dir_with_glob_argument + gemspec_dir = File.join @tempdir, 'build_command_gem' + gemspec_file = File.join gemspec_dir, @gem.spec_name + readme_file = File.join gemspec_dir, 'README.md' + + FileUtils.mkdir_p gemspec_dir + + File.open readme_file, 'w' do |f| + f.write "My awesome gem" + end + + File.open gemspec_file, 'w' do |gs| + gs.write @gem.to_ruby + end + + @cmd.options[:build_path] = gemspec_dir + @cmd.options[:args] = ["*.gemspec"] + + use_ui @ui do + @cmd.execute + end + + output = @ui.output.split "\n" + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: some_gem", output.shift + assert_equal " Version: 2", output.shift + assert_equal " File: some_gem-2.gem", output.shift + assert_equal [], output + + gem_file = File.join gemspec_dir, File.basename(@gem.cache_file) + assert File.exist?(gem_file) + + spec = Gem::Package.new(gem_file).spec + + assert_equal "some_gem", spec.name + assert_equal "this is a summary", spec.summary + end + + def test_execute_outside_dir_no_gemspec_present + gemspec_dir = File.join @tempdir, 'build_command_gem' + gemspec_file = File.join @tempdir, @gem.spec_name + readme_file = File.join gemspec_dir, 'README.md' + + FileUtils.mkdir_p gemspec_dir + + File.open readme_file, 'w' do |f| + f.write "My awesome gem" + end + + File.open gemspec_file, 'w' do |gs| + gs.write @gem.to_ruby + end + + @cmd.options[:build_path] = gemspec_dir + @cmd.options[:args] = ["*.gemspec"] + + use_ui @ui do + assert_raises Gem::MockGemUi::TermError do + @cmd.execute + end + end + + assert_equal "", @ui.output + assert_equal "ERROR: Couldn't find a gemspec file matching '*.gemspec' in #{gemspec_dir}\n", @ui.error + + gem_file = File.join gemspec_dir, File.basename(@gem.cache_file) + refute File.exist?(gem_file) + end + + def test_execute_outside_dir_without_gem_name + gemspec_dir = File.join(@tempdir, 'build_command_gem') + gemspec_file = File.join(gemspec_dir, @gem.spec_name) + + readme_file = File.join gemspec_dir, 'README.md' + + FileUtils.mkdir_p(gemspec_dir) + + File.open readme_file, 'w' do |f| + f.write "My awesome gem" + end + + File.open(gemspec_file, "w") do |gs| + gs.write(@gem.to_ruby) + end + + @cmd.options[:build_path] = gemspec_dir + @cmd.options[:args] = [] + + use_ui @ui do + Dir.chdir(gemspec_dir) do + @cmd.execute + end + end + + output = @ui.output.split("\n") + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: some_gem", output.shift + assert_equal " Version: 2", output.shift + assert_equal " File: some_gem-2.gem", output.shift + assert_equal [], output + + gem_file = File.join gemspec_dir, File.basename(@gem.cache_file) + assert File.exist?(gem_file) + + spec = Gem::Package.new(gem_file).spec + + assert_equal "some_gem", spec.name + assert_equal "this is a summary", spec.summary + end + + def test_execute_outside_dir_with_external_gemspec + gemspec_dir = File.join @tempdir, 'gemspec_dir' + gemspec_file = File.join gemspec_dir, @gem.spec_name + + gemcode_dir = File.join @tempdir, 'build_command_gem' + readme_file = File.join gemcode_dir, 'README.md' + + FileUtils.mkdir_p gemspec_dir + FileUtils.mkdir_p gemcode_dir + + File.open readme_file, 'w' do |f| + f.write "My awesome gem in nested directory" + end + + File.open gemspec_file, 'w' do |gs| + gs.write @gem.to_ruby + end + + @cmd.options[:build_path] = gemcode_dir + @cmd.options[:args] = [gemspec_file] + + use_ui @ui do + @cmd.execute + end + + output = @ui.output.split "\n" + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: some_gem", output.shift + assert_equal " Version: 2", output.shift + assert_equal " File: some_gem-2.gem", output.shift + assert_equal [], output + + gem_file = File.join gemcode_dir, File.basename(@gem.cache_file) + assert File.exist?(gem_file) + + spec = Gem::Package.new(gem_file).spec + + assert_equal "some_gem", spec.name + assert_equal "this is a summary", spec.summary + end + + def test_execute_outside_dir_with_external_relative_gemspec + gemspec_dir = File.join @tempdir, 'gemspec_dir' + gemspec_file = File.join gemspec_dir, @gem.spec_name + + gemcode_dir = File.join @tempdir, 'build_command_gem' + readme_file = File.join gemcode_dir, 'README.md' + + FileUtils.mkdir_p gemspec_dir + FileUtils.mkdir_p gemcode_dir + + File.open readme_file, 'w' do |f| + f.write "My awesome gem in nested directory" + end + + File.open gemspec_file, 'w' do |gs| + gs.write @gem.to_ruby + end + + @cmd.options[:build_path] = gemcode_dir + @cmd.options[:args] = [File.join("..", "gemspec_dir", @gem.spec_name)] + + use_ui @ui do + @cmd.execute + end + + output = @ui.output.split "\n" + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: some_gem", output.shift + assert_equal " Version: 2", output.shift + assert_equal " File: some_gem-2.gem", output.shift + assert_equal [], output + + gem_file = File.join gemcode_dir, File.basename(@gem.cache_file) + assert File.exist?(gem_file) + + spec = Gem::Package.new(gem_file).spec + + assert_equal "some_gem", spec.name + assert_equal "this is a summary", spec.summary + end + def test_can_find_gemspecs_without_dot_gemspec - gemspec_file = File.join(@tempdir, @gem.spec_name) + gemspec_file = File.join(@tempdir, @gem.name) File.open gemspec_file + ".gemspec", 'w' do |gs| gs.write @gem.to_ruby @@ -390,7 +582,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_signed_gem - skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform? + skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? trust_dir = Gem::Security.trust_dir @@ -417,7 +609,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_signed_gem_with_cert_expiration_length_days - skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform? + skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? gem_path = File.join Gem.user_home, ".gem" Dir.mkdir gem_path @@ -461,7 +653,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase end def test_build_auto_resign_cert - skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform? + skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform? gem_path = File.join Gem.user_home, ".gem" Dir.mkdir gem_path diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb index c4693b07cf..19867bf37a 100644 --- a/test/rubygems/test_gem_commands_cert_command.rb +++ b/test/rubygems/test_gem_commands_cert_command.rb @@ -2,7 +2,7 @@ require 'rubygems/test_case' require 'rubygems/commands/cert_command' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping `gem cert` tests. openssl not found.' end @@ -805,4 +805,4 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis assert_equal "invalid argument: --sign #{nonexistent}: does not exist", e.message end -end if defined?(OpenSSL::SSL) && !Gem.java_platform? +end if Gem::HAVE_OPENSSL && !Gem.java_platform? diff --git a/test/rubygems/test_gem_commands_cleanup_command.rb b/test/rubygems/test_gem_commands_cleanup_command.rb index 81f9a24db5..d937a5e549 100644 --- a/test/rubygems/test_gem_commands_cleanup_command.rb +++ b/test/rubygems/test_gem_commands_cleanup_command.rb @@ -221,7 +221,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase @b_2 = util_spec 'b', 3 install_gem @b_1 - install_default_specs @b_default + install_default_gems @b_default install_gem @b_2 @cmd.options[:args] = [] diff --git a/test/rubygems/test_gem_commands_contents_command.rb b/test/rubygems/test_gem_commands_contents_command.rb index 07b0e0f340..7c89c67dd4 100644 --- a/test/rubygems/test_gem_commands_contents_command.rb +++ b/test/rubygems/test_gem_commands_contents_command.rb @@ -227,7 +227,7 @@ lib/foo.rb nil, "default/gem.rb") default_gem_spec.executables = ["default_command"] default_gem_spec.files += ["default_gem.so"] - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) @cmd.options[:args] = %w[default] @@ -238,7 +238,7 @@ lib/foo.rb expected = [ [RbConfig::CONFIG['bindir'], 'default_command'], [RbConfig::CONFIG['rubylibdir'], 'default/gem.rb'], - [RbConfig::CONFIG['archdir'], 'default_gem.so'] + [RbConfig::CONFIG['archdir'], 'default_gem.so'], ].sort.map{|a|File.join a }.join "\n" assert_equal expected, @ui.output.chomp diff --git a/test/rubygems/test_gem_commands_help_command.rb b/test/rubygems/test_gem_commands_help_command.rb index 26e22d79be..8d20563a60 100644 --- a/test/rubygems/test_gem_commands_help_command.rb +++ b/test/rubygems/test_gem_commands_help_command.rb @@ -40,10 +40,12 @@ class TestGemCommandsHelpCommand < Gem::TestCase util_gem 'commands' do |out, err| mgr.command_names.each do |cmd| - assert_match(/\s+#{cmd}\s+\S+/, out) + unless mgr[cmd].deprecated? + assert_match(/\s+#{cmd}\s+\S+/, out) + end end - if defined?(OpenSSL::SSL) + if Gem::HAVE_OPENSSL assert_empty err refute_match 'No command found for ', out @@ -51,6 +53,17 @@ class TestGemCommandsHelpCommand < Gem::TestCase end end + def test_gem_help_commands_omits_deprecated_commands + mgr = Gem::CommandManager.new + + util_gem 'commands' do |out, err| + deprecated_commands = mgr.command_names.select {|cmd| mgr[cmd].deprecated? } + deprecated_commands.each do |cmd| + refute_match(/\A\s+#{cmd}\s+\S+\z/, out) + end + end + end + def test_gem_no_args_shows_help util_gem do |out, err| assert_match(/Usage:/, out) diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index ccaa4ec2dc..08530bfeca 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -410,7 +410,7 @@ ERROR: Possible alternatives: non_existent_with_hint expected = [ "ERROR: Could not find a valid gem 'non-existent_with-hint' (>= 0) in any repository", - "ERROR: Possible alternatives: nonexistent-with_hint" + "ERROR: Possible alternatives: nonexistent-with_hint", ] output = @ui.error.split "\n" diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index 1602ae6839..4280fedff3 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -247,7 +247,7 @@ EOF @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [ [response_fail, 401, 'Unauthorized'], - [response_success, 200, 'OK'] + [response_success, 200, 'OK'], ] @otp_ui = Gem::MockGemUi.new "111111\n" @@ -275,4 +275,52 @@ EOF assert_match 'Code: ', @otp_ui.output assert_equal '111111', @stub_fetcher.last_request['OTP'] end + + def test_remove_owners_unathorized_api_key + response_forbidden = "The API key doesn't have access" + response_success = "Owner removed successfully." + + @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [ + [response_forbidden, 403, 'Forbidden'], + [response_success, 200, "OK"], + ] + @stub_fetcher.data["#{Gem.host}/api/v1/api_key"] = ["", 200, "OK"] + @cmd.instance_variable_set :@scope, :remove_owner + + @stub_ui = Gem::MockGemUi.new "some@mail.com\npass\n" + use_ui @stub_ui do + @cmd.remove_owners("freewill", ["some@example"]) + end + + access_notice = "The existing key doesn't have access of remove_owner on RubyGems.org. Please sign in to update access." + assert_match access_notice, @stub_ui.output + assert_match "Email:", @stub_ui.output + assert_match "Password:", @stub_ui.output + assert_match "Added remove_owner scope to the existing API key", @stub_ui.output + assert_match response_success, @stub_ui.output + end + + def test_add_owners_unathorized_api_key + response_forbidden = "The API key doesn't have access" + response_success = "Owner added successfully." + + @stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [ + [response_forbidden, 403, 'Forbidden'], + [response_success, 200, "OK"], + ] + @stub_fetcher.data["#{Gem.host}/api/v1/api_key"] = ["", 200, "OK"] + @cmd.instance_variable_set :@scope, :add_owner + + @stub_ui = Gem::MockGemUi.new "some@mail.com\npass\n" + use_ui @stub_ui do + @cmd.add_owners("freewill", ["some@example"]) + end + + access_notice = "The existing key doesn't have access of add_owner on RubyGems.org. Please sign in to update access." + assert_match access_notice, @stub_ui.output + assert_match "Email:", @stub_ui.output + assert_match "Password:", @stub_ui.output + assert_match "Added add_owner scope to the existing API key", @stub_ui.output + assert_match response_success, @stub_ui.output + end end diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 75243e5fa2..59f34af249 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -568,7 +568,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase assert_equal([ "Restoring gems to pristine condition...", "Cached gem for a-2 not found, attempting to fetch...", - "Skipped a-2, it was not found from cache and remote sources" + "Skipped a-2, it was not found from cache and remote sources", ], @ui.output.split("\n")) assert_empty @ui.error @@ -577,7 +577,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase def test_execute_default_gem default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) @cmd.options[:args] = %w[default] diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index c23760a8ca..68681af22f 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -152,7 +152,7 @@ class TestGemCommandsPushCommand < Gem::TestCase keys = { :rubygems_api_key => 'KEY', - @host => @api_key + @host => @api_key, } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path @@ -187,7 +187,7 @@ class TestGemCommandsPushCommand < Gem::TestCase keys = { :rubygems_api_key => 'KEY', - @host => @api_key + @host => @api_key, } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path @@ -271,7 +271,7 @@ class TestGemCommandsPushCommand < Gem::TestCase keys = { :rubygems_api_key => 'KEY', - @host => @api_key + @host => @api_key, } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path @@ -302,7 +302,7 @@ class TestGemCommandsPushCommand < Gem::TestCase api_key = "PRIVKEY" keys = { - host => api_key + host => api_key, } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path @@ -373,7 +373,7 @@ class TestGemCommandsPushCommand < Gem::TestCase @fetcher.data["#{Gem.host}/api/v1/gems"] = [ [response_fail, 401, 'Unauthorized'], - [response_success, 200, 'OK'] + [response_success, 200, 'OK'], ] @otp_ui = Gem::MockGemUi.new "111111\n" @@ -404,6 +404,32 @@ class TestGemCommandsPushCommand < Gem::TestCase assert_equal '111111', @fetcher.last_request['OTP'] end + def test_sending_gem_unathorized_api_key + response_forbidden = "The API key doesn't have access" + response_success = 'Successfully registered gem: freewill (1.0.0)' + + @fetcher.data["#{@host}/api/v1/gems"] = [ + [response_forbidden, 403, 'Forbidden'], + [response_success, 200, "OK"], + ] + + @fetcher.data["#{@host}/api/v1/api_key"] = ["", 200, "OK"] + @cmd.instance_variable_set :@host, @host + @cmd.instance_variable_set :@scope, :push_rubygem + + @ui = Gem::MockGemUi.new "some@mail.com\npass\n" + use_ui @ui do + @cmd.send_gem(@path) + end + + access_notice = "The existing key doesn't have access of push_rubygem on https://rubygems.example. Please sign in to update access." + assert_match access_notice, @ui.output + assert_match "Email:", @ui.output + assert_match "Password:", @ui.output + assert_match "Added push_rubygem scope to the existing API key", @ui.output + assert_match response_success, @ui.output + end + private def singleton_gem_class diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb index f2f70a10ff..a21bc690fb 100644 --- a/test/rubygems/test_gem_commands_query_command.rb +++ b/test/rubygems/test_gem_commands_query_command.rb @@ -644,7 +644,7 @@ a (2 universal-darwin, 1 ruby x86-linux) spec_fetcher {|fetcher| fetcher.spec 'a', 2 } a1 = new_default_spec 'a', 1 - install_default_specs a1 + install_default_gems a1 use_ui @stub_ui do @cmd.execute @@ -663,7 +663,7 @@ EOF def test_execute_show_default_gems_with_platform a1 = new_default_spec 'a', 1 a1.platform = 'java' - install_default_specs a1 + install_default_gems a1 use_ui @stub_ui do @cmd.execute @@ -685,7 +685,7 @@ EOF end a1 = new_default_spec 'a', 1 - install_default_specs a1 + install_default_gems a1 @cmd.handle_options %w[-l -d] diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index 9b6aa87861..afdc5d0979 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -26,12 +26,12 @@ class TestGemCommandsSetupCommand < Gem::TestCase bundler/exe/bundle bundler/lib/bundler.rb bundler/lib/bundler/b.rb + bundler/lib/bundler/man/bundle-b.1.ronn + bundler/lib/bundler/man/gemfile.5.ronn bundler/lib/bundler/templates/.circleci/config.yml bundler/lib/bundler/templates/.travis.yml bundler/man/bundle-b.1 - bundler/man/bundle-b.1.ronn bundler/man/gemfile.5 - bundler/man/gemfile.5.ronn ] create_dummy_files(filelist) @@ -155,23 +155,18 @@ class TestGemCommandsSetupCommand < Gem::TestCase assert_match %r{\A#!\s*#{bin_env}#{ruby_exec}}, File.read(gem_bin_path) end - def test_pem_files_in - assert_equal %w[rubygems/ssl_certs/rubygems.org/foo.pem], - @cmd.pem_files_in('lib').sort - end - - def test_rb_files_in - assert_equal %w[rubygems.rb rubygems/test_case.rb], - @cmd.rb_files_in('lib').sort + def test_files_in + assert_equal %w[rubygems.rb rubygems/ssl_certs/rubygems.org/foo.pem rubygems/test_case.rb], + @cmd.files_in('lib').sort end def test_bundler_man1_files_in - assert_equal %w[bundle-b.1 bundle-b.1.ronn], + assert_equal %w[bundle-b.1], @cmd.bundler_man1_files_in('bundler/man').sort end def test_bundler_man5_files_in - assert_equal %w[gemfile.5 gemfile.5.ronn], + assert_equal %w[gemfile.5], @cmd.bundler_man5_files_in('bundler/man').sort end @@ -187,7 +182,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase assert_path_exists File.join(dir, 'bundler.rb') assert_path_exists File.join(dir, 'bundler/b.rb') - assert_path_exists File.join(dir, 'bundler/templates/.circleci/config.yml') + assert_path_exists File.join(dir, 'bundler/templates/.circleci/config.yml') unless RUBY_ENGINE == "truffleruby" # https://github.com/oracle/truffleruby/issues/2116 assert_path_exists File.join(dir, 'bundler/templates/.travis.yml') end end @@ -199,9 +194,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase @cmd.install_man dir assert_path_exists File.join("#{dir}/man1", 'bundle-b.1') - assert_path_exists File.join("#{dir}/man1", 'bundle-b.1.ronn') + refute_path_exists File.join("#{dir}/man1", 'bundle-b.1.ronn') assert_path_exists File.join("#{dir}/man5", 'gemfile.5') - assert_path_exists File.join("#{dir}/man5", 'gemfile.5.ronn') + refute_path_exists File.join("#{dir}/man5", 'gemfile.5.ronn') end end @@ -297,7 +292,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase @cmd.remove_old_lib_files lib - files_that_go.each {|file| refute_path_exists file } + files_that_go.each {|file| refute_path_exists(file) unless file == old_bundler_ci && RUBY_ENGINE == "truffleruby" } # https://github.com/oracle/truffleruby/issues/2116 files_that_stay.each {|file| assert_path_exists file } end @@ -313,8 +308,8 @@ class TestGemCommandsSetupCommand < Gem::TestCase gemfile_5_ronn = File.join man, 'man5', 'gemfile.5.ronn' gemfile_5_txt = File.join man, 'man5', 'gemfile.5.txt' - files_that_go = [bundle_b_1_txt, gemfile_5_txt] - files_that_stay = [ruby_1, bundle_b_1, bundle_b_1_ronn, gemfile_5, gemfile_5_ronn] + files_that_go = [bundle_b_1_txt, bundle_b_1_ronn, gemfile_5_txt, gemfile_5_ronn] + files_that_stay = [ruby_1, bundle_b_1, gemfile_5] create_dummy_files(files_that_go + files_that_stay) diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb index 21d19fffc9..f8262466b1 100644 --- a/test/rubygems/test_gem_commands_signin_command.rb +++ b/test/rubygems/test_gem_commands_signin_command.rb @@ -73,14 +73,38 @@ class TestGemCommandsSigninCommand < Gem::TestCase assert_equal api_key, credentials[:rubygems_api_key] end + def test_excute_with_key_name_and_scope + email = 'you@example.com' + password = 'secret' + api_key = '1234' + fetcher = Gem::RemoteFetcher.fetcher + + key_name_ui = Gem::MockGemUi.new "#{email}\n#{password}\ntest-key\n\ny\n\n\n\n\n\n" + util_capture(key_name_ui, nil, api_key, fetcher) { @cmd.execute } + + user = ENV["USER"] || ENV["USERNAME"] + + assert_match "API Key name [#{Socket.gethostname}-#{user}", key_name_ui.output + assert_match "index_rubygems [y/N]", key_name_ui.output + assert_match "push_rubygem [y/N]", key_name_ui.output + assert_match "yank_rubygem [y/N]", key_name_ui.output + assert_match "add_owner [y/N]", key_name_ui.output + assert_match "remove_owner [y/N]", key_name_ui.output + assert_match "access_webhooks [y/N]", key_name_ui.output + assert_match "show_dashboard [y/N]", key_name_ui.output + assert_equal "name=test-key&push_rubygem=true", fetcher.last_request.body + + credentials = YAML.load_file Gem.configuration.credentials_path + assert_equal api_key, credentials[:rubygems_api_key] + end + # Utility method to capture IO/UI within the block passed - def util_capture(ui_stub = nil, host = nil, api_key = nil) + def util_capture(ui_stub = nil, host = nil, api_key = nil, fetcher = Gem::FakeFetcher.new) api_key ||= 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903' response = [api_key, 200, 'OK'] email = 'you@example.com' password = 'secret' - fetcher = Gem::FakeFetcher.new # Set the expected response for the Web-API supplied ENV['RUBYGEMS_HOST'] = host || Gem::DEFAULT_HOST @@ -88,7 +112,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase fetcher.data[data_key] = response Gem::RemoteFetcher.fetcher = fetcher - sign_in_ui = ui_stub || Gem::MockGemUi.new("#{email}\n#{password}\n") + sign_in_ui = ui_stub || Gem::MockGemUi.new("#{email}\n#{password}\n\n\n\n\n\n\n\n\n") use_ui sign_in_ui do yield diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb index ff00b9aa23..59acfb1ed6 100644 --- a/test/rubygems/test_gem_commands_sources_command.rb +++ b/test/rubygems/test_gem_commands_sources_command.rb @@ -107,6 +107,36 @@ class TestGemCommandsSourcesCommand < Gem::TestCase assert_empty ui.error end + def test_execute_add_allow_typo_squatting_source_forced + rubygems_org = "https://rubyems.org" + + spec_fetcher do |fetcher| + fetcher.spec("a", 1) + end + + specs = Gem::Specification.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + specs_dump_gz = StringIO.new + Zlib::GzipWriter.wrap(specs_dump_gz) do |io| + Marshal.dump(specs, io) + end + + @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string + @cmd.handle_options %W[--force --add #{rubygems_org}] + + @cmd.execute + + expected = "https://rubyems.org added to sources\n" + assert_equal expected, ui.output + + source = Gem::Source.new(rubygems_org) + assert Gem.sources.include?(source) + + assert_empty ui.error + end + def test_execute_add_deny_typo_squatting_source rubygems_org = "https://rubyems.org" @@ -283,6 +313,36 @@ source http://gems.example.com/ already present in the cache assert_empty @ui.error end + def test_execute_add_http_rubygems_org_forced + rubygems_org = "http://rubygems.org" + + spec_fetcher do |fetcher| + fetcher.spec("a", 1) + end + + specs = Gem::Specification.map do |spec| + [spec.name, spec.version, spec.original_platform] + end + + specs_dump_gz = StringIO.new + Zlib::GzipWriter.wrap(specs_dump_gz) do |io| + Marshal.dump(specs, io) + end + + @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string + @cmd.handle_options %W[--force --add #{rubygems_org}] + + @cmd.execute + + expected = "http://rubygems.org added to sources\n" + assert_equal expected, ui.output + + source = Gem::Source.new(rubygems_org) + assert Gem.sources.include?(source) + + assert_empty ui.error + end + def test_execute_add_https_rubygems_org https_rubygems_org = 'https://rubygems.org/' diff --git a/test/rubygems/test_gem_commands_specification_command.rb b/test/rubygems/test_gem_commands_specification_command.rb index e055246cdc..732278eb6f 100644 --- a/test/rubygems/test_gem_commands_specification_command.rb +++ b/test/rubygems/test_gem_commands_specification_command.rb @@ -186,6 +186,34 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase assert_equal Gem::Version.new("1"), spec.version end + def test_execute_remote_with_version_and_platform + original_platforms = Gem.platforms.dup + + spec_fetcher do |fetcher| + fetcher.spec 'foo', "1" + fetcher.spec 'foo', "1" do |s| + s.platform = 'x86_64-linux' + end + end + + @cmd.options[:args] = %w[foo] + @cmd.options[:version] = "1" + @cmd.options[:domain] = :remote + @cmd.options[:added_platform] = true + Gem.platforms = [Gem::Platform::RUBY, Gem::Platform.new("x86_64-linux")] + + use_ui @ui do + @cmd.execute + end + + spec = Gem::Specification.from_yaml @ui.output + + assert_equal Gem::Version.new("1"), spec.version + assert_equal Gem::Platform.new("x86_64-linux"), spec.platform + ensure + Gem.platforms = original_platforms + end + def test_execute_remote_without_prerelease spec_fetcher do |fetcher| fetcher.spec 'foo', '2.0.0' diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb index cacde06fd5..749e9bee20 100644 --- a/test/rubygems/test_gem_commands_update_command.rb +++ b/test/rubygems/test_gem_commands_update_command.rb @@ -198,13 +198,13 @@ class TestGemCommandsUpdateCommand < Gem::TestCase def test_execute_system_specific_newer_than_or_equal_to_3_2_leaves_plugins_dir_alone spec_fetcher do |fetcher| - fetcher.download 'rubygems-update', 3.2 do |s| + fetcher.download 'rubygems-update', "3.2.a" do |s| s.files = %w[setup.rb] end end @cmd.options[:args] = [] - @cmd.options[:system] = "3.2" + @cmd.options[:system] = "3.2.a" FileUtils.mkdir_p Gem.plugindir plugin_file = File.join(Gem.plugindir, 'a_plugin.rb') diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb index 8e453dfabf..3046655aa8 100644 --- a/test/rubygems/test_gem_commands_yank_command.rb +++ b/test/rubygems/test_gem_commands_yank_command.rb @@ -70,7 +70,7 @@ class TestGemCommandsYankCommand < Gem::TestCase yank_uri = 'http://example/api/v1/gems/yank' @fetcher.data[yank_uri] = [ [response_fail, 401, 'Unauthorized'], - ['Successfully yanked', 200, 'OK'] + ['Successfully yanked', 200, 'OK'], ] @cmd.options[:args] = %w[a] @@ -147,4 +147,34 @@ class TestGemCommandsYankCommand < Gem::TestCase assert_equal 'key', @fetcher.last_request['Authorization'] assert_equal [yank_uri], @fetcher.paths end + + def test_yank_gem_unathorized_api_key + response_forbidden = "The API key doesn't have access" + response_success = 'Successfully yanked' + host = 'http://example' + + @fetcher.data["#{host}/api/v1/gems/yank"] = [ + [response_forbidden, 403, 'Forbidden'], + [response_success, 200, "OK"], + ] + + @fetcher.data["#{host}/api/v1/api_key"] = ["", 200, "OK"] + @cmd.options[:args] = %w[a] + @cmd.options[:added_platform] = true + @cmd.options[:version] = req('= 1.0') + @cmd.instance_variable_set :@host, host + @cmd.instance_variable_set :@scope, :yank_rubygem + + @ui = Gem::MockGemUi.new "some@mail.com\npass\n" + use_ui @ui do + @cmd.execute + end + + access_notice = "The existing key doesn't have access of yank_rubygem on http://example. Please sign in to update access." + assert_match access_notice, @ui.output + assert_match "Email:", @ui.output + assert_match "Password:", @ui.output + assert_match "Added yank_rubygem scope to the existing API key", @ui.output + assert_match response_success, @ui.output + end end diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 803b95e88c..fe8a74c750 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -528,6 +528,40 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal %w[a-1 e-1], inst.installed_gems.map {|s| s.full_name } end + def test_install_no_minimal_deps + util_setup_gems + + _, e1_gem = util_gem 'e', '1' do |s| + s.add_dependency 'b' + end + + _, b2_gem = util_gem 'b', '2' do |s| + s.add_dependency 'a' + end + + FileUtils.mv @a1_gem, @tempdir + FileUtils.mv @b1_gem, @tempdir + FileUtils.mv b2_gem, @tempdir + FileUtils.mv e1_gem, @tempdir + + inst = nil + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :ignore_dependencies => true + inst.install 'b', req('= 1') + end + + assert_equal %w[b-1], inst.installed_gems.map {|s| s.full_name }, + 'sanity check' + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new :minimal_deps => false + inst.install 'e' + end + + assert_equal %w[a-1 b-2 e-1], inst.installed_gems.map {|s| s.full_name } + end + def test_install_no_document util_setup_gems @@ -749,7 +783,7 @@ class TestGemDependencyInstaller < Gem::TestCase inst = nil Dir.chdir @tempdir do - inst = Gem::DependencyInstaller.new + inst = Gem::DependencyInstaller.new :force => true inst.install 'a' end @@ -842,7 +876,7 @@ class TestGemDependencyInstaller < Gem::TestCase require 'rubygems/openssl' - if defined? OpenSSL + if Gem::HAVE_OPENSSL def test_install_security_policy util_setup_gems diff --git a/test/rubygems/test_gem_dependency_list.rb b/test/rubygems/test_gem_dependency_list.rb index d8ef3d4f0e..097e680596 100644 --- a/test/rubygems/test_gem_dependency_list.rb +++ b/test/rubygems/test_gem_dependency_list.rb @@ -139,8 +139,8 @@ class TestGemDependencyList < Gem::TestCase exp = { "b" => [ - Gem::Dependency.new("a", ">= 1") - ] + Gem::Dependency.new("a", ">= 1"), + ], } assert_equal exp, @deplist.why_not_ok? diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb index abd33d237a..6e6bf89f9c 100644 --- a/test/rubygems/test_gem_ext_builder.rb +++ b/test/rubygems/test_gem_ext_builder.rb @@ -30,9 +30,8 @@ class TestGemExtBuilder < Gem::TestCase ENV['DESTDIR'] = 'destination' results = [] - Dir.chdir @ext do - File.open 'Makefile', 'w' do |io| - io.puts <<-MAKEFILE + File.open File.join(@ext, 'Makefile'), 'w' do |io| + io.puts <<-MAKEFILE all: \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" @@ -41,12 +40,11 @@ clean: install: \t@#{Gem.ruby} -e "puts %Q{install: \#{ENV['DESTDIR']}}" - MAKEFILE - end - - Gem::Ext::Builder.make @dest_path, results + MAKEFILE end + Gem::Ext::Builder.make @dest_path, results, @ext + results = results.join("\n").b assert_match %r{"DESTDIR=#{ENV['DESTDIR']}" clean$}, results @@ -64,20 +62,18 @@ install: ENV['DESTDIR'] = 'destination' results = [] - Dir.chdir @ext do - File.open 'Makefile', 'w' do |io| - io.puts <<-MAKEFILE + File.open File.join(@ext, 'Makefile'), 'w' do |io| + io.puts <<-MAKEFILE all: \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" install: \t@#{Gem.ruby} -e "puts %Q{install: \#{ENV['DESTDIR']}}" - MAKEFILE - end - - Gem::Ext::Builder.make @dest_path, results + MAKEFILE end + Gem::Ext::Builder.make @dest_path, results, @ext + results = results.join("\n").b assert_match %r{"DESTDIR=#{ENV['DESTDIR']}" clean$}, results diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb index 60a1fa13f6..e347359679 100644 --- a/test/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/rubygems/test_gem_ext_cmake_builder.rb @@ -36,9 +36,7 @@ install (FILES test.txt DESTINATION bin) output = [] - Dir.chdir @ext do - Gem::Ext::CmakeBuilder.build nil, @dest_path, output - end + Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext output = output.join "\n" @@ -54,9 +52,7 @@ install (FILES test.txt DESTINATION bin) output = [] error = assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::CmakeBuilder.build nil, @dest_path, output - end + Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext end output = output.join "\n" @@ -77,9 +73,7 @@ install (FILES test.txt DESTINATION bin) output = [] - Dir.chdir @ext do - Gem::Ext::CmakeBuilder.build nil, @dest_path, output - end + Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext output = output.join "\n" diff --git a/test/rubygems/test_gem_ext_configure_builder.rb b/test/rubygems/test_gem_ext_configure_builder.rb index 7d7f3759ea..a74f5ce055 100644 --- a/test/rubygems/test_gem_ext_configure_builder.rb +++ b/test/rubygems/test_gem_ext_configure_builder.rb @@ -25,9 +25,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase output = [] - Dir.chdir @ext do - Gem::Ext::ConfigureBuilder.build nil, @dest_path, output - end + Gem::Ext::ConfigureBuilder.build nil, @dest_path, output, [], nil, @ext assert_match(/^current directory:/, output.shift) assert_equal "sh ./configure --prefix=#{@dest_path}", output.shift @@ -48,9 +46,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase output = [] error = assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::ConfigureBuilder.build nil, @dest_path, output - end + Gem::Ext::ConfigureBuilder.build nil, @dest_path, output, [], nil, @ext end shell_error_msg = %r{(\./configure: .*)|((?:[Cc]an't|cannot) open '?\./configure'?(?:: No such file or directory)?)} @@ -74,9 +70,7 @@ class TestGemExtConfigureBuilder < Gem::TestCase end output = [] - Dir.chdir @ext do - Gem::Ext::ConfigureBuilder.build nil, @dest_path, output - end + Gem::Ext::ConfigureBuilder.build nil, @dest_path, output, [], nil, @ext assert_contains_make_command 'clean', output[1] assert_contains_make_command '', output[4] diff --git a/test/rubygems/test_gem_ext_ext_conf_builder.rb b/test/rubygems/test_gem_ext_ext_conf_builder.rb index 05ac538ec2..21fe27166b 100644 --- a/test/rubygems/test_gem_ext_ext_conf_builder.rb +++ b/test/rubygems/test_gem_ext_ext_conf_builder.rb @@ -29,12 +29,9 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] - Dir.chdir @ext do - result = - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output + result = Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext - assert_same result, output - end + assert_same result, output assert_match(/^current directory:/, output[0]) assert_match(/^#{Gem.ruby}.* extconf.rb/, output[1]) @@ -59,9 +56,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output - end + Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext assert_equal "creating Makefile\n", output[2] assert_contains_make_command 'clean', output[4] @@ -86,9 +81,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output - end + Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext end assert_equal "creating Makefile\n", output[2] @@ -112,9 +105,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] error = assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output - end + Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext end assert_equal 'extconf failed, exit code 1', error.message @@ -139,9 +130,7 @@ class TestGemExtExtConfBuilder < Gem::TestCase output = [] - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output - end + Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext refute_includes(output, "To see why this extension failed to compile, please check the mkmf.log which can be found here:\n") @@ -181,9 +170,7 @@ end output = [] - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output - end + Gem::Ext::ExtConfBuilder.build 'extconf.rb', @dest_path, output, [], nil, @ext assert_contains_make_command 'clean', output[4] assert_contains_make_command '', output[7] @@ -207,9 +194,7 @@ end makefile.puts "install:" end - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.make @ext, output - end + Gem::Ext::ExtConfBuilder.make @ext, output, @ext assert_contains_make_command 'clean', output[1] assert_contains_make_command '', output[4] @@ -218,9 +203,7 @@ end def test_class_make_no_Makefile error = assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::ExtConfBuilder.make @ext, ['output'] - end + Gem::Ext::ExtConfBuilder.make @ext, ['output'], @ext end assert_equal 'Makefile not found', error.message diff --git a/test/rubygems/test_gem_ext_rake_builder.rb b/test/rubygems/test_gem_ext_rake_builder.rb index a13b09c98b..8cb96f7dd3 100644 --- a/test/rubygems/test_gem_ext_rake_builder.rb +++ b/test/rubygems/test_gem_ext_rake_builder.rb @@ -18,9 +18,7 @@ class TestGemExtRakeBuilder < Gem::TestCase output = [] build_rake_in do |rake| - Dir.chdir @ext do - Gem::Ext::RakeBuilder.build 'mkrf_conf.rb', @dest_path, output - end + Gem::Ext::RakeBuilder.build 'mkrf_conf.rb', @dest_path, output, [], nil, @ext output = output.join "\n" @@ -38,10 +36,8 @@ class TestGemExtRakeBuilder < Gem::TestCase output = [] build_rake_in do |rake| - Dir.chdir @ext do - non_empty_args_list = [''] - Gem::Ext::RakeBuilder.build 'mkrf_conf.rb', @dest_path, output, non_empty_args_list - end + non_empty_args_list = [''] + Gem::Ext::RakeBuilder.build 'mkrf_conf.rb', @dest_path, output, non_empty_args_list, nil, @ext output = output.join "\n" @@ -55,9 +51,7 @@ class TestGemExtRakeBuilder < Gem::TestCase output = [] build_rake_in do |rake| - Dir.chdir @ext do - Gem::Ext::RakeBuilder.build "ext/Rakefile", @dest_path, output, ["test1", "test2"] - end + Gem::Ext::RakeBuilder.build "ext/Rakefile", @dest_path, output, ["test1", "test2"], nil, @ext output = output.join "\n" @@ -72,9 +66,7 @@ class TestGemExtRakeBuilder < Gem::TestCase build_rake_in(false) do |rake| error = assert_raises Gem::InstallError do - Dir.chdir @ext do - Gem::Ext::RakeBuilder.build "mkrf_conf.rb", @dest_path, output - end + Gem::Ext::RakeBuilder.build "mkrf_conf.rb", @dest_path, output, [], nil, @ext end assert_match %r{^rake failed}, error.message diff --git a/test/rubygems/test_gem_gem_runner.rb b/test/rubygems/test_gem_gem_runner.rb index 72a5c83431..8df11ecebc 100644 --- a/test/rubygems/test_gem_gem_runner.rb +++ b/test/rubygems/test_gem_gem_runner.rb @@ -74,17 +74,18 @@ class TestGemGemRunner < Gem::TestCase args = %w[query] use_ui @ui do - assert_nil @runner.run(args) + @runner.run(args) end assert_match(/WARNING: query command is deprecated. It will be removed in Rubygems [0-9]+/, @ui.error) + assert_match(/WARNING: It is recommended that you use `gem search` or `gem list` instead/, @ui.error) end def test_info_succeeds args = %w[info] use_ui @ui do - assert_nil @runner.run(args) + @runner.run(args) end assert_empty @ui.error @@ -94,7 +95,7 @@ class TestGemGemRunner < Gem::TestCase args = %w[list] use_ui @ui do - assert_nil @runner.run(args) + @runner.run(args) end assert_empty @ui.error @@ -104,7 +105,7 @@ class TestGemGemRunner < Gem::TestCase args = %w[search] use_ui @ui do - assert_nil @runner.run(args) + @runner.run(args) end assert_empty @ui.error diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb index 48f6b09715..3290a3a908 100644 --- a/test/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/rubygems/test_gem_gemcutter_utilities.rb @@ -32,7 +32,7 @@ class TestGemGemcutterUtilities < Gem::TestCase def test_alternate_key_alternate_host keys = { :rubygems_api_key => 'KEY', - "http://rubygems.engineyard.com" => "EYKEY" + "http://rubygems.engineyard.com" => "EYKEY", } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path @@ -202,7 +202,7 @@ class TestGemGemcutterUtilities < Gem::TestCase assert_match 'You have enabled multi-factor authentication. Please enter OTP code.', @sign_in_ui.output assert_match 'Code: ', @sign_in_ui.output - assert_match 'Signed in.', @sign_in_ui.output + assert_match 'Signed in with API key:', @sign_in_ui.output assert_equal '111111', @fetcher.last_request['OTP'] end @@ -233,7 +233,7 @@ class TestGemGemcutterUtilities < Gem::TestCase @fetcher.data["#{host}/api/v1/api_key"] = response Gem::RemoteFetcher.fetcher = @fetcher - @sign_in_ui = Gem::MockGemUi.new("#{email}\n#{password}\n" + extra_input) + @sign_in_ui = Gem::MockGemUi.new("#{email}\n#{password}\n\n\n\n\n\n\n\n\n" + extra_input) use_ui @sign_in_ui do if args.length > 0 diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb index d9da22b129..b4528dba17 100644 --- a/test/rubygems/test_gem_install_update_options.rb +++ b/test/rubygems/test_gem_install_update_options.rb @@ -30,7 +30,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase args.concat %w[--vendor] unless Gem.java_platform? - args.concat %w[-P HighSecurity] if defined?(OpenSSL::SSL) + args.concat %w[-P HighSecurity] if Gem::HAVE_OPENSSL assert @cmd.handles?(args) end @@ -92,7 +92,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase end def test_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @cmd.handle_options %w[-P HighSecurity] @@ -100,7 +100,7 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase end def test_security_policy_unknown - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @cmd.add_install_update_options @@ -192,4 +192,16 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase assert_equal true, @cmd.options[:post_install_message] end + + def test_minimal_deps_no + @cmd.handle_options %w[--no-minimal-deps] + + assert_equal false, @cmd.options[:minimal_deps] + end + + def test_minimal_deps + @cmd.handle_options %w[--minimal-deps] + + assert_equal true, @cmd.options[:minimal_deps] + end end diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index e984c7079c..0a9a2e38dd 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -83,7 +83,7 @@ end end def test_check_executable_overwrite_default_bin_dir - installer = setup_base_installer + installer = setup_base_installer(false) bindir(Gem.bindir) do util_conflict_executable false @@ -143,7 +143,7 @@ gem 'other', version end def test_check_executable_overwrite_other_gem - installer = setup_base_installer + installer = setup_base_installer(false) util_conflict_executable true @@ -287,7 +287,7 @@ gem 'other', version end def test_ensure_loadable_spec_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL _, a_gem = util_gem 'a', 2 do |s| s.add_dependency 'garbage ~> 5' @@ -345,7 +345,7 @@ gem 'other', version options = { :bin_dir => bin_dir, - :install_dir => "/non/existent" + :install_dir => "/non/existent", } inst = Gem::Installer.at '', options @@ -794,10 +794,35 @@ gem 'other', version assert_equal spec, installer.install - assert !File.exist?(system_path), 'plugin not written to user plugins_dir' + assert !File.exist?(system_path), 'plugin incorrectly written to system plugins_dir' assert File.exist?(user_path), 'plugin not written to user plugins_dir' end + def test_generate_plugins_with_build_root + spec = quick_gem 'a' do |s| + write_file File.join(@tempdir, 'lib', 'rubygems_plugin.rb') do |io| + io.write "puts __FILE__" + end + + s.files += %w[lib/rubygems_plugin.rb] + end + + util_build_gem spec + + File.chmod(0555, Gem.plugindir) + system_path = File.join(Gem.plugindir, 'a_plugin.rb') + + build_root = File.join(@tempdir, 'build_root') + build_root_path = File.join(build_root, Gem.plugindir.gsub(/^[a-zA-Z]:/, ''), 'a_plugin.rb') + + installer = Gem::Installer.at spec.cache_file, :build_root => build_root + + assert_equal spec, installer.install + + assert !File.exist?(system_path), 'plugin written incorrect written to system plugins_dir' + assert File.exist?(build_root_path), 'plugin not written to build_root' + end + def test_keeps_plugins_up_to_date # NOTE: version a-2 is already installed by setup hooks @@ -1134,7 +1159,7 @@ gem 'other', version Gem::Package.build @spec end end - installer = Gem::Installer.at @gem + installer = Gem::Installer.at @gem, :force => true build_rake_in do use_ui @ui do assert_equal @spec, installer.install @@ -1156,6 +1181,15 @@ gem 'other', version assert_path_exists gem_dir end + def test_install_build_root + build_root = File.join(@tempdir, 'build_root') + + @gem = setup_base_gem + installer = Gem::Installer.at @gem, :build_root => build_root + + assert_equal @spec, installer.install + end + def test_install_missing_dirs installer = setup_base_installer @@ -1337,7 +1371,7 @@ gem 'other', version # reinstall the gem, this is also the same as pristine use_ui @ui do - installer = Gem::Installer.at path + installer = Gem::Installer.at path, :force => true installer.install end @@ -1537,6 +1571,7 @@ gem 'other', version installer = setup_base_installer @spec.add_dependency 'b', '> 5' installer = util_setup_gem + installer.force = false use_ui @ui do assert_raises Gem::InstallError do @@ -1781,13 +1816,24 @@ gem 'other', version def test_process_options_build_root build_root = File.join @tempdir, 'build_root' + bin_dir = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ''), 'bin') + gem_home = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, '')) + plugins_dir = File.join(build_root, @gemhome.gsub(/^[a-zA-Z]:/, ''), 'plugins') @gem = setup_base_gem - installer = Gem::Installer.at @gem, :build_root => build_root + installer = use_ui(@ui) { Gem::Installer.at @gem, :build_root => build_root } - assert_equal Pathname(build_root), installer.build_root - assert_equal File.join(build_root, @gemhome, 'bin'), installer.bin_dir - assert_equal File.join(build_root, @gemhome), installer.gem_home + assert_equal build_root, installer.build_root + assert_equal bin_dir, installer.bin_dir + assert_equal gem_home, installer.gem_home + + errors = @ui.error.split("\n") + + assert_equal "WARNING: You build with buildroot.", errors.shift + assert_equal " Build root: #{build_root}", errors.shift + assert_equal " Bin dir: #{bin_dir}", errors.shift + assert_equal " Gem home: #{gem_home}", errors.shift + assert_equal " Plugins dir: #{plugins_dir}", errors.shift end def test_shebang_arguments @@ -2169,6 +2215,23 @@ gem 'other', version assert_equal ['exe/executable'], default_spec.files end + def test_default_gem_to_specific_install_dir + @gem = setup_base_gem + installer = util_installer @spec, "#{@gemhome}2" + installer.options[:install_as_default] = true + + use_ui @ui do + installer.install + end + + assert_directory_exists File.join("#{@gemhome}2", 'specifications') + assert_directory_exists File.join("#{@gemhome}2", 'specifications', 'default') + + default_spec = eval File.read File.join("#{@gemhome}2", 'specifications', 'default', 'a-2.gemspec') + assert_equal Gem::Version.new("2"), default_spec.version + assert_equal ['bin/executable'], default_spec.files + end + def test_package_attribute gem = quick_gem 'c' do |spec| util_make_exec spec, '#!/usr/bin/ruby', 'exe' diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 5e9c3b7b81..fd28f9a2a5 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -95,7 +95,7 @@ class TestGemPackage < Gem::Package::TarTestCase 'SHA256' => { 'metadata.gz' => metadata_sha256, 'data.tar.gz' => Digest::SHA256.hexdigest(tar), - } + }, } assert_equal expected, YAML.load(checksums) @@ -252,7 +252,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_auto_signed - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL FileUtils.mkdir_p File.join(Gem.user_home, '.gem') @@ -295,7 +295,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_auto_signed_encrypted_key - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL FileUtils.mkdir_p File.join(Gem.user_home, '.gem') @@ -364,7 +364,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_signed - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL spec = Gem::Specification.new 'build', '1' spec.summary = 'build' @@ -401,7 +401,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_build_signed_encrypted_key - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL spec = Gem::Specification.new 'build', '1' spec.summary = 'build' @@ -957,7 +957,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL package = Gem::Package.new @gem package.security_policy = Gem::Security::HighSecurity @@ -974,7 +974,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy_low_security - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.signing_key = PRIVATE_KEY @@ -994,7 +994,7 @@ class TestGemPackage < Gem::Package::TarTestCase end def test_verify_security_policy_checksum_missing - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @spec.cert_chain = [PUBLIC_CERT.to_pem] @spec.signing_key = PRIVATE_KEY diff --git a/test/rubygems/test_gem_package_old.rb b/test/rubygems/test_gem_package_old.rb index f2b1719dc3..8c4c20006b 100644 --- a/test/rubygems/test_gem_package_old.rb +++ b/test/rubygems/test_gem_package_old.rb @@ -23,7 +23,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_contents_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity @@ -44,7 +44,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_extract_files_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity @@ -58,7 +58,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_spec_security_policy - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL @package.security_policy = Gem::Security::AlmostNoSecurity @@ -68,7 +68,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file end def test_verify - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL assert @package.verify diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb index e31efdd55f..25dac5f148 100644 --- a/test/rubygems/test_gem_package_tar_writer.rb +++ b/test/rubygems/test_gem_package_tar_writer.rb @@ -117,7 +117,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase end def test_add_file_signer - skip 'openssl is missing' unless defined?(OpenSSL::SSL) + skip 'openssl is missing' unless Gem::HAVE_OPENSSL signer = Gem::Security::Signer.new PRIVATE_KEY, [PUBLIC_CERT] diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb index 030033d2af..83a6f24de4 100644 --- a/test/rubygems/test_gem_platform.rb +++ b/test/rubygems/test_gem_platform.rb @@ -11,10 +11,69 @@ class TestGemPlatform < Gem::TestCase end def test_self_match - assert Gem::Platform.match(nil), 'nil == ruby' - assert Gem::Platform.match(Gem::Platform.local), 'exact match' - assert Gem::Platform.match(Gem::Platform.local.to_s), '=~ match' - assert Gem::Platform.match(Gem::Platform::RUBY), 'ruby' + Gem::Deprecate.skip_during do + assert Gem::Platform.match(nil), 'nil == ruby' + assert Gem::Platform.match(Gem::Platform.local), 'exact match' + assert Gem::Platform.match(Gem::Platform.local.to_s), '=~ match' + assert Gem::Platform.match(Gem::Platform::RUBY), 'ruby' + end + end + + def test_self_match_gem? + assert Gem::Platform.match_gem?(nil, 'json'), 'nil == ruby' + assert Gem::Platform.match_gem?(Gem::Platform.local, 'json'), 'exact match' + assert Gem::Platform.match_gem?(Gem::Platform.local.to_s, 'json'), '=~ match' + assert Gem::Platform.match_gem?(Gem::Platform::RUBY, 'json'), 'ruby' + end + + def test_self_match_spec? + make_spec = -> platform do + util_spec 'mygem-for-platform-match_spec', '1' do |s| + s.platform = platform + end + end + + assert Gem::Platform.match_spec?(make_spec.call(nil)), 'nil == ruby' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform.local)), 'exact match' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform.local.to_s)), '=~ match' + assert Gem::Platform.match_spec?(make_spec.call(Gem::Platform::RUBY)), 'ruby' + end + + def test_self_match_spec_with_match_gem_override + make_spec = -> name, platform do + util_spec name, '1' do |s| + s.platform = platform + end + end + + class << Gem::Platform + alias_method :original_match_gem?, :match_gem? + def match_gem?(platform, gem_name) + # e.g., sassc and libv8 are such gems, their native extensions do not use the Ruby C API + if gem_name == 'gem-with-ruby-impl-independent-precompiled-ext' + match_platforms?(platform, [Gem::Platform::RUBY, Gem::Platform.local]) + else + match_platforms?(platform, Gem.platforms) + end + end + end + + platforms = Gem.platforms + Gem.platforms = [Gem::Platform::RUBY] + begin + assert_equal true, Gem::Platform.match_spec?(make_spec.call('mygem', Gem::Platform::RUBY)) + assert_equal false, Gem::Platform.match_spec?(make_spec.call('mygem', Gem::Platform.local)) + + name = 'gem-with-ruby-impl-independent-precompiled-ext' + assert_equal true, Gem::Platform.match_spec?(make_spec.call(name, Gem::Platform.local)) + ensure + Gem.platforms = platforms + class << Gem::Platform + remove_method :match_gem? + alias_method :match_gem?, :original_match_gem? # rubocop:disable Lint/DuplicateMethods + remove_method :original_match_gem? + end + end end def test_self_new diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb index f6ca00589f..371998497b 100644 --- a/test/rubygems/test_gem_remote_fetcher.rb +++ b/test/rubygems/test_gem_remote_fetcher.rb @@ -2,13 +2,9 @@ require 'rubygems/test_case' require 'webrick' -begin - require 'webrick/https' -rescue LoadError => e - raise unless e.path == 'openssl' -end +require 'webrick/https' if Gem::HAVE_OPENSSL -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::RemoteFetcher tests. openssl not found.' end @@ -667,7 +663,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_config_creds Gem.configuration[:s3_source] = { - 'my-bucket' => {:id => 'testuser', :secret => 'testpass'} + 'my-bucket' => {:id => 'testuser', :secret => 'testpass'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -679,7 +675,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_config_creds_with_region Gem.configuration[:s3_source] = { - 'my-bucket' => {:id => 'testuser', :secret => 'testpass', :region => 'us-west-2'} + 'my-bucket' => {:id => 'testuser', :secret => 'testpass', :region => 'us-west-2'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -691,7 +687,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_config_creds_with_token Gem.configuration[:s3_source] = { - 'my-bucket' => {:id => 'testuser', :secret => 'testpass', :security_token => 'testtoken'} + 'my-bucket' => {:id => 'testuser', :secret => 'testpass', :security_token => 'testtoken'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -706,7 +702,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== ENV['AWS_SECRET_ACCESS_KEY'] = 'testpass' ENV['AWS_SESSION_TOKEN'] = nil Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'env'} + 'my-bucket' => {:provider => 'env'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -722,7 +718,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== ENV['AWS_SECRET_ACCESS_KEY'] = 'testpass' ENV['AWS_SESSION_TOKEN'] = nil Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'env', :region => 'us-west-2'} + 'my-bucket' => {:provider => 'env', :region => 'us-west-2'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -738,7 +734,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== ENV['AWS_SECRET_ACCESS_KEY'] = 'testpass' ENV['AWS_SESSION_TOKEN'] = 'testtoken' Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'env'} + 'my-bucket' => {:provider => 'env'}, } url = 's3://my-bucket/gems/specs.4.8.gz' Time.stub :now, Time.at(1561353581) do @@ -758,7 +754,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_instance_profile_creds Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'instance_profile'} + 'my-bucket' => {:provider => 'instance_profile'}, } url = 's3://my-bucket/gems/specs.4.8.gz' @@ -772,7 +768,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_instance_profile_creds_with_region Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'instance_profile', :region => 'us-west-2'} + 'my-bucket' => {:provider => 'instance_profile', :region => 'us-west-2'}, } url = 's3://my-bucket/gems/specs.4.8.gz' @@ -786,7 +782,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_instance_profile_creds_with_token Gem.configuration[:s3_source] = { - 'my-bucket' => {:provider => 'instance_profile'} + 'my-bucket' => {:provider => 'instance_profile'}, } url = 's3://my-bucket/gems/specs.4.8.gz' @@ -816,7 +812,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def test_fetch_s3_no_host Gem.configuration[:s3_source] = { - 'my-bucket' => {:id => 'testuser', :secret => 'testpass'} + 'my-bucket' => {:id => 'testuser', :secret => 'testpass'}, } url = 's3://other-bucket/gems/specs.4.8.gz' @@ -1062,7 +1058,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== :SSLCertificate => cert('ssl_cert.pem'), :SSLPrivateKey => key('ssl_key.pem'), :SSLVerifyClient => nil, - :SSLCertName => nil + :SSLCertName => nil, }.merge(config)) server.mount_proc("/yaml") do |req, res| res.body = "--- true\n" @@ -1145,4 +1141,4 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg== def key(filename) OpenSSL::PKey::RSA.new(File.read(File.join(__dir__, filename))) end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb index 82b95b47c3..47e5f97074 100644 --- a/test/rubygems/test_gem_request.rb +++ b/test/rubygems/test_gem_request.rb @@ -4,7 +4,7 @@ require 'rubygems/request' require 'ostruct' require 'base64' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Request tests. openssl not found.' end @@ -506,4 +506,4 @@ ERROR: Certificate is an invalid CA certificate @response end end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem_request_set_lockfile.rb b/test/rubygems/test_gem_request_set_lockfile.rb index e7921d6b26..44a47f2e00 100644 --- a/test/rubygems/test_gem_request_set_lockfile.rb +++ b/test/rubygems/test_gem_request_set_lockfile.rb @@ -51,7 +51,7 @@ class TestGemRequestSetLockfile < Gem::TestCase expected = [ 'DEPENDENCIES', ' a', - nil + nil, ] assert_equal expected, out @@ -78,7 +78,7 @@ class TestGemRequestSetLockfile < Gem::TestCase expected = [ 'DEPENDENCIES', ' a (~> 2.0)', - nil + nil, ] assert_equal expected, out @@ -111,7 +111,7 @@ class TestGemRequestSetLockfile < Gem::TestCase ' a (2)', ' b', ' b (2)', - nil + nil, ] assert_equal expected, out @@ -139,7 +139,7 @@ class TestGemRequestSetLockfile < Gem::TestCase 'PLATFORMS', ' ruby', ' x86-darwin-8', - nil + nil, ] assert_equal expected, out diff --git a/test/rubygems/test_gem_resolver_api_set.rb b/test/rubygems/test_gem_resolver_api_set.rb index 6942b8587d..4b545b1b9a 100644 --- a/test/rubygems/test_gem_resolver_api_set.rb +++ b/test/rubygems/test_gem_resolver_api_set.rb @@ -39,7 +39,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data @@ -49,7 +49,7 @@ class TestGemResolverAPISet < Gem::TestCase a_dep = @DR::DependencyRequest.new dep('a'), nil expected = [ - @DR::APISpecification.new(set, data.first) + @DR::APISpecification.new(set, data.first), ] assert_equal expected, set.find_all(a_dep) @@ -62,7 +62,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data @@ -74,7 +74,7 @@ class TestGemResolverAPISet < Gem::TestCase set.prefetch [a_dep] expected = [ - @DR::APISpecification.new(set, data.first) + @DR::APISpecification.new(set, data.first), ] assert_equal expected, set.find_all(a_dep) @@ -114,7 +114,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a,b"] = Marshal.dump data @@ -138,7 +138,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data @@ -163,7 +163,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a,b"] = Marshal.dump data @@ -187,7 +187,7 @@ class TestGemResolverAPISet < Gem::TestCase { :name => 'a', :number => '1', :platform => 'ruby', - :dependencies => [], }, + :dependencies => [] }, ] @fetcher.data["#{@dep_uri}?gems=a,b"] = Marshal.dump data diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb index 5a3ed80670..ff5fe9bae3 100644 --- a/test/rubygems/test_gem_resolver_conflict.rb +++ b/test/rubygems/test_gem_resolver_conflict.rb @@ -73,7 +73,7 @@ class TestGemResolverConflict < Gem::TestCase expected = [ 'net-ssh (>= 2.0.13), 2.2.2 activated', - 'rye (= 0.9.8), 0.9.8 activated' + 'rye (= 0.9.8), 0.9.8 activated', ] assert_equal expected, conflict.request_path(child.requester) diff --git a/test/rubygems/test_gem_resolver_vendor_set.rb b/test/rubygems/test_gem_resolver_vendor_set.rb index 99e2c5ae5f..d14b877b45 100644 --- a/test/rubygems/test_gem_resolver_vendor_set.rb +++ b/test/rubygems/test_gem_resolver_vendor_set.rb @@ -51,7 +51,7 @@ class TestGemResolverVendorSet < Gem::TestCase source = Gem::Source::Vendor.new directory expected = [ - Gem::Resolver::VendorSpecification.new(@set, spec, source) + Gem::Resolver::VendorSpecification.new(@set, spec, source), ] assert_equal expected, found diff --git a/test/rubygems/test_gem_security.rb b/test/rubygems/test_gem_security.rb index 4d07887d36..92f9e55b21 100644 --- a/test/rubygems/test_gem_security.rb +++ b/test/rubygems/test_gem_security.rb @@ -2,7 +2,7 @@ require 'rubygems/test_case' require 'rubygems/security' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security tests. openssl not found.' end @@ -309,4 +309,4 @@ class TestGemSecurity < Gem::TestCase assert_equal key.to_pem, key_from_file.to_pem end -end if defined?(OpenSSL::SSL) && !Gem.java_platform? +end if Gem::HAVE_OPENSSL && !Gem.java_platform? diff --git a/test/rubygems/test_gem_security_policy.rb b/test/rubygems/test_gem_security_policy.rb index 86100d7c74..85e4590655 100644 --- a/test/rubygems/test_gem_security_policy.rb +++ b/test/rubygems/test_gem_security_policy.rb @@ -2,7 +2,7 @@ require 'rubygems/test_case' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::Policy tests. openssl not found.' end @@ -532,4 +532,4 @@ class TestGemSecurityPolicy < Gem::TestCase return digests, signatures end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem_security_signer.rb b/test/rubygems/test_gem_security_signer.rb index 050748a8b5..8a09f97f26 100644 --- a/test/rubygems/test_gem_security_signer.rb +++ b/test/rubygems/test_gem_security_signer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'rubygems/test_case' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::Signer tests. openssl not found.' end @@ -214,4 +214,4 @@ toqvglr0kdbknSRRjBVLK6tsgr07aLT9gNP7mTW2PA== signer.sign 'hello' end end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem_security_trust_dir.rb b/test/rubygems/test_gem_security_trust_dir.rb index 64871f7bd3..201de9d36b 100644 --- a/test/rubygems/test_gem_security_trust_dir.rb +++ b/test/rubygems/test_gem_security_trust_dir.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'rubygems/test_case' -unless defined?(OpenSSL::SSL) +unless Gem::HAVE_OPENSSL warn 'Skipping Gem::Security::TrustDir tests. openssl not found.' end @@ -95,4 +95,4 @@ class TestGemSecurityTrustDir < Gem::TestCase assert_equal mask, File.stat(@dest_dir).mode unless win_platform? end -end if defined?(OpenSSL::SSL) +end if Gem::HAVE_OPENSSL diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb index 599d490d95..09c510f096 100644 --- a/test/rubygems/test_gem_source.rb +++ b/test/rubygems/test_gem_source.rb @@ -240,6 +240,11 @@ class TestGemSource < Gem::TestCase refute rubygems_source.typo_squatting?("rubysertgems.org") end + def test_typo_squatting_false_positive + rubygems_source = Gem::Source.new("https://rubygems.org") + refute rubygems_source.typo_squatting?("rubygems.org") + end + def test_typo_squatting_custom_distance_threshold rubygems_source = Gem::Source.new("https://rubgems.org") distance_threshold = 5 diff --git a/test/rubygems/test_gem_source_subpath_problem.rb b/test/rubygems/test_gem_source_subpath_problem.rb new file mode 100644 index 0000000000..eb92b7d404 --- /dev/null +++ b/test/rubygems/test_gem_source_subpath_problem.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require 'rubygems/test_case' +require 'rubygems/source' + +class TestGemSourceSubpathProblem < Gem::TestCase + def tuple(*args) + Gem::NameTuple.new(*args) + end + + def setup + super + + @gem_repo = "http://gems.example.com/private" + + spec_fetcher + + @source = Gem::Source.new(@gem_repo) + + util_make_gems + end + + def test_dependency_resolver_set + response = Net::HTTPResponse.new '1.1', 200, 'OK' + response.uri = URI('http://example') if response.respond_to? :uri + + @fetcher.data["#{@gem_repo}/api/v1/dependencies"] = response + + set = @source.dependency_resolver_set + + assert_kind_of Gem::Resolver::APISet, set + end + + def test_fetch_spec + @fetcher.data["#{@gem_repo}/#{Gem::MARSHAL_SPEC_DIR}#{@a1.spec_name}.rz"] = Zlib::Deflate.deflate(Marshal.dump(@a1)) + + spec = @source.fetch_spec tuple('a', Gem::Version.new(1), 'ruby') + assert_equal @a1.full_name, spec.full_name + end + + def test_load_specs + @fetcher.data["#{@gem_repo}/latest_specs.#{Gem.marshal_version}.gz"] = util_gzip(Marshal.dump([ + Gem::NameTuple.new(@a1.name, @a1.version, 'ruby'), + Gem::NameTuple.new(@b2.name, @b2.version, 'ruby'), + ])) + + released = @source.load_specs(:latest).map {|spec| spec.full_name } + assert_equal %W[a-1 b-2], released + end +end diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index f635be859b..76072184bd 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'benchmark' require 'rubygems/test_case' +require 'date' require 'pathname' require 'stringio' require 'rubygems/ext' @@ -1999,7 +2000,7 @@ dependencies: [] test_cases = { 'i386-mswin32' => 'a-1-x86-mswin32-60', 'i386-mswin32_80' => 'a-1-x86-mswin32-80', - 'i386-mingw32' => 'a-1-x86-mingw32' + 'i386-mingw32' => 'a-1-x86-mingw32', } test_cases.each do |arch, expected| @@ -3035,7 +3036,7 @@ Please report a bug if this causes problems. specification.define_singleton_method(:find_all_by_name) do |dep_name| [ specification.new {|s| s.name = "z", s.version = Gem::Version.new("1") }, - specification.new {|s| s.name = "z", s.version = Gem::Version.new("2") } + specification.new {|s| s.name = "z", s.version = Gem::Version.new("2") }, ] end @@ -3578,7 +3579,7 @@ Did you mean 'Ruby'? "one" => "two", "home" => "three", "homepage_uri" => "https://example.com/user/repo", - "funding_uri" => "https://example.com/donate" + "funding_uri" => "https://example.com/donate", } end @@ -3838,7 +3839,7 @@ end default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - spec_path = File.join(@default_spec_dir, default_gem_spec.spec_name) + spec_path = File.join(@gemhome, "specifications", "default", default_gem_spec.spec_name) write_file(spec_path) do |file| file.print(default_gem_spec.to_ruby) end diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb index 1c084c4c5d..1ca4991d1e 100644 --- a/test/rubygems/test_gem_uninstaller.rb +++ b/test/rubygems/test_gem_uninstaller.rb @@ -177,7 +177,7 @@ class TestGemUninstaller < Gem::InstallerTestCase @spec.files += %w[lib/rubygems_plugin.rb] - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install plugin_path = File.join Gem.plugindir, 'a_plugin.rb' assert File.exist?(plugin_path), 'plugin not written' @@ -194,7 +194,7 @@ class TestGemUninstaller < Gem::InstallerTestCase @spec.files += %w[lib/rubygems_plugin.rb] - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install plugin_path = File.join Gem.plugindir, 'a_plugin.rb' assert File.exist?(plugin_path), 'plugin not written' @@ -212,7 +212,7 @@ class TestGemUninstaller < Gem::InstallerTestCase @spec.files += %w[lib/rubygems_plugin.rb] - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install plugin_path = File.join Gem.plugindir, 'a_plugin.rb' assert File.exist?(plugin_path), 'plugin not written' @@ -314,7 +314,7 @@ create_makefile '#{@spec.name}' use_ui @ui do path = Gem::Package.build @spec - installer = Gem::Installer.at path + installer = Gem::Installer.at path, :force => true installer.install end @@ -633,19 +633,19 @@ create_makefile '#{@spec.name}' plugin_path = File.join Gem.plugindir, 'a_plugin.rb' @spec.version = '1' - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install refute File.exist?(plugin_path), 'version without plugin installed, but plugin written' @spec.files += %w[lib/rubygems_plugin.rb] @spec.version = '2' - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install assert File.exist?(plugin_path), 'version with plugin installed, but plugin not written' assert_match %r{\Arequire.*a-2/lib/rubygems_plugin\.rb}, File.read(plugin_path), 'written plugin has incorrect content' @spec.version = '3' - Gem::Installer.at(Gem::Package.build(@spec)).install + Gem::Installer.at(Gem::Package.build(@spec), :force => true).install assert File.exist?(plugin_path), 'version with plugin installed, but plugin removed' assert_match %r{\Arequire.*a-3/lib/rubygems_plugin\.rb}, File.read(plugin_path), 'old version installed, but plugin updated' diff --git a/test/rubygems/test_gem_validator.rb b/test/rubygems/test_gem_validator.rb index d4159d59e2..5158543fa9 100644 --- a/test/rubygems/test_gem_validator.rb +++ b/test/rubygems/test_gem_validator.rb @@ -26,7 +26,7 @@ class TestGemValidator < Gem::TestCase @spec.file_name => [ Gem::Validator::ErrorData.new('lib/b.rb', 'Missing file'), Gem::Validator::ErrorData.new('lib/c.rb', 'Extra file'), - ] + ], } assert_equal expected, alien diff --git a/test/rubygems/test_gem_version_option.rb b/test/rubygems/test_gem_version_option.rb index 396fc6277c..49a8513dbe 100644 --- a/test/rubygems/test_gem_version_option.rb +++ b/test/rubygems/test_gem_version_option.rb @@ -56,7 +56,7 @@ class TestGemVersionOption < Gem::TestCase @cmd.handle_options %w[--platform ruby] expected = [ - Gem::Platform::RUBY + Gem::Platform::RUBY, ] assert_equal expected, Gem.platforms diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb index 2b6620cc65..8c7d06edc3 100644 --- a/test/rubygems/test_require.rb +++ b/test/rubygems/test_require.rb @@ -120,7 +120,7 @@ class TestGemRequire < Gem::TestCase c1 = new_default_spec "c", "1", nil, "c/c.rb" c2 = new_default_spec "c", "2", nil, "c/c.rb" - install_default_specs c1, c2, b1, a1 + install_default_gems c1, c2, b1, a1 dir = Dir.mktmpdir("test_require", @tempdir) dash_i_arg = File.join dir, 'lib' @@ -405,8 +405,8 @@ class TestGemRequire < Gem::TestCase # Remove an old default gem version directly from disk as if someone ran # gem cleanup. - FileUtils.rm_rf(File.join @default_dir, "#{b1.full_name}") - FileUtils.rm_rf(File.join @default_spec_dir, "#{b1.full_name}.gemspec") + FileUtils.rm_rf(File.join @gemhome, "#{b1.full_name}") + FileUtils.rm_rf(File.join @gemhome, "specifications", "default", "#{b1.full_name}.gemspec") # Require gems that have not been removed. assert_require 'a/b' @@ -433,7 +433,7 @@ class TestGemRequire < Gem::TestCase def test_default_gem_only default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) assert_require "default/gem" assert_equal %w[default-2.0.0.0], loaded_spec_names end @@ -441,7 +441,7 @@ class TestGemRequire < Gem::TestCase def test_default_gem_require_activates_just_once default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) assert_require "default/gem" @@ -506,7 +506,7 @@ class TestGemRequire < Gem::TestCase def test_default_gem_and_normal_gem default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) normal_gem_spec = util_spec("default", "3.0", nil, "lib/default/gem.rb") install_specs(normal_gem_spec) @@ -544,11 +544,11 @@ class TestGemRequire < Gem::TestCase def test_default_gem_prerelease default_gem_spec = new_default_spec("default", "2.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) normal_gem_higher_prerelease_spec = util_spec("default", "3.0.0.rc2", nil, "lib/default/gem.rb") - install_default_specs(normal_gem_higher_prerelease_spec) + install_default_gems(normal_gem_higher_prerelease_spec) assert_require "default/gem" assert_equal %w[default-3.0.0.rc2], loaded_spec_names @@ -586,7 +586,7 @@ class TestGemRequire < Gem::TestCase def test_require_when_gem_defined default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") - install_default_specs(default_gem_spec) + install_default_gems(default_gem_spec) c = Class.new do def self.gem(*args) raise "received #gem with #{args.inspect}" @@ -677,6 +677,47 @@ class TestGemRequire < Gem::TestCase end end end + + def test_no_crash_when_overriding_warn_with_warning_module + skip "https://github.com/oracle/truffleruby/issues/2109" if RUBY_ENGINE == "truffleruby" + + Dir.mktmpdir("warn_test") do |dir| + File.write(dir + "/main.rb", "module Warning; def warn(str); super; end; end; warn 'Foo Bar'") + _, err = capture_subprocess_io do + system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "main.rb") + end + assert_match(/Foo Bar\n$/, err) + _, err = capture_subprocess_io do + system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "main.rb") + end + assert_match(/Foo Bar\n$/, err) + end + end + + def test_expected_backtrace_location_when_inheriting_from_basic_object_and_including_kernel + Dir.mktmpdir("warn_test") do |dir| + File.write(dir + "/main.rb", "\nrequire 'sub'\n") + File.write(dir + "/sub.rb", <<-'RUBY') + require 'rubygems' + class C < BasicObject + include ::Kernel + def deprecated + warn "This is a deprecated method", uplevel: 2 + end + end + C.new.deprecated + RUBY + + _, err = capture_subprocess_io do + system(*ruby_with_rubygems_in_load_path, "-w", "--disable=gems", "-C", dir, "-I", dir, "main.rb") + end + assert_match(/main\.rb:2: warning: This is a deprecated method$/, err) + _, err = capture_subprocess_io do + system(*ruby_with_rubygems_in_load_path, "-w", "--enable=gems", "-C", dir, "-I", dir, "main.rb") + end + assert_match(/main\.rb:2: warning: This is a deprecated method$/, err) + end + end end private