class Solve::RubySolver

Attributes

demands_array[R]

@example Demands are Arrays of Arrays with an artifact name and optional constraint:

[['nginx', '= 1.0.0'], ['mysql']]

@return [Array<String>, Array<Array<String, String>>] demands

graph[R]

Graph object with references to all known artifacts and dependency constraints.

@return [Solve::Graph]

Public Class Methods

activate() click to toggle source

For optional solver engines, this attempts to load depenencies. The RubySolver is a non-optional component, so this is a no-op

# File lib/solve/ruby_solver.rb, line 20
def activate
  true
end
new(graph, demands, options = {}) click to toggle source

@example Basic use:

graph = Solve::Graph.new
graph.artifacts("mysql", "1.2.0")
demands = [["mysql"]]
RubySolver.new(graph, demands)

@param [Solve::Graph] graph @param [Array<String>, Array<Array<String, String>>] demands

# File lib/solve/ruby_solver.rb, line 43
def initialize(graph, demands, options = {})
  @graph         = graph
  @demands_array = demands
  @timeout_ms    = self.class.timeout

  @ui = options[:ui] # could be nil, but that's okay
  @dependency_source = options[:dependency_source] || "user-specified dependency"

  @molinillo_graph = Molinillo::DependencyGraph.new
  @resolver = Molinillo::Resolver.new(self, self)
end
timeout() click to toggle source

The timeout (in seconds) to use when resolving graphs. Default is 10. This can be configured by setting the SOLVE_TIMEOUT environment variable.

@return [Integer]

# File lib/solve/ruby_solver.rb, line 13
def timeout
  seconds = 30 unless ( seconds = ENV["SOLVE_TIMEOUT"] )
  seconds.to_i * 1_000
end

Public Instance Methods

after_resolution() click to toggle source

Callback required by Molinillo, called when the solve is complete. @return nil

# File lib/solve/ruby_solver.rb, line 115
def after_resolution
  @ui.say("Finished dependency resolution") if @ui
end
allow_missing?(dependency) click to toggle source

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

@param [Object] dependency @return [Boolean] whether this dependency can safely be skipped.

# File lib/solve/ruby_solver.rb, line 253
def allow_missing?(dependency)
  false
end
before_resolution() click to toggle source

Callback required by Molinillo, called when the solve starts @return nil

# File lib/solve/ruby_solver.rb, line 109
def before_resolution
  @ui.say("Starting dependency resolution") if @ui
end
debug(current_resolver_depth = 0) { || ... } click to toggle source

Callback required by Molinillo, gives debug information about the solution @return nil

# File lib/solve/ruby_solver.rb, line 127
def debug(current_resolver_depth = 0)
  # debug info will be returned if you call yield here, but it seems to be
  # broken in current Molinillo
  @ui.say(yield) if @ui
end
demands() click to toggle source

The problem demands given as Demand model objects @return [Array<Solve::Demand>]

# File lib/solve/ruby_solver.rb, line 57
def demands
  demands_array.map do |name, constraint|
    Demand.new(self, name, constraint)
  end
end
dependencies_for(specification) click to toggle source

Callback required by Molinillo Returns the dependencies of `specification`. @note This method should be 'pure', i.e. the return value should depend

only on the `specification` parameter.

@param [Object] specification @return [Array<Solve::Dependency>] the dependencies of the given artifact

# File lib/solve/ruby_solver.rb, line 157
def dependencies_for(specification)
  specification.dependencies
end
indicate_progress() click to toggle source

Callback required by Molinillo, called when resolving every progress_rate @return nil

# File lib/solve/ruby_solver.rb, line 121
def indicate_progress
  nil
end
name_for(dependency) click to toggle source

Callback required by Molinillo Returns the name for the given `dependency`. @note This method should be 'pure', i.e. the return value should depend

only on the `dependency` parameter.

@param [Object] dependency @return [String] the name for the given `dependency`.

# File lib/solve/ruby_solver.rb, line 212
def name_for(dependency)
  dependency.name
end
name_for_explicit_dependency_source() click to toggle source

Callback required by Molinillo @return [String] the name of the source of explicit dependencies, i.e.

those passed to {Resolver#resolve} directly.
# File lib/solve/ruby_solver.rb, line 219
def name_for_explicit_dependency_source
  @dependency_source
end
progress_rate() click to toggle source

Callback required by Molinillo, called when the solve starts @return [Integer]

# File lib/solve/ruby_solver.rb, line 103
def progress_rate
  1
end
requirement_satisfied_by?(requirement, activated, spec) click to toggle source

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

@param [Object] requirement @param [DependencyGraph] activated the current dependency graph in the

resolution process.

@param [Object] spec @return [Boolean] whether `requirement` is satisfied by `spec` in the

context of the current `activated` dependency graph.
# File lib/solve/ruby_solver.rb, line 171
def requirement_satisfied_by?(requirement, activated, spec)
  version = spec.version
  return false unless requirement.constraint.satisfies?(version)

  shared_possibility_versions = possibility_versions(requirement, activated)
  return false if !shared_possibility_versions.empty? && !shared_possibility_versions.include?(version)

  true
end
resolve(options = {}) click to toggle source

@option options [Boolean] :sorted

return the solution as a sorted list instead of a Hash

@return [Hash, List] Returns a hash like { “Artifact Name” => “Version”,… }

unless the :sorted option is true, then it returns a list like [["Artifact Name", "Version],...]

@raise [Errors::NoSolutionError] when the demands cannot be met for the

given graph.

@raise [Errors::UnsortableSolutionError] when the :sorted option is true

and the demands have a solution, but the solution contains a cyclic
dependency
# File lib/solve/ruby_solver.rb, line 73
def resolve(options = {})
  @ui = options[:ui] if options[:ui]

  solved_graph = resolve_with_error_wrapping

  solution = solved_graph.map(&:payload)

  unsorted_solution = solution.inject({}) do |stringified_soln, artifact|
    stringified_soln[artifact.name] = artifact.version.to_s
    stringified_soln
  end

  if options[:sorted]
    build_sorted_solution(unsorted_solution)
  else
    unsorted_solution
  end
end
search_for(dependency) click to toggle source

Callback required by Molinillo Search for the specifications that match the given dependency. The specifications in the returned array will be considered in reverse order, so the latest version ought to be last. @note This method should be 'pure', i.e. the return value should depend

only on the `dependency` parameter.

@param [Object] dependency @return [Array<Solve::Artifact>] the artifacts that match the dependency.

# File lib/solve/ruby_solver.rb, line 144
def search_for(dependency)
  # This array gets mutated by Molinillo; it's okay because sort returns a
  # new array.
  graph.versions(dependency.name, dependency.constraint).sort
end
sort_dependencies(dependencies, activated, conflicts) click to toggle source

Callback required by Molinillo Sort dependencies so that the ones that are easiest to resolve are first. Easiest to resolve is (usually) defined by:

1) Is this dependency already activated?
2) How relaxed are the requirements?
3) Are there any conflicts for this dependency?
4) How many possibilities are there to satisfy this dependency?

@param [Array<Object>] dependencies @param [DependencyGraph] activated the current dependency graph in the

resolution process.

@param [{String => Array<Conflict>}] conflicts @return [Array<Solve::Dependency>] the dependencies sorted by preference.

# File lib/solve/ruby_solver.rb, line 236
def sort_dependencies(dependencies, activated, conflicts)
  dependencies.sort_by do |dependency|
    name = name_for(dependency)
    [
      activated.vertex_named(name).payload ? 0 : 1,
      conflicts[name] ? 0 : 1,
      search_for(dependency).count,
    ]
  end
end

Private Instance Methods

build_sorted_solution(unsorted_solution) click to toggle source
# File lib/solve/ruby_solver.rb, line 265
def build_sorted_solution(unsorted_solution)
  nodes = {}
  unsorted_solution.each do |name, version|
    nodes[name] = @graph.artifact(name, version).dependencies.map(&:name)
  end

  # Modified from http://ruby-doc.org/stdlib-1.9.3/libdoc/tsort/rdoc/TSort.html
  class << nodes
    include TSort
    alias tsort_each_node each_key
    def tsort_each_child(node, &block)
      fetch(node).each(&block)
    end
  end
  begin
    sorted_names = nodes.tsort
  rescue TSort::Cyclic => e
    raise Solve::Errors::UnsortableSolutionError.new(e, unsorted_solution)
  end

  sorted_names.map do |artifact|
    [artifact, unsorted_solution[artifact]]
  end
end
possibility_versions(requirement, activated) click to toggle source

Searches the current dependency graph to find previously activated requirements for the current artifact.

@param [Object] requirement @param [DependencyGraph] activated the current dependency graph in the

resolution process.

@return [Array<Semverse::Version> the list of currently activated versions of this requirement

# File lib/solve/ruby_solver.rb, line 189
def possibility_versions(requirement, activated)
  activated.vertices.values.flat_map do |vertex|

    next unless vertex.payload

    next unless vertex.name == requirement.name

    if vertex.payload.respond_to?(:possibilities)
      vertex.payload.possibilities.map(&:version)
    else
      vertex.payload.version
    end
  end.compact
end
resolve_with_error_wrapping() click to toggle source
# File lib/solve/ruby_solver.rb, line 259
def resolve_with_error_wrapping
  @resolver.resolve(demands, @molinillo_graph)
rescue Molinillo::VersionConflict, Molinillo::CircularDependencyError => e
  raise Solve::Errors::NoSolutionError.new(e.message)
end