diff --git a/etc/spack/defaults/concretizer.yaml b/etc/spack/defaults/concretizer.yaml index 7311354c28b..c627c12cfb5 100644 --- a/etc/spack/defaults/concretizer.yaml +++ b/etc/spack/defaults/concretizer.yaml @@ -15,22 +15,38 @@ concretizer: # as possible, rather than building. If `false`, we'll always give you a fresh # concretization. reuse: true + + # If `true`, Spack will consider minimizing builds its *topmost* priority. + # Note that this can result in weird package configurations. In particular, + # Spack will disable variants and might downgrade versions to avoid building + # new packages for an install. By default, Spack respects defaults from + # packages and preferences *before* minimizing the number of builds. + # + # Example for intuition: `cmake` can optionally build without openssl, but + # it's enabled by default because many builds use that functionality. Using + # `minimal: true` will build `cmake~openssl` unless the user asks for + # `cmake+openssl` explicitly. + minimal: false + # Options that tune which targets are considered for concretization. The # concretization process is very sensitive to the number targets, and the time # needed to reach a solution increases noticeably with the number of targets # considered. targets: + # Determine whether we want to target specific or generic microarchitectures. # An example of the first kind might be for instance "skylake" or "bulldozer", # while generic microarchitectures are for instance "aarch64" or "x86_64_v4". granularity: microarchitectures + # If "false" allow targets that are incompatible with the current host (for # instance concretize with target "icelake" while running on "haswell"). # If "true" only allow targets that are compatible with the host. host_compatible: true + # When "true" concretize root specs of environments together, so that each unique # package in an environment corresponds to one concrete spec. This ensures # environments can always be activated. When "false" perform concretization separately # on each root spec, allowing different versions and variants of the same package in # an environment. - unify: false \ No newline at end of file + unify: false diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py index bcfc2866c1b..536432171bd 100644 --- a/lib/spack/spack/cmd/common/arguments.py +++ b/lib/spack/spack/cmd/common/arguments.py @@ -380,6 +380,11 @@ def add_concretizer_args(subparser): const=False, default=None, help='do not reuse installed deps; build newest configuration' ) + subgroup.add_argument( + '--minimal', action=ConfigSetAction, dest="concretizer:minimal", + const=True, default=None, + help='minimize builds (disables default variants, may choose older versions)' + ) subgroup.add_argument( '--reuse', action=ConfigSetAction, dest="concretizer:reuse", const=True, default=None, diff --git a/lib/spack/spack/schema/concretizer.py b/lib/spack/spack/schema/concretizer.py index 46d0e9126b3..c146c3c6f3b 100644 --- a/lib/spack/spack/schema/concretizer.py +++ b/lib/spack/spack/schema/concretizer.py @@ -15,6 +15,7 @@ 'additionalProperties': False, 'properties': { 'reuse': {'type': 'boolean'}, + 'minimal': {'type': 'boolean'}, 'targets': { 'type': 'object', 'properties': { diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index b4739b616d2..38a411c97cd 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -654,7 +654,7 @@ def stringify(x): class SpackSolverSetup(object): """Class to set up and run a Spack concretization solve.""" - def __init__(self, reuse=False, tests=False): + def __init__(self, reuse=None, minimal=None, tests=False): self.gen = None # set by setup() self.declared_versions = {} @@ -679,10 +679,11 @@ def __init__(self, reuse=False, tests=False): # 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 + # Solver paramters that affect setup -- see Solver documentation + self.reuse = spack.config.get( + "concretizer:reuse", False) if reuse is None else reuse + self.minimal = spack.config.get( + "concretizer:minimal", False) if minimal is None else minimal self.tests = tests def pkg_version_rules(self, pkg): @@ -827,7 +828,7 @@ def package_compiler_defaults(self, pkg): pkg.name, cspec.name, cspec.version, -i * 100 )) - def pkg_rules(self, pkg, tests): + def pkg_rules(self, pkg): pkg = packagize(pkg) # versions @@ -1809,10 +1810,14 @@ def setup(self, driver, specs): self.gen.h1("Concrete input spec definitions") self.define_concrete_input_specs(specs, possible) + self.gen.h1("Concretizer options") + if self.reuse: + self.gen.fact(fn.optimize_for_reuse()) + if self.minimal: + self.gen.fact(fn.minimal_installs()) + if self.reuse: self.gen.h1("Installed packages") - self.gen.fact(fn.optimize_for_reuse()) - self.gen.newline() self.define_installed_packages(specs, possible) self.gen.h1('General Constraints') @@ -1833,7 +1838,7 @@ def setup(self, driver, specs): self.gen.h1('Package Constraints') for pkg in sorted(pkgs): self.gen.h2('Package rules: %s' % pkg) - self.pkg_rules(pkg, tests=self.tests) + self.pkg_rules(pkg) self.gen.h2('Package preferences: %s' % pkg) self.preferred_variants(pkg) self.preferred_targets(pkg) @@ -2191,6 +2196,10 @@ class Solver(object): ``reuse (bool)`` Whether to try to reuse existing installs/binaries + ``minimal (bool)`` + If ``True`` make minimizing nodes the top priority, even higher + than defaults from packages and preferences. + """ def __init__(self): self.driver = PyclingoDriver() @@ -2198,6 +2207,7 @@ def __init__(self): # These properties are settable via spack configuration, and overridable # by setting them directly as properties. self.reuse = spack.config.get("concretizer:reuse", False) + self.minimal = spack.config.get("concretizer:minimal", False) def solve( self, @@ -2228,7 +2238,7 @@ def solve( continue spack.spec.Spec.ensure_valid_variants(s) - setup = SpackSolverSetup(reuse=self.reuse, tests=tests) + setup = SpackSolverSetup(reuse=self.reuse, minimal=self.minimal, tests=tests) return self.driver.solve( setup, specs, diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index 1e7d0f66deb..d10f28abb9d 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -962,26 +962,36 @@ impose(Hash) :- hash(Package, Hash). % if we haven't selected a hash for a package, we'll be building it build(Package) :- not hash(Package, _), node(Package). -% Minimizing builds is tricky. We want a minimizing criterion - -% because we want to reuse what is avaialble, but -% we also want things that are built to stick to *default preferences* from -% the package and from the user. We therefore treat built specs differently and apply -% a different set of optimization criteria to them. Spack's *first* priority is to -% reuse what it *can*, but if it builds something, the built specs will respect -% defaults and preferences. This is implemented by bumping the priority of optimization -% criteria for built specs -- so that they take precedence over the otherwise -% topmost-priority criterion to reuse what is installed. +% Minimizing builds is tricky. We want a minimizing criterion because we want to reuse +% what is avaialble, but we also want things that are built to stick to *default +% preferences* from the package and from the user. We therefore treat built specs +% differently and apply a different set of optimization criteria to them. Spack's first +% priority is to reuse what it can, but if it builds something, the built specs will +% respect defaults and preferences. +% +% This is implemented by bumping the priority of optimization criteria for built specs +% -- so that they take precedence over the otherwise topmost-priority criterion to reuse +% what is installed. +% +% If the user explicitly asks for *minimal* installs, we don't differentiate between +% built and reused specs - the top priority is just minimizing builds. % % The priority ranges are: % 200+ Shifted priorities for build nodes; correspond to priorities 0 - 99. % 100 - 199 Unshifted priorities. Currently only includes minimizing #builds. % 0 - 99 Priorities for non-built nodes. -build_priority(Package, 200) :- build(Package), node(Package), optimize_for_reuse(). -build_priority(Package, 0) :- not build(Package), node(Package), optimize_for_reuse(). +build_priority(Package, 200) :- node(Package), build(Package), optimize_for_reuse(), + not minimal_installs(). +build_priority(Package, 0) :- node(Package), not build(Package), optimize_for_reuse(). -% don't adjust build priorities if reuse is not enabled +% Don't adjust build priorities if reusing, or if doing minimal installs +% With minimal, minimizing builds is the TOP priority build_priority(Package, 0) :- node(Package), not optimize_for_reuse(). +build_priority(Package, 0) :- node(Package), minimal_installs(). + +% Minimize builds with both --reuse and with --minimal +minimize_builds() :- optimize_for_reuse(). +minimize_builds() :- minimal_installs(). % don't assign versions from installed packages unless reuse is enabled % NOTE: that "installed" means the declared version was only included because @@ -1000,6 +1010,7 @@ build_priority(Package, 0) :- node(Package), not optimize_for_reuse(). not optimize_for_reuse(). #defined installed_hash/2. +#defined minimal_installs/0. %----------------------------------------------------------------- % Optimization to avoid errors @@ -1029,7 +1040,7 @@ build_priority(Package, 0) :- node(Package), not optimize_for_reuse(). % Try hard to reuse installed packages (i.e., minimize the number built) opt_criterion(100, "number of packages to build (vs. reuse)"). #minimize { 0@100: #true }. -#minimize { 1@100,Package : build(Package), optimize_for_reuse() }. +#minimize { 1@100,Package : build(Package), minimize_builds() }. #defined optimize_for_reuse/0. % Minimize the number of deprecated versions being used diff --git a/lib/spack/spack/test/cmd/common/arguments.py b/lib/spack/spack/test/cmd/common/arguments.py index 5f1299a94a9..c220937697e 100644 --- a/lib/spack/spack/test/cmd/common/arguments.py +++ b/lib/spack/spack/test/cmd/common/arguments.py @@ -120,11 +120,19 @@ def test_concretizer_arguments(mutable_config, mock_packages): spec = spack.main.SpackCommand("spec") assert spack.config.get("concretizer:reuse", None) is None + assert spack.config.get("concretizer:minimal", None) is None spec("--reuse", "zlib") assert spack.config.get("concretizer:reuse", None) is True + assert spack.config.get("concretizer:minimal", None) is None spec("--fresh", "zlib") assert spack.config.get("concretizer:reuse", None) is False + assert spack.config.get("concretizer:minimal", None) is None + + spec("--minimal", "zlib") + + assert spack.config.get("concretizer:reuse", None) is False + assert spack.config.get("concretizer:minimal", None) is True