aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/dependency_resolver
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-07-09 23:21:36 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-07-09 23:21:36 +0000
commit47f0248b0858898dd24d1e654cedf174059ca677 (patch)
tree493e84160f8609db408d88349f0624a3ff92c3c2 /lib/rubygems/dependency_resolver
parentcd9f9e471977447a991ced4ea38efb2309459ef5 (diff)
downloadruby-47f0248b0858898dd24d1e654cedf174059ca677.tar.gz
* lib/rubygems: Import RubyGems 2.1
* test/rubygems: Ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@41873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems/dependency_resolver')
-rw-r--r--lib/rubygems/dependency_resolver/activation_request.rb109
-rw-r--r--lib/rubygems/dependency_resolver/api_set.rb65
-rw-r--r--lib/rubygems/dependency_resolver/api_specification.rb36
-rw-r--r--lib/rubygems/dependency_resolver/composed_set.rb18
-rw-r--r--lib/rubygems/dependency_resolver/current_set.rb16
-rw-r--r--lib/rubygems/dependency_resolver/dependency_conflict.rb85
-rw-r--r--lib/rubygems/dependency_resolver/dependency_request.rb51
-rw-r--r--lib/rubygems/dependency_resolver/index_set.rb59
-rw-r--r--lib/rubygems/dependency_resolver/index_specification.rb53
-rw-r--r--lib/rubygems/dependency_resolver/installed_specification.rb38
-rw-r--r--lib/rubygems/dependency_resolver/installer_set.rb130
11 files changed, 660 insertions, 0 deletions
diff --git a/lib/rubygems/dependency_resolver/activation_request.rb b/lib/rubygems/dependency_resolver/activation_request.rb
new file mode 100644
index 0000000000..25af6378ac
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/activation_request.rb
@@ -0,0 +1,109 @@
+##
+# Specifies a Specification object that should be activated.
+# Also contains a dependency that was used to introduce this
+# activation.
+
+class Gem::DependencyResolver::ActivationRequest
+
+ attr_reader :request
+
+ attr_reader :spec
+
+ def initialize spec, req, others_possible = true
+ @spec = spec
+ @request = req
+ @others_possible = others_possible
+ end
+
+ def == other
+ case other
+ when Gem::Specification
+ @spec == other
+ when Gem::DependencyResolver::ActivationRequest
+ @spec == other.spec && @request == other.request
+ else
+ false
+ end
+ end
+
+ def download path
+ if @spec.respond_to? :source
+ source = @spec.source
+ else
+ source = Gem.sources.first
+ end
+
+ Gem.ensure_gem_subdirectories path
+
+ source.download full_spec, path
+ end
+
+ def full_name
+ @spec.full_name
+ end
+
+ def full_spec
+ Gem::Specification === @spec ? @spec : @spec.spec
+ end
+
+ def inspect # :nodoc:
+ others_possible = nil
+ others_possible = ' (others possible)' if @others_possible
+
+ '#<%s for %p from %s%s>' % [
+ self.class, @spec, @request, others_possible
+ ]
+ end
+
+ ##
+ # Indicates if the requested gem has already been installed.
+
+ def installed?
+ this_spec = full_spec
+
+ Gem::Specification.any? do |s|
+ s == this_spec
+ end
+ end
+
+ def name
+ @spec.name
+ end
+
+ ##
+ # Indicate if this activation is one of a set of possible
+ # requests for the same Dependency request.
+
+ def others_possible?
+ @others_possible
+ end
+
+ ##
+ # Return the ActivationRequest that contained the dependency
+ # that we were activated for.
+
+ def parent
+ @request.requester
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[Activation request', ']' do
+ q.breakable
+ q.pp @spec
+
+ q.breakable
+ q.text ' for '
+ q.pp @request
+
+
+ q.breakable
+ q.text ' (other possible)' if @others_possible
+ end
+ end
+
+ def version
+ @spec.version
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/api_set.rb b/lib/rubygems/dependency_resolver/api_set.rb
new file mode 100644
index 0000000000..469c005a09
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/api_set.rb
@@ -0,0 +1,65 @@
+##
+# The global rubygems pool, available via the rubygems.org API.
+# Returns instances of APISpecification.
+
+class Gem::DependencyResolver::APISet
+
+ def initialize
+ @data = Hash.new { |h,k| h[k] = [] }
+ @dep_uri = URI 'https://rubygems.org/api/v1/dependencies'
+ end
+
+ ##
+ # Return an array of APISpecification objects matching
+ # DependencyRequest +req+.
+
+ def find_all req
+ res = []
+
+ versions(req.name).each do |ver|
+ if req.dependency.match? req.name, ver[:number]
+ res << Gem::DependencyResolver::APISpecification.new(self, ver)
+ end
+ end
+
+ res
+ end
+
+ ##
+ # A hint run by the resolver to allow the Set to fetch
+ # data for DependencyRequests +reqs+.
+
+ def prefetch reqs
+ names = reqs.map { |r| r.dependency.name }
+ needed = names.find_all { |d| !@data.key?(d) }
+
+ return if needed.empty?
+
+ uri = @dep_uri + "?gems=#{needed.sort.join ','}"
+ str = Gem::RemoteFetcher.fetcher.fetch_path uri
+
+ Marshal.load(str).each do |ver|
+ @data[ver[:name]] << ver
+ end
+ end
+
+ ##
+ # Return data for all versions of the gem +name+.
+
+ def versions name
+ if @data.key?(name)
+ return @data[name]
+ end
+
+ uri = @dep_uri + "?gems=#{name}"
+ str = Gem::RemoteFetcher.fetcher.fetch_path uri
+
+ Marshal.load(str).each do |ver|
+ @data[ver[:name]] << ver
+ end
+
+ @data[name]
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/api_specification.rb b/lib/rubygems/dependency_resolver/api_specification.rb
new file mode 100644
index 0000000000..5ad07396cf
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/api_specification.rb
@@ -0,0 +1,36 @@
+##
+# 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.
+
+class Gem::DependencyResolver::APISpecification
+
+ attr_reader :dependencies
+ attr_reader :name
+ attr_reader :set # :nodoc:
+ attr_reader :version
+
+ def initialize(set, api_data)
+ @set = set
+ @name = api_data[:name]
+ @version = Gem::Version.new api_data[:number]
+ @dependencies = api_data[:dependencies].map do |name, ver|
+ Gem::Dependency.new name, ver.split(/\s*,\s*/)
+ end
+ end
+
+ def == other # :nodoc:
+ self.class === other and
+ @set == other.set and
+ @name == other.name and
+ @version == other.version and
+ @dependencies == other.dependencies
+ end
+
+ def full_name
+ "#{@name}-#{@version}"
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/composed_set.rb b/lib/rubygems/dependency_resolver/composed_set.rb
new file mode 100644
index 0000000000..fb38128bb0
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/composed_set.rb
@@ -0,0 +1,18 @@
+class Gem::DependencyResolver::ComposedSet
+
+ def initialize *sets
+ @sets = sets
+ end
+
+ def find_all req
+ res = []
+ @sets.each { |s| res += s.find_all(req) }
+ res
+ end
+
+ def prefetch reqs
+ @sets.each { |s| s.prefetch(reqs) }
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/current_set.rb b/lib/rubygems/dependency_resolver/current_set.rb
new file mode 100644
index 0000000000..13bc490e9e
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/current_set.rb
@@ -0,0 +1,16 @@
+##
+# A set which represents the installed gems. Respects
+# all the normal settings that control where to look
+# for installed gems.
+
+class Gem::DependencyResolver::CurrentSet
+
+ def find_all req
+ req.dependency.matching_specs
+ end
+
+ def prefetch gems
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/dependency_conflict.rb b/lib/rubygems/dependency_resolver/dependency_conflict.rb
new file mode 100644
index 0000000000..1755d910c3
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/dependency_conflict.rb
@@ -0,0 +1,85 @@
+##
+# Used internally to indicate that a dependency conflicted
+# with a spec that would be activated.
+
+class Gem::DependencyResolver::DependencyConflict
+
+ attr_reader :activated
+
+ attr_reader :dependency
+
+ def initialize(dependency, activated, failed_dep=dependency)
+ @dependency = dependency
+ @activated = activated
+ @failed_dep = failed_dep
+ end
+
+ ##
+ # Return the 2 dependency objects that conflicted
+
+ def conflicting_dependencies
+ [@failed_dep.dependency, @activated.request.dependency]
+ end
+
+ ##
+ # Explanation of the conflict used by exceptions to print useful messages
+
+ def explanation
+ activated = @activated.spec.full_name
+ requirement = @failed_dep.dependency.requirement
+
+ " Activated %s instead of (%s) via:\n %s\n" % [
+ activated, requirement, request_path.join(', ')
+ ]
+ end
+
+ def for_spec?(spec)
+ @dependency.name == spec.name
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[Dependency conflict: ', ']' do
+ q.breakable
+
+ q.text 'activated '
+ q.pp @activated
+
+ q.breakable
+ q.text ' dependency '
+ q.pp @dependency
+
+ q.breakable
+ if @dependency == @failed_dep then
+ q.text ' failed'
+ else
+ q.text ' failed dependency '
+ q.pp @failed_dep
+ end
+ end
+ end
+
+ ##
+ # Path of specifications that requested this dependency
+
+ def request_path
+ current = requester
+ path = []
+
+ while current do
+ path << current.spec.full_name
+
+ current = current.request.requester
+ end
+
+ path
+ end
+
+ ##
+ # Return the Specification that listed the dependency
+
+ def requester
+ @failed_dep.requester
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/dependency_request.rb b/lib/rubygems/dependency_resolver/dependency_request.rb
new file mode 100644
index 0000000000..05e447c3be
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/dependency_request.rb
@@ -0,0 +1,51 @@
+##
+# Used Internally. Wraps a Dependency object to also track which spec
+# contained the Dependency.
+
+class Gem::DependencyResolver::DependencyRequest
+
+ attr_reader :dependency
+
+ attr_reader :requester
+
+ def initialize(dep, act)
+ @dependency = dep
+ @requester = act
+ end
+
+ def ==(other)
+ case other
+ when Gem::Dependency
+ @dependency == other
+ when Gem::DependencyResolver::DependencyRequest
+ @dependency == other.dependency && @requester == other.requester
+ else
+ false
+ end
+ end
+
+ def matches_spec?(spec)
+ @dependency.matches_spec? spec
+ end
+
+ def name
+ @dependency.name
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[Dependency request ', ']' do
+ q.breakable
+ q.text @dependency.to_s
+
+ q.breakable
+ q.text ' requested by '
+ q.pp @requester
+ end
+ end
+
+ def to_s # :nodoc:
+ @dependency.to_s
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/index_set.rb b/lib/rubygems/dependency_resolver/index_set.rb
new file mode 100644
index 0000000000..fcf919d81b
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/index_set.rb
@@ -0,0 +1,59 @@
+##
+# The global rubygems pool represented via the traditional
+# source index.
+
+class Gem::DependencyResolver::IndexSet
+
+ def initialize
+ @f = Gem::SpecFetcher.fetcher
+
+ @all = Hash.new { |h,k| h[k] = [] }
+
+ list, = @f.available_specs :released
+
+ list.each do |uri, specs|
+ specs.each do |n|
+ @all[n.name] << [uri, n]
+ end
+ end
+
+ @specs = {}
+ end
+
+ ##
+ # Return an array of IndexSpecification objects matching
+ # DependencyRequest +req+.
+
+ def find_all req
+ res = []
+
+ name = req.dependency.name
+
+ @all[name].each do |uri, n|
+ if req.dependency.match? n
+ res << Gem::DependencyResolver::IndexSpecification.new(
+ self, n.name, n.version, uri, n.platform)
+ end
+ end
+
+ res
+ end
+
+ ##
+ # Called from IndexSpecification to get a true Specification
+ # object.
+
+ def load_spec name, ver, source
+ key = "#{name}-#{ver}"
+ @specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver))
+ end
+
+ ##
+ # No prefetching needed since we load the whole index in
+ # initially.
+
+ def prefetch gems
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/index_specification.rb b/lib/rubygems/dependency_resolver/index_specification.rb
new file mode 100644
index 0000000000..371018ba44
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/index_specification.rb
@@ -0,0 +1,53 @@
+##
+# Represents a possible Specification object returned
+# from IndexSet. Used to delay needed to download full
+# Specification objects when only the +name+ and +version+
+# are needed.
+
+class Gem::DependencyResolver::IndexSpecification
+
+ attr_reader :name
+
+ attr_reader :source
+
+ attr_reader :version
+
+ def initialize set, name, version, source, plat
+ @set = set
+ @name = name
+ @version = version
+ @source = source
+ @platform = plat
+
+ @spec = nil
+ end
+
+ def dependencies
+ spec.dependencies
+ end
+
+ def full_name
+ "#{@name}-#{@version}"
+ end
+
+ def inspect # :nodoc:
+ '#<%s %s source %s>' % [self.class, full_name, @source]
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[Index specification', ']' do
+ q.breakable
+ q.text full_name
+
+ q.breakable
+ q.text ' source '
+ q.pp @source
+ end
+ end
+
+ def spec
+ @spec ||= @set.load_spec(@name, @version, @source)
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/installed_specification.rb b/lib/rubygems/dependency_resolver/installed_specification.rb
new file mode 100644
index 0000000000..af167572bf
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/installed_specification.rb
@@ -0,0 +1,38 @@
+class Gem::DependencyResolver::InstalledSpecification
+
+ attr_reader :spec
+
+ def initialize set, spec, source=nil
+ @set = set
+ @source = source
+ @spec = spec
+ end
+
+ 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 source
+ @source ||= Gem::Source::Installed.new
+ end
+
+ def version
+ @spec.version
+ end
+
+end
+
diff --git a/lib/rubygems/dependency_resolver/installer_set.rb b/lib/rubygems/dependency_resolver/installer_set.rb
new file mode 100644
index 0000000000..7de052df77
--- /dev/null
+++ b/lib/rubygems/dependency_resolver/installer_set.rb
@@ -0,0 +1,130 @@
+class Gem::DependencyResolver::InstallerSet
+
+ ##
+ # List of Gem::Specification objects that must always be installed.
+
+ attr_reader :always_install
+
+ ##
+ # Only install gems in the always_install list
+
+ attr_accessor :ignore_dependencies
+
+ ##
+ # 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
+
+ def initialize domain
+ @domain = domain
+
+ @f = Gem::SpecFetcher.fetcher
+
+ @all = Hash.new { |h,k| h[k] = [] }
+ @always_install = []
+ @ignore_dependencies = false
+ @ignore_installed = false
+ @loaded_remote_specs = []
+ @specs = {}
+ end
+
+ ##
+ # Should local gems should be considered?
+
+ def consider_local?
+ @domain == :both or @domain == :local
+ end
+
+ ##
+ # Should remote gems should be considered?
+
+ def consider_remote?
+ @domain == :both or @domain == :remote
+ end
+
+ ##
+ # Returns an array of IndexSpecification objects matching DependencyRequest
+ # +req+.
+
+ def find_all req
+ res = []
+
+ dep = req.dependency
+
+ return res if @ignore_dependencies and
+ @always_install.none? { |spec| dep.matches_spec? spec }
+
+ name = dep.name
+
+ dep.matching_specs.each do |gemspec|
+ next if @always_install.include? gemspec
+
+ res << Gem::DependencyResolver::InstalledSpecification.new(self, gemspec)
+ end unless @ignore_installed
+
+ if consider_local? then
+ local_source = Gem::Source::Local.new
+
+ if spec = local_source.find_gem(name, dep.requirement) then
+ res << Gem::DependencyResolver::IndexSpecification.new(
+ self, spec.name, spec.version, local_source, spec.platform)
+ end
+ end
+
+ if consider_remote? then
+ load_remote_specs dep
+
+ @all[name].each do |remote_source, n|
+ if dep.match? n then
+ res << Gem::DependencyResolver::IndexSpecification.new(
+ self, n.name, n.version, remote_source, n.platform)
+ end
+ end
+ end
+
+ res
+ end
+
+ def inspect # :nodoc:
+ '#<%s domain: %s specs: %p>' % [ self.class, @domain, @specs.keys ]
+ end
+
+ ##
+ # Loads remote prerelease specs if +dep+ is a prerelease dependency
+
+ def load_remote_specs dep
+ types = [:released]
+ types << :prerelease if dep.prerelease?
+
+ types.each do |type|
+ next if @loaded_remote_specs.include? type
+ @loaded_remote_specs << type
+
+ list, = @f.available_specs type
+
+ list.each do |uri, specs|
+ specs.each do |n|
+ @all[n.name] << [uri, n]
+ end
+ end
+ end
+ end
+
+ ##
+ # Called from IndexSpecification to get a true Specification
+ # object.
+
+ def load_spec name, ver, source
+ key = "#{name}-#{ver}"
+ @specs[key] ||= source.fetch_spec Gem::NameTuple.new name, ver
+ end
+
+ ##
+ # No prefetching needed since we load the whole index in initially.
+
+ def prefetch(reqs)
+ end
+
+end
+