summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPeter Scheibel <scheibel1@llnl.gov>2018-02-06 10:53:48 -0500
committerTodd Gamblin <tgamblin@llnl.gov>2018-03-20 00:29:54 -0700
commit3501f2f4b5db871d8f2f63f05b29147b264c0920 (patch)
treebc4a8c464ee154e4ed04822286e6b6646edeb988 /lib
parente97c28e5b3c8b65dc57d6fe737a14b7bc2a5d8ac (diff)
downloadspack-3501f2f4b5db871d8f2f63f05b29147b264c0920.tar.gz
spack-3501f2f4b5db871d8f2f63f05b29147b264c0920.tar.bz2
spack-3501f2f4b5db871d8f2f63f05b29147b264c0920.tar.xz
spack-3501f2f4b5db871d8f2f63f05b29147b264c0920.zip
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.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/package.py25
-rw-r--r--lib/spack/spack/test/packages.py31
2 files changed, 56 insertions, 0 deletions
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 19a565e019..8ea6f4415c 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -33,9 +33,11 @@ Homebrew makes it very easy to create packages. For a complete
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 import directory_layout
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 @@ class PackageBase(with_metaclass(PackageMeta, object)):
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('.')
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index 9533627b24..0fd115dac2 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -29,6 +29,7 @@ from llnl.util.filesystem import join_path
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 @@ class TestPackage(object):
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):