Make cycle detection optional, to speed-up grounding and solving
This commit is contained in:
		
				
					committed by
					
						
						Todd Gamblin
					
				
			
			
				
	
			
			
			
						parent
						
							c1a73878ea
						
					
				
				
					commit
					a410b22098
				
			@@ -781,6 +781,9 @@ def visit(node):
 | 
			
		||||
        self.control.load(os.path.join(parent_dir, "display.lp"))
 | 
			
		||||
        if not setup.concretize_everything:
 | 
			
		||||
            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")
 | 
			
		||||
 | 
			
		||||
        # 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
 | 
			
		||||
        self.concretize_everything = True
 | 
			
		||||
        self.use_cycle_detection = False
 | 
			
		||||
 | 
			
		||||
        # Set during the call to setup
 | 
			
		||||
        self.pkgs = None
 | 
			
		||||
@@ -2797,10 +2801,17 @@ def build_specs(self, function_tuples):
 | 
			
		||||
        # fix flags after all specs are constructed
 | 
			
		||||
        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
 | 
			
		||||
        # roots here, because the specs aren't complete, and the hash
 | 
			
		||||
        # 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)
 | 
			
		||||
        for root in roots.values():
 | 
			
		||||
            spack.spec.Spec.inject_patches_variant(root)
 | 
			
		||||
@@ -2932,6 +2943,11 @@ def solve(self, specs, out=None, timers=False, stats=False, tests=False, setup_o
 | 
			
		||||
        reusable_specs.extend(self._reusable_specs(specs))
 | 
			
		||||
        setup = SpackSolverSetup(tests=tests)
 | 
			
		||||
        output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
@@ -3012,3 +3028,7 @@ def __init__(self, provided, conflicts):
 | 
			
		||||
        # Add attribute expected of the superclass interface
 | 
			
		||||
        self.required = 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)),
 | 
			
		||||
     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.
 | 
			
		||||
 | 
			
		||||
%-----------------------------------------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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).
 | 
			
		||||
		Reference in New Issue
	
	Block a user