mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Merge prepare version of RubyGems 3.2.0
This commit is contained in:
parent
6a6a24df9b
commit
4aca77edde
Notes:
git
2020-12-08 17:30:31 +09:00
134 changed files with 2002 additions and 745 deletions
|
@ -8,7 +8,7 @@
|
|||
require 'rbconfig'
|
||||
|
||||
module Gem
|
||||
VERSION = "3.2.0.rc.2".freeze
|
||||
VERSION = "3.2.0".freeze
|
||||
end
|
||||
|
||||
# Must be first since it unloads the prelude from 1.9.2
|
||||
|
@ -119,6 +119,10 @@ module Gem
|
|||
# to avoid deprecation warnings in Ruby 2.7.
|
||||
UNTAINT = RUBY_VERSION < '2.7' ? :untaint.to_sym : proc{}
|
||||
|
||||
# When https://bugs.ruby-lang.org/issues/17259 is available, there is no need to override Kernel#warn
|
||||
KERNEL_WARN_IGNORES_INTERNAL_ENTRIES = RUBY_ENGINE == "truffleruby" ||
|
||||
(RUBY_ENGINE == "ruby" && RUBY_VERSION >= '3.0')
|
||||
|
||||
##
|
||||
# An Array of Regexps that match windows Ruby platforms.
|
||||
|
||||
|
@ -975,7 +979,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
|
|||
val = RbConfig::CONFIG[key]
|
||||
next unless val and not val.empty?
|
||||
".#{val}"
|
||||
end
|
||||
end,
|
||||
].compact.uniq
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -322,13 +322,10 @@ By default, this RubyGems will install gem as:
|
|||
libs.each do |tool, path|
|
||||
say "Installing #{tool}" if @verbose
|
||||
|
||||
lib_files = rb_files_in path
|
||||
lib_files.concat(bundler_template_files) if tool == 'Bundler'
|
||||
|
||||
pem_files = pem_files_in path
|
||||
lib_files = files_in path
|
||||
|
||||
Dir.chdir path do
|
||||
install_file_list(lib_files + pem_files, lib_dir)
|
||||
install_file_list(lib_files, lib_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -394,10 +391,6 @@ By default, this RubyGems will install gem as:
|
|||
specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform?
|
||||
mkdir_p specs_dir, :mode => 0755
|
||||
|
||||
# Workaround for non-git environment.
|
||||
gemspec = File.open('bundler/bundler.gemspec', 'rb'){|f| f.read.gsub(/`git ls-files -z`/, "''") }
|
||||
File.open('bundler/bundler.gemspec', 'w'){|f| f.write gemspec }
|
||||
|
||||
bundler_spec = Gem::Specification.load("bundler/bundler.gemspec")
|
||||
bundler_spec.files = Dir.chdir("bundler") { Dir["{*.md,{lib,exe,man}/**/*}"] }
|
||||
bundler_spec.executables -= %w[bundler bundle_ruby]
|
||||
|
@ -518,44 +511,24 @@ By default, this RubyGems will install gem as:
|
|||
[lib_dir, bin_dir]
|
||||
end
|
||||
|
||||
def pem_files_in(dir)
|
||||
def files_in(dir)
|
||||
Dir.chdir dir do
|
||||
Dir[File.join('**', '*pem')]
|
||||
end
|
||||
end
|
||||
|
||||
def rb_files_in(dir)
|
||||
Dir.chdir dir do
|
||||
Dir[File.join('**', '*rb')]
|
||||
Dir.glob(File.join('**', '*'), File::FNM_DOTMATCH).
|
||||
select{|f| !File.directory?(f) }
|
||||
end
|
||||
end
|
||||
|
||||
# for installation of bundler as default gems
|
||||
def bundler_man1_files_in(dir)
|
||||
Dir.chdir dir do
|
||||
Dir['bundle*.1{,.txt,.ronn}']
|
||||
Dir['bundle*.1']
|
||||
end
|
||||
end
|
||||
|
||||
# for installation of bundler as default gems
|
||||
def bundler_man5_files_in(dir)
|
||||
Dir.chdir dir do
|
||||
Dir['gemfile.5{,.txt,.ronn}']
|
||||
end
|
||||
end
|
||||
|
||||
def bundler_template_files
|
||||
Dir.chdir "bundler/lib" do
|
||||
Dir.glob(File.join('bundler', 'templates', '**', '*'), File::FNM_DOTMATCH).
|
||||
select{|f| !File.directory?(f) }
|
||||
end
|
||||
end
|
||||
|
||||
# for cleanup old bundler files
|
||||
def template_files_in(dir)
|
||||
Dir.chdir dir do
|
||||
Dir.glob(File.join('templates', '**', '*'), File::FNM_DOTMATCH).
|
||||
select{|f| !File.directory?(f) }
|
||||
Dir['gemfile.5']
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -595,11 +568,9 @@ abort "#{deprecation_message}"
|
|||
lib_dirs = { File.join(lib_dir, 'rubygems') => 'lib/rubygems' }
|
||||
lib_dirs[File.join(lib_dir, 'bundler')] = 'bundler/lib/bundler'
|
||||
lib_dirs.each do |old_lib_dir, new_lib_dir|
|
||||
lib_files = rb_files_in(new_lib_dir)
|
||||
lib_files.concat(template_files_in(new_lib_dir)) if new_lib_dir =~ /bundler/
|
||||
lib_files = files_in(new_lib_dir)
|
||||
|
||||
old_lib_files = rb_files_in(old_lib_dir)
|
||||
old_lib_files.concat(template_files_in(old_lib_dir)) if old_lib_dir =~ /bundler/
|
||||
old_lib_files = files_in(old_lib_dir)
|
||||
|
||||
to_remove = old_lib_files - lib_files
|
||||
|
||||
|
@ -617,16 +588,25 @@ abort "#{deprecation_message}"
|
|||
def remove_old_man_files(man_dir)
|
||||
man_dirs = { man_dir => "bundler/man" }
|
||||
man_dirs.each do |old_man_dir, new_man_dir|
|
||||
["1", "5"].each do |section|
|
||||
man_files = send(:"bundler_man#{section}_files_in", new_man_dir)
|
||||
man1_files = bundler_man1_files_in(new_man_dir)
|
||||
|
||||
old_man_dir_with_section = "#{old_man_dir}/man#{section}"
|
||||
old_man_files = send(:"bundler_man#{section}_files_in", old_man_dir_with_section)
|
||||
old_man1_dir = "#{old_man_dir}/man1"
|
||||
old_man1_files = bundler_man1_files_in(old_man1_dir)
|
||||
old_man1_files += Dir.chdir(old_man1_dir) { Dir["bundle*.1.{txt,ronn}"] }
|
||||
|
||||
man_to_remove = old_man_files - man_files
|
||||
man1_to_remove = old_man1_files - man1_files
|
||||
|
||||
remove_file_list(man_to_remove, old_man_dir_with_section)
|
||||
end
|
||||
remove_file_list(man1_to_remove, old_man1_dir)
|
||||
|
||||
man5_files = bundler_man5_files_in(new_man_dir)
|
||||
|
||||
old_man5_dir = "#{old_man_dir}/man5"
|
||||
old_man5_files = bundler_man5_files_in(old_man5_dir)
|
||||
old_man5_files += Dir.chdir(old_man5_dir) { Dir["gemfile.5.{txt,ronn}"] }
|
||||
|
||||
man5_to_remove = old_man5_files - man5_files
|
||||
|
||||
remove_file_list(man5_to_remove, old_man5_dir)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,14 +10,6 @@ require_relative '../user_interaction'
|
|||
class Gem::Ext::Builder
|
||||
include Gem::UserInteraction
|
||||
|
||||
##
|
||||
# The builder shells-out to run various commands after changing the
|
||||
# directory. This means multiple installations cannot be allowed to build
|
||||
# extensions in parallel as they may change each other's directories leading
|
||||
# to broken extensions or failed installations.
|
||||
|
||||
CHDIR_MUTEX = Mutex.new # :nodoc:
|
||||
|
||||
attr_accessor :build_args # :nodoc:
|
||||
|
||||
def self.class_name
|
||||
|
@ -25,8 +17,8 @@ class Gem::Ext::Builder
|
|||
$1.downcase
|
||||
end
|
||||
|
||||
def self.make(dest_path, results)
|
||||
unless File.exist? 'Makefile'
|
||||
def self.make(dest_path, results, make_dir = Dir.pwd)
|
||||
unless File.exist? File.join(make_dir, 'Makefile')
|
||||
raise Gem::InstallError, 'Makefile not found'
|
||||
end
|
||||
|
||||
|
@ -44,32 +36,32 @@ class Gem::Ext::Builder
|
|||
cmd = [
|
||||
make_program,
|
||||
destdir,
|
||||
target
|
||||
target,
|
||||
].join(' ').rstrip
|
||||
begin
|
||||
run(cmd, results, "make #{target}".rstrip)
|
||||
run(cmd, results, "make #{target}".rstrip, make_dir)
|
||||
rescue Gem::InstallError
|
||||
raise unless target == 'clean' # ignore clean failure
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.run(command, results, command_name = nil)
|
||||
def self.run(command, results, command_name = nil, dir = Dir.pwd)
|
||||
verbose = Gem.configuration.really_verbose
|
||||
|
||||
begin
|
||||
rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
|
||||
if verbose
|
||||
puts("current directory: #{Dir.pwd}")
|
||||
puts("current directory: #{dir}")
|
||||
p(command)
|
||||
end
|
||||
results << "current directory: #{Dir.pwd}"
|
||||
results << "current directory: #{dir}"
|
||||
results << (command.respond_to?(:shelljoin) ? command.shelljoin : command)
|
||||
|
||||
require "open3"
|
||||
# Set $SOURCE_DATE_EPOCH for the subprocess.
|
||||
env = {'SOURCE_DATE_EPOCH' => Gem.source_date_epoch_string}
|
||||
output, status = Open3.capture2e(env, *command)
|
||||
output, status = Open3.capture2e(env, *command, :chdir => dir)
|
||||
if verbose
|
||||
puts output
|
||||
else
|
||||
|
@ -161,22 +153,10 @@ EOF
|
|||
begin
|
||||
FileUtils.mkdir_p dest_path
|
||||
|
||||
CHDIR_MUTEX.synchronize do
|
||||
pwd = Dir.getwd
|
||||
Dir.chdir extension_dir
|
||||
begin
|
||||
results = builder.build(extension, dest_path,
|
||||
results, @build_args, lib_dir)
|
||||
results = builder.build(extension, dest_path,
|
||||
results, @build_args, lib_dir, extension_dir)
|
||||
|
||||
verbose { results.join("\n") }
|
||||
ensure
|
||||
begin
|
||||
Dir.chdir pwd
|
||||
rescue SystemCallError
|
||||
Dir.chdir dest_path
|
||||
end
|
||||
end
|
||||
end
|
||||
verbose { results.join("\n") }
|
||||
|
||||
write_gem_make_out results.join "\n"
|
||||
rescue => e
|
||||
|
@ -201,6 +181,7 @@ EOF
|
|||
|
||||
dest_path = @spec.extension_dir
|
||||
|
||||
require "fileutils"
|
||||
FileUtils.rm_f @spec.gem_build_complete_path
|
||||
|
||||
@spec.extensions.each do |extension|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
require 'rubygems'
|
||||
require 'rubygems/package'
|
||||
require 'time'
|
||||
require 'tmpdir'
|
||||
|
||||
##
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
##
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ class Gem::Package::TarHeader
|
|||
gname,
|
||||
oct(devmajor, 7),
|
||||
oct(devminor, 7),
|
||||
prefix
|
||||
prefix,
|
||||
]
|
||||
|
||||
header = header.pack PACK_FORMAT
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gem::Resolver::Molinillo
|
||||
module Delegates
|
||||
# Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gem::Resolver::Molinillo
|
||||
class DependencyGraph
|
||||
# An action that modifies a {DependencyGraph} that is reversible.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
# @return [Integer] The index to which the resolution should unwind in the
|
||||
# case of conflict.
|
||||
def state_index_for_unwind
|
||||
current_requirement = requirement
|
||||
existing_requirement = requirement_for_existing_name(name)
|
||||
index = -1
|
||||
[current_requirement, existing_requirement].each do |r|
|
||||
until r.nil?
|
||||
current_state = find_state_for(r)
|
||||
if state_any?(current_state)
|
||||
current_index = states.index(current_state)
|
||||
index = current_index if current_index > index
|
||||
break
|
||||
# Raises a VersionConflict error, or any underlying error, if there is no
|
||||
# current state
|
||||
# @return [void]
|
||||
def raise_error_unless_state(conflicts)
|
||||
return if state
|
||||
|
||||
error = conflicts.values.map(&:underlying_error).compact.first
|
||||
raise error || VersionConflict.new(conflicts, specification_provider)
|
||||
end
|
||||
|
||||
# @return [UnwindDetails] Details of the nearest index to which we could unwind
|
||||
def build_details_for_unwind
|
||||
# Get the possible unwinds for the current conflict
|
||||
current_conflict = conflicts[name]
|
||||
binding_requirements = binding_requirements_for_conflict(current_conflict)
|
||||
unwind_details = unwind_options_for_requirements(binding_requirements)
|
||||
|
||||
last_detail_for_current_unwind = unwind_details.sort.last
|
||||
current_detail = last_detail_for_current_unwind
|
||||
|
||||
# Look for past conflicts that could be unwound to affect the
|
||||
# requirement tree for the current conflict
|
||||
relevant_unused_unwinds = unused_unwind_options.select do |alternative|
|
||||
intersecting_requirements =
|
||||
last_detail_for_current_unwind.all_requirements &
|
||||
alternative.requirements_unwound_to_instead
|
||||
next if intersecting_requirements.empty?
|
||||
# Find the highest index unwind whilst looping through
|
||||
current_detail = alternative if alternative > current_detail
|
||||
alternative
|
||||
end
|
||||
|
||||
# Add the current unwind options to the `unused_unwind_options` array.
|
||||
# The "used" option will be filtered out during `unwind_for_conflict`.
|
||||
state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 }
|
||||
|
||||
# Update the requirements_unwound_to_instead on any relevant unused unwinds
|
||||
relevant_unused_unwinds.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
|
||||
unwind_details.each { |d| d.requirements_unwound_to_instead << current_detail.state_requirement }
|
||||
|
||||
current_detail
|
||||
end
|
||||
|
||||
# @param [Array<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
|
||||
r = parent_of(r)
|
||||
parent_r = grandparent_r
|
||||
grandparent_r = parent_of(parent_r)
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
unwind_details
|
||||
end
|
||||
|
||||
# @param [DependencyState] state
|
||||
# @param [Array] binding_requirements array of requirements
|
||||
# @return [Boolean] whether or not the given state has any possibilities
|
||||
# that could satisfy the given requirements
|
||||
def conflict_fixing_possibilities?(state, binding_requirements)
|
||||
return false unless state
|
||||
|
||||
state.possibilities.any? do |possibility_set|
|
||||
possibility_set.possibilities.any? do |poss|
|
||||
possibility_satisfies_requirements?(poss, binding_requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Filter's a state's possibilities to remove any that would not fix the
|
||||
# conflict we've just rewound from
|
||||
# @param [UnwindDetails] unwind_details details of the conflict just
|
||||
# unwound from
|
||||
# @return [void]
|
||||
def filter_possibilities_after_unwind(unwind_details)
|
||||
return unless state && !state.possibilities.empty?
|
||||
|
||||
if unwind_details.unwinding_to_primary_requirement?
|
||||
filter_possibilities_for_primary_unwind(unwind_details)
|
||||
else
|
||||
filter_possibilities_for_parent_unwind(unwind_details)
|
||||
end
|
||||
end
|
||||
|
||||
# Filter's a state's possibilities to remove any that would not satisfy
|
||||
# the requirements in the conflict we've just rewound from
|
||||
# @param [UnwindDetails] unwind_details details of the conflict just unwound from
|
||||
# @return [void]
|
||||
def filter_possibilities_for_primary_unwind(unwind_details)
|
||||
unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
|
||||
unwinds_to_state << unwind_details
|
||||
unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements)
|
||||
|
||||
state.possibilities.reject! do |possibility_set|
|
||||
possibility_set.possibilities.none? do |poss|
|
||||
unwind_requirement_sets.any? do |requirements|
|
||||
possibility_satisfies_requirements?(poss, requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Object] possibility a single possibility
|
||||
# @param [Array] requirements an array of requirements
|
||||
# @return [Boolean] whether the possibility satisfies all of the
|
||||
# given requirements
|
||||
def possibility_satisfies_requirements?(possibility, requirements)
|
||||
name = name_for(possibility)
|
||||
|
||||
activated.tag(:swap)
|
||||
activated.set_payload(name, possibility) if activated.vertex_named(name)
|
||||
satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) }
|
||||
activated.rewind_to(:swap)
|
||||
|
||||
satisfied
|
||||
end
|
||||
|
||||
# Filter's a state's possibilities to remove any that would (eventually)
|
||||
# create a requirement in the conflict we've just rewound from
|
||||
# @param [UnwindDetails] unwind_details details of the conflict just unwound from
|
||||
# @return [void]
|
||||
def filter_possibilities_for_parent_unwind(unwind_details)
|
||||
unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index }
|
||||
unwinds_to_state << unwind_details
|
||||
|
||||
primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq
|
||||
parent_unwinds = unwinds_to_state.uniq - primary_unwinds
|
||||
|
||||
allowed_possibility_sets = primary_unwinds.flat_map do |unwind|
|
||||
states[unwind.state_index].possibilities.select do |possibility_set|
|
||||
possibility_set.possibilities.any? do |poss|
|
||||
possibility_satisfies_requirements?(poss, unwind.conflicting_requirements)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid)
|
||||
|
||||
state.possibilities.reject! do |possibility_set|
|
||||
!allowed_possibility_sets.include?(possibility_set) &&
|
||||
(requirements_to_avoid - possibility_set.dependencies).empty?
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Conflict] conflict
|
||||
# @return [Array] minimal array of requirements that would cause the passed
|
||||
# conflict to occur.
|
||||
def binding_requirements_for_conflict(conflict)
|
||||
return [conflict.requirement] if conflict.possibility.nil?
|
||||
|
||||
possible_binding_requirements = conflict.requirements.values.flatten(1).uniq
|
||||
|
||||
# When there's a `CircularDependency` error the conflicting requirement
|
||||
# (the one causing the circular) won't be `conflict.requirement`
|
||||
# (which won't be for the right state, because we won't have created it,
|
||||
# because it's circular).
|
||||
# We need to make sure we have that requirement in the conflict's list,
|
||||
# otherwise we won't be able to unwind properly, so we just return all
|
||||
# the requirements for the conflict.
|
||||
return possible_binding_requirements if conflict.underlying_error
|
||||
|
||||
possibilities = search_for(conflict.requirement)
|
||||
|
||||
# If all the requirements together don't filter out all possibilities,
|
||||
# then the only two requirements we need to consider are the initial one
|
||||
# (where the dependency's version was first chosen) and the last
|
||||
if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities)
|
||||
return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact
|
||||
end
|
||||
|
||||
# Loop through the possible binding requirements, removing each one
|
||||
# that doesn't bind. Use a `reverse_each` as we want the earliest set of
|
||||
# binding requirements, and don't use `reject!` as we wish to refine the
|
||||
# array *on each iteration*.
|
||||
binding_requirements = possible_binding_requirements.dup
|
||||
possible_binding_requirements.reverse_each do |req|
|
||||
next if req == conflict.requirement
|
||||
unless binding_requirement_in_set?(req, binding_requirements, possibilities)
|
||||
binding_requirements -= [req]
|
||||
end
|
||||
end
|
||||
|
||||
binding_requirements
|
||||
end
|
||||
|
||||
# @param [Object] requirement we wish to check
|
||||
# @param [Array] possible_binding_requirements array of requirements
|
||||
# @param [Array] possibilities array of possibilities the requirements will be used to filter
|
||||
# @return [Boolean] whether or not the given requirement is required to filter
|
||||
# out all elements of the array of possibilities.
|
||||
def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities)
|
||||
possibilities.any? do |poss|
|
||||
possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement])
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Object] requirement
|
||||
# @return [Object] the requirement that led to `requirement` being added
|
||||
# to the list of requirements.
|
||||
def parent_of(requirement)
|
||||
|
@ -219,29 +568,27 @@ module Gem::Resolver::Molinillo
|
|||
parent_state.requirement
|
||||
end
|
||||
|
||||
# @param [String] name
|
||||
# @return [Object] the requirement that led to a version of a possibility
|
||||
# with the given name being activated.
|
||||
def requirement_for_existing_name(name)
|
||||
return nil unless activated.vertex_named(name).payload
|
||||
return nil unless vertex = activated.vertex_named(name)
|
||||
return nil unless vertex.payload
|
||||
states.find { |s| s.name == name }.requirement
|
||||
end
|
||||
|
||||
# @param [Object] requirement
|
||||
# @return [ResolutionState] the state whose `requirement` is the given
|
||||
# `requirement`.
|
||||
def find_state_for(requirement)
|
||||
return nil unless requirement
|
||||
states.reverse_each.find { |i| requirement == i.requirement && i.is_a?(DependencyState) }
|
||||
end
|
||||
|
||||
# @return [Boolean] whether or not the given state has any possibilities
|
||||
# left.
|
||||
def state_any?(state)
|
||||
state && state.possibilities.any?
|
||||
states.find { |i| requirement == i.requirement }
|
||||
end
|
||||
|
||||
# @param [Object] underlying_error
|
||||
# @return [Conflict] a {Conflict} that reflects the failure to activate
|
||||
# the {#possibility} in conjunction with the current {#state}
|
||||
def create_conflict
|
||||
def create_conflict(underlying_error = nil)
|
||||
vertex = activated.vertex_named(name)
|
||||
locked_requirement = locked_requirement_named(name)
|
||||
|
||||
|
@ -250,18 +597,21 @@ module Gem::Resolver::Molinillo
|
|||
requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements
|
||||
end
|
||||
requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement
|
||||
vertex.incoming_edges.each { |edge| (requirements[edge.origin.payload] ||= []).unshift(edge.requirement) }
|
||||
vertex.incoming_edges.each do |edge|
|
||||
(requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement)
|
||||
end
|
||||
|
||||
activated_by_name = {}
|
||||
activated.each { |v| activated_by_name[v.name] = v.payload if v.payload }
|
||||
activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload }
|
||||
conflicts[name] = Conflict.new(
|
||||
requirement,
|
||||
requirements,
|
||||
vertex.payload,
|
||||
vertex.payload && vertex.payload.latest_version,
|
||||
possibility,
|
||||
locked_requirement,
|
||||
requirement_trees,
|
||||
activated_by_name
|
||||
activated_by_name,
|
||||
underlying_error
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -272,6 +622,7 @@ module Gem::Resolver::Molinillo
|
|||
vertex.requirements.map { |r| requirement_tree_for(r) }
|
||||
end
|
||||
|
||||
# @param [Object] requirement
|
||||
# @return [Array<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
|
||||
latest = possibility.latest_version
|
||||
possibility.possibilities.select! do |possibility|
|
||||
requirement_satisfied_by?(requirement, activated, possibility)
|
||||
end
|
||||
if possibility.latest_version.nil?
|
||||
# ensure there's a possibility for better error messages
|
||||
possibility.possibilities << latest if latest
|
||||
create_conflict
|
||||
unwind_for_conflict
|
||||
else
|
||||
activate_new_spec
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to activate the current {#possibility} (given that it has
|
||||
# already been activated)
|
||||
# Attempts to update the existing vertex's `PossibilitySet` with a filtered version
|
||||
# @return [void]
|
||||
def attempt_to_activate_existing_spec(existing_node)
|
||||
existing_spec = existing_node.payload
|
||||
if requirement_satisfied_by?(requirement, activated, existing_spec)
|
||||
def attempt_to_filter_existing_spec(vertex)
|
||||
filtered_set = filtered_possibility_set(vertex)
|
||||
if !filtered_set.possibilities.empty?
|
||||
activated.set_payload(name, filtered_set)
|
||||
new_requirements = requirements.dup
|
||||
push_state_for_requirements(new_requirements, false)
|
||||
else
|
||||
return if attempt_to_swap_possibility
|
||||
create_conflict
|
||||
debug(depth) { "Unsatisfied by existing spec (#{existing_node.payload})" }
|
||||
debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" }
|
||||
unwind_for_conflict
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to swp the current {#possibility} with the already-activated
|
||||
# spec with the given name
|
||||
# @return [Boolean] Whether the possibility was swapped into {#activated}
|
||||
def attempt_to_swap_possibility
|
||||
activated.tag(:swap)
|
||||
vertex = activated.vertex_named(name)
|
||||
activated.set_payload(name, possibility)
|
||||
if !vertex.requirements.
|
||||
all? { |r| requirement_satisfied_by?(r, activated, possibility) } ||
|
||||
!new_spec_satisfied?
|
||||
activated.rewind_to(:swap)
|
||||
return
|
||||
end
|
||||
fixup_swapped_children(vertex)
|
||||
activate_spec
|
||||
end
|
||||
|
||||
# Ensures there are no orphaned successors to the given {vertex}.
|
||||
# @param [DependencyGraph::Vertex] vertex the vertex to fix up.
|
||||
# @return [void]
|
||||
def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity
|
||||
payload = vertex.payload
|
||||
deps = dependencies_for(payload).group_by(&method(:name_for))
|
||||
vertex.outgoing_edges.each do |outgoing_edge|
|
||||
requirement = outgoing_edge.requirement
|
||||
parent_index = @parents_of[requirement].last
|
||||
succ = outgoing_edge.destination
|
||||
matching_deps = Array(deps[succ.name])
|
||||
dep_matched = matching_deps.include?(requirement)
|
||||
|
||||
# only push the current index when it was originally required by the
|
||||
# same named spec
|
||||
if parent_index && states[parent_index].name == name
|
||||
@parents_of[requirement].push(states.size - 1)
|
||||
end
|
||||
|
||||
if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex]
|
||||
debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" }
|
||||
succ.requirements.each { |r| @parents_of.delete(r) }
|
||||
|
||||
removed_names = activated.detach_vertex_named(succ.name).map(&:name)
|
||||
requirements.delete_if do |r|
|
||||
# the only removed vertices are those with no other requirements,
|
||||
# so it's safe to delete only based upon name here
|
||||
removed_names.include?(name_for(r))
|
||||
end
|
||||
elsif !dep_matched
|
||||
debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" }
|
||||
# also reset if we're removing the edge, but only if its parent has
|
||||
# already been fixed up
|
||||
@parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty?
|
||||
|
||||
activated.delete_edge(outgoing_edge)
|
||||
requirements.delete(requirement)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to activate the current {#possibility} (given that it hasn't
|
||||
# already been activated)
|
||||
# @return [void]
|
||||
def attempt_to_activate_new_spec
|
||||
if new_spec_satisfied?
|
||||
activate_spec
|
||||
else
|
||||
create_conflict
|
||||
unwind_for_conflict
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Boolean] whether the current spec is satisfied as a new
|
||||
# possibility.
|
||||
def new_spec_satisfied?
|
||||
unless requirement_satisfied_by?(requirement, activated, possibility)
|
||||
debug(depth) { 'Unsatisfied by requested spec' }
|
||||
return false
|
||||
end
|
||||
|
||||
locked_requirement = locked_requirement_named(name)
|
||||
|
||||
locked_spec_satisfied = !locked_requirement ||
|
||||
requirement_satisfied_by?(locked_requirement, activated, possibility)
|
||||
debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied
|
||||
|
||||
locked_spec_satisfied
|
||||
# Generates a filtered version of the existing vertex's `PossibilitySet` using the
|
||||
# current state's `requirement`
|
||||
# @param [Object] vertex existing vertex
|
||||
# @return [PossibilitySet] filtered possibility set
|
||||
def filtered_possibility_set(vertex)
|
||||
PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities)
|
||||
end
|
||||
|
||||
# @param [String] requirement_name the spec name to search for
|
||||
|
@ -434,7 +716,7 @@ module Gem::Resolver::Molinillo
|
|||
# Add the current {#possibility} to the dependency graph of the current
|
||||
# {#state}
|
||||
# @return [void]
|
||||
def activate_spec
|
||||
def activate_new_spec
|
||||
conflicts.delete(name)
|
||||
debug(depth) { "Activated #{name} at #{possibility}" }
|
||||
activated.set_payload(name, possibility)
|
||||
|
@ -442,14 +724,14 @@ module Gem::Resolver::Molinillo
|
|||
end
|
||||
|
||||
# Requires the dependencies that the recently activated spec has
|
||||
# @param [Object] activated_spec the specification that has just been
|
||||
# @param [Object] possibility_set the PossibilitySet that has just been
|
||||
# activated
|
||||
# @return [void]
|
||||
def require_nested_dependencies_for(activated_spec)
|
||||
nested_dependencies = dependencies_for(activated_spec)
|
||||
def require_nested_dependencies_for(possibility_set)
|
||||
nested_dependencies = dependencies_for(possibility_set.latest_version)
|
||||
debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" }
|
||||
nested_dependencies.each do |d|
|
||||
activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d)
|
||||
activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d)
|
||||
parent_index = states.size - 1
|
||||
parents = @parents_of[d]
|
||||
parents << parent_index if parents.empty?
|
||||
|
@ -461,23 +743,82 @@ module Gem::Resolver::Molinillo
|
|||
# Pushes a new {DependencyState} that encapsulates both existing and new
|
||||
# requirements
|
||||
# @param [Array] new_requirements
|
||||
# @param [Boolean] requires_sort
|
||||
# @param [Object] new_activated
|
||||
# @return [void]
|
||||
def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated)
|
||||
new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort
|
||||
new_requirement = new_requirements.shift
|
||||
new_requirement = nil
|
||||
loop do
|
||||
new_requirement = new_requirements.shift
|
||||
break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement }
|
||||
end
|
||||
new_name = new_requirement ? name_for(new_requirement) : ''.freeze
|
||||
possibilities = new_requirement ? search_for(new_requirement) : []
|
||||
possibilities = possibilities_for_requirement(new_requirement)
|
||||
handle_missing_or_push_dependency_state DependencyState.new(
|
||||
new_name, new_requirements, new_activated,
|
||||
new_requirement, possibilities, depth, conflicts.dup
|
||||
new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup
|
||||
)
|
||||
end
|
||||
|
||||
# Checks a proposed requirement with any existing locked requirement
|
||||
# before generating an array of possibilities for it.
|
||||
# @param [Object] requirement the proposed requirement
|
||||
# @param [Object] activated
|
||||
# @return [Array] possibilities
|
||||
def possibilities_for_requirement(requirement, activated = self.activated)
|
||||
return [] unless requirement
|
||||
if locked_requirement_named(name_for(requirement))
|
||||
return locked_requirement_possibility_set(requirement, activated)
|
||||
end
|
||||
|
||||
group_possibilities(search_for(requirement))
|
||||
end
|
||||
|
||||
# @param [Object] requirement the proposed requirement
|
||||
# @param [Object] activated
|
||||
# @return [Array] possibility set containing only the locked requirement, if any
|
||||
def locked_requirement_possibility_set(requirement, activated = self.activated)
|
||||
all_possibilities = search_for(requirement)
|
||||
locked_requirement = locked_requirement_named(name_for(requirement))
|
||||
|
||||
# Longwinded way to build a possibilities array with either the locked
|
||||
# requirement or nothing in it. Required, since the API for
|
||||
# locked_requirement isn't guaranteed.
|
||||
locked_possibilities = all_possibilities.select do |possibility|
|
||||
requirement_satisfied_by?(locked_requirement, activated, possibility)
|
||||
end
|
||||
|
||||
group_possibilities(locked_possibilities)
|
||||
end
|
||||
|
||||
# Build an array of PossibilitySets, with each element representing a group of
|
||||
# dependency versions that all have the same sub-dependency version constraints
|
||||
# and are contiguous.
|
||||
# @param [Array] possibilities an array of possibilities
|
||||
# @return [Array<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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class Gem::Security::Signer
|
|||
attr_reader :options
|
||||
|
||||
DEFAULT_OPTIONS = {
|
||||
expiration_length_days: 365
|
||||
expiration_length_days: 365,
|
||||
}.freeze
|
||||
|
||||
##
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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-----
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'rubygems/test_case'
|
|||
require 'net/http'
|
||||
require 'rubygems/openssl'
|
||||
|
||||
unless defined?(OpenSSL::SSL)
|
||||
unless Gem::HAVE_OPENSSL
|
||||
warn 'Skipping bundled certificates tests. openssl not found.'
|
||||
end
|
||||
|
||||
|
@ -46,11 +46,15 @@ class TestBundledCA < Gem::TestCase
|
|||
assert_https('rubygems.org')
|
||||
end
|
||||
|
||||
def test_accessing_fastly
|
||||
assert_https('rubygems.global.ssl.fastly.net')
|
||||
def test_accessing_www_rubygems
|
||||
assert_https('www.rubygems.org')
|
||||
end
|
||||
|
||||
def test_accessing_staging
|
||||
assert_https('staging.rubygems.org')
|
||||
end
|
||||
|
||||
def test_accessing_new_index
|
||||
assert_https('fastly.rubygems.org')
|
||||
assert_https('index.rubygems.org')
|
||||
end
|
||||
end if defined?(OpenSSL::SSL)
|
||||
end if Gem::HAVE_OPENSSL
|
||||
|
|
|
@ -164,7 +164,7 @@ class TestGem < Gem::TestCase
|
|||
:prog_mode => win_platform? ? 0410 : 0510,
|
||||
:data_mode => 0640,
|
||||
:wrappers => true,
|
||||
:format_executable => format_executable
|
||||
:format_executable => format_executable,
|
||||
}
|
||||
Dir.chdir @tempdir do
|
||||
Dir.mkdir 'bin'
|
||||
|
@ -765,7 +765,7 @@ class TestGem < Gem::TestCase
|
|||
|
||||
expected = [
|
||||
File.expand_path('test/rubygems/sff/discover.rb', PROJECT_DIR),
|
||||
File.join(foo1.full_gem_path, discover_path)
|
||||
File.join(foo1.full_gem_path, discover_path),
|
||||
].sort
|
||||
|
||||
assert_equal expected, Gem.find_files('sff/discover').sort
|
||||
|
@ -1532,7 +1532,7 @@ class TestGem < Gem::TestCase
|
|||
|
||||
tests = [
|
||||
[:dir0, [ Gem.dir, Gem.user_dir], m0],
|
||||
[:dir1, [ Gem.user_dir, Gem.dir], m1]
|
||||
[:dir1, [ Gem.user_dir, Gem.dir], m1],
|
||||
]
|
||||
|
||||
tests.each do |_name, _paths, expected|
|
||||
|
|
|
@ -231,7 +231,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase
|
|||
end
|
||||
|
||||
assert_equal '', @ui.output
|
||||
assert_equal "ERROR: Gemspec file not found: some_gem.gemspec\n", @ui.error
|
||||
assert_equal "ERROR: Couldn't find a gemspec file matching 'some_gem' in #{@tempdir}\n", @ui.error
|
||||
end
|
||||
|
||||
def test_execute_outside_dir
|
||||
|
@ -272,8 +272,200 @@ class TestGemCommandsBuildCommand < Gem::TestCase
|
|||
assert_equal "this is a summary", spec.summary
|
||||
end
|
||||
|
||||
def test_execute_outside_dir_with_glob_argument
|
||||
gemspec_dir = File.join @tempdir, 'build_command_gem'
|
||||
gemspec_file = File.join gemspec_dir, @gem.spec_name
|
||||
readme_file = File.join gemspec_dir, 'README.md'
|
||||
|
||||
FileUtils.mkdir_p gemspec_dir
|
||||
|
||||
File.open readme_file, 'w' do |f|
|
||||
f.write "My awesome gem"
|
||||
end
|
||||
|
||||
File.open gemspec_file, 'w' do |gs|
|
||||
gs.write @gem.to_ruby
|
||||
end
|
||||
|
||||
@cmd.options[:build_path] = gemspec_dir
|
||||
@cmd.options[:args] = ["*.gemspec"]
|
||||
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
||||
output = @ui.output.split "\n"
|
||||
assert_equal " Successfully built RubyGem", output.shift
|
||||
assert_equal " Name: some_gem", output.shift
|
||||
assert_equal " Version: 2", output.shift
|
||||
assert_equal " File: some_gem-2.gem", output.shift
|
||||
assert_equal [], output
|
||||
|
||||
gem_file = File.join gemspec_dir, File.basename(@gem.cache_file)
|
||||
assert File.exist?(gem_file)
|
||||
|
||||
spec = Gem::Package.new(gem_file).spec
|
||||
|
||||
assert_equal "some_gem", spec.name
|
||||
assert_equal "this is a summary", spec.summary
|
||||
end
|
||||
|
||||
def test_execute_outside_dir_no_gemspec_present
|
||||
gemspec_dir = File.join @tempdir, 'build_command_gem'
|
||||
gemspec_file = File.join @tempdir, @gem.spec_name
|
||||
readme_file = File.join gemspec_dir, 'README.md'
|
||||
|
||||
FileUtils.mkdir_p gemspec_dir
|
||||
|
||||
File.open readme_file, 'w' do |f|
|
||||
f.write "My awesome gem"
|
||||
end
|
||||
|
||||
File.open gemspec_file, 'w' do |gs|
|
||||
gs.write @gem.to_ruby
|
||||
end
|
||||
|
||||
@cmd.options[:build_path] = gemspec_dir
|
||||
@cmd.options[:args] = ["*.gemspec"]
|
||||
|
||||
use_ui @ui do
|
||||
assert_raises Gem::MockGemUi::TermError do
|
||||
@cmd.execute
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal "", @ui.output
|
||||
assert_equal "ERROR: Couldn't find a gemspec file matching '*.gemspec' in #{gemspec_dir}\n", @ui.error
|
||||
|
||||
gem_file = File.join gemspec_dir, File.basename(@gem.cache_file)
|
||||
refute File.exist?(gem_file)
|
||||
end
|
||||
|
||||
def test_execute_outside_dir_without_gem_name
|
||||
gemspec_dir = File.join(@tempdir, 'build_command_gem')
|
||||
gemspec_file = File.join(gemspec_dir, @gem.spec_name)
|
||||
|
||||
readme_file = File.join gemspec_dir, 'README.md'
|
||||
|
||||
FileUtils.mkdir_p(gemspec_dir)
|
||||
|
||||
File.open readme_file, 'w' do |f|
|
||||
f.write "My awesome gem"
|
||||
end
|
||||
|
||||
File.open(gemspec_file, "w") do |gs|
|
||||
gs.write(@gem.to_ruby)
|
||||
end
|
||||
|
||||
@cmd.options[:build_path] = gemspec_dir
|
||||
@cmd.options[:args] = []
|
||||
|
||||
use_ui @ui do
|
||||
Dir.chdir(gemspec_dir) do
|
||||
@cmd.execute
|
||||
end
|
||||
end
|
||||
|
||||
output = @ui.output.split("\n")
|
||||
assert_equal " Successfully built RubyGem", output.shift
|
||||
assert_equal " Name: some_gem", output.shift
|
||||
assert_equal " Version: 2", output.shift
|
||||
assert_equal " File: some_gem-2.gem", output.shift
|
||||
assert_equal [], output
|
||||
|
||||
gem_file = File.join gemspec_dir, File.basename(@gem.cache_file)
|
||||
assert File.exist?(gem_file)
|
||||
|
||||
spec = Gem::Package.new(gem_file).spec
|
||||
|
||||
assert_equal "some_gem", spec.name
|
||||
assert_equal "this is a summary", spec.summary
|
||||
end
|
||||
|
||||
def test_execute_outside_dir_with_external_gemspec
|
||||
gemspec_dir = File.join @tempdir, 'gemspec_dir'
|
||||
gemspec_file = File.join gemspec_dir, @gem.spec_name
|
||||
|
||||
gemcode_dir = File.join @tempdir, 'build_command_gem'
|
||||
readme_file = File.join gemcode_dir, 'README.md'
|
||||
|
||||
FileUtils.mkdir_p gemspec_dir
|
||||
FileUtils.mkdir_p gemcode_dir
|
||||
|
||||
File.open readme_file, 'w' do |f|
|
||||
f.write "My awesome gem in nested directory"
|
||||
end
|
||||
|
||||
File.open gemspec_file, 'w' do |gs|
|
||||
gs.write @gem.to_ruby
|
||||
end
|
||||
|
||||
@cmd.options[:build_path] = gemcode_dir
|
||||
@cmd.options[:args] = [gemspec_file]
|
||||
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
||||
output = @ui.output.split "\n"
|
||||
assert_equal " Successfully built RubyGem", output.shift
|
||||
assert_equal " Name: some_gem", output.shift
|
||||
assert_equal " Version: 2", output.shift
|
||||
assert_equal " File: some_gem-2.gem", output.shift
|
||||
assert_equal [], output
|
||||
|
||||
gem_file = File.join gemcode_dir, File.basename(@gem.cache_file)
|
||||
assert File.exist?(gem_file)
|
||||
|
||||
spec = Gem::Package.new(gem_file).spec
|
||||
|
||||
assert_equal "some_gem", spec.name
|
||||
assert_equal "this is a summary", spec.summary
|
||||
end
|
||||
|
||||
def test_execute_outside_dir_with_external_relative_gemspec
|
||||
gemspec_dir = File.join @tempdir, 'gemspec_dir'
|
||||
gemspec_file = File.join gemspec_dir, @gem.spec_name
|
||||
|
||||
gemcode_dir = File.join @tempdir, 'build_command_gem'
|
||||
readme_file = File.join gemcode_dir, 'README.md'
|
||||
|
||||
FileUtils.mkdir_p gemspec_dir
|
||||
FileUtils.mkdir_p gemcode_dir
|
||||
|
||||
File.open readme_file, 'w' do |f|
|
||||
f.write "My awesome gem in nested directory"
|
||||
end
|
||||
|
||||
File.open gemspec_file, 'w' do |gs|
|
||||
gs.write @gem.to_ruby
|
||||
end
|
||||
|
||||
@cmd.options[:build_path] = gemcode_dir
|
||||
@cmd.options[:args] = [File.join("..", "gemspec_dir", @gem.spec_name)]
|
||||
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
||||
output = @ui.output.split "\n"
|
||||
assert_equal " Successfully built RubyGem", output.shift
|
||||
assert_equal " Name: some_gem", output.shift
|
||||
assert_equal " Version: 2", output.shift
|
||||
assert_equal " File: some_gem-2.gem", output.shift
|
||||
assert_equal [], output
|
||||
|
||||
gem_file = File.join gemcode_dir, File.basename(@gem.cache_file)
|
||||
assert File.exist?(gem_file)
|
||||
|
||||
spec = Gem::Package.new(gem_file).spec
|
||||
|
||||
assert_equal "some_gem", spec.name
|
||||
assert_equal "this is a summary", spec.summary
|
||||
end
|
||||
|
||||
def test_can_find_gemspecs_without_dot_gemspec
|
||||
gemspec_file = File.join(@tempdir, @gem.spec_name)
|
||||
gemspec_file = File.join(@tempdir, @gem.name)
|
||||
|
||||
File.open gemspec_file + ".gemspec", 'w' do |gs|
|
||||
gs.write @gem.to_ruby
|
||||
|
@ -390,7 +582,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_build_signed_gem
|
||||
skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform?
|
||||
skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform?
|
||||
|
||||
trust_dir = Gem::Security.trust_dir
|
||||
|
||||
|
@ -417,7 +609,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_build_signed_gem_with_cert_expiration_length_days
|
||||
skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform?
|
||||
skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform?
|
||||
|
||||
gem_path = File.join Gem.user_home, ".gem"
|
||||
Dir.mkdir gem_path
|
||||
|
@ -461,7 +653,7 @@ class TestGemCommandsBuildCommand < Gem::TestCase
|
|||
end
|
||||
|
||||
def test_build_auto_resign_cert
|
||||
skip 'openssl is missing' unless defined?(OpenSSL::SSL) && !java_platform?
|
||||
skip 'openssl is missing' unless Gem::HAVE_OPENSSL && !java_platform?
|
||||
|
||||
gem_path = File.join Gem.user_home, ".gem"
|
||||
Dir.mkdir gem_path
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
require 'rubygems/test_case'
|
||||
require 'rubygems/commands/cert_command'
|
||||
|
||||
unless defined?(OpenSSL::SSL)
|
||||
unless Gem::HAVE_OPENSSL
|
||||
warn 'Skipping `gem cert` tests. openssl not found.'
|
||||
end
|
||||
|
||||
|
@ -805,4 +805,4 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
|
|||
assert_equal "invalid argument: --sign #{nonexistent}: does not exist",
|
||||
e.message
|
||||
end
|
||||
end if defined?(OpenSSL::SSL) && !Gem.java_platform?
|
||||
end if Gem::HAVE_OPENSSL && !Gem.java_platform?
|
||||
|
|
|
@ -221,7 +221,7 @@ class TestGemCommandsCleanupCommand < Gem::TestCase
|
|||
@b_2 = util_spec 'b', 3
|
||||
|
||||
install_gem @b_1
|
||||
install_default_specs @b_default
|
||||
install_default_gems @b_default
|
||||
install_gem @b_2
|
||||
|
||||
@cmd.options[:args] = []
|
||||
|
|
|
@ -227,7 +227,7 @@ lib/foo.rb
|
|||
nil, "default/gem.rb")
|
||||
default_gem_spec.executables = ["default_command"]
|
||||
default_gem_spec.files += ["default_gem.so"]
|
||||
install_default_specs(default_gem_spec)
|
||||
install_default_gems(default_gem_spec)
|
||||
|
||||
@cmd.options[:args] = %w[default]
|
||||
|
||||
|
@ -238,7 +238,7 @@ lib/foo.rb
|
|||
expected = [
|
||||
[RbConfig::CONFIG['bindir'], 'default_command'],
|
||||
[RbConfig::CONFIG['rubylibdir'], 'default/gem.rb'],
|
||||
[RbConfig::CONFIG['archdir'], 'default_gem.so']
|
||||
[RbConfig::CONFIG['archdir'], 'default_gem.so'],
|
||||
].sort.map{|a|File.join a }.join "\n"
|
||||
|
||||
assert_equal expected, @ui.output.chomp
|
||||
|
|
|
@ -40,10 +40,12 @@ class TestGemCommandsHelpCommand < Gem::TestCase
|
|||
|
||||
util_gem 'commands' do |out, err|
|
||||
mgr.command_names.each do |cmd|
|
||||
assert_match(/\s+#{cmd}\s+\S+/, out)
|
||||
unless mgr[cmd].deprecated?
|
||||
assert_match(/\s+#{cmd}\s+\S+/, out)
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(OpenSSL::SSL)
|
||||
if Gem::HAVE_OPENSSL
|
||||
assert_empty err
|
||||
|
||||
refute_match 'No command found for ', out
|
||||
|
@ -51,6 +53,17 @@ class TestGemCommandsHelpCommand < Gem::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_gem_help_commands_omits_deprecated_commands
|
||||
mgr = Gem::CommandManager.new
|
||||
|
||||
util_gem 'commands' do |out, err|
|
||||
deprecated_commands = mgr.command_names.select {|cmd| mgr[cmd].deprecated? }
|
||||
deprecated_commands.each do |cmd|
|
||||
refute_match(/\A\s+#{cmd}\s+\S+\z/, out)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_gem_no_args_shows_help
|
||||
util_gem do |out, err|
|
||||
assert_match(/Usage:/, out)
|
||||
|
|
|
@ -410,7 +410,7 @@ ERROR: Possible alternatives: non_existent_with_hint
|
|||
|
||||
expected = [
|
||||
"ERROR: Could not find a valid gem 'non-existent_with-hint' (>= 0) in any repository",
|
||||
"ERROR: Possible alternatives: nonexistent-with_hint"
|
||||
"ERROR: Possible alternatives: nonexistent-with_hint",
|
||||
]
|
||||
|
||||
output = @ui.error.split "\n"
|
||||
|
|
|
@ -247,7 +247,7 @@ EOF
|
|||
|
||||
@stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [
|
||||
[response_fail, 401, 'Unauthorized'],
|
||||
[response_success, 200, 'OK']
|
||||
[response_success, 200, 'OK'],
|
||||
]
|
||||
|
||||
@otp_ui = Gem::MockGemUi.new "111111\n"
|
||||
|
@ -275,4 +275,52 @@ EOF
|
|||
assert_match 'Code: ', @otp_ui.output
|
||||
assert_equal '111111', @stub_fetcher.last_request['OTP']
|
||||
end
|
||||
|
||||
def test_remove_owners_unathorized_api_key
|
||||
response_forbidden = "The API key doesn't have access"
|
||||
response_success = "Owner removed successfully."
|
||||
|
||||
@stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [
|
||||
[response_forbidden, 403, 'Forbidden'],
|
||||
[response_success, 200, "OK"],
|
||||
]
|
||||
@stub_fetcher.data["#{Gem.host}/api/v1/api_key"] = ["", 200, "OK"]
|
||||
@cmd.instance_variable_set :@scope, :remove_owner
|
||||
|
||||
@stub_ui = Gem::MockGemUi.new "some@mail.com\npass\n"
|
||||
use_ui @stub_ui do
|
||||
@cmd.remove_owners("freewill", ["some@example"])
|
||||
end
|
||||
|
||||
access_notice = "The existing key doesn't have access of remove_owner on RubyGems.org. Please sign in to update access."
|
||||
assert_match access_notice, @stub_ui.output
|
||||
assert_match "Email:", @stub_ui.output
|
||||
assert_match "Password:", @stub_ui.output
|
||||
assert_match "Added remove_owner scope to the existing API key", @stub_ui.output
|
||||
assert_match response_success, @stub_ui.output
|
||||
end
|
||||
|
||||
def test_add_owners_unathorized_api_key
|
||||
response_forbidden = "The API key doesn't have access"
|
||||
response_success = "Owner added successfully."
|
||||
|
||||
@stub_fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners"] = [
|
||||
[response_forbidden, 403, 'Forbidden'],
|
||||
[response_success, 200, "OK"],
|
||||
]
|
||||
@stub_fetcher.data["#{Gem.host}/api/v1/api_key"] = ["", 200, "OK"]
|
||||
@cmd.instance_variable_set :@scope, :add_owner
|
||||
|
||||
@stub_ui = Gem::MockGemUi.new "some@mail.com\npass\n"
|
||||
use_ui @stub_ui do
|
||||
@cmd.add_owners("freewill", ["some@example"])
|
||||
end
|
||||
|
||||
access_notice = "The existing key doesn't have access of add_owner on RubyGems.org. Please sign in to update access."
|
||||
assert_match access_notice, @stub_ui.output
|
||||
assert_match "Email:", @stub_ui.output
|
||||
assert_match "Password:", @stub_ui.output
|
||||
assert_match "Added add_owner scope to the existing API key", @stub_ui.output
|
||||
assert_match response_success, @stub_ui.output
|
||||
end
|
||||
end
|
||||
|
|
|
@ -568,7 +568,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase
|
|||
assert_equal([
|
||||
"Restoring gems to pristine condition...",
|
||||
"Cached gem for a-2 not found, attempting to fetch...",
|
||||
"Skipped a-2, it was not found from cache and remote sources"
|
||||
"Skipped a-2, it was not found from cache and remote sources",
|
||||
], @ui.output.split("\n"))
|
||||
|
||||
assert_empty @ui.error
|
||||
|
@ -577,7 +577,7 @@ class TestGemCommandsPristineCommand < Gem::TestCase
|
|||
def test_execute_default_gem
|
||||
default_gem_spec = new_default_spec("default", "2.0.0.0",
|
||||
nil, "default/gem.rb")
|
||||
install_default_specs(default_gem_spec)
|
||||
install_default_gems(default_gem_spec)
|
||||
|
||||
@cmd.options[:args] = %w[default]
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
|
||||
keys = {
|
||||
:rubygems_api_key => 'KEY',
|
||||
@host => @api_key
|
||||
@host => @api_key,
|
||||
}
|
||||
|
||||
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
||||
|
@ -187,7 +187,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
|
||||
keys = {
|
||||
:rubygems_api_key => 'KEY',
|
||||
@host => @api_key
|
||||
@host => @api_key,
|
||||
}
|
||||
|
||||
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
||||
|
@ -271,7 +271,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
|
||||
keys = {
|
||||
:rubygems_api_key => 'KEY',
|
||||
@host => @api_key
|
||||
@host => @api_key,
|
||||
}
|
||||
|
||||
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
||||
|
@ -302,7 +302,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
api_key = "PRIVKEY"
|
||||
|
||||
keys = {
|
||||
host => api_key
|
||||
host => api_key,
|
||||
}
|
||||
|
||||
FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path
|
||||
|
@ -373,7 +373,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
|
||||
@fetcher.data["#{Gem.host}/api/v1/gems"] = [
|
||||
[response_fail, 401, 'Unauthorized'],
|
||||
[response_success, 200, 'OK']
|
||||
[response_success, 200, 'OK'],
|
||||
]
|
||||
|
||||
@otp_ui = Gem::MockGemUi.new "111111\n"
|
||||
|
@ -404,6 +404,32 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||
assert_equal '111111', @fetcher.last_request['OTP']
|
||||
end
|
||||
|
||||
def test_sending_gem_unathorized_api_key
|
||||
response_forbidden = "The API key doesn't have access"
|
||||
response_success = 'Successfully registered gem: freewill (1.0.0)'
|
||||
|
||||
@fetcher.data["#{@host}/api/v1/gems"] = [
|
||||
[response_forbidden, 403, 'Forbidden'],
|
||||
[response_success, 200, "OK"],
|
||||
]
|
||||
|
||||
@fetcher.data["#{@host}/api/v1/api_key"] = ["", 200, "OK"]
|
||||
@cmd.instance_variable_set :@host, @host
|
||||
@cmd.instance_variable_set :@scope, :push_rubygem
|
||||
|
||||
@ui = Gem::MockGemUi.new "some@mail.com\npass\n"
|
||||
use_ui @ui do
|
||||
@cmd.send_gem(@path)
|
||||
end
|
||||
|
||||
access_notice = "The existing key doesn't have access of push_rubygem on https://rubygems.example. Please sign in to update access."
|
||||
assert_match access_notice, @ui.output
|
||||
assert_match "Email:", @ui.output
|
||||
assert_match "Password:", @ui.output
|
||||
assert_match "Added push_rubygem scope to the existing API key", @ui.output
|
||||
assert_match response_success, @ui.output
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def singleton_gem_class
|
||||
|
|
|
@ -644,7 +644,7 @@ a (2 universal-darwin, 1 ruby x86-linux)
|
|||
spec_fetcher {|fetcher| fetcher.spec 'a', 2 }
|
||||
|
||||
a1 = new_default_spec 'a', 1
|
||||
install_default_specs a1
|
||||
install_default_gems a1
|
||||
|
||||
use_ui @stub_ui do
|
||||
@cmd.execute
|
||||
|
@ -663,7 +663,7 @@ EOF
|
|||
def test_execute_show_default_gems_with_platform
|
||||
a1 = new_default_spec 'a', 1
|
||||
a1.platform = 'java'
|
||||
install_default_specs a1
|
||||
install_default_gems a1
|
||||
|
||||
use_ui @stub_ui do
|
||||
@cmd.execute
|
||||
|
@ -685,7 +685,7 @@ EOF
|
|||
end
|
||||
|
||||
a1 = new_default_spec 'a', 1
|
||||
install_default_specs a1
|
||||
install_default_gems a1
|
||||
|
||||
@cmd.handle_options %w[-l -d]
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
bundler/exe/bundle
|
||||
bundler/lib/bundler.rb
|
||||
bundler/lib/bundler/b.rb
|
||||
bundler/lib/bundler/man/bundle-b.1.ronn
|
||||
bundler/lib/bundler/man/gemfile.5.ronn
|
||||
bundler/lib/bundler/templates/.circleci/config.yml
|
||||
bundler/lib/bundler/templates/.travis.yml
|
||||
bundler/man/bundle-b.1
|
||||
bundler/man/bundle-b.1.ronn
|
||||
bundler/man/gemfile.5
|
||||
bundler/man/gemfile.5.ronn
|
||||
]
|
||||
|
||||
create_dummy_files(filelist)
|
||||
|
@ -155,23 +155,18 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
assert_match %r{\A#!\s*#{bin_env}#{ruby_exec}}, File.read(gem_bin_path)
|
||||
end
|
||||
|
||||
def test_pem_files_in
|
||||
assert_equal %w[rubygems/ssl_certs/rubygems.org/foo.pem],
|
||||
@cmd.pem_files_in('lib').sort
|
||||
end
|
||||
|
||||
def test_rb_files_in
|
||||
assert_equal %w[rubygems.rb rubygems/test_case.rb],
|
||||
@cmd.rb_files_in('lib').sort
|
||||
def test_files_in
|
||||
assert_equal %w[rubygems.rb rubygems/ssl_certs/rubygems.org/foo.pem rubygems/test_case.rb],
|
||||
@cmd.files_in('lib').sort
|
||||
end
|
||||
|
||||
def test_bundler_man1_files_in
|
||||
assert_equal %w[bundle-b.1 bundle-b.1.ronn],
|
||||
assert_equal %w[bundle-b.1],
|
||||
@cmd.bundler_man1_files_in('bundler/man').sort
|
||||
end
|
||||
|
||||
def test_bundler_man5_files_in
|
||||
assert_equal %w[gemfile.5 gemfile.5.ronn],
|
||||
assert_equal %w[gemfile.5],
|
||||
@cmd.bundler_man5_files_in('bundler/man').sort
|
||||
end
|
||||
|
||||
|
@ -187,7 +182,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
assert_path_exists File.join(dir, 'bundler.rb')
|
||||
assert_path_exists File.join(dir, 'bundler/b.rb')
|
||||
|
||||
assert_path_exists File.join(dir, 'bundler/templates/.circleci/config.yml')
|
||||
assert_path_exists File.join(dir, 'bundler/templates/.circleci/config.yml') unless RUBY_ENGINE == "truffleruby" # https://github.com/oracle/truffleruby/issues/2116
|
||||
assert_path_exists File.join(dir, 'bundler/templates/.travis.yml')
|
||||
end
|
||||
end
|
||||
|
@ -199,9 +194,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
@cmd.install_man dir
|
||||
|
||||
assert_path_exists File.join("#{dir}/man1", 'bundle-b.1')
|
||||
assert_path_exists File.join("#{dir}/man1", 'bundle-b.1.ronn')
|
||||
refute_path_exists File.join("#{dir}/man1", 'bundle-b.1.ronn')
|
||||
assert_path_exists File.join("#{dir}/man5", 'gemfile.5')
|
||||
assert_path_exists File.join("#{dir}/man5", 'gemfile.5.ronn')
|
||||
refute_path_exists File.join("#{dir}/man5", 'gemfile.5.ronn')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -297,7 +292,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
|
||||
@cmd.remove_old_lib_files lib
|
||||
|
||||
files_that_go.each {|file| refute_path_exists file }
|
||||
files_that_go.each {|file| refute_path_exists(file) unless file == old_bundler_ci && RUBY_ENGINE == "truffleruby" } # https://github.com/oracle/truffleruby/issues/2116
|
||||
|
||||
files_that_stay.each {|file| assert_path_exists file }
|
||||
end
|
||||
|
@ -313,8 +308,8 @@ class TestGemCommandsSetupCommand < Gem::TestCase
|
|||
gemfile_5_ronn = File.join man, 'man5', 'gemfile.5.ronn'
|
||||
gemfile_5_txt = File.join man, 'man5', 'gemfile.5.txt'
|
||||
|
||||
files_that_go = [bundle_b_1_txt, gemfile_5_txt]
|
||||
files_that_stay = [ruby_1, bundle_b_1, bundle_b_1_ronn, gemfile_5, gemfile_5_ronn]
|
||||
files_that_go = [bundle_b_1_txt, bundle_b_1_ronn, gemfile_5_txt, gemfile_5_ronn]
|
||||
files_that_stay = [ruby_1, bundle_b_1, gemfile_5]
|
||||
|
||||
create_dummy_files(files_that_go + files_that_stay)
|
||||
|
||||
|
|
|
@ -73,14 +73,38 @@ class TestGemCommandsSigninCommand < Gem::TestCase
|
|||
assert_equal api_key, credentials[:rubygems_api_key]
|
||||
end
|
||||
|
||||
def test_excute_with_key_name_and_scope
|
||||
email = 'you@example.com'
|
||||
password = 'secret'
|
||||
api_key = '1234'
|
||||
fetcher = Gem::RemoteFetcher.fetcher
|
||||
|
||||
key_name_ui = Gem::MockGemUi.new "#{email}\n#{password}\ntest-key\n\ny\n\n\n\n\n\n"
|
||||
util_capture(key_name_ui, nil, api_key, fetcher) { @cmd.execute }
|
||||
|
||||
user = ENV["USER"] || ENV["USERNAME"]
|
||||
|
||||
assert_match "API Key name [#{Socket.gethostname}-#{user}", key_name_ui.output
|
||||
assert_match "index_rubygems [y/N]", key_name_ui.output
|
||||
assert_match "push_rubygem [y/N]", key_name_ui.output
|
||||
assert_match "yank_rubygem [y/N]", key_name_ui.output
|
||||
assert_match "add_owner [y/N]", key_name_ui.output
|
||||
assert_match "remove_owner [y/N]", key_name_ui.output
|
||||
assert_match "access_webhooks [y/N]", key_name_ui.output
|
||||
assert_match "show_dashboard [y/N]", key_name_ui.output
|
||||
assert_equal "name=test-key&push_rubygem=true", fetcher.last_request.body
|
||||
|
||||
credentials = YAML.load_file Gem.configuration.credentials_path
|
||||
assert_equal api_key, credentials[:rubygems_api_key]
|
||||
end
|
||||
|
||||
# Utility method to capture IO/UI within the block passed
|
||||
|
||||
def util_capture(ui_stub = nil, host = nil, api_key = nil)
|
||||
def util_capture(ui_stub = nil, host = nil, api_key = nil, fetcher = Gem::FakeFetcher.new)
|
||||
api_key ||= 'a5fdbb6ba150cbb83aad2bb2fede64cf040453903'
|
||||
response = [api_key, 200, 'OK']
|
||||
email = 'you@example.com'
|
||||
password = 'secret'
|
||||
fetcher = Gem::FakeFetcher.new
|
||||
|
||||
# Set the expected response for the Web-API supplied
|
||||
ENV['RUBYGEMS_HOST'] = host || Gem::DEFAULT_HOST
|
||||
|
@ -88,7 +112,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase
|
|||
fetcher.data[data_key] = response
|
||||
Gem::RemoteFetcher.fetcher = fetcher
|
||||
|
||||
sign_in_ui = ui_stub || Gem::MockGemUi.new("#{email}\n#{password}\n")
|
||||
sign_in_ui = ui_stub || Gem::MockGemUi.new("#{email}\n#{password}\n\n\n\n\n\n\n\n\n")
|
||||
|
||||
use_ui sign_in_ui do
|
||||
yield
|
||||
|
|
|
@ -107,6 +107,36 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
|
|||
assert_empty ui.error
|
||||
end
|
||||
|
||||
def test_execute_add_allow_typo_squatting_source_forced
|
||||
rubygems_org = "https://rubyems.org"
|
||||
|
||||
spec_fetcher do |fetcher|
|
||||
fetcher.spec("a", 1)
|
||||
end
|
||||
|
||||
specs = Gem::Specification.map do |spec|
|
||||
[spec.name, spec.version, spec.original_platform]
|
||||
end
|
||||
|
||||
specs_dump_gz = StringIO.new
|
||||
Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
|
||||
Marshal.dump(specs, io)
|
||||
end
|
||||
|
||||
@fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
|
||||
@cmd.handle_options %W[--force --add #{rubygems_org}]
|
||||
|
||||
@cmd.execute
|
||||
|
||||
expected = "https://rubyems.org added to sources\n"
|
||||
assert_equal expected, ui.output
|
||||
|
||||
source = Gem::Source.new(rubygems_org)
|
||||
assert Gem.sources.include?(source)
|
||||
|
||||
assert_empty ui.error
|
||||
end
|
||||
|
||||
def test_execute_add_deny_typo_squatting_source
|
||||
rubygems_org = "https://rubyems.org"
|
||||
|
||||
|
@ -283,6 +313,36 @@ source http://gems.example.com/ already present in the cache
|
|||
assert_empty @ui.error
|
||||
end
|
||||
|
||||
def test_execute_add_http_rubygems_org_forced
|
||||
rubygems_org = "http://rubygems.org"
|
||||
|
||||
spec_fetcher do |fetcher|
|
||||
fetcher.spec("a", 1)
|
||||
end
|
||||
|
||||
specs = Gem::Specification.map do |spec|
|
||||
[spec.name, spec.version, spec.original_platform]
|
||||
end
|
||||
|
||||
specs_dump_gz = StringIO.new
|
||||
Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
|
||||
Marshal.dump(specs, io)
|
||||
end
|
||||
|
||||
@fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
|
||||
@cmd.handle_options %W[--force --add #{rubygems_org}]
|
||||
|
||||
@cmd.execute
|
||||
|
||||
expected = "http://rubygems.org added to sources\n"
|
||||
assert_equal expected, ui.output
|
||||
|
||||
source = Gem::Source.new(rubygems_org)
|
||||
assert Gem.sources.include?(source)
|
||||
|
||||
assert_empty ui.error
|
||||
end
|
||||
|
||||
def test_execute_add_https_rubygems_org
|
||||
https_rubygems_org = 'https://rubygems.org/'
|
||||
|
||||
|
|
|
@ -186,6 +186,34 @@ class TestGemCommandsSpecificationCommand < Gem::TestCase
|
|||
assert_equal Gem::Version.new("1"), spec.version
|
||||
end
|
||||
|
||||
def test_execute_remote_with_version_and_platform
|
||||
original_platforms = Gem.platforms.dup
|
||||
|
||||
spec_fetcher do |fetcher|
|
||||
fetcher.spec 'foo', "1"
|
||||
fetcher.spec 'foo', "1" do |s|
|
||||
s.platform = 'x86_64-linux'
|
||||
end
|
||||
end
|
||||
|
||||
@cmd.options[:args] = %w[foo]
|
||||
@cmd.options[:version] = "1"
|
||||
@cmd.options[:domain] = :remote
|
||||
@cmd.options[:added_platform] = true
|
||||
Gem.platforms = [Gem::Platform::RUBY, Gem::Platform.new("x86_64-linux")]
|
||||
|
||||
use_ui @ui do
|
||||
@cmd.execute
|
||||
end
|
||||
|
||||
spec = Gem::Specification.from_yaml @ui.output
|
||||
|
||||
assert_equal Gem::Version.new("1"), spec.version
|
||||
assert_equal Gem::Platform.new("x86_64-linux"), spec.platform
|
||||
ensure
|
||||
Gem.platforms = original_platforms
|
||||
end
|
||||
|
||||
def test_execute_remote_without_prerelease
|
||||
spec_fetcher do |fetcher|
|
||||
fetcher.spec 'foo', '2.0.0'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue