summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2020-09-03 19:49:36 +0200
committerGitHub <noreply@github.com>2020-09-03 10:49:36 -0700
commitfab2622a71e5729fdd9fa90d5702250696bc9407 (patch)
tree94255b1f1ad5e841fc515fdaa849df03b76f2203 /lib
parent741bb9bafe078d0dd39387e8ea63dca9386bb5a9 (diff)
downloadspack-fab2622a71e5729fdd9fa90d5702250696bc9407.tar.gz
spack-fab2622a71e5729fdd9fa90d5702250696bc9407.tar.bz2
spack-fab2622a71e5729fdd9fa90d5702250696bc9407.tar.xz
spack-fab2622a71e5729fdd9fa90d5702250696bc9407.zip
Hashing: force hash consistency for values read from config (#18446)
The 'external_modules' attribute on a Spec, when read from a YAML configuration file, may contain extra formatting that is lost when that Spec is written-to/read-from JSON format. This was resulting in a hashing instability (when the Spec was read back, it would report a different hash). This commit adds a function which removes the extra formatting from 'external_modules' as it is passed to the Spec in __init__ to ensure a consistent hash.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/spec.py24
-rw-r--r--lib/spack/spack/test/cmd/env.py35
2 files changed, 56 insertions, 3 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index cc2d5ca552..8c3969b8c9 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1008,7 +1008,7 @@ class Spec(object):
self._normal = normal
self._concrete = concrete
self.external_path = external_path
- self.external_modules = external_modules
+ self.external_modules = Spec._format_module_list(external_modules)
self._full_hash = full_hash
# This attribute is used to store custom information for
@@ -1025,6 +1025,24 @@ class Spec(object):
elif spec_like is not None:
raise TypeError("Can't make spec out of %s" % type(spec_like))
+ @staticmethod
+ def _format_module_list(modules):
+ """Return a module list that is suitable for YAML serialization
+ and hash computation.
+
+ Given a module list, possibly read from a configuration file,
+ return an object that serializes to a consistent YAML string
+ before/after round-trip serialization to/from a Spec dictionary
+ (stored in JSON format): when read in, the module list may
+ contain YAML formatting that is discarded (non-essential)
+ when stored as a Spec dictionary; we take care in this function
+ to discard such formatting such that the Spec hash does not
+ change before/after storage in JSON.
+ """
+ if modules:
+ modules = list(modules)
+ return modules
+
@property
def external(self):
return bool(self.external_path) or bool(self.external_modules)
@@ -1383,8 +1401,8 @@ class Spec(object):
"""
# TODO: curently we strip build dependencies by default. Rethink
# this when we move to using package hashing on all specs.
- yaml_text = syaml.dump(
- self.to_node_dict(hash=hash), default_flow_style=True)
+ node_dict = self.to_node_dict(hash=hash)
+ yaml_text = syaml.dump(node_dict, default_flow_style=True)
sha = hashlib.sha1(yaml_text.encode('utf-8'))
b32_hash = base64.b32encode(sha.digest()).lower()
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index b7aa02e607..5b719b2403 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -2154,3 +2154,38 @@ spack:
# Check that an update does not raise
env('update', '-y', str(abspath.dirname))
+
+
+@pytest.mark.regression('18338')
+def test_newline_in_commented_sequence_is_not_an_issue(tmpdir):
+ spack_yaml = """
+spack:
+ specs:
+ - dyninst
+ packages:
+ libelf:
+ externals:
+ - spec: libelf@0.8.13
+ modules:
+ - libelf/3.18.1
+
+ concretization: together
+"""
+ abspath = tmpdir.join('spack.yaml')
+ abspath.write(spack_yaml)
+
+ def extract_build_hash(environment):
+ _, dyninst = next(iter(environment.specs_by_hash.items()))
+ return dyninst['libelf'].build_hash()
+
+ # Concretize a first time and create a lockfile
+ with ev.Environment(str(tmpdir)) as e:
+ concretize()
+ libelf_first_hash = extract_build_hash(e)
+
+ # Check that a second run won't error
+ with ev.Environment(str(tmpdir)) as e:
+ concretize()
+ libelf_second_hash = extract_build_hash(e)
+
+ assert libelf_first_hash == libelf_second_hash