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
|
rundown on spack and how it differs from homebrew, look at the
|
||||||
README.
|
README.
|
||||||
"""
|
"""
|
||||||
|
import base64
|
||||||
import contextlib
|
import contextlib
|
||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
|
import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
@ -74,6 +76,7 @@
|
||||||
from spack.util.executable import which
|
from spack.util.executable import which
|
||||||
from spack.stage import Stage, ResourceStage, StageComposite
|
from spack.stage import Stage, ResourceStage, StageComposite
|
||||||
from spack.util.environment import dump_environment
|
from spack.util.environment import dump_environment
|
||||||
|
from spack.util.package_hash import package_hash
|
||||||
from spack.version import Version
|
from spack.version import Version
|
||||||
|
|
||||||
"""Allowed URL schemes for spack packages."""
|
"""Allowed URL schemes for spack packages."""
|
||||||
|
@ -1161,6 +1164,28 @@ def patches_to_apply(self):
|
||||||
if self.spec.satisfies(spec))
|
if self.spec.satisfies(spec))
|
||||||
return sorted(patchesToApply, key=lambda p: p.path_or_url)
|
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
|
@property
|
||||||
def namespace(self):
|
def namespace(self):
|
||||||
namespace, dot, module = self.__module__.rpartition('.')
|
namespace, dot, module = self.__module__.rpartition('.')
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
from spack.repository import Repo
|
from spack.repository import Repo
|
||||||
from spack.util.naming import mod_to_class
|
from spack.util.naming import mod_to_class
|
||||||
from spack.spec import Spec
|
from spack.spec import Spec
|
||||||
|
from spack.util.package_hash import package_content
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('config', 'builtin_mock')
|
@pytest.mark.usefixtures('config', 'builtin_mock')
|
||||||
|
@ -67,6 +68,36 @@ def test_package_class_names(self):
|
||||||
assert 'Pmgrcollective' == mod_to_class('PmgrCollective')
|
assert 'Pmgrcollective' == mod_to_class('PmgrCollective')
|
||||||
assert '_3db' == mod_to_class('3db')
|
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
|
# Below tests target direct imports of spack packages from the
|
||||||
# spack.pkg namespace
|
# spack.pkg namespace
|
||||||
def test_import_package(self):
|
def test_import_package(self):
|
||||||
|
|
Loading…
Reference in a new issue