aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2020-12-08 16:33:39 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2020-12-08 17:30:02 +0900
commit4aca77edde91f826aa243e268bf1ef5214530583 (patch)
treeef0cf1a95fcced00ca5fa40f3412c567bf95d705 /lib/rubygems
parent6a6a24df9b72750d12f9b15192bdb7517e668efb (diff)
downloadruby-4aca77edde91f826aa243e268bf1ef5214530583.tar.gz
Merge prepare version of RubyGems 3.2.0
Diffstat (limited to 'lib/rubygems')
-rw-r--r--lib/rubygems/available_set.rb2
-rw-r--r--lib/rubygems/command_manager.rb4
-rw-r--r--lib/rubygems/commands/build_command.rb57
-rw-r--r--lib/rubygems/commands/cert_command.rb2
-rw-r--r--lib/rubygems/commands/help_command.rb2
-rw-r--r--lib/rubygems/commands/owner_command.rb12
-rw-r--r--lib/rubygems/commands/pristine_command.rb2
-rw-r--r--lib/rubygems/commands/push_command.rb10
-rw-r--r--lib/rubygems/commands/query_command.rb17
-rw-r--r--lib/rubygems/commands/server_command.rb4
-rw-r--r--lib/rubygems/commands/setup_command.rb70
-rw-r--r--lib/rubygems/commands/sources_command.rb8
-rw-r--r--lib/rubygems/commands/specification_command.rb6
-rw-r--r--lib/rubygems/commands/yank_command.rb8
-rw-r--r--lib/rubygems/core_ext/kernel_require.rb3
-rw-r--r--lib/rubygems/core_ext/kernel_warn.rb13
-rw-r--r--lib/rubygems/defaults.rb4
-rw-r--r--lib/rubygems/dependency.rb2
-rw-r--r--lib/rubygems/dependency_installer.rb7
-rw-r--r--lib/rubygems/ext/builder.rb45
-rw-r--r--lib/rubygems/ext/cmake_builder.rb8
-rw-r--r--lib/rubygems/ext/configure_builder.rb8
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb33
-rw-r--r--lib/rubygems/ext/rake_builder.rb6
-rw-r--r--lib/rubygems/gemcutter_utilities.rb104
-rw-r--r--lib/rubygems/indexer.rb1
-rw-r--r--lib/rubygems/install_update_options.rb4
-rw-r--r--lib/rubygems/installer.rb21
-rw-r--r--lib/rubygems/installer_test_case.rb13
-rw-r--r--lib/rubygems/name_tuple.rb2
-rw-r--r--lib/rubygems/openssl.rb8
-rw-r--r--lib/rubygems/package.rb5
-rw-r--r--lib/rubygems/package/tar_header.rb2
-rw-r--r--lib/rubygems/package/tar_test_case.rb2
-rw-r--r--lib/rubygems/platform.rb25
-rw-r--r--lib/rubygems/query_utils.rb9
-rw-r--r--lib/rubygems/remote_fetcher.rb3
-rw-r--r--lib/rubygems/request.rb7
-rw-r--r--lib/rubygems/request_set/gem_dependency_api.rb8
-rw-r--r--lib/rubygems/requirement.rb2
-rw-r--r--lib/rubygems/resolver.rb2
-rw-r--r--lib/rubygems/resolver/activation_request.rb10
-rw-r--r--lib/rubygems/resolver/api_specification.rb6
-rw-r--r--lib/rubygems/resolver/conflict.rb2
-rw-r--r--lib/rubygems/resolver/dependency_request.rb2
-rw-r--r--lib/rubygems/resolver/index_specification.rb11
-rw-r--r--lib/rubygems/resolver/installer_set.rb3
-rw-r--r--lib/rubygems/resolver/lock_set.rb2
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo.rb11
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/delegates/resolution_state.rb7
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/delegates/specification_provider.rb1
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb44
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action.rb1
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/add_vertex.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/delete_edge.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/log.rb13
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/set_payload.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/tag.rb7
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph/vertex.rb53
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/errors.rb82
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/gem_metadata.rb3
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/modules/specification_provider.rb1
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/modules/ui.rb4
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/resolution.rb671
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/resolver.rb5
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/state.rb12
-rw-r--r--lib/rubygems/resolver/specification.rb2
-rw-r--r--lib/rubygems/s3_uri_signer.rb2
-rw-r--r--lib/rubygems/security.rb3
-rw-r--r--lib/rubygems/security/policy.rb2
-rw-r--r--lib/rubygems/security/signer.rb2
-rw-r--r--lib/rubygems/server.rb2
-rw-r--r--lib/rubygems/source.rb14
-rw-r--r--lib/rubygems/spec_fetcher.rb2
-rw-r--r--lib/rubygems/specification.rb16
-rw-r--r--lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem23
-rw-r--r--lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem (renamed from lib/rubygems/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem)0
-rw-r--r--lib/rubygems/stub_specification.rb2
-rw-r--r--lib/rubygems/test_case.rb39
-rw-r--r--lib/rubygems/uri_formatter.rb3
-rw-r--r--lib/rubygems/version_option.rb6
83 files changed, 1124 insertions, 511 deletions
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)
- Dir.chdir dir do
- Dir[File.join('**', '*pem')]
- end
- end
-
- def rb_files_in(dir)
+ def 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 ? "<internal:#{__FILE__}>" : __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)
-
- verbose { results.join("\n") }
- ensure
- begin
- Dir.chdir pwd
- rescue SystemCallError
- Dir.chdir dest_path
- end
- end
- end
+ results = builder.build(extension, dest_path,
+ results, @build_args, lib_dir, extension_dir)
+
+ 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<Vertex>] 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<Object>] 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<Edge>] the edges of {#graph} that have `self` as their
@@ -49,14 +50,25 @@ module Gem::Resolver::Molinillo
incoming_edges.map(&:origin)
end
- # @return [Array<Vertex>] the vertices of {#graph} where `self` is a
+ # @return [Set<Vertex>] 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<Vertex>] vertices the set to add the predecessors to
+ # @return [Set<Vertex>] 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<Vertex>] 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<Vertex>] the vertices of {#graph} where `self` is an
+ # @return [Set<Vertex>] 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<Vertex>] vertices the set to add the successors to
+ # @return [Set<Vertex>] 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<Vertex>] 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<Object>] 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<DependencyGraph::Vertex>] nodes the nodes in the dependency
+ # @param [Array<DependencyGraph::Vertex>] 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<Array<Object>>] 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
+
+ # 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<Object>] binding_requirements array of requirements that combine to create a conflict
+ # @return [Array<UnwindDetails>] 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
+ parent_r = grandparent_r
+ grandparent_r = parent_of(parent_r)
+ end
+ end
+
+ 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
- # @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
+ # @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
- r = parent_of(r)
end
end
- index
+ 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<Object>] 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
- end
- end
-
- # Attempts to activate the current {#possibility} (given that it has
- # already been activated)
- # @return [void]
- def attempt_to_activate_existing_spec(existing_node)
- existing_spec = existing_node.payload
- if requirement_satisfied_by?(requirement, activated, existing_spec)
- 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})" }
- 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)
+ latest = possibility.latest_version
+ possibility.possibilities.select! do |possibility|
+ requirement_satisfied_by?(requirement, activated, possibility)
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)
+ 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 hasn't
- # already been activated)
+ # Attempts to update the existing vertex's `PossibilitySet` with a filtered version
# @return [void]
- def attempt_to_activate_new_spec
- if new_spec_satisfied?
- activate_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
create_conflict
+ debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
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<PossibilitySet>] 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<Object>] conflicts unresolved conflicts
+ # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name
+ # @attr [Array<UnwindDetails>] 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
index f4ce4ca43d..f4ce4ca43d 100644
--- a/lib/rubygems/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem
+++ b/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