Source code for spack.spec_list

# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import itertools

from six import string_types

import spack.variant
from spack.error import SpackError
from spack.spec import Spec

[docs]class SpecList(object): def __init__(self, name='specs', yaml_list=None, reference=None): # Normalize input arguments yaml_list = yaml_list or [] reference = reference or {} = name self._reference = reference # TODO: Do we need defensive copy here? # Validate yaml_list before assigning if not all(isinstance(s, string_types) or isinstance(s, (list, dict)) for s in yaml_list): raise ValueError( "yaml_list can contain only valid YAML types! Found:\n %s" % [type(s) for s in yaml_list]) self.yaml_list = yaml_list[:] # Expansions can be expensive to compute and difficult to keep updated # We cache results and invalidate when self.yaml_list changes self._expanded_list = None self._constraints = None self._specs = None @property def specs_as_yaml_list(self): if self._expanded_list is None: self._expanded_list = self._expand_references(self.yaml_list) return self._expanded_list @property def specs_as_constraints(self): if self._constraints is None: constraints = [] for item in self.specs_as_yaml_list: if isinstance(item, dict): # matrix of specs constraints.extend(_expand_matrix_constraints(item)) else: # individual spec constraints.append([Spec(item)]) self._constraints = constraints return self._constraints @property def specs(self): if self._specs is None: specs = [] # This could be slightly faster done directly from yaml_list, # but this way is easier to maintain. for constraint_list in self.specs_as_constraints: spec = constraint_list[0].copy() for const in constraint_list[1:]: spec.constrain(const) specs.append(spec) self._specs = specs return self._specs
[docs] def add(self, spec): self.yaml_list.append(str(spec)) # expanded list can be updated without invalidation if self._expanded_list is not None: self._expanded_list.append(str(spec)) # Invalidate cache variables when we change the list self._constraints = None self._specs = None
[docs] def remove(self, spec): # Get spec to remove from list remove = [s for s in self.yaml_list if (isinstance(s, string_types) and not s.startswith('$')) and Spec(s) == Spec(spec)] if not remove: msg = 'Cannot remove %s from SpecList %s\n' % (spec, msg += 'Either %s is not in %s or %s is ' % (spec,, spec) msg += 'expanded from a matrix and cannot be removed directly.' raise SpecListError(msg) assert len(remove) == 1 self.yaml_list.remove(remove[0]) # invalidate cache variables when we change the list self._expanded_list = None self._constraints = None self._specs = None
[docs] def extend(self, other, copy_reference=True): self.yaml_list.extend(other.yaml_list) self._expanded_list = None self._constraints = None self._specs = None if copy_reference: self._reference = other._reference
[docs] def update_reference(self, reference): self._reference = reference self._expanded_list = None self._constraints = None self._specs = None
def _parse_reference(self, name): sigil = '' name = name[1:] # Parse specs as constraints if name.startswith('^') or name.startswith('%'): sigil = name[0] name = name[1:] # Make sure the reference is valid if name not in self._reference: msg = 'SpecList %s refers to ' % msg += 'named list %s ' % name msg += 'which does not appear in its reference dict' raise UndefinedReferenceError(msg) return (name, sigil) def _expand_references(self, yaml): if isinstance(yaml, list): ret = [] for item in yaml: # if it's a reference, expand it if isinstance(item, string_types) and item.startswith('$'): # replace the reference and apply the sigil if needed name, sigil = self._parse_reference(item) referent = [ _sigilify(item, sigil) for item in self._reference[name].specs_as_yaml_list ] ret.extend(referent) else: # else just recurse ret.append(self._expand_references(item)) return ret elif isinstance(yaml, dict): # There can't be expansions in dicts return dict((name, self._expand_references(val)) for (name, val) in yaml.items()) else: # Strings are just returned return yaml def __len__(self): return len(self.specs) def __getitem__(self, key): return self.specs[key]
def _expand_matrix_constraints(matrix_config): # recurse so we can handle nested matrices expanded_rows = [] for row in matrix_config['matrix']: new_row = [] for r in row: if isinstance(r, dict): # Flatten the nested matrix into a single row of constraints new_row.extend( [[' '.join([str(c) for c in expanded_constraint_list])] for expanded_constraint_list in _expand_matrix_constraints(r)] ) else: new_row.append([r]) expanded_rows.append(new_row) excludes = matrix_config.get('exclude', []) # only compute once sigil = matrix_config.get('sigil', '') results = [] for combo in itertools.product(*expanded_rows): # Construct a combined spec to test against excludes flat_combo = [constraint for constraint_list in combo for constraint in constraint_list] flat_combo = [Spec(x) for x in flat_combo] test_spec = flat_combo[0].copy() for constraint in flat_combo[1:]: test_spec.constrain(constraint) # Abstract variants don't have normal satisfaction semantics # Convert all variants to concrete types. # This method is best effort, so all existing variants will be # converted before any error is raised. # Catch exceptions because we want to be able to operate on # abstract specs without needing package information try: spack.variant.substitute_abstract_variants(test_spec) except spack.variant.UnknownVariantError: pass if any(test_spec.satisfies(x) for x in excludes): continue if sigil: flat_combo[0] = Spec(sigil + str(flat_combo[0])) # Add to list of constraints results.append(flat_combo) return results def _sigilify(item, sigil): if isinstance(item, dict): if sigil: item['sigil'] = sigil return item else: return sigil + item
[docs]class SpecListError(SpackError): """Error class for all errors related to SpecList objects."""
[docs]class UndefinedReferenceError(SpecListError): """Error class for undefined references in Spack stacks."""
[docs]class InvalidSpecConstraintError(SpecListError): """Error class for invalid spec constraints at concretize time."""