diff --git a/news/5908.feature b/news/5908.feature new file mode 100644 index 00000000000..4e63748593f --- /dev/null +++ b/news/5908.feature @@ -0,0 +1,2 @@ +Log the final filename and SHA256 of a ``.whl`` file when done building a +wheel. diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py index 1bdbe93ab7e..6e4a3ee6f5b 100644 --- a/src/pip/_internal/wheel.py +++ b/src/pip/_internal/wheel.py @@ -68,8 +68,8 @@ def normpath(src, p): return os.path.relpath(src, p).replace(os.path.sep, '/') -def rehash(path, blocksize=1 << 20): - # type: (str, int) -> Tuple[str, str] +def hash_file(path, blocksize=1 << 20): + # type: (str, int) -> Tuple[Any, int] """Return (hash, length) for path using hashlib.sha256()""" h = hashlib.sha256() length = 0 @@ -77,6 +77,13 @@ def rehash(path, blocksize=1 << 20): for block in read_chunks(f, size=blocksize): length += len(block) h.update(block) + return (h, length) # type: ignore + + +def rehash(path, blocksize=1 << 20): + # type: (str, int) -> Tuple[str, str] + """Return (encoded_digest, length) for path using hashlib.sha256()""" + h, length = hash_file(path, blocksize) digest = 'sha256=' + urlsafe_b64encode( h.digest() ).decode('latin1').rstrip('=') @@ -885,7 +892,12 @@ def _build_one_inside_env(self, req, output_dir, python_tag=None): wheel_name = os.path.basename(wheel_path) dest_path = os.path.join(output_dir, wheel_name) try: + wheel_hash, length = hash_file(wheel_path) shutil.move(wheel_path, dest_path) + logger.info('Created wheel for %s: ' + 'filename=%s size=%d sha256=%s', + req.name, wheel_name, length, + wheel_hash.hexdigest()) logger.info('Stored in directory: %s', output_dir) return dest_path except Exception: diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index f67720f165a..ee0b19e47a4 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -1,5 +1,6 @@ """'pip wheel' tests""" import os +import re from os.path import exists import pytest @@ -48,6 +49,12 @@ def test_pip_wheel_success(script, data): ) wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch / wheel_file_name + assert re.search( + r"Created wheel for simple: " + r"filename=%s size=\d+ sha256=[A-Fa-f0-9]{64}" + % re.escape(wheel_file_name), result.stdout) + assert re.search( + r"^\s+Stored in directory: ", result.stdout, re.M) assert wheel_file_path in result.files_created, result.stdout assert "Successfully built simple" in result.stdout, result.stdout diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 19a3259ede4..74ecc4f607d 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -763,3 +763,29 @@ def test_missing_PATH_env_treated_as_empty_PATH_env(self): retval_empty = wheel.message_about_scripts_not_on_PATH(scripts) assert retval_missing == retval_empty + + +class TestWheelHashCalculators(object): + + def prep(self, tmpdir): + self.test_file = tmpdir.join("hash.file") + # Want this big enough to trigger the internal read loops. + self.test_file_len = 2 * 1024 * 1024 + with open(str(self.test_file), "w") as fp: + fp.truncate(self.test_file_len) + self.test_file_hash = \ + '5647f05ec18958947d32874eeb788fa396a05d0bab7c1b71f112ceb7e9b31eee' + self.test_file_hash_encoded = \ + 'sha256=VkfwXsGJWJR9ModO63iPo5agXQurfBtx8RLOt-mzHu4' + + def test_hash_file(self, tmpdir): + self.prep(tmpdir) + h, length = wheel.hash_file(self.test_file) + assert length == self.test_file_len + assert h.hexdigest() == self.test_file_hash + + def test_rehash(self, tmpdir): + self.prep(tmpdir) + h, length = wheel.rehash(self.test_file) + assert length == str(self.test_file_len) + assert h == self.test_file_hash_encoded