summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHarmen Stoppels <me@harmenstoppels.nl>2024-08-24 09:45:23 +0200
committerGitHub <noreply@github.com>2024-08-24 09:45:23 +0200
commit94c99fc5d44a7e0a2c81fbfe8ca08356bde7a5c7 (patch)
treec846bd5e03d842d98856bd2b28ee22f7d9b5b728 /lib
parent1f1021a47fe389e1a6108acb14602fe86e23b3bd (diff)
downloadspack-94c99fc5d44a7e0a2c81fbfe8ca08356bde7a5c7.tar.gz
spack-94c99fc5d44a7e0a2c81fbfe8ca08356bde7a5c7.tar.bz2
spack-94c99fc5d44a7e0a2c81fbfe8ca08356bde7a5c7.tar.xz
spack-94c99fc5d44a7e0a2c81fbfe8ca08356bde7a5c7.zip
variant.py: extract spec bits into spec.py (#45941)
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/package_prefs.py2
-rw-r--r--lib/spack/spack/spec.py155
-rw-r--r--lib/spack/spack/spec_list.py3
-rw-r--r--lib/spack/spack/test/variant.py2
-rw-r--r--lib/spack/spack/variant.py160
5 files changed, 156 insertions, 166 deletions
diff --git a/lib/spack/spack/package_prefs.py b/lib/spack/spack/package_prefs.py
index 117308e84e..55cad78392 100644
--- a/lib/spack/spack/package_prefs.py
+++ b/lib/spack/spack/package_prefs.py
@@ -5,8 +5,10 @@
import stat
import warnings
+import spack.config
import spack.error
import spack.repo
+import spack.spec
from spack.config import ConfigError
from spack.util.path import canonicalize_path
from spack.version import Version
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 19242bc06a..5e0abcf20c 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -51,6 +51,7 @@ line is a spec for a particular installation of the mpileaks package.
import collections
import collections.abc
import enum
+import io
import itertools
import os
import pathlib
@@ -1427,7 +1428,7 @@ class Spec:
# init an empty spec that matches anything.
self.name = None
self.versions = vn.VersionList(":")
- self.variants = vt.VariantMap(self)
+ self.variants = VariantMap(self)
self.architecture = None
self.compiler = None
self.compiler_flags = FlagMap(self)
@@ -2592,7 +2593,7 @@ class Spec:
extra_attributes = syaml.sorted_dict(extra_attributes or {})
# This is needed to be able to validate multi-valued variants,
# otherwise they'll still be abstract in the context of detection.
- vt.substitute_abstract_variants(s)
+ substitute_abstract_variants(s)
s.extra_attributes = extra_attributes
return s
@@ -2915,7 +2916,7 @@ class Spec:
# Ensure correctness of variants (if the spec is not virtual)
if not spec.virtual:
Spec.ensure_valid_variants(spec)
- vt.substitute_abstract_variants(spec)
+ substitute_abstract_variants(spec)
@staticmethod
def ensure_valid_variants(spec):
@@ -3884,7 +3885,7 @@ class Spec:
if part.startswith("_"):
raise SpecFormatStringError("Attempted to format private attribute")
else:
- if part == "variants" and isinstance(current, vt.VariantMap):
+ if part == "variants" and isinstance(current, VariantMap):
# subscript instead of getattr for variant names
current = current[part]
else:
@@ -4339,6 +4340,152 @@ class Spec:
v.attach_lookup(spack.version.git_ref_lookup.GitRefLookup(self.fullname))
+class VariantMap(lang.HashableMap):
+ """Map containing variant instances. New values can be added only
+ if the key is not already present."""
+
+ def __init__(self, spec: Spec):
+ super().__init__()
+ self.spec = spec
+
+ def __setitem__(self, name, vspec):
+ # Raise a TypeError if vspec is not of the right type
+ if not isinstance(vspec, vt.AbstractVariant):
+ raise TypeError(
+ "VariantMap accepts only values of variant types "
+ f"[got {type(vspec).__name__} instead]"
+ )
+
+ # Raise an error if the variant was already in this map
+ if name in self.dict:
+ msg = 'Cannot specify variant "{0}" twice'.format(name)
+ raise vt.DuplicateVariantError(msg)
+
+ # Raise an error if name and vspec.name don't match
+ if name != vspec.name:
+ raise KeyError(
+ f'Inconsistent key "{name}", must be "{vspec.name}" to ' "match VariantSpec"
+ )
+
+ # Set the item
+ super().__setitem__(name, vspec)
+
+ def substitute(self, vspec):
+ """Substitutes the entry under ``vspec.name`` with ``vspec``.
+
+ Args:
+ vspec: variant spec to be substituted
+ """
+ if vspec.name not in self:
+ raise KeyError(f"cannot substitute a key that does not exist [{vspec.name}]")
+
+ # Set the item
+ super().__setitem__(vspec.name, vspec)
+
+ def satisfies(self, other):
+ return all(k in self and self[k].satisfies(other[k]) for k in other)
+
+ def intersects(self, other):
+ return all(self[k].intersects(other[k]) for k in other if k in self)
+
+ def constrain(self, other: "VariantMap") -> bool:
+ """Add all variants in other that aren't in self to self. Also constrain all multi-valued
+ variants that are already present. Return True iff self changed"""
+ if other.spec is not None and other.spec._concrete:
+ for k in self:
+ if k not in other:
+ raise vt.UnsatisfiableVariantSpecError(self[k], "<absent>")
+
+ changed = False
+ for k in other:
+ if k in self:
+ # If they are not compatible raise an error
+ if not self[k].compatible(other[k]):
+ raise vt.UnsatisfiableVariantSpecError(self[k], other[k])
+ # If they are compatible merge them
+ changed |= self[k].constrain(other[k])
+ else:
+ # If it is not present copy it straight away
+ self[k] = other[k].copy()
+ changed = True
+
+ return changed
+
+ @property
+ def concrete(self):
+ """Returns True if the spec is concrete in terms of variants.
+
+ Returns:
+ bool: True or False
+ """
+ return self.spec._concrete or all(v in self for v in self.spec.package_class.variants)
+
+ def copy(self) -> "VariantMap":
+ clone = VariantMap(self.spec)
+ for name, variant in self.items():
+ clone[name] = variant.copy()
+ return clone
+
+ def __str__(self):
+ if not self:
+ return ""
+
+ # print keys in order
+ sorted_keys = sorted(self.keys())
+
+ # Separate boolean variants from key-value pairs as they print
+ # differently. All booleans go first to avoid ' ~foo' strings that
+ # break spec reuse in zsh.
+ bool_keys = []
+ kv_keys = []
+ for key in sorted_keys:
+ bool_keys.append(key) if isinstance(self[key].value, bool) else kv_keys.append(key)
+
+ # add spaces before and after key/value variants.
+ string = io.StringIO()
+
+ for key in bool_keys:
+ string.write(str(self[key]))
+
+ for key in kv_keys:
+ string.write(" ")
+ string.write(str(self[key]))
+
+ return string.getvalue()
+
+
+def substitute_abstract_variants(spec: Spec):
+ """Uses the information in `spec.package` to turn any variant that needs
+ it into a SingleValuedVariant.
+
+ This method is best effort. All variants that can be substituted will be
+ substituted before any error is raised.
+
+ Args:
+ spec: spec on which to operate the substitution
+ """
+ # This method needs to be best effort so that it works in matrix exlusion
+ # in $spack/lib/spack/spack/spec_list.py
+ failed = []
+ for name, v in spec.variants.items():
+ if name == "dev_path":
+ spec.variants.substitute(vt.SingleValuedVariant(name, v._original_value))
+ continue
+ elif name in vt.reserved_names:
+ continue
+ elif name not in spec.package_class.variants:
+ failed.append(name)
+ continue
+ pkg_variant, _ = spec.package_class.variants[name]
+ new_variant = pkg_variant.make_variant(v._original_value)
+ pkg_variant.validate_or_raise(new_variant, spec.package_class)
+ spec.variants.substitute(new_variant)
+
+ # Raise all errors at once
+ if failed:
+ raise vt.UnknownVariantError(spec, failed)
+
+
def parse_with_version_concrete(spec_like: Union[str, Spec], compiler: bool = False):
"""Same as Spec(string), but interprets @x as @=x"""
s: Union[CompilerSpec, Spec] = CompilerSpec(spec_like) if compiler else Spec(spec_like)
diff --git a/lib/spack/spack/spec_list.py b/lib/spack/spack/spec_list.py
index db844a3e4c..e62b2608dc 100644
--- a/lib/spack/spack/spec_list.py
+++ b/lib/spack/spack/spec_list.py
@@ -5,6 +5,7 @@
import itertools
from typing import List
+import spack.spec
import spack.variant
from spack.error import SpackError
from spack.spec import Spec
@@ -225,7 +226,7 @@ def _expand_matrix_constraints(matrix_config):
# 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)
+ spack.spec.substitute_abstract_variants(test_spec)
except spack.variant.UnknownVariantError:
pass
diff --git a/lib/spack/spack/test/variant.py b/lib/spack/spack/test/variant.py
index 0dad484ed9..d44aacee5e 100644
--- a/lib/spack/spack/test/variant.py
+++ b/lib/spack/spack/test/variant.py
@@ -8,6 +8,7 @@ import pytest
import spack.error
import spack.variant
+from spack.spec import VariantMap
from spack.variant import (
BoolValuedVariant,
DuplicateVariantError,
@@ -18,7 +19,6 @@ from spack.variant import (
SingleValuedVariant,
UnsatisfiableVariantSpecError,
Variant,
- VariantMap,
disjoint_sets,
)
diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py
index 71a31aa914..83f3ecca83 100644
--- a/lib/spack/spack/variant.py
+++ b/lib/spack/spack/variant.py
@@ -9,7 +9,6 @@ variants both in packages and in specs.
import collections.abc
import functools
import inspect
-import io
import itertools
import re
@@ -550,165 +549,6 @@ class BoolValuedVariant(SingleValuedVariant):
return "{0}{1}".format("+" if self.value else "~", self.name)
-class VariantMap(lang.HashableMap):
- """Map containing variant instances. New values can be added only
- if the key is not already present.
- """
-
- def __init__(self, spec):
- super().__init__()
- self.spec = spec
-
- def __setitem__(self, name, vspec):
- # Raise a TypeError if vspec is not of the right type
- if not isinstance(vspec, AbstractVariant):
- msg = "VariantMap accepts only values of variant types"
- msg += " [got {0} instead]".format(type(vspec).__name__)
- raise TypeError(msg)
-
- # Raise an error if the variant was already in this map
- if name in self.dict:
- msg = 'Cannot specify variant "{0}" twice'.format(name)
- raise DuplicateVariantError(msg)
-
- # Raise an error if name and vspec.name don't match
- if name != vspec.name:
- msg = 'Inconsistent key "{0}", must be "{1}" to match VariantSpec'
- raise KeyError(msg.format(name, vspec.name))
-
- # Set the item
- super().__setitem__(name, vspec)
-
- def substitute(self, vspec):
- """Substitutes the entry under ``vspec.name`` with ``vspec``.
-
- Args:
- vspec: variant spec to be substituted
- """
- if vspec.name not in self:
- msg = "cannot substitute a key that does not exist [{0}]"
- raise KeyError(msg.format(vspec.name))
-
- # Set the item
- super().__setitem__(vspec.name, vspec)
-
- def satisfies(self, other):
- return all(k in self and self[k].satisfies(other[k]) for k in other)
-
- def intersects(self, other):
- return all(self[k].intersects(other[k]) for k in other if k in self)
-
- def constrain(self, other):
- """Add all variants in other that aren't in self to self. Also
- constrain all multi-valued variants that are already present.
- Return True if self changed, False otherwise
-
- Args:
- other (VariantMap): instance against which we constrain self
-
- Returns:
- bool: True or False
- """
- if other.spec is not None and other.spec._concrete:
- for k in self:
- if k not in other:
- raise UnsatisfiableVariantSpecError(self[k], "<absent>")
-
- changed = False
- for k in other:
- if k in self:
- # If they are not compatible raise an error
- if not self[k].compatible(other[k]):
- raise UnsatisfiableVariantSpecError(self[k], other[k])
- # If they are compatible merge them
- changed |= self[k].constrain(other[k])
- else:
- # If it is not present copy it straight away
- self[k] = other[k].copy()
- changed = True
-
- return changed
-
- @property
- def concrete(self):
- """Returns True if the spec is concrete in terms of variants.
-
- Returns:
- bool: True or False
- """
- return self.spec._concrete or all(v in self for v in self.spec.package_class.variants)
-
- def copy(self):
- """Return an instance of VariantMap equivalent to self.
-
- Returns:
- VariantMap: a copy of self
- """
- clone = VariantMap(self.spec)
- for name, variant in self.items():
- clone[name] = variant.copy()
- return clone
-
- def __str__(self):
- if not self:
- return ""
-
- # print keys in order
- sorted_keys = sorted(self.keys())
-
- # Separate boolean variants from key-value pairs as they print
- # differently. All booleans go first to avoid ' ~foo' strings that
- # break spec reuse in zsh.
- bool_keys = []
- kv_keys = []
- for key in sorted_keys:
- bool_keys.append(key) if isinstance(self[key].value, bool) else kv_keys.append(key)
-
- # add spaces before and after key/value variants.
- string = io.StringIO()
-
- for key in bool_keys:
- string.write(str(self[key]))
-
- for key in kv_keys:
- string.write(" ")
- string.write(str(self[key]))
-
- return string.getvalue()
-
-
-def substitute_abstract_variants(spec):
- """Uses the information in `spec.package` to turn any variant that needs
- it into a SingleValuedVariant.
-
- This method is best effort. All variants that can be substituted will be
- substituted before any error is raised.
-
- Args:
- spec: spec on which to operate the substitution
- """
- # This method needs to be best effort so that it works in matrix exlusion
- # in $spack/lib/spack/spack/spec_list.py
- failed = []
- for name, v in spec.variants.items():
- if name in reserved_names:
- if name == "dev_path":
- new_variant = SingleValuedVariant(name, v._original_value)
- spec.variants.substitute(new_variant)
- continue
- if name not in spec.package_class.variants:
- failed.append(name)
- continue
- pkg_variant, _ = spec.package_class.variants[name]
- new_variant = pkg_variant.make_variant(v._original_value)
- pkg_variant.validate_or_raise(new_variant, spec.package_class)
- spec.variants.substitute(new_variant)
-
- # Raise all errors at once
- if failed:
- raise UnknownVariantError(spec, failed)
-
-
# The class below inherit from Sequence to disguise as a tuple and comply
# with the semantic expected by the 'values' argument of the variant directive
class DisjointSetsOfValues(collections.abc.Sequence):