From 1903e45eec3673e2e6f1d3341f61748e8bfba55a Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Sun, 16 Jan 2022 22:40:48 -0800 Subject: refactor: convert spack.solver.asp.solve() to a class The solver has a lot of configuration associated with it. Rather than adding arguments to everything, we should encapsulate that in a class. This is the start of that work; it replaces `solve()` and its kwargs with a class and properties. --- lib/spack/spack/cmd/solve.py | 14 ++++-- lib/spack/spack/concretize.py | 11 +++-- lib/spack/spack/solver/asp.py | 110 ++++++++++++++++++++++++++++-------------- lib/spack/spack/spec.py | 6 ++- 4 files changed, 95 insertions(+), 46 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py index aaf631a1c5..b3207d7070 100644 --- a/lib/spack/spack/cmd/solve.py +++ b/lib/spack/spack/cmd/solve.py @@ -102,11 +102,15 @@ def solve(parser, args): specs = spack.cmd.parse_specs(args.specs) - # dump generated ASP program - result = asp.solve( - specs, dump=dump, models=models, timers=args.timers, stats=args.stats, - reuse=args.reuse, - ) + # set up solver parameters + solver = asp.Solver() + solver.reuse = args.reuse + solver.dump = dump + solver.models = models + solver.timers = args.timers + solver.stats = args.stats + + result = solver.solve(specs) if 'solutions' not in dump: return diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py index 35b4e82f30..de04fecffc 100644 --- a/lib/spack/spack/concretize.py +++ b/lib/spack/spack/concretize.py @@ -748,11 +748,12 @@ def concretize_specs_together(*abstract_specs, **kwargs): def _concretize_specs_together_new(*abstract_specs, **kwargs): import spack.solver.asp - concretization_kwargs = { - 'tests': kwargs.get('tests', False), - 'reuse': kwargs.get('reuse', False) - } - result = spack.solver.asp.solve(abstract_specs, **concretization_kwargs) + + solver = spack.solver.asp.Solver() + solver.tests = kwargs.get('tests', False) + solver.reuse = kwargs.get('reuse', False) + + result = solver.solve(abstract_specs) result.raise_if_unsat() return [s.copy() for s in result.specs] diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 6b656efba0..a87fa445f9 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -529,7 +529,7 @@ class PyclingoDriver(object): def solve( self, solver_setup, specs, dump=None, nmodels=0, - timers=False, stats=False, tests=False, reuse=False, + timers=False, stats=False, ): timer = spack.util.timer.Timer() @@ -545,7 +545,7 @@ class PyclingoDriver(object): self.assumptions = [] with self.control.backend() as backend: self.backend = backend - solver_setup.setup(self, specs, tests=tests, reuse=reuse) + solver_setup.setup(self, specs) timer.phase("setup") # read in the main ASP program and display logic -- these are @@ -640,7 +640,7 @@ class PyclingoDriver(object): class SpackSolverSetup(object): """Class to set up and run a Spack concretization solve.""" - def __init__(self): + def __init__(self, reuse=False, tests=False): self.gen = None # set by setup() self.declared_versions = {} @@ -665,6 +665,12 @@ class SpackSolverSetup(object): # Caches to optimize the setup phase of the solver self.target_specs_cache = None + # whether to add installed/binary hashes to the solve + self.reuse = reuse + + # whether to add installed/binary hashes to the solve + self.tests = tests + def pkg_version_rules(self, pkg): """Output declared versions of a package. @@ -866,7 +872,7 @@ class SpackSolverSetup(object): self.package_provider_rules(pkg) # dependencies - self.package_dependencies_rules(pkg, tests) + self.package_dependencies_rules(pkg) # virtual preferences self.virtual_preferences( @@ -932,17 +938,17 @@ class SpackSolverSetup(object): )) self.gen.newline() - def package_dependencies_rules(self, pkg, tests): + def package_dependencies_rules(self, pkg): """Translate 'depends_on' directives into ASP logic.""" for _, conditions in sorted(pkg.dependencies.items()): for cond, dep in sorted(conditions.items()): deptypes = dep.type.copy() # Skip test dependencies if they're not requested - if not tests: + if not self.tests: deptypes.discard("test") # ... or if they are requested only for certain packages - if not isinstance(tests, bool) and pkg.name not in tests: + if not isinstance(self.tests, bool) and pkg.name not in self.tests: deptypes.discard("test") # if there are no dependency types to be considered @@ -1642,7 +1648,7 @@ class SpackSolverSetup(object): # TODO: (or any mirror really) doesn't have binaries. pass - def setup(self, driver, specs, tests=False, reuse=False): + def setup(self, driver, specs): """Generate an ASP program with relevant constraints for specs. This calls methods on the solve driver to set up the problem with @@ -1689,7 +1695,7 @@ class SpackSolverSetup(object): self.gen.h1("Concrete input spec definitions") self.define_concrete_input_specs(specs, possible) - if reuse: + if self.reuse: self.gen.h1("Installed packages") self.gen.fact(fn.optimize_for_reuse()) self.gen.newline() @@ -1713,7 +1719,7 @@ class SpackSolverSetup(object): self.gen.h1('Package Constraints') for pkg in sorted(pkgs): self.gen.h2('Package rules: %s' % pkg) - self.pkg_rules(pkg, tests=tests) + self.pkg_rules(pkg, tests=self.tests) self.gen.h2('Package preferences: %s' % pkg) self.preferred_variants(pkg) self.preferred_targets(pkg) @@ -2016,33 +2022,67 @@ def _develop_specs_from_env(spec, env): spec.constrain(dev_info['spec']) -# -# These are handwritten parts for the Spack ASP model. -# -def solve(specs, dump=(), models=0, timers=False, stats=False, tests=False, - reuse=False): - """Solve for a stable model of specs. - - Arguments: - specs (list): list of Specs to solve. - dump (tuple): what to dump - models (int): number of models to search (default: 0) +class Solver(object): + """This is the main external interface class for solving. + + It manages solver configuration and preferences in once place. It sets up the solve + and passes the setup method to the driver, as well. + + Properties of interest: + + ``reuse (bool)`` + Whether to try to reuse existing installs/binaries + + ``show (tuple)`` + What information to print to the console while running. Options are: + * asp: asp program text + * opt: optimization criteria for best model + * output: raw clingo output + * solutions: models found by asp program + * all: all of the above + + ``models (int)`` + Number of models to search (default: 0 for unlimited) + + ``timers (bool)`` + Print out coarse fimers for different solve phases. + + ``stats (bool)`` + Print out detailed stats from clingo + + ``tests (bool or tuple)`` + If ``True``, concretize test dependencies for all packages. If + a tuple of package names, concretize test dependencies for named + packages. If ``False``, do not concretize test dependencies. + """ - driver = PyclingoDriver() - if "asp" in dump: - driver.out = sys.stdout - - # Check upfront that the variants are admissible - for root in specs: - for s in root.traverse(): - if s.virtual: - continue - spack.spec.Spec.ensure_valid_variants(s) + def __init__(self): + self.set_default_configuration() + + def set_default_configuration(self): + self.reuse = False + self.dump = () + self.models = 0 + self.timers = False + self.stats = False + self.tests = False + + def solve(self, specs): + driver = PyclingoDriver() + if "asp" in self.dump: + driver.out = sys.stdout + + # Check upfront that the variants are admissible + for root in specs: + for s in root.traverse(): + if s.virtual: + continue + spack.spec.Spec.ensure_valid_variants(s) - setup = SpackSolverSetup() - return driver.solve( - setup, specs, dump, models, timers, stats, tests, reuse - ) + setup = SpackSolverSetup(reuse=self.reuse, tests=self.tests) + return driver.solve( + setup, specs, self.dump, self.models, self.timers, self.stats + ) class UnsatisfiableSpecError(spack.error.UnsatisfiableSpecError): diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 8b50b1fca3..4011bd10a7 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -2615,7 +2615,11 @@ class Spec(object): if self._concrete: return - result = spack.solver.asp.solve([self], tests=tests, reuse=reuse) + solver = spack.solver.asp.Solver() + solver.reuse = reuse + solver.tests = tests + + result = solver.solve([self]) result.raise_if_unsat() # take the best answer -- cgit v1.2.3-70-g09d2