diff options
author | Massimiliano Culpo <massimiliano.culpo@gmail.com> | 2024-11-01 21:43:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-01 13:43:16 -0700 |
commit | 0cf8cb70f43ae325e8895eb241a92f5aa4680399 (patch) | |
tree | 424c0d3f5e0aa99ba4d8fce4b8a82328425a299a | |
parent | 7b2450c22ad4adf4280a8f024ec4778b22d58816 (diff) | |
download | spack-0cf8cb70f43ae325e8895eb241a92f5aa4680399.tar.gz spack-0cf8cb70f43ae325e8895eb241a92f5aa4680399.tar.bz2 spack-0cf8cb70f43ae325e8895eb241a92f5aa4680399.tar.xz spack-0cf8cb70f43ae325e8895eb241a92f5aa4680399.zip |
Fix pickle round-trip of specs propagating variants (#47351)
This changes `Spec` serialization to include information about propagation for abstract specs.
This was previously not included in the JSON representation for abstract specs, and couldn't be
stored.
Now, there is a separate `propagate` dictionary alongside the `parameters` dictionary. This isn't
beautiful, but when we bump the spec version for Spack `v0.24`, we can clean up this and other
aspects of the schema.
-rw-r--r-- | lib/spack/spack/spec.py | 30 | ||||
-rw-r--r-- | lib/spack/spack/test/spec_yaml.py | 24 | ||||
-rw-r--r-- | lib/spack/spack/variant.py | 10 |
3 files changed, 50 insertions, 14 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index e5b9cad431..0ec0a3009d 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -2199,6 +2199,18 @@ class Spec: if params: d["parameters"] = params + if params and not self.concrete: + flag_names = [ + name + for name, flags in self.compiler_flags.items() + if any(x.propagate for x in flags) + ] + d["propagate"] = sorted( + itertools.chain( + [v.name for v in self.variants.values() if v.propagate], flag_names + ) + ) + if self.external: d["external"] = syaml.syaml_dict( [ @@ -2371,16 +2383,10 @@ class Spec: spec is concrete, the full hash is added as well. If 'build' is in the hash_type, the build hash is also added.""" node = self.to_node_dict(hash) + # All specs have at least a DAG hash node[ht.dag_hash.name] = self.dag_hash() - # dag_hash is lazily computed -- but if we write a spec out, we want it - # to be included. This is effectively the last chance we get to compute - # it accurately. - if self.concrete: - # all specs have at least a DAG hash - node[ht.dag_hash.name] = self.dag_hash() - - else: + if not self.concrete: node["concrete"] = False # we can also give them other hash types if we want @@ -4731,13 +4737,17 @@ class SpecfileReaderBase: else: spec.compiler = None + propagated_names = node.get("propagate", []) for name, values in node.get("parameters", {}).items(): + propagate = name in propagated_names if name in _valid_compiler_flags: spec.compiler_flags[name] = [] for val in values: - spec.compiler_flags.add_flag(name, val, False) + spec.compiler_flags.add_flag(name, val, propagate) else: - spec.variants[name] = vt.MultiValuedVariant.from_node_dict(name, values) + spec.variants[name] = vt.MultiValuedVariant.from_node_dict( + name, values, propagate=propagate + ) spec.external_path = None spec.external_modules = None diff --git a/lib/spack/spack/test/spec_yaml.py b/lib/spack/spack/test/spec_yaml.py index 5b64822b38..79f128e948 100644 --- a/lib/spack/spack/test/spec_yaml.py +++ b/lib/spack/spack/test/spec_yaml.py @@ -16,6 +16,7 @@ import inspect import io import json import os +import pickle import pytest import ruamel.yaml @@ -551,3 +552,26 @@ d: *id001 e: *id002 """ ) + + +@pytest.mark.parametrize( + "spec_str", + [ + "hdf5 ++mpi", + "hdf5 cflags==-g", + "hdf5 foo==bar", + "hdf5~~mpi++shared", + "hdf5 cflags==-g foo==bar cxxflags==-O3", + "hdf5 cflags=-g foo==bar cxxflags==-O3", + ], +) +def test_pickle_roundtrip_for_abstract_specs(spec_str): + """Tests that abstract specs correctly round trip when pickled. + + This test compares both spec objects and their string representation, due to some + inconsistencies in how `Spec.__eq__` is implemented. + """ + s = spack.spec.Spec(spec_str) + t = pickle.loads(pickle.dumps(s)) + assert s == t + assert str(s) == str(t) diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py index 0756841a63..a15c760a0a 100644 --- a/lib/spack/spack/variant.py +++ b/lib/spack/spack/variant.py @@ -307,19 +307,21 @@ class AbstractVariant: self.value = value @staticmethod - def from_node_dict(name: str, value: Union[str, List[str]]) -> "AbstractVariant": + def from_node_dict( + name: str, value: Union[str, List[str]], *, propagate: bool = False + ) -> "AbstractVariant": """Reconstruct a variant from a node dict.""" if isinstance(value, list): # read multi-value variants in and be faithful to the YAML - mvar = MultiValuedVariant(name, ()) + mvar = MultiValuedVariant(name, (), propagate=propagate) mvar._value = tuple(value) mvar._original_value = mvar._value return mvar elif str(value).upper() == "TRUE" or str(value).upper() == "FALSE": - return BoolValuedVariant(name, value) + return BoolValuedVariant(name, value, propagate=propagate) - return SingleValuedVariant(name, value) + return SingleValuedVariant(name, value, propagate=propagate) def yaml_entry(self) -> Tuple[str, SerializedValueType]: """Returns a key, value tuple suitable to be an entry in a yaml dict. |