package: add a content_hash method for packages
This calculates a hash which depends on the complete content of the package including sources and the associated `package.py` file.
This commit is contained in:
parent
e97c28e5b3
commit
3501f2f4b5
2 changed files with 56 additions and 0 deletions
|
@ -33,9 +33,11 @@
|
|||
rundown on spack and how it differs from homebrew, look at the
|
||||
README.
|
||||
"""
|
||||
import base64
|
||||
import contextlib
|
||||
import copy
|
||||
import functools
|
||||
import hashlib
|
||||
import inspect
|
||||
import itertools
|
||||
import os
|
||||
|
@ -74,6 +76,7 @@
|
|||
from spack.util.executable import which
|
||||
from spack.stage import Stage, ResourceStage, StageComposite
|
||||
from spack.util.environment import dump_environment
|
||||
from spack.util.package_hash import package_hash
|
||||
from spack.version import Version
|
||||
|
||||
"""Allowed URL schemes for spack packages."""
|
||||
|
@ -1161,6 +1164,28 @@ def patches_to_apply(self):
|
|||
if self.spec.satisfies(spec))
|
||||
return sorted(patchesToApply, key=lambda p: p.path_or_url)
|
||||
|
||||
def content_hash(self, content=None):
|
||||
"""Create a hash based on the sources and logic used to build the
|
||||
package. This includes the contents of all applied patches and the
|
||||
contents of applicable functions in the package subclass."""
|
||||
hashContent = list()
|
||||
source_id = fs.for_package_version(self, self.version).source_id()
|
||||
if not source_id:
|
||||
# TODO? in cases where a digest or source_id isn't available,
|
||||
# should this attempt to download the source and set one? This
|
||||
# probably only happens for source repositories which are
|
||||
# referenced by branch name rather than tag or commit ID.
|
||||
message = 'Missing a source id for {s.name}@{s.version}'
|
||||
tty.warn(message.format(s=self))
|
||||
hashContent.append(''.encode('utf-8'))
|
||||
else:
|
||||
hashContent.append(source_id.encode('utf-8'))
|
||||
hashContent.extend(':'.join((p.sha256, str(p.level))).encode('utf-8')
|
||||
for p in self.patches_to_apply())
|
||||
hashContent.append(package_hash(self.spec, content))
|
||||
return base64.b32encode(
|
||||
hashlib.sha256(bytes().join(sorted(hashContent))).digest()).lower()
|
||||
|
||||
@property
|
||||
def namespace(self):
|
||||
namespace, dot, module = self.__module__.rpartition('.')
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
from spack.repository import Repo
|
||||
from spack.util.naming import mod_to_class
|
||||
from spack.spec import Spec
|
||||
from spack.util.package_hash import package_content
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('config', 'builtin_mock')
|
||||
|
@ -67,6 +68,36 @@ def test_package_class_names(self):
|
|||
assert 'Pmgrcollective' == mod_to_class('PmgrCollective')
|
||||
assert '_3db' == mod_to_class('3db')
|
||||
|
||||
def test_content_hash_all_same_but_patch_contents(self):
|
||||
spec1 = Spec("hash-test1@1.1")
|
||||
spec2 = Spec("hash-test2@1.1")
|
||||
content1 = package_content(spec1)
|
||||
content1 = content1.replace(spec1.package.__class__.__name__, '')
|
||||
content2 = package_content(spec2)
|
||||
content2 = content2.replace(spec2.package.__class__.__name__, '')
|
||||
assert spec1.package.content_hash(content=content1) != \
|
||||
spec2.package.content_hash(content=content2)
|
||||
|
||||
def test_content_hash_different_variants(self):
|
||||
spec1 = Spec("hash-test1@1.2 +variantx")
|
||||
spec2 = Spec("hash-test2@1.2 ~variantx")
|
||||
content1 = package_content(spec1)
|
||||
content1 = content1.replace(spec1.package.__class__.__name__, '')
|
||||
content2 = package_content(spec2)
|
||||
content2 = content2.replace(spec2.package.__class__.__name__, '')
|
||||
assert spec1.package.content_hash(content=content1) == \
|
||||
spec2.package.content_hash(content=content2)
|
||||
|
||||
def test_all_same_but_archive_hash(self):
|
||||
spec1 = Spec("hash-test1@1.3")
|
||||
spec2 = Spec("hash-test2@1.3")
|
||||
content1 = package_content(spec1)
|
||||
content1 = content1.replace(spec1.package.__class__.__name__, '')
|
||||
content2 = package_content(spec2)
|
||||
content2 = content2.replace(spec2.package.__class__.__name__, '')
|
||||
assert spec1.package.content_hash(content=content1) != \
|
||||
spec2.package.content_hash(content=content2)
|
||||
|
||||
# Below tests target direct imports of spack packages from the
|
||||
# spack.pkg namespace
|
||||
def test_import_package(self):
|
||||
|
|
Loading…
Reference in a new issue