diff options
author | Stéphane Bidoul <stephane.bidoul@gmail.com> | 2023-03-25 14:18:44 +0100 |
---|---|---|
committer | Stéphane Bidoul <stephane.bidoul@gmail.com> | 2023-04-14 08:03:48 +0200 |
commit | 40cd79d6e54d53bccfcad0b855cd87116de896b9 (patch) | |
tree | 585aabdf24e238a91bbc295aea1f977dd00ecabf | |
parent | 8e2205d8495474df088d773b4658aa4a40aefcac (diff) | |
download | pip-40cd79d6e54d53bccfcad0b855cd87116de896b9.tar.gz |
Check hashes of cached built wheels agains origin source archive
-rw-r--r-- | news/5037.feature.rst | 1 | ||||
-rw-r--r-- | src/pip/_internal/operations/prepare.py | 34 | ||||
-rw-r--r-- | src/pip/_internal/resolution/resolvelib/factory.py | 2 | ||||
-rw-r--r-- | tests/functional/test_install.py | 38 |
4 files changed, 73 insertions, 2 deletions
diff --git a/news/5037.feature.rst b/news/5037.feature.rst new file mode 100644 index 000000000..b0a25aaee --- /dev/null +++ b/news/5037.feature.rst @@ -0,0 +1 @@ +Support wheel cache when using --require-hashes. diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index dda92d29b..6fa6cf5b4 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -536,9 +536,41 @@ class RequirementPreparer: assert req.link link = req.link - self._ensure_link_req_src_dir(req, parallel_builds) hashes = self._get_linked_req_hashes(req) + if ( + hashes + and link.is_wheel + and link.is_file + and req.original_link_is_in_wheel_cache + ): + assert req.download_info is not None + # We need to verify hashes, and we have found the requirement in the cache + # of locally built wheels. + if ( + isinstance(req.download_info.info, ArchiveInfo) + and req.download_info.info.hashes + and hashes.has_one_of(req.download_info.info.hashes) + ): + # At this point we know the requirement was built from a hashable source + # artifact, and we verified that the cache entry's hash of the original + # artifact matches one of the hashes we expect. We don't verify hashes + # against the cached wheel, because the wheel is not the original. + hashes = None + else: + logger.warning( + "The hashes of the source archive found in cache entry " + "don't match, ignoring cached built wheel " + "and re-downloading source." + ) + # For some reason req.original_link is not set here, even though + # req.original_link_is_in_wheel_cache is True. So we get the original + # link from download_info. + req.link = Link(req.download_info.url) # TODO comes_from? + link = req.link + + self._ensure_link_req_src_dir(req, parallel_builds) + if link.is_existing_dir(): local_file = None elif link.url not in self._downloaded: diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 0ad4641b1..0331297b8 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -535,7 +535,7 @@ class Factory: hash mismatches. Furthermore, cached wheels at present have nondeterministic contents due to file modification times. """ - if self._wheel_cache is None or self.preparer.require_hashes: + if self._wheel_cache is None: return None return self._wheel_cache.get_cache_entry( link=link, diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index e50779688..bc974d1a8 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -729,6 +729,44 @@ def test_bad_link_hash_in_dep_install_failure( assert "THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr, result.stderr +def test_hashed_install_from_cache( + script: PipTestEnvironment, data: TestData, tmpdir: Path +) -> None: + """ + Test that installing from a cached built wheel works and that the hash is verified + against the hash of the original source archived stored in the cache entry. + """ + with requirements_file( + "simple2==1.0 --hash=sha256:" + "9336af72ca661e6336eb87bc7de3e8844d853e3848c2b9bbd2e8bf01db88c2c7\n", + tmpdir, + ) as reqs_file: + result = script.pip_install_local( + "--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve() + ) + assert "Created wheel for simple2" in result.stdout + script.pip("uninstall", "simple2", "-y") + result = script.pip_install_local( + "--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve() + ) + assert "Using cached simple2" in result.stdout + # now try with an invalid hash + with requirements_file( + "simple2==1.0 --hash=sha256:invalid\n", + tmpdir, + ) as reqs_file: + script.pip("uninstall", "simple2", "-y") + result = script.pip_install_local( + "--use-pep517", + "--no-build-isolation", + "-r", + reqs_file.resolve(), + expect_error=True, + ) + assert "Using cached simple2" in result.stdout + assert "ERROR: THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr + + def assert_re_match(pattern: str, text: str) -> None: assert re.search(pattern, text), f"Could not find {pattern!r} in {text!r}" |