summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2020-12-23 08:45:19 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2020-12-23 10:17:41 +0900
commit339227363ce0cf967fa17efa4489d823932ddabd (patch)
tree576482ce00d03439f2dbf4714a6f309293884c2f /lib
parent733ed1e18498f97250b788f169c37b170e0cf2b6 (diff)
Merge RubyGems 3.2.3 and Bundler 2.2.3
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/3982
Diffstat (limited to 'lib')
-rw-r--r--lib/bundler.rb9
-rw-r--r--lib/bundler/cli/update.rb2
-rw-r--r--lib/bundler/compact_index_client/cache.rb18
-rw-r--r--lib/bundler/compact_index_client/gem_parser.rb28
-rw-r--r--lib/bundler/definition.rb40
-rw-r--r--lib/bundler/gem_helpers.rb54
-rw-r--r--lib/bundler/lazy_specification.rb21
-rw-r--r--lib/bundler/resolver/spec_group.rb30
-rw-r--r--lib/bundler/rubygems_integration.rb5
-rw-r--r--lib/bundler/spec_set.rb13
-rw-r--r--lib/bundler/version.rb2
-rw-r--r--lib/rubygems.rb2
-rw-r--r--lib/rubygems/dependency_installer.rb1
-rw-r--r--lib/rubygems/installer.rb23
-rw-r--r--lib/rubygems/remote_fetcher.rb2
-rw-r--r--lib/rubygems/request_set.rb15
-rw-r--r--lib/rubygems/resolver.rb7
-rw-r--r--lib/rubygems/resolver/api_set.rb47
-rw-r--r--lib/rubygems/resolver/api_set/gem_parser.rb20
-rw-r--r--lib/rubygems/resolver/api_specification.rb7
-rw-r--r--lib/rubygems/resolver/best_set.rb2
-rw-r--r--lib/rubygems/resolver/index_specification.rb15
-rw-r--r--lib/rubygems/resolver/installer_set.rb64
-rw-r--r--lib/rubygems/resolver/spec_specification.rb14
-rw-r--r--lib/rubygems/resolver/specification.rb12
-rw-r--r--lib/rubygems/source.rb16
26 files changed, 295 insertions, 174 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb
index 7a01de5ddb..c72ad27c40 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -212,13 +212,10 @@ module Bundler
end
end
- def locked_bundler_version
- return nil unless defined?(@definition) && @definition
+ def most_specific_locked_platform?(platform)
+ return false unless defined?(@definition) && @definition
- locked_gems = definition.locked_gems
- return nil unless locked_gems
-
- locked_gems.bundler_version
+ definition.most_specific_locked_platform == platform
end
def ruby_scope
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index ae908be65e..94699484d4 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -82,7 +82,7 @@ module Bundler
locked_spec = locked_info[:spec]
new_spec = Bundler.definition.specs[name].first
unless new_spec
- if Bundler.rubygems.platforms.none? {|p| locked_spec.match_platform(p) }
+ unless locked_spec.match_platform(Bundler.local_platform)
Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one"
end
diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb
index 8f73298fbe..c2cd069ec1 100644
--- a/lib/bundler/compact_index_client/cache.rb
+++ b/lib/bundler/compact_index_client/cache.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require_relative "gem_parser"
+
module Bundler
class CompactIndexClient
class Cache
@@ -92,19 +94,9 @@ module Bundler
header ? lines[header + 1..-1] : lines
end
- def parse_gem(string)
- version_and_platform, rest = string.split(" ", 2)
- version, platform = version_and_platform.split("-", 2)
- dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
- dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
- requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
- [version, platform, dependencies, requirements]
- end
-
- def parse_dependency(string)
- dependency = string.split(":")
- dependency[-1] = dependency[-1].split("&") if dependency.size > 1
- dependency
+ def parse_gem(line)
+ @dependency_parser ||= GemParser.new
+ @dependency_parser.parse(line)
end
def info_roots
diff --git a/lib/bundler/compact_index_client/gem_parser.rb b/lib/bundler/compact_index_client/gem_parser.rb
new file mode 100644
index 0000000000..e7bf4c6001
--- /dev/null
+++ b/lib/bundler/compact_index_client/gem_parser.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Bundler
+ class CompactIndexClient
+ if defined?(Gem::Resolver::APISet::GemParser)
+ GemParser = Gem::Resolver::APISet::GemParser
+ else
+ class GemParser
+ def parse(line)
+ version_and_platform, rest = line.split(" ", 2)
+ version, platform = version_and_platform.split("-", 2)
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
+ requirements = requirements ? requirements.map {|d| parse_dependency(d) } : []
+ [version, platform, dependencies, requirements]
+ end
+
+ private
+
+ def parse_dependency(string)
+ dependency = string.split(":")
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index fdd093fbb3..b22363d119 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -118,7 +118,7 @@ module Bundler
end
@unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
- add_current_platform unless Bundler.frozen_bundle?
+ add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle?
converge_path_sources_to_gemspec_sources
@path_changes = converge_paths
@@ -157,7 +157,7 @@ module Bundler
end
def resolve_remotely!
- raise "Specs already loaded" if @specs
+ return if @specs
@remote = true
sources.remote!
specs
@@ -269,9 +269,8 @@ module Bundler
else
# Run a resolve against the locally available gems
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
- platforms_for_resolve = platforms.one? {|p| generic(p) == Gem::Platform::RUBY } ? platforms : platforms.reject{|p| p == Gem::Platform::RUBY }
- expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote, platforms_for_resolve.map {|p| generic(p) })
- last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms_for_resolve)
+ expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
end
# filter out gems that _can_ be installed on multiple platforms, but don't need
@@ -507,15 +506,11 @@ module Bundler
end
def validate_platforms!
- return if @platforms.any? do |bundle_platform|
- Bundler.rubygems.platforms.any? do |local_platform|
- MatchPlatform.platforms_match?(bundle_platform, local_platform)
- end
- end
+ return if current_platform_locked?
raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
- "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
- "there's no compatible match between those two lists."
+ "but your local platform is #{Bundler.local_platform}. " \
+ "Add the current platform to the lockfile with `bundle lock --add-platform #{Bundler.local_platform}` and try again."
end
def add_platform(platform)
@@ -528,6 +523,12 @@ module Bundler
raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
end
+ def most_specific_locked_platform
+ @platforms.min_by do |bundle_platform|
+ platform_specificity_match(bundle_platform, local_platform)
+ end
+ end
+
def find_resolved_spec(current_spec)
specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
end
@@ -549,6 +550,18 @@ module Bundler
private
+ def current_ruby_platform_locked?
+ return false unless generic_local_platform == Gem::Platform::RUBY
+
+ current_platform_locked?
+ end
+
+ def current_platform_locked?
+ @platforms.any? do |bundle_platform|
+ MatchPlatform.platforms_match?(bundle_platform, Bundler.local_platform)
+ end
+ end
+
def add_current_platform
add_platform(local_platform)
end
@@ -871,8 +884,7 @@ module Bundler
end
end
- def expand_dependencies(dependencies, remote = false, platforms = nil)
- platforms ||= @platforms
+ def expand_dependencies(dependencies, remote = false)
deps = []
dependencies.each do |dep|
dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb
index 2a097375c0..b271b8d229 100644
--- a/lib/bundler/gem_helpers.rb
+++ b/lib/bundler/gem_helpers.rb
@@ -35,41 +35,33 @@ module Bundler
def platform_specificity_match(spec_platform, user_platform)
spec_platform = Gem::Platform.new(spec_platform)
- return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
- return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
-
- PlatformMatch.new(
- PlatformMatch.os_match(spec_platform, user_platform),
- PlatformMatch.cpu_match(spec_platform, user_platform),
- PlatformMatch.platform_version_match(spec_platform, user_platform)
- )
+
+ PlatformMatch.specificity_score(spec_platform, user_platform)
end
module_function :platform_specificity_match
def select_best_platform_match(specs, platform)
- specs.select {|spec| spec.match_platform(platform) }.
- min_by {|spec| platform_specificity_match(spec.platform, platform) }
+ matching = specs.select {|spec| spec.match_platform(platform) }
+ exact = matching.select {|spec| spec.platform == platform }
+ return exact if exact.any?
+
+ sorted_matching = matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) }
+ exemplary_spec = sorted_matching.first
+
+ sorted_matching.take_while{|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) }
end
module_function :select_best_platform_match
- PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
class PlatformMatch
- def <=>(other)
- return nil unless other.is_a?(PlatformMatch)
+ def self.specificity_score(spec_platform, user_platform)
+ return -1 if spec_platform == user_platform
+ return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
- m = os_match <=> other.os_match
- return m unless m.zero?
-
- m = cpu_match <=> other.cpu_match
- return m unless m.zero?
-
- m = platform_version_match <=> other.platform_version_match
- m
+ os_match(spec_platform, user_platform) +
+ cpu_match(spec_platform, user_platform) * 10 +
+ platform_version_match(spec_platform, user_platform) * 100
end
- EXACT_MATCH = new(-1, -1, -1).freeze
- WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze
-
def self.os_match(spec_platform, user_platform)
if spec_platform.os == user_platform.os
0
@@ -100,5 +92,19 @@ module Bundler
end
end
end
+
+ def same_specificity(platform, spec, exemplary_spec)
+ platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
+ end
+ module_function :same_specificity
+
+ def same_deps(spec, exemplary_spec)
+ same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort
+ return same_runtime_deps unless spec.is_a?(Gem::Specification) && exemplary_spec.is_a?(Gem::Specification)
+
+ same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version
+ same_runtime_deps && same_metadata_deps
+ end
+ module_function :same_deps
end
end
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 7b1d28b0c3..1081910816 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -4,7 +4,7 @@ require_relative "match_platform"
module Bundler
class LazySpecification
- Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
+ Identifier = Struct.new(:name, :version, :platform)
class Identifier
include Comparable
def <=>(other)
@@ -108,7 +108,7 @@ module Bundler
end
def identifier
- @__identifier ||= Identifier.new(name, version, source, platform, dependencies)
+ @__identifier ||= Identifier.new(name, version, platform)
end
def git_version
@@ -131,17 +131,16 @@ module Bundler
end
#
- # Bundler 2.2.0 was the first version that records the full resolution
- # including platform specific gems in the lockfile, which means that if a
- # gem with RUBY platform is recorded, the RUBY platform version of the gem
- # should be installed. Previously bundler would record only generic versions
- # in the lockfile and then install the most specific platform variant if
- # available.
+ # For backwards compatibility with existing lockfiles, if the most specific
+ # locked platform is RUBY, we keep the previous behaviour of resolving the
+ # best platform variant at materiliazation time. For previous bundler
+ # versions (before 2.2.0) this was always the case (except when the lockfile
+ # only included non-ruby platforms), but we're also keeping this behaviour
+ # on newer bundlers unless users generate the lockfile from scratch or
+ # explicitly add a more specific platform.
#
def ruby_platform_materializes_to_ruby_platform?
- locked_bundler_version = Bundler.locked_bundler_version
-
- locked_bundler_version.nil? || Gem::Version.new(locked_bundler_version) >= Gem::Version.new("2.2.0")
+ !Bundler.most_specific_locked_platform?(Gem::Platform::RUBY)
end
end
end
diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb
index 38dc175ff9..34780f9528 100644
--- a/lib/bundler/resolver/spec_group.rb
+++ b/lib/bundler/resolver/spec_group.rb
@@ -25,11 +25,15 @@ module Bundler
def to_specs
@activated_platforms.map do |p|
- next unless s = @specs[p]
- lazy_spec = LazySpecification.new(name, version, s.platform, source)
- lazy_spec.dependencies.replace s.dependencies
- lazy_spec
- end.compact.uniq
+ specs = @specs[p]
+ next unless specs.any?
+
+ specs.map do |s|
+ lazy_spec = LazySpecification.new(name, version, s.platform, source)
+ lazy_spec.dependencies.replace s.dependencies
+ lazy_spec
+ end
+ end.flatten.compact.uniq
end
def copy_for(platforms)
@@ -42,12 +46,8 @@ module Bundler
copied_sg
end
- def spec_for(platform)
- @specs[platform]
- end
-
def for?(platform)
- !spec_for(platform).nil?
+ @specs[platform].any?
end
def to_s
@@ -58,7 +58,7 @@ module Bundler
def dependencies_for_activated_platforms
dependencies = @activated_platforms.map {|p| __dependencies[p] }
metadata_dependencies = @activated_platforms.map do |platform|
- metadata_dependencies(@specs[platform], platform)
+ metadata_dependencies(@specs[platform].first, platform)
end
dependencies.concat(metadata_dependencies).flatten
end
@@ -94,7 +94,8 @@ module Bundler
def __dependencies
@dependencies = Hash.new do |dependencies, platform|
dependencies[platform] = []
- if spec = @specs[platform]
+ specs = @specs[platform]
+ if spec = specs.first
spec.dependencies.each do |dep|
next if dep.type == :development
next if @ignores_bundler_dependencies && dep.name == "bundler".freeze
@@ -106,10 +107,7 @@ module Bundler
end
def metadata_dependencies(spec, platform)
- return [] unless spec
- # Only allow endpoint specifications since they won't hit the network to
- # fetch the full gemspec when calling required_ruby_version
- return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
+ return [] unless spec && spec.is_a?(Gem::Specification)
dependencies = []
if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
dependencies << DepProxy.new(Gem::Dependency.new("Ruby\0", spec.required_ruby_version), platform)
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index c577001043..d060e21f50 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -110,11 +110,6 @@ module Bundler
obj.to_s
end
- def platforms
- return [Gem::Platform::RUBY] if Bundler.settings[:force_ruby_platform]
- Gem.platforms
- end
-
def configuration
require_relative "psyched_yaml"
Gem.configuration
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index aef854ce0c..a0b9552c18 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -22,10 +22,11 @@ module Bundler
break unless dep = deps.shift
next if !handled.add?(dep) || skip.include?(dep.name)
- if spec = spec_for_dependency(dep, match_current_platform)
- specs << spec
+ specs_for_dep = spec_for_dependency(dep, match_current_platform)
+ if specs_for_dep.any?
+ specs += specs_for_dep
- spec.dependencies.each do |d|
+ specs_for_dep.first.dependencies.each do |d|
next if d.type == :development
d = DepProxy.new(d, dep.__platform) unless match_current_platform
deps << d
@@ -184,11 +185,7 @@ module Bundler
def spec_for_dependency(dep, match_current_platform)
specs_for_platforms = lookup[dep.name]
if match_current_platform
- Bundler.rubygems.platforms.reverse_each do |pl|
- match = GemHelpers.select_best_platform_match(specs_for_platforms, pl)
- return match if match
- end
- nil
+ GemHelpers.select_best_platform_match(specs_for_platforms, Bundler.local_platform)
else
GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform)
end
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index c730934258..8db404377c 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.2.2".freeze
+ VERSION = "2.2.3".freeze
def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 5da8b6904b..fe52d72cc3 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -8,7 +8,7 @@
require 'rbconfig'
module Gem
- VERSION = "3.2.2".freeze
+ VERSION = "3.2.3".freeze
end
# Must be first since it unloads the prelude from 1.9.2
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index fb555a46d4..400a5de5cf 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -286,6 +286,7 @@ class Gem::DependencyInstaller
installer_set = Gem::Resolver::InstallerSet.new @domain
installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir
+ installer_set.force = @force
if consider_local?
if dep_or_name =~ /\.gem$/ and File.file? dep_or_name
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 2c583743b9..3ec6d743a2 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -638,27 +638,6 @@ class Gem::Installer
end
end
- def ensure_required_ruby_version_met # :nodoc:
- if rrv = spec.required_ruby_version
- ruby_version = Gem.ruby_version
- unless rrv.satisfied_by? ruby_version
- raise Gem::RuntimeRequirementNotMetError,
- "#{spec.name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}."
- end
- end
- end
-
- def ensure_required_rubygems_version_met # :nodoc:
- if rrgv = spec.required_rubygems_version
- unless rrgv.satisfied_by? Gem.rubygems_version
- rg_version = Gem::VERSION
- raise Gem::RuntimeRequirementNotMetError,
- "#{spec.name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " +
- "Try 'gem update --system' to update RubyGems itself."
- end
- end
- end
-
def ensure_dependencies_met # :nodoc:
deps = spec.runtime_dependencies
deps |= spec.development_dependencies if @development
@@ -914,8 +893,6 @@ TEXT
return true if @force
- ensure_required_ruby_version_met
- ensure_required_rubygems_version_met
ensure_dependencies_met unless @ignore_dependencies
true
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 8ebe6acc70..285c80a95f 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -215,7 +215,7 @@ class Gem::RemoteFetcher
case response
when Net::HTTPOK, Net::HTTPNotModified then
- response.uri = uri if response.respond_to? :uri
+ response.uri = uri
head ? response : response.body
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
Net::HTTPTemporaryRedirect then
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index a8a06d0b95..5190cfc904 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -195,19 +195,8 @@ class Gem::RequestSet
yield req, installer if block_given?
end
rescue Gem::RuntimeRequirementNotMetError => e
- recent_match = req.spec.set.find_all(req.request).sort_by(&:version).reverse_each.find do |s|
- s = s.spec
- s.required_ruby_version.satisfied_by?(Gem.ruby_version) &&
- s.required_rubygems_version.satisfied_by?(Gem.rubygems_version) &&
- Gem::Platform.installable?(s)
- end
- if recent_match
- suggestion = "The last version of #{req.request} to support your Ruby & RubyGems was #{recent_match.version}. Try installing it with `gem install #{recent_match.name} -v #{recent_match.version}`"
- suggestion += " and then running the current command again" unless @always_install.include?(req.spec.spec)
- else
- suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems"
- suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec)
- end
+ suggestion = "There are no versions of #{req.request} compatible with your Ruby & RubyGems"
+ suggestion += ". Maybe try installing an older version of the gem you're looking for?" unless @always_install.include?(req.spec.spec)
e.suggestion = suggestion
raise
end
diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb
index e1c0d2dd0a..71c35ea3d3 100644
--- a/lib/rubygems/resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -261,7 +261,12 @@ class Gem::Resolver
end
def requirement_satisfied_by?(requirement, activated, spec)
- requirement.matches_spec? spec
+ matches_spec = requirement.matches_spec? spec
+ return matches_spec if @soft_missing
+
+ matches_spec &&
+ spec.spec.required_ruby_version.satisfied_by?(Gem.ruby_version) &&
+ spec.spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version)
end
def name_for(dependency)
diff --git a/lib/rubygems/resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb
index cafcd5b472..21c9b8920c 100644
--- a/lib/rubygems/resolver/api_set.rb
+++ b/lib/rubygems/resolver/api_set.rb
@@ -4,6 +4,8 @@
# Returns instances of APISpecification.
class Gem::Resolver::APISet < Gem::Resolver::Set
+ autoload :GemParser, File.expand_path("api_set/gem_parser", __dir__)
+
##
# The URI for the dependency API this APISet uses.
@@ -24,13 +26,13 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
# API URL +dep_uri+ which is described at
# https://guides.rubygems.org/rubygems-org-api
- def initialize(dep_uri = 'https://rubygems.org/api/v1/dependencies')
+ def initialize(dep_uri = 'https://index.rubygems.org/info/')
super()
dep_uri = URI dep_uri unless URI === dep_uri
@dep_uri = dep_uri
- @uri = dep_uri + '../..'
+ @uri = dep_uri + '..'
@data = Hash.new {|h,k| h[k] = [] }
@source = Gem::Source.new @uri
@@ -75,20 +77,8 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
def prefetch_now # :nodoc:
needed, @to_fetch = @to_fetch, []
- uri = @dep_uri + "?gems=#{needed.sort.join ','}"
- str = Gem::RemoteFetcher.fetcher.fetch_path uri
-
- loaded = []
-
- Marshal.load(str).each do |ver|
- name = ver[:name]
-
- @data[name] << ver
- loaded << name
- end
-
- (needed - loaded).each do |missing|
- @data[missing] = []
+ needed.sort.each do |name|
+ versions(name)
end
end
@@ -111,13 +101,32 @@ class Gem::Resolver::APISet < Gem::Resolver::Set
return @data[name]
end
- uri = @dep_uri + "?gems=#{name}"
+ uri = @dep_uri + name
str = Gem::RemoteFetcher.fetcher.fetch_path uri
- Marshal.load(str).each do |ver|
- @data[ver[:name]] << ver
+ lines(str).each do |ver|
+ number, platform, dependencies, requirements = parse_gem(ver)
+
+ platform ||= "ruby"
+ dependencies = dependencies.map {|dep_name, reqs| [dep_name, reqs.join(", ")] }
+ requirements = requirements.map {|req_name, reqs| [req_name.to_sym, reqs] }.to_h
+
+ @data[name] << { name: name, number: number, platform: platform, dependencies: dependencies, requirements: requirements }
end
@data[name]
end
+
+ private
+
+ def lines(str)
+ lines = str.split("\n")
+ header = lines.index("---")
+ header ? lines[header + 1..-1] : lines
+ end
+
+ def parse_gem(string)
+ @gem_parser ||= GemParser.new
+ @gem_parser.parse(string)
+ end
end
diff --git a/lib/rubygems/resolver/api_set/gem_parser.rb b/lib/rubygems/resolver/api_set/gem_parser.rb
new file mode 100644
index 0000000000..685c39558d
--- /dev/null
+++ b/lib/rubygems/resolver/api_set/gem_parser.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class Gem::Resolver::APISet::GemParser
+ def parse(line)
+ version_and_platform, rest = line.split(" ", 2)
+ version, platform = version_and_platform.split("-", 2)
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
+ requirements = requirements ? requirements.map {|d| parse_dependency(d) } : []
+ [version, platform, dependencies, requirements]
+ end
+
+ private
+
+ def parse_dependency(string)
+ dependency = string.split(":")
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency
+ end
+end
diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb
index 589ea1ba61..b5aa0b71d4 100644
--- a/lib/rubygems/resolver/api_specification.rb
+++ b/lib/rubygems/resolver/api_specification.rb
@@ -35,6 +35,8 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
@dependencies = api_data[:dependencies].map do |name, ver|
Gem::Dependency.new(name, ver.split(/\s*,\s*/)).freeze
end.freeze
+ @required_ruby_version = Gem::Requirement.new(api_data.dig(:requirements, :ruby)).freeze
+ @required_rubygems_version = Gem::Requirement.new(api_data.dig(:requirements, :rubygems)).freeze
end
def ==(other) # :nodoc:
@@ -42,12 +44,11 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification
@set == other.set and
@name == other.name and
@version == other.version and
- @platform == other.platform and
- @dependencies == other.dependencies
+ @platform == other.platform
end
def hash
- @set.hash ^ @name.hash ^ @version.hash ^ @platform.hash ^ @dependencies.hash
+ @set.hash ^ @name.hash ^ @version.hash ^ @platform.hash
end
def fetch_development_dependencies # :nodoc:
diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb
index 7a708ee391..b6a46a9403 100644
--- a/lib/rubygems/resolver/best_set.rb
+++ b/lib/rubygems/resolver/best_set.rb
@@ -60,7 +60,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
def replace_failed_api_set(error) # :nodoc:
uri = error.uri
uri = URI uri unless URI === uri
- uri.query = nil
+ uri = uri + "."
raise error unless api_set = @sets.find do |set|
Gem::Resolver::APISet === set and set.dep_uri == uri
diff --git a/lib/rubygems/resolver/index_specification.rb b/lib/rubygems/resolver/index_specification.rb
index 3b75b719b0..45ff130a14 100644
--- a/lib/rubygems/resolver/index_specification.rb
+++ b/lib/rubygems/resolver/index_specification.rb
@@ -33,6 +33,21 @@ class Gem::Resolver::IndexSpecification < Gem::Resolver::Specification
spec.dependencies
end
+ ##
+ # The required_ruby_version constraint for this specification
+
+ def required_ruby_version
+ spec.required_ruby_version
+ end
+
+ ##
+ # The required_rubygems_version constraint for this specification
+ #
+
+ def required_rubygems_version
+ spec.required_rubygems_version
+ end
+
def ==(other)
self.class === other &&
@name == other.name &&
diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb
index b0e4ce5a37..60181315b0 100644
--- a/lib/rubygems/resolver/installer_set.rb
+++ b/lib/rubygems/resolver/installer_set.rb
@@ -26,6 +26,12 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
attr_reader :remote_set # :nodoc:
##
+ # Ignore ruby & rubygems specification constraints.
+ #
+
+ attr_accessor :force # :nodoc:
+
+ ##
# Creates a new InstallerSet that will look for gems in +domain+.
def initialize(domain)
@@ -41,6 +47,7 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
@local = {}
@local_source = Gem::Source::Local.new
@remote_set = Gem::Resolver::BestSet.new
+ @force = false
@specs = {}
end
@@ -63,15 +70,30 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
Gem::Platform.local === s.platform
end
- if found.empty?
- exc = Gem::UnsatisfiableDependencyError.new request
- exc.errors = errors
-
- raise exc
+ found = found.sort_by do |s|
+ [s.version, s.platform == Gem::Platform::RUBY ? -1 : 1]
end
- newest = found.max_by do |s|
- [s.version, s.platform == Gem::Platform::RUBY ? -1 : 1]
+ newest = found.last
+
+ unless @force
+ found_matching_metadata = found.select do |spec|
+ metadata_satisfied?(spec)
+ end
+
+ if found_matching_metadata.empty?
+ if newest
+ ensure_required_ruby_version_met(newest.spec)
+ ensure_required_rubygems_version_met(newest.spec)
+ else
+ exc = Gem::UnsatisfiableDependencyError.new request
+ exc.errors = errors
+
+ raise exc
+ end
+ else
+ newest = found_matching_metadata.last
+ end
end
@always_install << newest.spec
@@ -221,4 +243,32 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
@domain = :local unless remote
end
end
+
+ private
+
+ def metadata_satisfied?(spec)
+ spec.required_ruby_version.satisfied_by?(Gem.ruby_version) &&
+ spec.required_rubygems_version.satisfied_by?(Gem.rubygems_version)
+ end
+
+ def ensure_required_ruby_version_met(spec) # :nodoc:
+ if rrv = spec.required_ruby_version
+ ruby_version = Gem.ruby_version
+ unless rrv.satisfied_by? ruby_version
+ raise Gem::RuntimeRequirementNotMetError,
+ "#{spec.full_name} requires Ruby version #{rrv}. The current ruby version is #{ruby_version}."
+ end
+ end
+ end
+
+ def ensure_required_rubygems_version_met(spec) # :nodoc:
+ if rrgv = spec.required_rubygems_version
+ unless rrgv.satisfied_by? Gem.rubygems_version
+ rg_version = Gem::VERSION
+ raise Gem::RuntimeRequirementNotMetError,
+ "#{spec.full_name} requires RubyGems version #{rrgv}. The current RubyGems version is #{rg_version}. " +
+ "Try 'gem update --system' to update RubyGems itself."
+ end
+ end
+ end
end
diff --git a/lib/rubygems/resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb
index bde5d9cddc..7b665fe876 100644
--- a/lib/rubygems/resolver/spec_specification.rb
+++ b/lib/rubygems/resolver/spec_specification.rb
@@ -23,6 +23,20 @@ class Gem::Resolver::SpecSpecification < Gem::Resolver::Specification
end
##
+ # The required_ruby_version constraint for this specification
+
+ def required_ruby_version
+ spec.required_ruby_version
+ end
+
+ ##
+ # The required_rubygems_version constraint for this specification
+
+ def required_rubygems_version
+ spec.required_rubygems_version
+ end
+
+ ##
# The name and version of the specification.
#
# Unlike Gem::Specification#full_name, the platform is not included.
diff --git a/lib/rubygems/resolver/specification.rb b/lib/rubygems/resolver/specification.rb
index 5ae5f15813..8c6fc9afcf 100644
--- a/lib/rubygems/resolver/specification.rb
+++ b/lib/rubygems/resolver/specification.rb
@@ -44,6 +44,16 @@ class Gem::Resolver::Specification
attr_reader :version
##
+ # The required_ruby_version constraint for this specification.
+
+ attr_reader :required_ruby_version
+
+ ##
+ # The required_ruby_version constraint for this specification.
+
+ attr_reader :required_rubygems_version
+
+ ##
# Sets default instance variables for the specification.
def initialize
@@ -53,6 +63,8 @@ class Gem::Resolver::Specification
@set = nil
@source = nil
@version = nil
+ @required_ruby_version = Gem::Requirement.default
+ @required_rubygems_version = Gem::Requirement.default
end
##
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index a2848112e7..4ae84cf532 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -79,7 +79,15 @@ class Gem::Source
def dependency_resolver_set # :nodoc:
return Gem::Resolver::IndexSet.new self if 'file' == uri.scheme
- bundler_api_uri = enforce_trailing_slash(uri) + './api/v1/dependencies'
+ fetch_uri = if uri.host == "rubygems.org"
+ index_uri = uri.dup
+ index_uri.host = "index.rubygems.org"
+ index_uri
+ else
+ uri
+ end
+
+ bundler_api_uri = enforce_trailing_slash(fetch_uri)
begin
fetcher = Gem::RemoteFetcher.fetcher
@@ -87,11 +95,7 @@ class Gem::Source
rescue Gem::RemoteFetcher::FetchError
Gem::Resolver::IndexSet.new self
else
- if response.respond_to? :uri
- Gem::Resolver::APISet.new response.uri
- else
- Gem::Resolver::APISet.new bundler_api_uri
- end
+ Gem::Resolver::APISet.new response.uri + "./info/"
end
end