* lib/rubygems: Update to RubyGems master 4bdc4f2. Important changes

in this commit:

  RubyGems now chooses the test server port reliably.  Patch by akr.

  Partial implementation of bundler's Gemfile format.

  Refactorings to improve the new resolver.

  Fixes bugs in the resolver.

* test/rubygems:  Tests for the above.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43643 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2013-11-10 17:51:40 +00:00
parent 31d355aaa9
commit 4f6779bac7
75 changed files with 3143 additions and 616 deletions

View file

@ -47,11 +47,21 @@ class Gem::DependencyResolver::ActivationRequest
end
def inspect # :nodoc:
others_possible = nil
others_possible = ' (others possible)' if @others_possible
others =
case @others_possible
when true then # TODO remove at RubyGems 3
' (others possible)'
when false then # TODO remove at RubyGems 3
nil
else
unless @others_possible.empty? then
others = @others_possible.map { |s| s.full_name }
" (others possible: #{others.join ', '})"
end
end
'#<%s for %p from %s%s>' % [
self.class, @spec, @request, others_possible
self.class, @spec, @request, others
]
end
@ -59,10 +69,15 @@ class Gem::DependencyResolver::ActivationRequest
# Indicates if the requested gem has already been installed.
def installed?
this_spec = full_spec
case @spec
when Gem::DependencyResolver::VendorSpecification then
true
else
this_spec = full_spec
Gem::Specification.any? do |s|
s == this_spec
Gem::Specification.any? do |s|
s == this_spec
end
end
end
@ -75,7 +90,12 @@ class Gem::DependencyResolver::ActivationRequest
# requests for the same Dependency request.
def others_possible?
@others_possible
case @others_possible
when true, false then
@others_possible
else
not @others_possible.empty?
end
end
##
@ -95,9 +115,18 @@ class Gem::DependencyResolver::ActivationRequest
q.text ' for '
q.pp @request
q.breakable
q.text ' (other possible)' if @others_possible
case @others_possible
when false then
when true then
q.breakable
q.text 'others possible'
else
unless @others_possible.empty? then
q.breakable
q.text 'others '
q.pp @others_possible.map { |s| s.full_name }
end
end
end
end

View file

@ -2,11 +2,21 @@
# The global rubygems pool, available via the rubygems.org API.
# Returns instances of APISpecification.
class Gem::DependencyResolver::APISet
class Gem::DependencyResolver::APISet < Gem::DependencyResolver::Set
def initialize
##
# The URI for the dependency API this APISet uses.
attr_reader :dep_uri # :nodoc:
##
# Creates a new APISet that will retrieve gems from +uri+ using the RubyGems
# API described at http://guides.rubygems.org/rubygems-org-api
def initialize uri = 'https://rubygems.org/api/v1/dependencies'
uri = URI uri unless URI === uri # for ruby 1.8
@data = Hash.new { |h,k| h[k] = [] }
@dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
@dep_uri = uri
end
##
@ -46,7 +56,7 @@ class Gem::DependencyResolver::APISet
##
# Return data for all versions of the gem +name+.
def versions name
def versions name # :nodoc:
if @data.key?(name)
return @data[name]
end

View file

@ -1,18 +1,21 @@
##
# Represents a specification retrieved via the rubygems.org
# API. This is used to avoid having to load the full
# Specification object when all we need is the name, version,
# and dependencies.
# Represents a specification retrieved via the rubygems.org API.
#
# This is used to avoid loading the full Specification object when all we need
# is the name, version, and dependencies.
class Gem::DependencyResolver::APISpecification
class Gem::DependencyResolver::APISpecification < Gem::DependencyResolver::Specification
attr_reader :dependencies
attr_reader :name
attr_reader :platform
attr_reader :set # :nodoc:
attr_reader :version
##
# Creates an APISpecification for the given +set+ from the rubygems.org
# +api_data+.
#
# See http://guides.rubygems.org/rubygems-org-api/#misc_methods for the
# format of the +api_data+.
def initialize(set, api_data)
super()
@set = set
@name = api_data[:name]
@version = Gem::Version.new api_data[:number]
@ -31,9 +34,5 @@ class Gem::DependencyResolver::APISpecification
@dependencies == other.dependencies
end
def full_name
"#{@name}-#{@version}"
end
end

View file

@ -0,0 +1,21 @@
##
# The BestSet chooses the best available method to query a remote index.
#
# It combines IndexSet and APISet
class Gem::DependencyResolver::BestSet < Gem::DependencyResolver::ComposedSet
##
# Creates a BestSet for the given +sources+ or Gem::sources if none are
# specified. +sources+ must be a Gem::SourceList.
def initialize sources = Gem.sources
super()
sources.each_source do |source|
@sets << source.dependency_resolver_set
end
end
end

View file

@ -1,4 +1,6 @@
class Gem::DependencyResolver::ComposedSet
class Gem::DependencyResolver::ComposedSet < Gem::DependencyResolver::Set
attr_reader :sets # :nodoc:
def initialize *sets
@sets = sets

View file

@ -3,14 +3,11 @@
# all the normal settings that control where to look
# for installed gems.
class Gem::DependencyResolver::CurrentSet
class Gem::DependencyResolver::CurrentSet < Gem::DependencyResolver::Set
def find_all req
req.dependency.matching_specs
end
def prefetch gems
end
end

View file

@ -8,12 +8,21 @@ class Gem::DependencyResolver::DependencyConflict
attr_reader :dependency
attr_reader :failed_dep # :nodoc:
def initialize(dependency, activated, failed_dep=dependency)
@dependency = dependency
@activated = activated
@failed_dep = failed_dep
end
def == other
self.class === other and
@dependency == other.dependency and
@activated == other.activated and
@failed_dep == other.failed_dep
end
##
# Return the 2 dependency objects that conflicted
@ -71,6 +80,8 @@ class Gem::DependencyResolver::DependencyConflict
current = current.request.requester
end
path = ['user request (gem command or Gemfile)'] if path.empty?
path
end

View file

@ -32,6 +32,22 @@ class Gem::DependencyResolver::DependencyRequest
@dependency.name
end
# Indicate that the request is for a gem explicitly requested by the user
def explicit?
@requester.nil?
end
# Indicate that the requset is for a gem requested as a dependency of another gem
def implicit?
!explicit?
end
# Return a String indicating who caused this request to be added (only
# valid for implicit requests)
def request_context
@requester ? @requester.request : "(unknown)"
end
def pretty_print q # :nodoc:
q.group 2, '[Dependency request ', ']' do
q.breakable
@ -43,6 +59,10 @@ class Gem::DependencyResolver::DependencyRequest
end
end
def requirement
@dependency.requirement
end
def to_s # :nodoc:
@dependency.to_s
end

View file

@ -2,10 +2,17 @@
# The global rubygems pool represented via the traditional
# source index.
class Gem::DependencyResolver::IndexSet
class Gem::DependencyResolver::IndexSet < Gem::DependencyResolver::Set
def initialize
@f = Gem::SpecFetcher.fetcher
def initialize source = nil # :nodoc:
@f =
if source then
sources = Gem::SourceList.from [source]
Gem::SpecFetcher.new sources
else
Gem::SpecFetcher.fetcher
end
@all = Hash.new { |h,k| h[k] = [] }
@ -39,26 +46,5 @@ class Gem::DependencyResolver::IndexSet
res
end
##
# Called from IndexSpecification to get a true Specification
# object.
def load_spec name, ver, platform, source
key = "#{name}-#{ver}-#{platform}"
@specs.fetch key do
tuple = Gem::NameTuple.new name, ver, platform
@specs[key] = source.fetch_spec tuple
end
end
##
# No prefetching needed since we load the whole index in
# initially.
def prefetch gems
end
end

View file

@ -3,17 +3,20 @@
# delay needed to download full Specification objects when only the +name+
# and +version+ are needed.
class Gem::DependencyResolver::IndexSpecification
class Gem::DependencyResolver::IndexSpecification < Gem::DependencyResolver::Specification
attr_reader :name
attr_reader :platform
attr_reader :source
attr_reader :version
##
# An IndexSpecification is created from the index format described in `gem
# help generate_index`.
#
# The +set+ contains other specifications for this (URL) +source+.
#
# The +name+, +version+ and +platform+ are the name, version and platform of
# the gem.
def initialize set, name, version, source, platform
super()
@set = set
@name = name
@version = version
@ -23,14 +26,13 @@ class Gem::DependencyResolver::IndexSpecification
@spec = nil
end
##
# The dependencies of the gem for this specification
def dependencies
spec.dependencies
end
def full_name
"#{@name}-#{@version}"
end
def inspect # :nodoc:
'#<%s %s source %s>' % [self.class, full_name, @source]
end
@ -51,8 +53,16 @@ class Gem::DependencyResolver::IndexSpecification
end
end
def spec
@spec ||= @set.load_spec(@name, @version, @platform, @source)
##
# Fetches a Gem::Specification for this IndexSpecification from the #source.
def spec # :nodoc:
@spec ||=
begin
tuple = Gem::NameTuple.new @name, @version, @platform
@source.fetch_spec tuple
end
end
end

View file

@ -1,12 +1,8 @@
class Gem::DependencyResolver::InstalledSpecification
##
# An InstalledSpecification represents a gem that is already installed
# locally.
attr_reader :spec
def initialize set, spec, source=nil
@set = set
@source = source
@spec = spec
end
class Gem::DependencyResolver::InstalledSpecification < Gem::DependencyResolver::SpecSpecification
def == other # :nodoc:
self.class === other and
@ -14,29 +10,25 @@ class Gem::DependencyResolver::InstalledSpecification
@spec == other.spec
end
def dependencies
@spec.dependencies
##
# Returns +true+ if this gem is installable for the current platform.
def installable_platform?
# BACKCOMPAT If the file is coming out of a specified file, then we
# ignore the platform. This code can be removed in RG 3.0.
if @source.kind_of? Gem::Source::SpecificFile
return true
else
Gem::Platform.match @spec.platform
end
end
def full_name
"#{@spec.name}-#{@spec.version}"
end
def name
@spec.name
end
def platform
@spec.platform
end
##
# The source for this specification
def source
@source ||= Gem::Source::Installed.new
end
def version
@spec.version
end
end

View file

@ -2,23 +2,23 @@
# A set of gems for installation sourced from remote sources and local .gem
# files
class Gem::DependencyResolver::InstallerSet
class Gem::DependencyResolver::InstallerSet < Gem::DependencyResolver::Set
##
# List of Gem::Specification objects that must always be installed.
attr_reader :always_install
attr_reader :always_install # :nodoc:
##
# Only install gems in the always_install list
attr_accessor :ignore_dependencies
attr_accessor :ignore_dependencies # :nodoc:
##
# Do not look in the installed set when finding specifications. This is
# used by the --install-dir option to `gem install`
attr_accessor :ignore_installed
attr_accessor :ignore_installed # :nodoc:
def initialize domain
@domain = domain
@ -36,14 +36,14 @@ class Gem::DependencyResolver::InstallerSet
##
# Should local gems should be considered?
def consider_local?
def consider_local? # :nodoc:
@domain == :both or @domain == :local
end
##
# Should remote gems should be considered?
def consider_remote?
def consider_remote? # :nodoc:
@domain == :both or @domain == :remote
end
@ -101,7 +101,7 @@ class Gem::DependencyResolver::InstallerSet
##
# Loads remote prerelease specs if +dep+ is a prerelease dependency
def load_remote_specs dep
def load_remote_specs dep # :nodoc:
types = [:released]
types << :prerelease if dep.prerelease?
@ -123,7 +123,7 @@ class Gem::DependencyResolver::InstallerSet
# Called from IndexSpecification to get a true Specification
# object.
def load_spec name, ver, platform, source
def load_spec name, ver, platform, source # :nodoc:
key = "#{name}-#{ver}-#{platform}"
@specs.fetch key do
@ -133,12 +133,6 @@ class Gem::DependencyResolver::InstallerSet
end
end
##
# No prefetching needed since we load the whole index in initially.
def prefetch(reqs)
end
def pretty_print q # :nodoc:
q.group 2, '[InstallerSet', ']' do
q.breakable

View file

@ -0,0 +1,60 @@
##
# A set of gems from a gem dependencies lockfile.
class Gem::DependencyResolver::LockSet < Gem::DependencyResolver::Set
attr_reader :specs # :nodoc:
##
# Creates a new LockSet from the given +source+
def initialize source
@source = source
@specs = []
end
##
# Creates a new IndexSpecification in this set using the given +name+,
# +version+ and +platform+.
#
# The specification's set will be the current set, and the source will be
# the current set's source.
def add name, version, platform # :nodoc:
version = Gem::Version.new version
spec =
Gem::DependencyResolver::IndexSpecification.new self, name, version,
@source, platform
@specs << spec
end
##
# Returns an Array of IndexSpecification objects matching the
# DependencyRequest +req+.
def find_all req
@specs.select do |spec|
req.matches_spec? spec
end
end
##
# Loads a Gem::Specification with the given +name+, +version+ and
# +platform+. +source+ is ignored.
def load_spec name, version, platform, source # :nodoc:
dep = Gem::Dependency.new name, version
found = @specs.find do |spec|
dep.matches_spec? spec and spec.platform == platform
end
tuple = Gem::NameTuple.new found.name, found.version, found.platform
found.source.fetch_spec tuple
end
end

View file

@ -0,0 +1,28 @@
##
# DependencyResolver sets are used to look up specifications (and their
# dependencies) used in resolution. This set is abstract.
class Gem::DependencyResolver::Set
##
# The find_all method must be implemented. It returns all
# DependencyResolver Specification objects matching the given
# DependencyRequest +req+.
def find_all req
raise NotImplementedError
end
##
# The #prefetch method may be overridden, but this is not necessary. This
# default implementation does nothing, which is suitable for sets where
# looking up a specification is cheap (such as installed gems).
#
# When overridden, the #prefetch method should look up specifications
# matching +reqs+.
def prefetch reqs
end
end

View file

@ -0,0 +1,58 @@
##
# The DependencyResolver::SpecSpecification contains common functionality for
# DependencyResolver specifications that are backed by a Gem::Specification.
class Gem::DependencyResolver::SpecSpecification < Gem::DependencyResolver::Specification
attr_reader :spec # :nodoc:
##
# A SpecSpecification is created for a +set+ for a Gem::Specification in
# +spec+. The +source+ is either where the +spec+ came from, or should be
# loaded from.
def initialize set, spec, source = nil
@set = set
@source = source
@spec = spec
end
##
# The dependencies of the gem for this specification
def dependencies
spec.dependencies
end
##
# The name and version of the specification.
#
# Unlike Gem::Specification#full_name, the platform is not included.
def full_name
"#{spec.name}-#{spec.version}"
end
##
# The name of the gem for this specification
def name
spec.name
end
##
# The platform this gem works on.
def platform
spec.platform
end
##
# The version of the gem for this specification.
def version
spec.version
end
end

View file

@ -0,0 +1,60 @@
##
# A DependencyResolver::Specification contains a subset of the information
# contained in a Gem::Specification. Only the information necessary for
# dependency resolution in the resolver is included.
class Gem::DependencyResolver::Specification
##
# The dependencies of the gem for this specification
attr_reader :dependencies
##
# The name of the gem for this specification
attr_reader :name
##
# The platform this gem works on.
attr_reader :platform
##
# The set this specification came from.
attr_reader :set
##
# The source for this specification
attr_reader :source
##
# The version of the gem for this specification.
attr_reader :version
##
# Sets default instance variables for the specification.
def initialize
@dependencies = nil
@name = nil
@platform = nil
@set = nil
@source = nil
@version = nil
end
##
# The name and version of the specification.
#
# Unlike Gem::Specification#full_name, the platform is not included.
def full_name
"#{@name}-#{@version}"
end
end

View file

@ -13,17 +13,18 @@
# The directory vendor/rake must contain an unpacked rake gem along with a
# rake.gemspec (watching the given name).
class Gem::DependencyResolver::VendorSet
class Gem::DependencyResolver::VendorSet < Gem::DependencyResolver::Set
def initialize
@specs = {}
def initialize # :nodoc:
@directories = {}
@specs = {}
end
##
# Adds a specification to the set with the given +name+ which has been
# unpacked into the given +directory+.
def add_vendor_gem name, directory
def add_vendor_gem name, directory # :nodoc:
gemspec = File.join directory, "#{name}.gemspec"
spec = Gem::Specification.load gemspec
@ -33,7 +34,8 @@ class Gem::DependencyResolver::VendorSet
key = "#{spec.name}-#{spec.version}-#{spec.platform}"
@specs[key] = spec
@specs[key] = spec
@directories[spec] = directory
end
##
@ -44,7 +46,8 @@ class Gem::DependencyResolver::VendorSet
@specs.values.select do |spec|
req.matches_spec? spec
end.map do |spec|
Gem::DependencyResolver::VendorSpecification.new self, spec, nil
source = Gem::Source::Vendor.new @directories[spec]
Gem::DependencyResolver::VendorSpecification.new self, spec, source
end
end
@ -53,17 +56,11 @@ class Gem::DependencyResolver::VendorSet
# +source+ is defined when the specification was added to index it is not
# used.
def load_spec name, version, platform, source
def load_spec name, version, platform, source # :nodoc:
key = "#{name}-#{version}-#{platform}"
@specs.fetch key
end
##
# No prefetch is needed as the index is loaded at creation time.
def prefetch gems
end
end

View file

@ -1,43 +1,15 @@
class Gem::DependencyResolver::VendorSpecification
##
# A VendorSpecification represents a gem that has been unpacked into a project
# and is being loaded through a gem dependencies file through the +path:+
# option.
attr_reader :spec
attr_reader :set
def initialize set, spec, source=nil
@set = set
@source = source
@spec = spec
end
class Gem::DependencyResolver::VendorSpecification < Gem::DependencyResolver::SpecSpecification
def == other # :nodoc:
self.class === other and
@set == other.set and
@spec == other.spec
end
def dependencies
@spec.dependencies
end
def full_name
"#{@spec.name}-#{@spec.version}"
end
def name
@spec.name
end
def platform
@spec.platform
end
def source
@source ||= Gem::Source::Vendor.new
end
def version
@spec.version
@spec == other.spec and
@source == other.source
end
end