diff --git a/etc/spack/defaults/concretizer.yaml b/etc/spack/defaults/concretizer.yaml index eda51c09bee..f239a4d0838 100644 --- a/etc/spack/defaults/concretizer.yaml +++ b/etc/spack/defaults/concretizer.yaml @@ -63,3 +63,7 @@ concretizer: # Setting this to false yields unreproducible results, so we advise to use that value only # for debugging purposes (e.g. check which constraints can help Spack concretize faster). error_on_timeout: true + + # Static analysis may reduce the concretization time by generating smaller ASP problems, in + # cases where there are requirements that prevent part of the search space to be explored. + static_analysis: false diff --git a/lib/spack/spack/cmd/dependencies.py b/lib/spack/spack/cmd/dependencies.py index 1f89ebffc06..6c15020da4b 100644 --- a/lib/spack/spack/cmd/dependencies.py +++ b/lib/spack/spack/cmd/dependencies.py @@ -9,9 +9,9 @@ import spack.cmd import spack.environment as ev -import spack.package_base import spack.store from spack.cmd.common import arguments +from spack.solver.input_analysis import create_graph_analyzer description = "show dependencies of a package" section = "basic" @@ -68,15 +68,17 @@ def dependencies(parser, args): else: spec = specs[0] - dependencies = spack.package_base.possible_dependencies( + dependencies, virtuals, _ = create_graph_analyzer().possible_dependencies( spec, transitive=args.transitive, expand_virtuals=args.expand_virtuals, - depflag=args.deptype, + allowed_deps=args.deptype, ) + if not args.expand_virtuals: + dependencies.update(virtuals) if spec.name in dependencies: - del dependencies[spec.name] + dependencies.remove(spec.name) if dependencies: colify(sorted(dependencies)) diff --git a/lib/spack/spack/graph.py b/lib/spack/spack/graph.py index cc3918ae1de..558950686bc 100644 --- a/lib/spack/spack/graph.py +++ b/lib/spack/spack/graph.py @@ -42,10 +42,10 @@ import llnl.util.tty.color import spack.deptypes as dt -import spack.repo import spack.spec import spack.tengine import spack.traverse +from spack.solver.input_analysis import create_graph_analyzer def find(seq, predicate): @@ -537,10 +537,11 @@ def edge_entry(self, edge): def _static_edges(specs, depflag): for spec in specs: - pkg_cls = spack.repo.PATH.get_pkg_class(spec.name) - possible = pkg_cls.possible_dependencies(expand_virtuals=True, depflag=depflag) + *_, edges = create_graph_analyzer().possible_dependencies( + spec.name, expand_virtuals=True, allowed_deps=depflag + ) - for parent_name, dependencies in possible.items(): + for parent_name, dependencies in edges.items(): for dependency_name in dependencies: yield spack.spec.DependencySpec( spack.spec.Spec(parent_name), diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index dd35bfe287d..a4f7234e1f0 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -22,7 +22,6 @@ import textwrap import time import traceback -import typing from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union from typing_extensions import Literal @@ -822,104 +821,6 @@ def get_variant(self, name: str) -> spack.variant.Variant: except StopIteration: raise ValueError(f"No variant '{name}' on spec: {self.spec}") - @classmethod - def possible_dependencies( - cls, - transitive: bool = True, - expand_virtuals: bool = True, - depflag: dt.DepFlag = dt.ALL, - visited: Optional[dict] = None, - missing: Optional[dict] = None, - virtuals: Optional[set] = None, - ) -> Dict[str, Set[str]]: - """Return dict of possible dependencies of this package. - - Args: - transitive (bool or None): return all transitive dependencies if - True, only direct dependencies if False (default True).. - expand_virtuals (bool or None): expand virtual dependencies into - all possible implementations (default True) - depflag: dependency types to consider - visited (dict or None): dict of names of dependencies visited so - far, mapped to their immediate dependencies' names. - missing (dict or None): dict to populate with packages and their - *missing* dependencies. - virtuals (set): if provided, populate with virtuals seen so far. - - Returns: - (dict): dictionary mapping dependency names to *their* - immediate dependencies - - Each item in the returned dictionary maps a (potentially - transitive) dependency of this package to its possible - *immediate* dependencies. If ``expand_virtuals`` is ``False``, - virtual package names wil be inserted as keys mapped to empty - sets of dependencies. Virtuals, if not expanded, are treated as - though they have no immediate dependencies. - - Missing dependencies by default are ignored, but if a - missing dict is provided, it will be populated with package names - mapped to any dependencies they have that are in no - repositories. This is only populated if transitive is True. - - Note: the returned dict *includes* the package itself. - - """ - visited = {} if visited is None else visited - missing = {} if missing is None else missing - - visited.setdefault(cls.name, set()) - - for name, conditions in cls.dependencies_by_name(when=True).items(): - # check whether this dependency could be of the type asked for - depflag_union = 0 - for deplist in conditions.values(): - for dep in deplist: - depflag_union |= dep.depflag - if not (depflag & depflag_union): - continue - - # expand virtuals if enabled, otherwise just stop at virtuals - if spack.repo.PATH.is_virtual(name): - if virtuals is not None: - virtuals.add(name) - if expand_virtuals: - providers = spack.repo.PATH.providers_for(name) - dep_names = [spec.name for spec in providers] - else: - visited.setdefault(cls.name, set()).add(name) - visited.setdefault(name, set()) - continue - else: - dep_names = [name] - - # add the dependency names to the visited dict - visited.setdefault(cls.name, set()).update(set(dep_names)) - - # recursively traverse dependencies - for dep_name in dep_names: - if dep_name in visited: - continue - - visited.setdefault(dep_name, set()) - - # skip the rest if not transitive - if not transitive: - continue - - try: - dep_cls = spack.repo.PATH.get_pkg_class(dep_name) - except spack.repo.UnknownPackageError: - # log unknown packages - missing.setdefault(cls.name, set()).add(dep_name) - continue - - dep_cls.possible_dependencies( - transitive, expand_virtuals, depflag, visited, missing, virtuals - ) - - return visited - @classproperty def package_dir(cls): """Directory where the package.py file lives.""" @@ -2284,47 +2185,6 @@ def rpath_args(self): build_system_flags = PackageBase.build_system_flags -def possible_dependencies( - *pkg_or_spec: Union[str, spack.spec.Spec, typing.Type[PackageBase]], - transitive: bool = True, - expand_virtuals: bool = True, - depflag: dt.DepFlag = dt.ALL, - missing: Optional[dict] = None, - virtuals: Optional[set] = None, -) -> Dict[str, Set[str]]: - """Get the possible dependencies of a number of packages. - - See ``PackageBase.possible_dependencies`` for details. - """ - packages = [] - for pos in pkg_or_spec: - if isinstance(pos, PackageMeta) and issubclass(pos, PackageBase): - packages.append(pos) - continue - - if not isinstance(pos, spack.spec.Spec): - pos = spack.spec.Spec(pos) - - if spack.repo.PATH.is_virtual(pos.name): - packages.extend(p.package_class for p in spack.repo.PATH.providers_for(pos.name)) - continue - else: - packages.append(pos.package_class) - - visited: Dict[str, Set[str]] = {} - for pkg in packages: - pkg.possible_dependencies( - visited=visited, - transitive=transitive, - expand_virtuals=expand_virtuals, - depflag=depflag, - missing=missing, - virtuals=virtuals, - ) - - return visited - - def deprecated_version(pkg: PackageBase, version: Union[str, StandardVersion]) -> bool: """Return True iff the version is deprecated. diff --git a/lib/spack/spack/schema/concretizer.py b/lib/spack/spack/schema/concretizer.py index 7a866dfbbd7..99a70a716cd 100644 --- a/lib/spack/spack/schema/concretizer.py +++ b/lib/spack/spack/schema/concretizer.py @@ -87,6 +87,7 @@ "strategy": {"type": "string", "enum": ["none", "minimal", "full"]} }, }, + "static_analysis": {"type": "boolean"}, "timeout": {"type": "integer", "minimum": 0}, "error_on_timeout": {"type": "boolean"}, "os_compatible": {"type": "object", "additionalProperties": {"type": "array"}}, diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index ce0f0a48c58..564a610ae9f 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -62,7 +62,7 @@ parse_files, parse_term, ) -from .counter import FullDuplicatesCounter, MinimalDuplicatesCounter, NoDuplicatesCounter +from .input_analysis import create_counter, create_graph_analyzer from .requirements import RequirementKind, RequirementParser, RequirementRule from .version_order import concretization_version_order @@ -271,15 +271,6 @@ def remove_node(spec: spack.spec.Spec, facts: List[AspFunction]) -> List[AspFunc return list(filter(lambda x: x.args[0] not in ("node", "virtual_node"), facts)) -def _create_counter(specs: List[spack.spec.Spec], tests: bool): - strategy = spack.config.CONFIG.get("concretizer:duplicates:strategy", "none") - if strategy == "full": - return FullDuplicatesCounter(specs, tests=tests) - if strategy == "minimal": - return MinimalDuplicatesCounter(specs, tests=tests) - return NoDuplicatesCounter(specs, tests=tests) - - def all_libcs() -> Set[spack.spec.Spec]: """Return a set of all libc specs targeted by any configured compiler. If none, fall back to libc determined from the current Python process if dynamically linked.""" @@ -1121,6 +1112,8 @@ class SpackSolverSetup: """Class to set up and run a Spack concretization solve.""" def __init__(self, tests: bool = False): + self.possible_graph = create_graph_analyzer() + # these are all initialized in setup() self.gen: "ProblemInstanceBuilder" = ProblemInstanceBuilder() self.requirement_parser = RequirementParser(spack.config.CONFIG) @@ -2397,38 +2390,20 @@ def keyfun(os): def target_defaults(self, specs): """Add facts about targets and target compatibility.""" - self.gen.h2("Default target") - - platform = spack.platforms.host() - uarch = archspec.cpu.TARGETS.get(platform.default) - self.gen.h2("Target compatibility") - # Construct the list of targets which are compatible with the host - candidate_targets = [uarch] + uarch.ancestors - - # Get configuration options - granularity = spack.config.get("concretizer:targets:granularity") - host_compatible = spack.config.get("concretizer:targets:host_compatible") - - # Add targets which are not compatible with the current host - if not host_compatible: - additional_targets_in_family = sorted( - [ - t - for t in archspec.cpu.TARGETS.values() - if (t.family.name == uarch.family.name and t not in candidate_targets) - ], - key=lambda x: len(x.ancestors), - reverse=True, - ) - candidate_targets += additional_targets_in_family - - # Check if we want only generic architecture - if granularity == "generic": - candidate_targets = [t for t in candidate_targets if t.vendor == "generic"] - # Add targets explicitly requested from specs + candidate_targets = [] + for x in self.possible_graph.candidate_targets(): + if all( + self.possible_graph.unreachable(pkg_name=pkg_name, when_spec=f"target={x}") + for pkg_name in self.pkgs + ): + tty.debug(f"[{__name__}] excluding target={x}, cause no package can use it") + continue + candidate_targets.append(x) + + host_compatible = spack.config.CONFIG.get("concretizer:targets:host_compatible") for spec in specs: if not spec.architecture or not spec.architecture.target: continue @@ -2444,6 +2419,8 @@ def target_defaults(self, specs): if ancestor not in candidate_targets: candidate_targets.append(ancestor) + platform = spack.platforms.host() + uarch = archspec.cpu.TARGETS.get(platform.default) best_targets = {uarch.family.name} for compiler_id, known_compiler in enumerate(self.possible_compilers): if not known_compiler.available: @@ -2501,7 +2478,6 @@ def target_defaults(self, specs): self.gen.newline() self.default_targets = list(sorted(set(self.default_targets))) - self.target_preferences() def virtual_providers(self): @@ -2605,7 +2581,14 @@ def define_variant_values(self): # Tell the concretizer about possible values from specs seen in spec_clauses(). # We might want to order these facts by pkg and name if we are debugging. for pkg_name, variant_def_id, value in self.variant_values_from_specs: - vid = self.variant_ids_by_def_id[variant_def_id] + try: + vid = self.variant_ids_by_def_id[variant_def_id] + except KeyError: + tty.debug( + f"[{__name__}] cannot retrieve id of the {value} variant from {pkg_name}" + ) + continue + self.gen.fact(fn.pkg_fact(pkg_name, fn.variant_possible_value(vid, value))) def register_concrete_spec(self, spec, possible): @@ -2676,7 +2659,7 @@ def setup( """ check_packages_exist(specs) - node_counter = _create_counter(specs, tests=self.tests) + node_counter = create_counter(specs, tests=self.tests, possible_graph=self.possible_graph) self.possible_virtuals = node_counter.possible_virtuals() self.pkgs = node_counter.possible_dependencies() self.libcs = sorted(all_libcs()) # type: ignore[type-var] diff --git a/lib/spack/spack/solver/counter.py b/lib/spack/spack/solver/counter.py deleted file mode 100644 index 4e3150cb4a0..00000000000 --- a/lib/spack/spack/solver/counter.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright Spack Project Developers. See COPYRIGHT file for details. -# -# SPDX-License-Identifier: (Apache-2.0 OR MIT) -import collections -from typing import List, Set - -from llnl.util import lang - -import spack.deptypes as dt -import spack.package_base -import spack.repo -import spack.spec - -PossibleDependencies = Set[str] - - -class Counter: - """Computes the possible packages and the maximum number of duplicates - allowed for each of them. - - Args: - specs: abstract specs to concretize - tests: if True, add test dependencies to the list of possible packages - """ - - def __init__(self, specs: List["spack.spec.Spec"], tests: bool) -> None: - runtime_pkgs = spack.repo.PATH.packages_with_tags("runtime") - runtime_virtuals = set() - for x in runtime_pkgs: - pkg_class = spack.repo.PATH.get_pkg_class(x) - runtime_virtuals.update(pkg_class.provided_virtual_names()) - - self.specs = specs + [spack.spec.Spec(x) for x in runtime_pkgs] - - self.link_run_types: dt.DepFlag = dt.LINK | dt.RUN | dt.TEST - self.all_types: dt.DepFlag = dt.ALL - if not tests: - self.link_run_types = dt.LINK | dt.RUN - self.all_types = dt.LINK | dt.RUN | dt.BUILD - - self._possible_dependencies: PossibleDependencies = set() - self._possible_virtuals: Set[str] = ( - set(x.name for x in specs if x.virtual) | runtime_virtuals - ) - - def possible_dependencies(self) -> PossibleDependencies: - """Returns the list of possible dependencies""" - self.ensure_cache_values() - return self._possible_dependencies - - def possible_virtuals(self) -> Set[str]: - """Returns the list of possible virtuals""" - self.ensure_cache_values() - return self._possible_virtuals - - def ensure_cache_values(self) -> None: - """Ensure the cache values have been computed""" - if self._possible_dependencies: - return - self._compute_cache_values() - - def possible_packages_facts(self, gen: "spack.solver.asp.PyclingoDriver", fn) -> None: - """Emit facts associated with the possible packages""" - raise NotImplementedError("must be implemented by derived classes") - - def _compute_cache_values(self): - raise NotImplementedError("must be implemented by derived classes") - - -class NoDuplicatesCounter(Counter): - def _compute_cache_values(self): - result = spack.package_base.possible_dependencies( - *self.specs, virtuals=self._possible_virtuals, depflag=self.all_types - ) - self._possible_dependencies = set(result) - - def possible_packages_facts(self, gen, fn): - gen.h2("Maximum number of nodes (packages)") - for package_name in sorted(self.possible_dependencies()): - gen.fact(fn.max_dupes(package_name, 1)) - gen.newline() - gen.h2("Maximum number of nodes (virtual packages)") - for package_name in sorted(self.possible_virtuals()): - gen.fact(fn.max_dupes(package_name, 1)) - gen.newline() - gen.h2("Possible package in link-run subDAG") - for name in sorted(self.possible_dependencies()): - gen.fact(fn.possible_in_link_run(name)) - gen.newline() - - -class MinimalDuplicatesCounter(NoDuplicatesCounter): - def __init__(self, specs, tests): - super().__init__(specs, tests) - self._link_run: PossibleDependencies = set() - self._direct_build: PossibleDependencies = set() - self._total_build: PossibleDependencies = set() - self._link_run_virtuals: Set[str] = set() - - def _compute_cache_values(self): - self._link_run = set( - spack.package_base.possible_dependencies( - *self.specs, virtuals=self._possible_virtuals, depflag=self.link_run_types - ) - ) - self._link_run_virtuals.update(self._possible_virtuals) - for x in self._link_run: - build_dependencies = spack.repo.PATH.get_pkg_class(x).dependencies_of_type(dt.BUILD) - virtuals, reals = lang.stable_partition( - build_dependencies, spack.repo.PATH.is_virtual_safe - ) - - self._possible_virtuals.update(virtuals) - for virtual_dep in virtuals: - providers = spack.repo.PATH.providers_for(virtual_dep) - self._direct_build.update(str(x) for x in providers) - - self._direct_build.update(reals) - - self._total_build = set( - spack.package_base.possible_dependencies( - *self._direct_build, virtuals=self._possible_virtuals, depflag=self.all_types - ) - ) - self._possible_dependencies = set(self._link_run) | set(self._total_build) - - def possible_packages_facts(self, gen, fn): - build_tools = spack.repo.PATH.packages_with_tags("build-tools") - gen.h2("Packages with at most a single node") - for package_name in sorted(self.possible_dependencies() - build_tools): - gen.fact(fn.max_dupes(package_name, 1)) - gen.newline() - - gen.h2("Packages with at multiple possible nodes (build-tools)") - for package_name in sorted(self.possible_dependencies() & build_tools): - gen.fact(fn.max_dupes(package_name, 2)) - gen.fact(fn.multiple_unification_sets(package_name)) - gen.newline() - - gen.h2("Maximum number of nodes (virtual packages)") - for package_name in sorted(self.possible_virtuals()): - gen.fact(fn.max_dupes(package_name, 1)) - gen.newline() - - gen.h2("Possible package in link-run subDAG") - for name in sorted(self._link_run): - gen.fact(fn.possible_in_link_run(name)) - gen.newline() - - -class FullDuplicatesCounter(MinimalDuplicatesCounter): - def possible_packages_facts(self, gen, fn): - build_tools = spack.repo.PATH.packages_with_tags("build-tools") - counter = collections.Counter( - list(self._link_run) + list(self._total_build) + list(self._direct_build) - ) - gen.h2("Maximum number of nodes") - for pkg, count in sorted(counter.items(), key=lambda x: (x[1], x[0])): - count = min(count, 2) - gen.fact(fn.max_dupes(pkg, count)) - gen.newline() - - gen.h2("Build unification sets ") - for name in sorted(self.possible_dependencies() & build_tools): - gen.fact(fn.multiple_unification_sets(name)) - gen.newline() - - gen.h2("Possible package in link-run subDAG") - for name in sorted(self._link_run): - gen.fact(fn.possible_in_link_run(name)) - gen.newline() - - counter = collections.Counter( - list(self._link_run_virtuals) + list(self._possible_virtuals) - ) - gen.h2("Maximum number of virtual nodes") - for pkg, count in sorted(counter.items(), key=lambda x: (x[1], x[0])): - gen.fact(fn.max_dupes(pkg, count)) - gen.newline() diff --git a/lib/spack/spack/solver/input_analysis.py b/lib/spack/spack/solver/input_analysis.py new file mode 100644 index 00000000000..c316402288d --- /dev/null +++ b/lib/spack/spack/solver/input_analysis.py @@ -0,0 +1,524 @@ +# Copyright Spack Project Developers. See COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Classes to analyze the input of a solve, and provide information to set up the ASP problem""" +import collections +from typing import Dict, List, NamedTuple, Set, Tuple, Union + +import archspec.cpu + +from llnl.util import lang, tty + +import spack.binary_distribution +import spack.config +import spack.deptypes as dt +import spack.platforms +import spack.repo +import spack.spec +import spack.store +from spack.error import SpackError + +RUNTIME_TAG = "runtime" + + +class PossibleGraph(NamedTuple): + real_pkgs: Set[str] + virtuals: Set[str] + edges: Dict[str, Set[str]] + + +class PossibleDependencyGraph: + """Returns information needed to set up an ASP problem""" + + def unreachable(self, *, pkg_name: str, when_spec: spack.spec.Spec) -> bool: + """Returns true if the context can determine that the condition cannot ever + be met on pkg_name. + """ + raise NotImplementedError + + def candidate_targets(self) -> List[archspec.cpu.Microarchitecture]: + """Returns a list of targets that are candidate for concretization""" + raise NotImplementedError + + def possible_dependencies( + self, + *specs: Union[spack.spec.Spec, str], + allowed_deps: dt.DepFlag, + transitive: bool = True, + strict_depflag: bool = False, + expand_virtuals: bool = True, + ) -> PossibleGraph: + """Returns the set of possible dependencies, and the set of possible virtuals. + + Both sets always include runtime packages, which may be injected by compilers. + + Args: + transitive: return transitive dependencies if True, only direct dependencies if False + allowed_deps: dependency types to consider + strict_depflag: if True, only the specific dep type is considered, if False any + deptype that intersects with allowed deptype is considered + expand_virtuals: expand virtual dependencies into all possible implementations + """ + raise NotImplementedError + + +class NoStaticAnalysis(PossibleDependencyGraph): + """Implementation that tries to minimize the setup time (i.e. defaults to give fast + answers), rather than trying to reduce the ASP problem size with more complex analysis. + """ + + def __init__(self, *, configuration: spack.config.Configuration, repo: spack.repo.RepoPath): + self.configuration = configuration + self.repo = repo + self.runtime_pkgs = set(self.repo.packages_with_tags(RUNTIME_TAG)) + self.runtime_virtuals = set() + for x in self.runtime_pkgs: + pkg_class = self.repo.get_pkg_class(x) + self.runtime_virtuals.update(pkg_class.provided_virtual_names()) + + try: + self.libc_pkgs = [x.name for x in self.providers_for("libc")] + except spack.repo.UnknownPackageError: + self.libc_pkgs = [] + + def is_virtual(self, name: str) -> bool: + return self.repo.is_virtual(name) + + @lang.memoized + def is_allowed_on_this_platform(self, *, pkg_name: str) -> bool: + """Returns true if a package is allowed on the current host""" + pkg_cls = self.repo.get_pkg_class(pkg_name) + platform_condition = ( + f"platform={spack.platforms.host()} target={archspec.cpu.host().family}:" + ) + for when_spec, conditions in pkg_cls.requirements.items(): + if not when_spec.intersects(platform_condition): + continue + for requirements, _, _ in conditions: + if not any(x.intersects(platform_condition) for x in requirements): + tty.debug(f"[{__name__}] {pkg_name} is not for this platform") + return False + return True + + def providers_for(self, virtual_str: str) -> List[spack.spec.Spec]: + """Returns a list of possible providers for the virtual string in input.""" + return self.repo.providers_for(virtual_str) + + def can_be_installed(self, *, pkg_name) -> bool: + """Returns True if a package can be installed, False otherwise.""" + return True + + def unreachable(self, *, pkg_name: str, when_spec: spack.spec.Spec) -> bool: + """Returns true if the context can determine that the condition cannot ever + be met on pkg_name. + """ + return False + + def candidate_targets(self) -> List[archspec.cpu.Microarchitecture]: + """Returns a list of targets that are candidate for concretization""" + platform = spack.platforms.host() + default_target = archspec.cpu.TARGETS[platform.default] + + # Construct the list of targets which are compatible with the host + candidate_targets = [default_target] + default_target.ancestors + granularity = self.configuration.get("concretizer:targets:granularity") + host_compatible = self.configuration.get("concretizer:targets:host_compatible") + + # Add targets which are not compatible with the current host + if not host_compatible: + additional_targets_in_family = sorted( + [ + t + for t in archspec.cpu.TARGETS.values() + if (t.family.name == default_target.family.name and t not in candidate_targets) + ], + key=lambda x: len(x.ancestors), + reverse=True, + ) + candidate_targets += additional_targets_in_family + + # Check if we want only generic architecture + if granularity == "generic": + candidate_targets = [t for t in candidate_targets if t.vendor == "generic"] + + return candidate_targets + + def possible_dependencies( + self, + *specs: Union[spack.spec.Spec, str], + allowed_deps: dt.DepFlag, + transitive: bool = True, + strict_depflag: bool = False, + expand_virtuals: bool = True, + ) -> PossibleGraph: + stack = [x for x in self._package_list(specs)] + virtuals: Set[str] = set() + edges: Dict[str, Set[str]] = {} + + while stack: + pkg_name = stack.pop() + + if pkg_name in edges: + continue + + edges[pkg_name] = set() + + # Since libc is not buildable, there is no need to extend the + # search space with libc dependencies. + if pkg_name in self.libc_pkgs: + continue + + pkg_cls = self.repo.get_pkg_class(pkg_name=pkg_name) + for name, conditions in pkg_cls.dependencies_by_name(when=True).items(): + if all(self.unreachable(pkg_name=pkg_name, when_spec=x) for x in conditions): + tty.debug( + f"[{__name__}] Not adding {name} as a dep of {pkg_name}, because " + f"conditions cannot be met" + ) + continue + + if not self._has_deptypes( + conditions, allowed_deps=allowed_deps, strict=strict_depflag + ): + continue + + if name in virtuals: + continue + + dep_names = set() + if self.is_virtual(name): + virtuals.add(name) + if expand_virtuals: + providers = self.providers_for(name) + dep_names = {spec.name for spec in providers} + else: + dep_names = {name} + + edges[pkg_name].update(dep_names) + + if not transitive: + continue + + for dep_name in dep_names: + if dep_name in edges: + continue + + if not self._is_possible(pkg_name=dep_name): + continue + + stack.append(dep_name) + + real_packages = set(edges) + if not transitive: + # We exit early, so add children from the edges information + for root, children in edges.items(): + real_packages.update(x for x in children if self._is_possible(pkg_name=x)) + + virtuals.update(self.runtime_virtuals) + real_packages = real_packages | self.runtime_pkgs + return PossibleGraph(real_pkgs=real_packages, virtuals=virtuals, edges=edges) + + def _package_list(self, specs: Tuple[Union[spack.spec.Spec, str], ...]) -> List[str]: + stack = [] + for current_spec in specs: + if isinstance(current_spec, str): + current_spec = spack.spec.Spec(current_spec) + + if self.repo.is_virtual(current_spec.name): + stack.extend([p.name for p in self.providers_for(current_spec.name)]) + continue + + stack.append(current_spec.name) + return sorted(set(stack)) + + def _has_deptypes(self, dependencies, *, allowed_deps: dt.DepFlag, strict: bool) -> bool: + if strict is True: + return any( + dep.depflag == allowed_deps for deplist in dependencies.values() for dep in deplist + ) + return any( + dep.depflag & allowed_deps for deplist in dependencies.values() for dep in deplist + ) + + def _is_possible(self, *, pkg_name): + try: + return self.is_allowed_on_this_platform(pkg_name=pkg_name) and self.can_be_installed( + pkg_name=pkg_name + ) + except spack.repo.UnknownPackageError: + return False + + +class StaticAnalysis(NoStaticAnalysis): + """Performs some static analysis of the configuration, store, etc. to provide more precise + answers on whether some packages can be installed, or used as a provider. + + It increases the setup time, but might decrease the grounding and solve time considerably, + especially when requirements restrict the possible choices for providers. + """ + + def __init__( + self, + *, + configuration: spack.config.Configuration, + repo: spack.repo.RepoPath, + store: spack.store.Store, + binary_index: spack.binary_distribution.BinaryCacheIndex, + ): + super().__init__(configuration=configuration, repo=repo) + self.store = store + self.binary_index = binary_index + + @lang.memoized + def providers_for(self, virtual_str: str) -> List[spack.spec.Spec]: + candidates = super().providers_for(virtual_str) + result = [] + for spec in candidates: + if not self._is_provider_candidate(pkg_name=spec.name, virtual=virtual_str): + continue + result.append(spec) + return result + + @lang.memoized + def buildcache_specs(self) -> List[spack.spec.Spec]: + self.binary_index.update() + return self.binary_index.get_all_built_specs() + + @lang.memoized + def can_be_installed(self, *, pkg_name) -> bool: + if self.configuration.get(f"packages:{pkg_name}:buildable", True): + return True + + if self.configuration.get(f"packages:{pkg_name}:externals", []): + return True + + reuse = self.configuration.get("concretizer:reuse") + if reuse is not False and self.store.db.query(pkg_name): + return True + + if reuse is not False and any(x.name == pkg_name for x in self.buildcache_specs()): + return True + + tty.debug(f"[{__name__}] {pkg_name} cannot be installed") + return False + + @lang.memoized + def _is_provider_candidate(self, *, pkg_name: str, virtual: str) -> bool: + if not self.is_allowed_on_this_platform(pkg_name=pkg_name): + return False + + if not self.can_be_installed(pkg_name=pkg_name): + return False + + virtual_spec = spack.spec.Spec(virtual) + if self.unreachable(pkg_name=virtual_spec.name, when_spec=pkg_name): + tty.debug(f"[{__name__}] {pkg_name} cannot be a provider for {virtual}") + return False + + return True + + @lang.memoized + def unreachable(self, *, pkg_name: str, when_spec: spack.spec.Spec) -> bool: + """Returns true if the context can determine that the condition cannot ever + be met on pkg_name. + """ + candidates = self.configuration.get(f"packages:{pkg_name}:require", []) + if not candidates and pkg_name != "all": + return self.unreachable(pkg_name="all", when_spec=when_spec) + + if not candidates: + return False + + if isinstance(candidates, str): + candidates = [candidates] + + union_requirement = spack.spec.Spec() + for c in candidates: + if not isinstance(c, str): + continue + try: + union_requirement.constrain(c) + except SpackError: + # Less optimized, but shouldn't fail + pass + + if not union_requirement.intersects(when_spec): + return True + + return False + + +def create_graph_analyzer() -> PossibleDependencyGraph: + static_analysis = spack.config.CONFIG.get("concretizer:static_analysis", False) + if static_analysis: + return StaticAnalysis( + configuration=spack.config.CONFIG, + repo=spack.repo.PATH, + store=spack.store.STORE, + binary_index=spack.binary_distribution.BINARY_INDEX, + ) + return NoStaticAnalysis(configuration=spack.config.CONFIG, repo=spack.repo.PATH) + + +class Counter: + """Computes the possible packages and the maximum number of duplicates + allowed for each of them. + + Args: + specs: abstract specs to concretize + tests: if True, add test dependencies to the list of possible packages + """ + + def __init__( + self, specs: List["spack.spec.Spec"], tests: bool, possible_graph: PossibleDependencyGraph + ) -> None: + self.possible_graph = possible_graph + self.specs = specs + self.link_run_types: dt.DepFlag = dt.LINK | dt.RUN | dt.TEST + self.all_types: dt.DepFlag = dt.ALL + if not tests: + self.link_run_types = dt.LINK | dt.RUN + self.all_types = dt.LINK | dt.RUN | dt.BUILD + + self._possible_dependencies: Set[str] = set() + self._possible_virtuals: Set[str] = set(x.name for x in specs if x.virtual) + + def possible_dependencies(self) -> Set[str]: + """Returns the list of possible dependencies""" + self.ensure_cache_values() + return self._possible_dependencies + + def possible_virtuals(self) -> Set[str]: + """Returns the list of possible virtuals""" + self.ensure_cache_values() + return self._possible_virtuals + + def ensure_cache_values(self) -> None: + """Ensure the cache values have been computed""" + if self._possible_dependencies: + return + self._compute_cache_values() + + def possible_packages_facts(self, gen: "spack.solver.asp.ProblemInstanceBuilder", fn) -> None: + """Emit facts associated with the possible packages""" + raise NotImplementedError("must be implemented by derived classes") + + def _compute_cache_values(self) -> None: + raise NotImplementedError("must be implemented by derived classes") + + +class NoDuplicatesCounter(Counter): + def _compute_cache_values(self) -> None: + self._possible_dependencies, virtuals, _ = self.possible_graph.possible_dependencies( + *self.specs, allowed_deps=self.all_types + ) + self._possible_virtuals.update(virtuals) + + def possible_packages_facts(self, gen: "spack.solver.asp.ProblemInstanceBuilder", fn) -> None: + gen.h2("Maximum number of nodes (packages)") + for package_name in sorted(self.possible_dependencies()): + gen.fact(fn.max_dupes(package_name, 1)) + gen.newline() + gen.h2("Maximum number of nodes (virtual packages)") + for package_name in sorted(self.possible_virtuals()): + gen.fact(fn.max_dupes(package_name, 1)) + gen.newline() + gen.h2("Possible package in link-run subDAG") + for name in sorted(self.possible_dependencies()): + gen.fact(fn.possible_in_link_run(name)) + gen.newline() + + +class MinimalDuplicatesCounter(NoDuplicatesCounter): + def __init__( + self, specs: List["spack.spec.Spec"], tests: bool, possible_graph: PossibleDependencyGraph + ) -> None: + super().__init__(specs, tests, possible_graph) + self._link_run: Set[str] = set() + self._direct_build: Set[str] = set() + self._total_build: Set[str] = set() + self._link_run_virtuals: Set[str] = set() + + def _compute_cache_values(self) -> None: + self._link_run, virtuals, _ = self.possible_graph.possible_dependencies( + *self.specs, allowed_deps=self.link_run_types + ) + self._possible_virtuals.update(virtuals) + self._link_run_virtuals.update(virtuals) + for x in self._link_run: + reals, virtuals, _ = self.possible_graph.possible_dependencies( + x, allowed_deps=dt.BUILD, transitive=False, strict_depflag=True + ) + self._possible_virtuals.update(virtuals) + self._direct_build.update(reals) + + self._total_build, virtuals, _ = self.possible_graph.possible_dependencies( + *self._direct_build, allowed_deps=self.all_types + ) + self._possible_virtuals.update(virtuals) + self._possible_dependencies = set(self._link_run) | set(self._total_build) + + def possible_packages_facts(self, gen, fn): + build_tools = spack.repo.PATH.packages_with_tags("build-tools") + gen.h2("Packages with at most a single node") + for package_name in sorted(self.possible_dependencies() - build_tools): + gen.fact(fn.max_dupes(package_name, 1)) + gen.newline() + + gen.h2("Packages with at multiple possible nodes (build-tools)") + for package_name in sorted(self.possible_dependencies() & build_tools): + gen.fact(fn.max_dupes(package_name, 2)) + gen.fact(fn.multiple_unification_sets(package_name)) + gen.newline() + + gen.h2("Maximum number of nodes (virtual packages)") + for package_name in sorted(self.possible_virtuals()): + gen.fact(fn.max_dupes(package_name, 1)) + gen.newline() + + gen.h2("Possible package in link-run subDAG") + for name in sorted(self._link_run): + gen.fact(fn.possible_in_link_run(name)) + gen.newline() + + +class FullDuplicatesCounter(MinimalDuplicatesCounter): + def possible_packages_facts(self, gen, fn): + build_tools = spack.repo.PATH.packages_with_tags("build-tools") + counter = collections.Counter( + list(self._link_run) + list(self._total_build) + list(self._direct_build) + ) + gen.h2("Maximum number of nodes") + for pkg, count in sorted(counter.items(), key=lambda x: (x[1], x[0])): + count = min(count, 2) + gen.fact(fn.max_dupes(pkg, count)) + gen.newline() + + gen.h2("Build unification sets ") + for name in sorted(self.possible_dependencies() & build_tools): + gen.fact(fn.multiple_unification_sets(name)) + gen.newline() + + gen.h2("Possible package in link-run subDAG") + for name in sorted(self._link_run): + gen.fact(fn.possible_in_link_run(name)) + gen.newline() + + counter = collections.Counter( + list(self._link_run_virtuals) + list(self._possible_virtuals) + ) + gen.h2("Maximum number of virtual nodes") + for pkg, count in sorted(counter.items(), key=lambda x: (x[1], x[0])): + gen.fact(fn.max_dupes(pkg, count)) + gen.newline() + + +def create_counter( + specs: List[spack.spec.Spec], tests: bool, possible_graph: PossibleDependencyGraph +) -> Counter: + strategy = spack.config.CONFIG.get("concretizer:duplicates:strategy", "none") + if strategy == "full": + return FullDuplicatesCounter(specs, tests=tests, possible_graph=possible_graph) + if strategy == "minimal": + return MinimalDuplicatesCounter(specs, tests=tests, possible_graph=possible_graph) + return NoDuplicatesCounter(specs, tests=tests, possible_graph=possible_graph) diff --git a/lib/spack/spack/test/cmd/dependencies.py b/lib/spack/spack/test/cmd/dependencies.py index abe69bc01a5..e13439eba08 100644 --- a/lib/spack/spack/test/cmd/dependencies.py +++ b/lib/spack/spack/test/cmd/dependencies.py @@ -24,32 +24,24 @@ mpi_deps = ["fake"] -def test_direct_dependencies(mock_packages): - out = dependencies("mpileaks") - actual = set(re.split(r"\s+", out.strip())) - expected = set(["callpath"] + mpis) - assert expected == actual - - -def test_transitive_dependencies(mock_packages): - out = dependencies("--transitive", "mpileaks") - actual = set(re.split(r"\s+", out.strip())) - expected = set(["callpath", "dyninst", "libdwarf", "libelf"] + mpis + mpi_deps) - assert expected == actual - - -def test_transitive_dependencies_with_deptypes(mock_packages): - out = dependencies("--transitive", "--deptype=link,run", "dtbuild1") - deps = set(re.split(r"\s+", out.strip())) - assert set(["dtlink2", "dtrun2"]) == deps - - out = dependencies("--transitive", "--deptype=build", "dtbuild1") - deps = set(re.split(r"\s+", out.strip())) - assert set(["dtbuild2", "dtlink2"]) == deps - - out = dependencies("--transitive", "--deptype=link", "dtbuild1") - deps = set(re.split(r"\s+", out.strip())) - assert set(["dtlink2"]) == deps +@pytest.mark.parametrize( + "cli_args,expected", + [ + (["mpileaks"], set(["callpath"] + mpis)), + ( + ["--transitive", "mpileaks"], + set(["callpath", "dyninst", "libdwarf", "libelf"] + mpis + mpi_deps), + ), + (["--transitive", "--deptype=link,run", "dtbuild1"], {"dtlink2", "dtrun2"}), + (["--transitive", "--deptype=build", "dtbuild1"], {"dtbuild2", "dtlink2"}), + (["--transitive", "--deptype=link", "dtbuild1"], {"dtlink2"}), + ], +) +def test_direct_dependencies(cli_args, expected, mock_runtimes): + out = dependencies(*cli_args) + result = set(re.split(r"\s+", out.strip())) + expected.update(mock_runtimes) + assert expected == result @pytest.mark.db diff --git a/lib/spack/spack/test/concretization/requirements.py b/lib/spack/spack/test/concretization/requirements.py index 72552398a45..c2e5d4cadaf 100644 --- a/lib/spack/spack/test/concretization/requirements.py +++ b/lib/spack/spack/test/concretization/requirements.py @@ -1,7 +1,6 @@ # Copyright Spack Project Developers. See COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import os import pathlib import pytest @@ -200,11 +199,11 @@ def test_requirement_adds_version_satisfies( @pytest.mark.parametrize("require_checksum", (True, False)) def test_requirement_adds_git_hash_version( - require_checksum, concretize_scope, test_repo, mock_git_version_info, monkeypatch, working_env + require_checksum, concretize_scope, test_repo, mock_git_version_info, monkeypatch ): # A full commit sha is a checksummed version, so this test should pass in both cases if require_checksum: - os.environ["SPACK_CONCRETIZER_REQUIRE_CHECKSUM"] = "yes" + monkeypatch.setenv("SPACK_CONCRETIZER_REQUIRE_CHECKSUM", "yes") repo_path, filename, commits = mock_git_version_info monkeypatch.setattr( diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py index ceabdc53a13..2193e84e70c 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -2171,3 +2171,8 @@ def getcode(self): def info(self): return self.headers + + +@pytest.fixture() +def mock_runtimes(config, mock_packages): + return mock_packages.packages_with_tags("runtime") diff --git a/lib/spack/spack/test/package_class.py b/lib/spack/spack/test/package_class.py index 46de370e4e3..7edec99fabe 100644 --- a/lib/spack/spack/test/package_class.py +++ b/lib/spack/spack/test/package_class.py @@ -1,7 +1,6 @@ # Copyright Spack Project Developers. See COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - """Test class methods on Package objects. This doesn't include methods on package *instances* (like do_patch(), @@ -16,6 +15,7 @@ import llnl.util.filesystem as fs +import spack.binary_distribution import spack.compilers import spack.concretize import spack.deptypes as dt @@ -23,15 +23,11 @@ import spack.install_test import spack.package import spack.package_base -import spack.repo import spack.spec +import spack.store from spack.build_systems.generic import Package from spack.error import InstallError - - -@pytest.fixture(scope="module") -def mpi_names(mock_repo_path): - return [spec.name for spec in mock_repo_path.providers_for("mpi")] +from spack.solver.input_analysis import NoStaticAnalysis, StaticAnalysis @pytest.fixture() @@ -53,78 +49,94 @@ def mpileaks_possible_deps(mock_packages, mpi_names): return possible -def test_possible_dependencies(mock_packages, mpileaks_possible_deps): - pkg_cls = spack.repo.PATH.get_pkg_class("mpileaks") - expanded_possible_deps = pkg_cls.possible_dependencies(expand_virtuals=True) - assert mpileaks_possible_deps == expanded_possible_deps - assert { - "callpath": {"dyninst", "mpi"}, - "dyninst": {"libdwarf", "libelf"}, - "libdwarf": {"libelf"}, - "libelf": set(), - "mpi": set(), - "mpileaks": {"callpath", "mpi"}, - } == pkg_cls.possible_dependencies(expand_virtuals=False) - - -def test_possible_direct_dependencies(mock_packages, mpileaks_possible_deps): - pkg_cls = spack.repo.PATH.get_pkg_class("mpileaks") - deps = pkg_cls.possible_dependencies(transitive=False, expand_virtuals=False) - assert {"callpath": set(), "mpi": set(), "mpileaks": {"callpath", "mpi"}} == deps - - -def test_possible_dependencies_virtual(mock_packages, mpi_names): - expected = dict( - (name, set(dep for dep in spack.repo.PATH.get_pkg_class(name).dependencies_by_name())) - for name in mpi_names - ) - - # only one mock MPI has a dependency - expected["fake"] = set() - - assert expected == spack.package_base.possible_dependencies("mpi", transitive=False) - - -def test_possible_dependencies_missing(mock_packages): - pkg_cls = spack.repo.PATH.get_pkg_class("missing-dependency") - missing = {} - pkg_cls.possible_dependencies(transitive=True, missing=missing) - assert {"this-is-a-missing-dependency"} == missing["missing-dependency"] - - -def test_possible_dependencies_with_deptypes(mock_packages): - dtbuild1 = spack.repo.PATH.get_pkg_class("dtbuild1") - - assert { - "dtbuild1": {"dtrun2", "dtlink2"}, - "dtlink2": set(), - "dtrun2": set(), - } == dtbuild1.possible_dependencies(depflag=dt.LINK | dt.RUN) - - assert { - "dtbuild1": {"dtbuild2", "dtlink2"}, - "dtbuild2": set(), - "dtlink2": set(), - } == dtbuild1.possible_dependencies(depflag=dt.BUILD) - - assert {"dtbuild1": {"dtlink2"}, "dtlink2": set()} == dtbuild1.possible_dependencies( - depflag=dt.LINK +@pytest.fixture(params=[NoStaticAnalysis, StaticAnalysis]) +def mock_inspector(config, mock_packages, request): + inspector_cls = request.param + if inspector_cls is NoStaticAnalysis: + return inspector_cls(configuration=config, repo=mock_packages) + return inspector_cls( + configuration=config, + repo=mock_packages, + store=spack.store.STORE, + binary_index=spack.binary_distribution.BINARY_INDEX, ) -def test_possible_dependencies_with_multiple_classes(mock_packages, mpileaks_possible_deps): +@pytest.fixture +def mpi_names(mock_inspector): + return [spec.name for spec in mock_inspector.providers_for("mpi")] + + +@pytest.mark.parametrize( + "pkg_name,fn_kwargs,expected", + [ + ( + "mpileaks", + {"expand_virtuals": True, "allowed_deps": dt.ALL}, + { + "fake", + "mpileaks", + "multi-provider-mpi", + "callpath", + "dyninst", + "mpich2", + "libdwarf", + "zmpi", + "low-priority-provider", + "intel-parallel-studio", + "mpich", + "libelf", + }, + ), + ( + "mpileaks", + {"expand_virtuals": False, "allowed_deps": dt.ALL}, + {"callpath", "dyninst", "libdwarf", "libelf", "mpileaks"}, + ), + ( + "mpileaks", + {"expand_virtuals": False, "allowed_deps": dt.ALL, "transitive": False}, + {"callpath", "mpileaks"}, + ), + ("dtbuild1", {"allowed_deps": dt.LINK | dt.RUN}, {"dtbuild1", "dtrun2", "dtlink2"}), + ("dtbuild1", {"allowed_deps": dt.BUILD}, {"dtbuild1", "dtbuild2", "dtlink2"}), + ("dtbuild1", {"allowed_deps": dt.LINK}, {"dtbuild1", "dtlink2"}), + ], +) +def test_possible_dependencies(pkg_name, fn_kwargs, expected, mock_runtimes, mock_inspector): + """Tests possible nodes of mpileaks, under different scenarios.""" + expected.update(mock_runtimes) + result, *_ = mock_inspector.possible_dependencies(pkg_name, **fn_kwargs) + assert expected == result + + +def test_possible_dependencies_virtual(mock_inspector, mock_packages, mock_runtimes, mpi_names): + expected = set(mpi_names) + for name in mpi_names: + expected.update(dep for dep in mock_packages.get_pkg_class(name).dependencies_by_name()) + expected.update(mock_runtimes) + + real_pkgs, *_ = mock_inspector.possible_dependencies( + "mpi", transitive=False, allowed_deps=dt.ALL + ) + assert expected == real_pkgs + + +def test_possible_dependencies_missing(mock_inspector): + result, *_ = mock_inspector.possible_dependencies("missing-dependency", allowed_deps=dt.ALL) + assert "this-is-a-missing-dependency" not in result + + +def test_possible_dependencies_with_multiple_classes( + mock_inspector, mock_packages, mpileaks_possible_deps +): pkgs = ["dt-diamond", "mpileaks"] - expected = mpileaks_possible_deps.copy() - expected.update( - { - "dt-diamond": set(["dt-diamond-left", "dt-diamond-right"]), - "dt-diamond-left": set(["dt-diamond-bottom"]), - "dt-diamond-right": set(["dt-diamond-bottom"]), - "dt-diamond-bottom": set(), - } - ) + expected = set(mpileaks_possible_deps) + expected.update({"dt-diamond", "dt-diamond-left", "dt-diamond-right", "dt-diamond-bottom"}) + expected.update(mock_packages.packages_with_tags("runtime")) - assert expected == spack.package_base.possible_dependencies(*pkgs) + real_pkgs, *_ = mock_inspector.possible_dependencies(*pkgs, allowed_deps=dt.ALL) + assert set(expected) == real_pkgs def setup_install_test(source_paths, test_root): diff --git a/share/spack/gitlab/cloud_pipelines/stacks/developer-tools-x86_64_v3-linux-gnu/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/developer-tools-x86_64_v3-linux-gnu/spack.yaml index b1b754daef1..8c17c5f8266 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/developer-tools-x86_64_v3-linux-gnu/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/developer-tools-x86_64_v3-linux-gnu/spack.yaml @@ -2,10 +2,16 @@ spack: view: false packages: all: - require: target=x86_64_v3 + require: + - target=x86_64_v3 + - ~cuda + - ~rocm + concretizer: unify: true reuse: false + static_analysis: true + definitions: - default_specs: # editors diff --git a/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml b/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml index ef28a28de1b..deaca891398 100644 --- a/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml +++ b/share/spack/gitlab/cloud_pipelines/stacks/e4s/spack.yaml @@ -4,16 +4,32 @@ spack: concretizer: reuse: false unify: false + static_analysis: true packages: all: - require: '%gcc target=x86_64_v3' - providers: - blas: [openblas] - mpi: [mpich] + require: + - '%gcc target=x86_64_v3' variants: +mpi + mpi: + require: + - mpich + blas: + require: + - openblas + lapack: + require: + - openblas binutils: variants: +ld +gold +headers +libiberty ~nls + cmake: + require: + - "~qtgui" + - '%gcc target=x86_64_v3' + gmake: + require: + - "~guile" + - '%gcc target=x86_64_v3' hdf5: variants: +fortran +hl +shared libfabric: @@ -27,19 +43,25 @@ spack: +ifpack +ifpack2 +intrepid +intrepid2 +isorropia +kokkos +ml +minitensor +muelu +nox +piro +phalanx +rol +rythmos +sacado +stk +shards +shylu +stokhos +stratimikos +teko +tempus +tpetra +trilinoscouplings +zoltan +zoltan2 +superlu-dist gotype=long_long - mpi: - require: mpich mpich: - require: '~wrapperrpath ~hwloc target=x86_64_v3' + require: + - '~wrapperrpath ~hwloc' + - '%gcc target=x86_64_v3' tbb: - require: intel-tbb + require: + - intel-tbb vtk-m: - require: "+examples target=x86_64_v3" + require: + - "+examples" + - '%gcc target=x86_64_v3' visit: - require: "~gui target=x86_64_v3" + require: + - "~gui target=x86_64_v3" paraview: # Don't build GUI support or GLX rendering for HPC/container deployments - require: "+examples ~qt ^[virtuals=gl] osmesa target=x86_64_v3" + require: + - "+examples ~qt ^[virtuals=gl] osmesa target=x86_64_v3" + - '%gcc target=x86_64_v3' specs: # CPU