@@ -782,6 +782,9 @@ def visit(node):
782782 self .control .load (os .path .join (parent_dir , "display.lp" ))
783783 if not setup .concretize_everything :
784784 self .control .load (os .path .join (parent_dir , "when_possible.lp" ))
785+
786+ if setup .use_cycle_detection :
787+ self .control .load (os .path .join (parent_dir , "cycle_detection.lp" ))
785788 timer .stop ("load" )
786789
787790 # Grounding is the first step in the solve -- it turns our facts
@@ -904,6 +907,7 @@ def __init__(self, tests=False):
904907
905908 # If False allows for input specs that are not solved
906909 self .concretize_everything = True
910+ self .use_cycle_detection = False
907911
908912 # Set during the call to setup
909913 self .pkgs = None
@@ -2797,10 +2801,17 @@ def build_specs(self, function_tuples):
27972801 # fix flags after all specs are constructed
27982802 self .reorder_flags ()
27992803
2804+ # cycle detection
2805+ try :
2806+ roots = [spec .root for spec in self ._specs .values () if not spec .root .installed ]
2807+ except RecursionError as e :
2808+ raise CycleDetectedError (
2809+ "detected cycles using a fast solve, falling back to slower algorithm"
2810+ ) from e
2811+
28002812 # inject patches -- note that we' can't use set() to unique the
28012813 # roots here, because the specs aren't complete, and the hash
28022814 # function will loop forever.
2803- roots = [spec .root for spec in self ._specs .values () if not spec .root .installed ]
28042815 roots = dict ((id (r ), r ) for r in roots )
28052816 for root in roots .values ():
28062817 spack .spec .Spec .inject_patches_variant (root )
@@ -2930,7 +2941,12 @@ def solve(self, specs, out=None, timers=False, stats=False, tests=False, setup_o
29302941 reusable_specs .extend (self ._reusable_specs (specs ))
29312942 setup = SpackSolverSetup (tests = tests )
29322943 output = OutputConfiguration (timers = timers , stats = stats , out = out , setup_only = setup_only )
2933- result , _ , _ = self .driver .solve (setup , specs , reuse = reusable_specs , output = output )
2944+ try :
2945+ result , _ , _ = self .driver .solve (setup , specs , reuse = reusable_specs , output = output )
2946+ except CycleDetectedError as e :
2947+ warnings .warn (e )
2948+ setup .use_cycle_detection = True
2949+ result , _ , _ = self .driver .solve (setup , specs , reuse = reusable_specs , output = output )
29342950 return result
29352951
29362952 def solve_in_rounds (self , specs , out = None , timers = False , stats = False , tests = False ):
@@ -3010,3 +3026,7 @@ def __init__(self, provided, conflicts):
30103026 # Add attribute expected of the superclass interface
30113027 self .required = None
30123028 self .constraint_type = None
3029+
3030+
3031+ class CycleDetectedError (spack .error .SpackError ):
3032+ pass
0 commit comments