Make cycle detection optional, to speed-up grounding and solving
This commit is contained in:
parent
c1a73878ea
commit
a410b22098
@ -781,6 +781,9 @@ def visit(node):
|
|||||||
self.control.load(os.path.join(parent_dir, "display.lp"))
|
self.control.load(os.path.join(parent_dir, "display.lp"))
|
||||||
if not setup.concretize_everything:
|
if not setup.concretize_everything:
|
||||||
self.control.load(os.path.join(parent_dir, "when_possible.lp"))
|
self.control.load(os.path.join(parent_dir, "when_possible.lp"))
|
||||||
|
|
||||||
|
if setup.use_cycle_detection:
|
||||||
|
self.control.load(os.path.join(parent_dir, "cycle_detection.lp"))
|
||||||
timer.stop("load")
|
timer.stop("load")
|
||||||
|
|
||||||
# Grounding is the first step in the solve -- it turns our facts
|
# Grounding is the first step in the solve -- it turns our facts
|
||||||
@ -903,6 +906,7 @@ def __init__(self, tests=False):
|
|||||||
|
|
||||||
# If False allows for input specs that are not solved
|
# If False allows for input specs that are not solved
|
||||||
self.concretize_everything = True
|
self.concretize_everything = True
|
||||||
|
self.use_cycle_detection = False
|
||||||
|
|
||||||
# Set during the call to setup
|
# Set during the call to setup
|
||||||
self.pkgs = None
|
self.pkgs = None
|
||||||
@ -2797,10 +2801,17 @@ def build_specs(self, function_tuples):
|
|||||||
# fix flags after all specs are constructed
|
# fix flags after all specs are constructed
|
||||||
self.reorder_flags()
|
self.reorder_flags()
|
||||||
|
|
||||||
|
# cycle detection
|
||||||
|
try:
|
||||||
|
roots = [spec.root for spec in self._specs.values() if not spec.root.installed]
|
||||||
|
except RecursionError as e:
|
||||||
|
raise CycleDetectedError(
|
||||||
|
"detected cycles using a fast solve, falling back to slower algorithm"
|
||||||
|
) from e
|
||||||
|
|
||||||
# inject patches -- note that we' can't use set() to unique the
|
# inject patches -- note that we' can't use set() to unique the
|
||||||
# roots here, because the specs aren't complete, and the hash
|
# roots here, because the specs aren't complete, and the hash
|
||||||
# function will loop forever.
|
# function will loop forever.
|
||||||
roots = [spec.root for spec in self._specs.values() if not spec.root.installed]
|
|
||||||
roots = dict((id(r), r) for r in roots)
|
roots = dict((id(r), r) for r in roots)
|
||||||
for root in roots.values():
|
for root in roots.values():
|
||||||
spack.spec.Spec.inject_patches_variant(root)
|
spack.spec.Spec.inject_patches_variant(root)
|
||||||
@ -2932,7 +2943,12 @@ def solve(self, specs, out=None, timers=False, stats=False, tests=False, setup_o
|
|||||||
reusable_specs.extend(self._reusable_specs(specs))
|
reusable_specs.extend(self._reusable_specs(specs))
|
||||||
setup = SpackSolverSetup(tests=tests)
|
setup = SpackSolverSetup(tests=tests)
|
||||||
output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
|
output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
|
||||||
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
|
try:
|
||||||
|
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
|
||||||
|
except CycleDetectedError as e:
|
||||||
|
warnings.warn(e)
|
||||||
|
setup.use_cycle_detection = True
|
||||||
|
result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def solve_in_rounds(self, specs, out=None, timers=False, stats=False, tests=False):
|
def solve_in_rounds(self, specs, out=None, timers=False, stats=False, tests=False):
|
||||||
@ -3012,3 +3028,7 @@ def __init__(self, provided, conflicts):
|
|||||||
# Add attribute expected of the superclass interface
|
# Add attribute expected of the superclass interface
|
||||||
self.required = None
|
self.required = None
|
||||||
self.constraint_type = None
|
self.constraint_type = None
|
||||||
|
|
||||||
|
|
||||||
|
class CycleDetectedError(spack.error.SpackError):
|
||||||
|
pass
|
||||||
|
@ -407,15 +407,6 @@ error(10, "'{0}' is not a valid dependency for any package in the DAG", Package)
|
|||||||
:- attr("node", node(ID, Package)),
|
:- attr("node", node(ID, Package)),
|
||||||
not needed(node(ID, Package)).
|
not needed(node(ID, Package)).
|
||||||
|
|
||||||
% Avoid cycles in the DAG
|
|
||||||
% some combinations of conditional dependencies can result in cycles;
|
|
||||||
% this ensures that we solve around them
|
|
||||||
path(Parent, Child) :- depends_on(Parent, Child).
|
|
||||||
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
|
|
||||||
error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changing variants to avoid the cycle)", A, B)
|
|
||||||
:- path(A, B),
|
|
||||||
path(B, A).
|
|
||||||
|
|
||||||
#defined dependency_type/2.
|
#defined dependency_type/2.
|
||||||
|
|
||||||
%-----------------------------------------------------------------------------
|
%-----------------------------------------------------------------------------
|
||||||
|
13
lib/spack/spack/solver/cycle_detection.lp
Normal file
13
lib/spack/spack/solver/cycle_detection.lp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
% Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
||||||
|
% Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
%
|
||||||
|
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
% Avoid cycles in the DAG
|
||||||
|
% some combinations of conditional dependencies can result in cycles;
|
||||||
|
% this ensures that we solve around them
|
||||||
|
path(Parent, Child) :- depends_on(Parent, Child).
|
||||||
|
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
|
||||||
|
error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changing variants to avoid the cycle)", A, B)
|
||||||
|
:- path(A, B),
|
||||||
|
path(B, A).
|
Loading…
Reference in New Issue
Block a user