summaryrefslogtreecommitdiff
path: root/lib/rubygems/resolver
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/resolver')
-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
8 files changed, 151 insertions, 30 deletions
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
##