1
0
Fork 0
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:
Hiroshi SHIBATA 2020-12-08 16:33:39 +09:00
parent 6a6a24df9b
commit 4aca77edde
Notes: git 2020-12-08 17:30:31 +09:00
134 changed files with 2002 additions and 745 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'rubygems'
require 'rubygems/package'
require 'time'
require 'tmpdir'
##

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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
##

View file

@ -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

View file

@ -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,
}
)

View file

@ -229,7 +229,7 @@ class Gem::Package::TarHeader
gname,
oct(devmajor, 7),
oct(devminor, 7),
prefix
prefix,
]
header = header.pack PACK_FORMAT

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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?

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,4 +1,5 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
module Delegates
# Delegates all {Gem::Resolver::Molinillo::SpecificationProvider} methods to a

View file

@ -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

View file

@ -1,4 +1,5 @@
# frozen_string_literal: true
module Gem::Resolver::Molinillo
class DependencyGraph
# An action that modifies a {DependencyGraph} that is reversible.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -34,7 +34,7 @@ class Gem::Security::Signer
attr_reader :options
DEFAULT_OPTIONS = {
expiration_length_days: 365
expiration_length_days: 365,
}.freeze
##

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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|

View file

@ -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-----

View file

@ -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)

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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

View file

@ -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?

View file

@ -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] = []

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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]

View file

@ -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)

View file

@ -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

View file

@ -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/'

View file

@ -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