Skip to content

Commit 40cd79d

Browse files
committed
Check hashes of cached built wheels agains origin source archive
1 parent 8e2205d commit 40cd79d

File tree

4 files changed

+73
-2
lines changed

4 files changed

+73
-2
lines changed

news/5037.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support wheel cache when using --require-hashes.

src/pip/_internal/operations/prepare.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,41 @@ def _prepare_linked_requirement(
536536
assert req.link
537537
link = req.link
538538

539-
self._ensure_link_req_src_dir(req, parallel_builds)
540539
hashes = self._get_linked_req_hashes(req)
541540

541+
if (
542+
hashes
543+
and link.is_wheel
544+
and link.is_file
545+
and req.original_link_is_in_wheel_cache
546+
):
547+
assert req.download_info is not None
548+
# We need to verify hashes, and we have found the requirement in the cache
549+
# of locally built wheels.
550+
if (
551+
isinstance(req.download_info.info, ArchiveInfo)
552+
and req.download_info.info.hashes
553+
and hashes.has_one_of(req.download_info.info.hashes)
554+
):
555+
# At this point we know the requirement was built from a hashable source
556+
# artifact, and we verified that the cache entry's hash of the original
557+
# artifact matches one of the hashes we expect. We don't verify hashes
558+
# against the cached wheel, because the wheel is not the original.
559+
hashes = None
560+
else:
561+
logger.warning(
562+
"The hashes of the source archive found in cache entry "
563+
"don't match, ignoring cached built wheel "
564+
"and re-downloading source."
565+
)
566+
# For some reason req.original_link is not set here, even though
567+
# req.original_link_is_in_wheel_cache is True. So we get the original
568+
# link from download_info.
569+
req.link = Link(req.download_info.url) # TODO comes_from?
570+
link = req.link
571+
572+
self._ensure_link_req_src_dir(req, parallel_builds)
573+
542574
if link.is_existing_dir():
543575
local_file = None
544576
elif link.url not in self._downloaded:

src/pip/_internal/resolution/resolvelib/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ def get_wheel_cache_entry(
535535
hash mismatches. Furthermore, cached wheels at present have
536536
nondeterministic contents due to file modification times.
537537
"""
538-
if self._wheel_cache is None or self.preparer.require_hashes:
538+
if self._wheel_cache is None:
539539
return None
540540
return self._wheel_cache.get_cache_entry(
541541
link=link,

tests/functional/test_install.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,44 @@ def test_bad_link_hash_in_dep_install_failure(
729729
assert "THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr, result.stderr
730730

731731

732+
def test_hashed_install_from_cache(
733+
script: PipTestEnvironment, data: TestData, tmpdir: Path
734+
) -> None:
735+
"""
736+
Test that installing from a cached built wheel works and that the hash is verified
737+
against the hash of the original source archived stored in the cache entry.
738+
"""
739+
with requirements_file(
740+
"simple2==1.0 --hash=sha256:"
741+
"9336af72ca661e6336eb87bc7de3e8844d853e3848c2b9bbd2e8bf01db88c2c7\n",
742+
tmpdir,
743+
) as reqs_file:
744+
result = script.pip_install_local(
745+
"--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve()
746+
)
747+
assert "Created wheel for simple2" in result.stdout
748+
script.pip("uninstall", "simple2", "-y")
749+
result = script.pip_install_local(
750+
"--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve()
751+
)
752+
assert "Using cached simple2" in result.stdout
753+
# now try with an invalid hash
754+
with requirements_file(
755+
"simple2==1.0 --hash=sha256:invalid\n",
756+
tmpdir,
757+
) as reqs_file:
758+
script.pip("uninstall", "simple2", "-y")
759+
result = script.pip_install_local(
760+
"--use-pep517",
761+
"--no-build-isolation",
762+
"-r",
763+
reqs_file.resolve(),
764+
expect_error=True,
765+
)
766+
assert "Using cached simple2" in result.stdout
767+
assert "ERROR: THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr
768+
769+
732770
def assert_re_match(pattern: str, text: str) -> None:
733771
assert re.search(pattern, text), f"Could not find {pattern!r} in {text!r}"
734772

0 commit comments

Comments
 (0)