Compare commits

...

1 Commits

Author SHA1 Message Date
Todd Gamblin
3985b307a1 concretizer: add --minimal configuration option
The reusing concretizer minimizes builds, but it still preserves
defaults from packages and preferences while doing that. We can be
more aggressive by making minimization the top priority, at the
expense of "weird" concretizations.

This can be advantageous: if you write your packages as explicitly
as possible, then you can use that with `--minimal` to get the
smallest possible package configuration (at least in terms of the
number of packages in the build).  Conversely, you can use minimal
concretization as kind of a worst case to ensure that you have the
"right" constraints on your dependencies.

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.

- [x] add `minimal` option to `concretizer.yaml`
- [x] add `--minimal` CLI option to concretizer arguments
- [x] wire everything up
- [x] add some tests
2022-05-24 14:52:17 -04:00
6 changed files with 76 additions and 25 deletions

View File

@@ -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
unify: false

View File

@@ -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,

View File

@@ -15,6 +15,7 @@
'additionalProperties': False,
'properties': {
'reuse': {'type': 'boolean'},
'minimal': {'type': 'boolean'},
'targets': {
'type': 'object',
'properties': {

View File

@@ -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,

View File

@@ -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

View File

@@ -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