123456789_123456789_123456789_123456789_123456789_

Class: Gem::Resolver

Overview

Given a set of Dependency objects as needed and a way to query the set of available specs via set, calculates a set of ActivationRequest objects which indicate all the specs that should be activated to meet the all the requirements.

Constant Summary

Class Method Summary

Instance Attribute Summary

Molinillo::UI - Included

#debug?

Whether or not debug messages should be printed.

Instance Method Summary

Molinillo::SpecificationProvider - Included

#allow_missing?

Returns whether this dependency, which has no possible matching specifications, can safely be ignored.

#dependencies_equal?

Determines whether two arrays of dependencies are equal, and thus can be grouped.

#dependencies_for

Returns the dependencies of specification.

#name_for

Returns the name for the given dependency.

#name_for_explicit_dependency_source, #name_for_locking_dependency_source,
#requirement_satisfied_by?

Determines whether the given requirement is satisfied by the given spec, in the context of the current activated dependency graph.

#search_for

Search for the specifications that match the given dependency.

#sort_dependencies

Sort dependencies so that the ones that are easiest to resolve are first.

Molinillo::UI - Included

#after_resolution

Called after resolution ends (either successfully or with an error).

#before_resolution

Called before resolution begins.

#debug

Conveys debug information to the user.

#indicate_progress

Called roughly every #progress_rate, this method should convey progress to the user.

#output

The IO object that should be used to print output.

#progress_rate

How often progress should be conveyed to the user via #indicate_progress, in seconds.

Constructor Details

.new(needed, set = nil) ⇒ Resolver

Create Resolver object which will resolve the tree starting with needed Dependency objects.

set is an object that provides where to look for specifications to satisfy the Dependencies. This defaults to Resolver::IndexSet, which will query rubygems.org.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 99

def initialize(needed, set = nil)
  @set = set || Gem::Resolver::IndexSet.new
  @needed = needed

  @development         = false
  @development_shallow = false
  @ignore_dependencies = false
  @skip_gems           = {}
  @soft_missing        = false
  @stats               = Gem::Resolver::Stats.new
end

Class Method Details

.compose_sets(*sets)

Combines sets into a Resolver::ComposedSet that allows specification lookup in a uniform manner. If one of the sets is itself a Resolver::ComposedSet its sets are flattened into the result Resolver::ComposedSet.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 59

def self.compose_sets(*sets)
  sets.compact!

  sets = sets.map do |set|
    case set
    when Gem::Resolver::BestSet then
      set
    when Gem::Resolver::ComposedSet then
      set.sets
    else
      set
    end
  end.flatten

  case sets.length
  when 0 then
    raise ArgumentError, "one set in the composition must be non-nil"
  when 1 then
    sets.first
  else
    Gem::Resolver::ComposedSet.new(*sets)
  end
end

.for_current_gems(needed)

Creates a Resolver that queries only against the already installed gems for the needed dependencies.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 87

def self.for_current_gems(needed)
  new needed, Gem::Resolver::CurrentSet.new
end

Instance Attribute Details

#debug?Boolean (readonly)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 176

def debug?
  DEBUG_RESOLVER
end

#development (rw)

Resolver::Set to true if all development dependencies should be considered.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 26

attr_accessor :development

#development_shallow (rw)

Resolver::Set to true if immediate development dependencies should be considered.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 31

attr_accessor :development_shallow

#ignore_dependencies (rw)

When true, no dependencies are looked up for requested gems.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 36

attr_accessor :ignore_dependencies

#skip_gems (rw)

Hash of gems to skip resolution. Keyed by gem name, with arrays of gem specifications as values.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 47

attr_accessor :skip_gems

#soft_missing (rw)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 52

attr_accessor :soft_missing

#stats (readonly)

List of dependencies that could not be found in the configured sources.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 41

attr_reader :stats

Instance Method Details

#activation_request(dep, possible)

This method is for internal use only.

Creates an Resolver::ActivationRequest for the given dep and the last possible specification.

Returns the Resolver::Specification and the Resolver::ActivationRequest

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 135

def activation_request(dep, possible) # :nodoc:
  spec = possible.pop

  explain :activate, [spec.full_name, possible.size]
  explain :possible, possible

  activation_request =
    Gem::Resolver::ActivationRequest.new spec, dep, possible

  [spec, activation_request]
end

#allow_missing?(dependency) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 271

def allow_missing?(dependency)
  @soft_missing
end

#amount_constrained(dependency) (private)

returns an integer in (-infty, 0] a number closer to 0 means the dependency is less constraining

dependencies w/ 0 or 1 possibilities (ignoring version requirements) are given very negative values, so they always sort first, before dependencies that are unconstrained

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 297

def amount_constrained(dependency)
  @amount_constrained ||= {}
  @amount_constrained[dependency.name] ||= begin
    name_dependency = Gem::Dependency.new(dependency.name)
    dependency_request_for_name = Gem::Resolver::DependencyRequest.new(name_dependency, dependency.requester)
    all = @set.find_all(dependency_request_for_name).size

    if all <= 1
      all - SINGLE_POSSIBILITY_CONSTRAINT_PENALTY
    else
      search = search_for(dependency).size
      search - all
    end
  end
end

#dependencies_for(specification)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 252

def dependencies_for(specification)
  return [] if @ignore_dependencies
  spec = specification.spec
  requests(spec, specification)
end

#explain(stage, *data)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 111

def explain(stage, *data) # :nodoc:
  return unless DEBUG_RESOLVER

  d = data.map(&:pretty_inspect).join(", ")
  $stderr.printf "%10s %s\n", stage.to_s.upcase, d
end

#explain_list(stage)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 118

def explain_list(stage) # :nodoc:
  return unless DEBUG_RESOLVER

  data = yield
  $stderr.printf "%10s (%d entries)\n", stage.to_s.upcase, data.size
  unless data.empty?
    require "pp"
    PP.pp data, $stderr
  end
end

#find_possible(dependency)

This method is for internal use only.

Extracts the specifications that may be able to fulfill dependency and returns those that match the local platform and all those that match.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 198

def find_possible(dependency) # :nodoc:
  all = @set.find_all dependency

  if (skip_dep_gems = skip_gems[dependency.name]) && !skip_dep_gems.empty?
    matching = all.select do |api_spec|
      skip_dep_gems.any? {|s| api_spec.version == s.version }
    end

    all = matching unless matching.empty?
  end

  matching_platform = select_local_platforms all

  [matching_platform, all]
end

#name_for(dependency)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 267

def name_for(dependency)
  dependency.name
end

#output

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 172

def output
  @output ||= debug? ? $stdout : File.open(IO::NULL, "w")
end

#requests(s, act, reqs = [])

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 147

def requests(s, act, reqs=[]) # :nodoc:
  return reqs if @ignore_dependencies

  s.fetch_development_dependencies if @development

  s.dependencies.reverse_each do |d|
    next if d.type == :development && !@development
    next if d.type == :development && @development_shallow &&
            act.development?
    next if d.type == :development && @development_shallow &&
            act.parent

    reqs << Gem::Resolver::DependencyRequest.new(d, act)
    @stats.requirement!
  end

  @set.prefetch reqs

  @stats.record_requirements reqs

  reqs
end

#requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 258

def requirement_satisfied_by?(requirement, activated, 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

#resolve

Proceed with resolution! Returns an array of Resolver::ActivationRequest objects.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 185

def resolve
  Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.map(&:payload).compact
rescue Gem::Molinillo::VersionConflict => e
  conflict = e.conflicts.values.first
  raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement)
ensure
  @output.close if defined?(@output) && !debug?
end

#search_for(dependency)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 223

def search_for(dependency)
  possibles, all = find_possible(dependency)
  if !@soft_missing && possibles.empty?
    exc = Gem::UnsatisfiableDependencyError.new dependency, all
    exc.errors = @set.errors
    raise exc
  end

  groups = Hash.new {|hash, key| hash[key] = [] }

  # create groups & sources in the same loop
  sources = possibles.map do |spec|
    source = spec.source
    groups[source] << spec
    source
  end.uniq.reverse

  activation_requests = []

  sources.each do |source|
    groups[source].
      sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch
      map {|spec| ActivationRequest.new spec, dependency }.
      each {|activation_request| activation_requests << activation_request }
  end

  activation_requests
end

#select_local_platforms(specs)

This method is for internal use only.

Returns the gems in specs that match the local platform.

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 217

def select_local_platforms(specs) # :nodoc:
  specs.select do |spec|
    Gem::Platform.installable? spec
  end
end

#sort_dependencies(dependencies, activated, conflicts)

[ GitHub ]

  
# File 'lib/rubygems/resolver.rb', line 275

def sort_dependencies(dependencies, activated, conflicts)
  dependencies.sort_by.with_index do |dependency, i|
    name = name_for(dependency)
    [
      activated.vertex_named(name).payload ? 0 : 1,
      amount_constrained(dependency),
      conflicts[name] ? 0 : 1,
      activated.vertex_named(name).payload ? 0 : search_for(dependency).count,
      i, # for stable sort
    ]
  end
end