Skip to content

Commit

Permalink
Check hashes of cached built wheels agains origin source archive
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Mar 25, 2023
1 parent b1b885e commit a1c78e5
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 2 deletions.
1 change: 1 addition & 0 deletions news/5037.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support wheel cache when using --require-hashes.
34 changes: 33 additions & 1 deletion src/pip/_internal/operations/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,41 @@ def _prepare_linked_requirement(
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:
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ def get_wheel_cache_entry(
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,
Expand Down
39 changes: 39 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,45 @@ def test_hashed_install_failure(script: PipTestEnvironment, tmpdir: Path) -> Non
assert len(result.files_created) == 0


@pytest.mark.usefixtures("with_wheel")
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}"

Expand Down

0 comments on commit a1c78e5

Please sign in to comment.