Class: DepSelector::Selector

Inherits:
Object
  • Object
show all
Defined in:
lib/dep_selector/selector.rb

Constant Summary collapse

DEFAULT_ERROR_REPORTER =
ErrorReporter::SimpleTreeTraverser.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dep_graph, time_bound = 5, error_reporter = DEFAULT_ERROR_REPORTER) ⇒ Selector

Returns a new instance of Selector.



37
38
39
40
41
# File 'lib/dep_selector/selector.rb', line 37

def initialize(dep_graph, time_bound = 5, error_reporter = DEFAULT_ERROR_REPORTER)
  @dep_graph = dep_graph
  @time_bound = time_bound
  @error_reporter = error_reporter
end

Instance Attribute Details

#dep_graphObject

Returns the value of attribute dep_graph.



33
34
35
# File 'lib/dep_selector/selector.rb', line 33

def dep_graph
  @dep_graph
end

#error_reporterObject

Returns the value of attribute error_reporter.



33
34
35
# File 'lib/dep_selector/selector.rb', line 33

def error_reporter
  @error_reporter
end

#time_boundObject

Returns the value of attribute time_bound.



33
34
35
# File 'lib/dep_selector/selector.rb', line 33

def time_bound
  @time_bound
end

Instance Method Details

#find_solution(solution_constraints, valid_packages = nil) ⇒ Object

Based on solution_constraints, this method tries to find an assignment of PackageVersions that is compatible with the DependencyGraph. If one cannot be found, the constraints are added one at a time until the first unsatisfiable constraint is detected. Once the unsatisfiable solution constraint is identified, required non-existent packages and the most constrained packages are identified and thrown in a NoSolutionExists exception.

If a solution constraint refers to a package that doesn’t exist or the constraint matches no versions, it is considered invalid. All invalid solution constraints are collected and raised in an InvalidSolutionConstraints exception. If valid_packages is non-nil, it is considered the authoritative list of extant Packages; otherwise, Package#valid? is used. This is useful if the dependency graph represents an already filtered set of packages such that a Package actually exists in your domain but is added to the dependency graph with no versions, in which case Package#valid? would return false even though we don’t want to report that the package is non-existent.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/dep_selector/selector.rb', line 63

def find_solution(solution_constraints, valid_packages = nil)
  # this is a performance optimization so that packages that are
  # completely unreachable by the solution constraints don't get
  # added to the CSP
  packages_to_include_in_solve = trim_unreachable_packages(dep_graph, solution_constraints)

  begin
    # first, try to solve the whole set of constraints
    solve(dep_graph.clone, solution_constraints, valid_packages, packages_to_include_in_solve)
  rescue Exceptions::NoSolutionFound, Exceptions::TimeBoundExceededNoSolution
    # since we're here, solving the whole system failed, so add
    # the solution_constraints one-by-one and try to solve in
    # order to find the constraint that breaks the system in order
    # to give helpful debugging info
    #
    # TODO [cw,2010/11/28]: for an efficiency gain, instead of
    # continually re-building the problem and looking for a
    # solution, turn solution_constraints into a Generator and
    # iteratively add and solve in order to re-use
    # propagations. This will require separating setting up the
    # constraints from searching for the solution.
    Timeout::timeout(@time_bound, Exceptions::TimeBoundExceededNoSolution) do
      solution_constraints.each_index do |idx|
        workspace = dep_graph.clone
        begin
          solve(workspace, solution_constraints[0..idx], valid_packages, packages_to_include_in_solve)
        rescue Exceptions::NoSolutionFound => nsf
          disabled_packages =
            packages_to_include_in_solve.inject([]) do |acc, elt|
            pkg = workspace.package(elt.name)
            acc << pkg if nsf.unsatisfiable_problem.is_package_disabled?(pkg.gecode_package_id)
            acc
          end
          # disambiguate between packages disabled becuase they
          # don't exist and those that have otherwise problematic
          # constraints
          disabled_non_existent_packages = []
          disabled_most_constrained_packages = []
          disabled_packages.each do |disabled_pkg|
            disabled_collection =
              if disabled_pkg.valid? || (valid_packages && valid_packages.include?(disabled_pkg))
                disabled_most_constrained_packages
              else
                disabled_non_existent_packages
              end
            disabled_collection << disabled_pkg
          end

          # Pick the first non-existent or most-constrained package
          # that was required or the package whose constraints had
          # to be disabled in order to find a solution and generate
          # feedback for it. We only report feedback for one
          # package, because it is in fact actionable and dispalying
          # feedback for every disabled package would probably be
          # too long. The full set of disabled packages is
          # accessible in the NoSolutionExists exception.
          disabled_package_to_report_on = disabled_non_existent_packages.first ||
            disabled_most_constrained_packages.first
          feedback = error_reporter.give_feedback(dep_graph, solution_constraints, idx,
                                                  disabled_package_to_report_on)

          raise Exceptions::NoSolutionExists.new(feedback, solution_constraints[idx],
                                                 disabled_non_existent_packages,
                                                 disabled_most_constrained_packages)
        end
      end
    end
  end
end