diff options
author | Massimiliano Culpo <massimiliano.culpo@gmail.com> | 2020-09-03 19:49:36 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-03 10:49:36 -0700 |
commit | fab2622a71e5729fdd9fa90d5702250696bc9407 (patch) | |
tree | 94255b1f1ad5e841fc515fdaa849df03b76f2203 /lib | |
parent | 741bb9bafe078d0dd39387e8ea63dca9386bb5a9 (diff) | |
download | spack-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.py | 24 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/env.py | 35 |
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 |