summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2024-11-01 21:43:16 +0100
committerGitHub <noreply@github.com>2024-11-01 13:43:16 -0700
commit0cf8cb70f43ae325e8895eb241a92f5aa4680399 (patch)
tree424c0d3f5e0aa99ba4d8fce4b8a82328425a299a
parent7b2450c22ad4adf4280a8f024ec4778b22d58816 (diff)
downloadspack-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.py30
-rw-r--r--lib/spack/spack/test/spec_yaml.py24
-rw-r--r--lib/spack/spack/variant.py10
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.