From 39b605f6e0a06c52169a45c040e00c64806c579f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 03:42:23 +0000 Subject: [PATCH] Fix PR review comments for cache verification and docs wording Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com> --- cibuildwheel/util/python_build_standalone.py | 16 ++++- docs/_internal/pyodide-maintenance.md | 2 +- unit_test/python_build_standalone_test.py | 63 ++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 unit_test/python_build_standalone_test.py diff --git a/cibuildwheel/util/python_build_standalone.py b/cibuildwheel/util/python_build_standalone.py index 96783bc1..a48bf2f8 100644 --- a/cibuildwheel/util/python_build_standalone.py +++ b/cibuildwheel/util/python_build_standalone.py @@ -2,6 +2,7 @@ from __future__ import annotations import fnmatch import functools +import hashlib import json import platform import typing @@ -120,8 +121,19 @@ def _download_or_get_from_cache( with FileLock(cache_dir / (asset_filename + ".lock")): asset_cache_path = cache_dir / asset_filename if asset_cache_path.is_file(): - print(f"Using cached python_build_standalone: {asset_cache_path}") - return asset_cache_path + if sha256: + computed = hashlib.sha256(asset_cache_path.read_bytes()).hexdigest() + if computed != sha256: + print( + f"Cached python_build_standalone SHA256 mismatch for {asset_cache_path}; redownloading." + ) + asset_cache_path.unlink(missing_ok=True) + else: + print(f"Using cached python_build_standalone: {asset_cache_path}") + return asset_cache_path + else: + print(f"Using cached python_build_standalone: {asset_cache_path}") + return asset_cache_path print(f"Downloading python_build_standalone: {asset_url} to {asset_cache_path}") download(asset_url, asset_cache_path, sha256=sha256 or None) diff --git a/docs/_internal/pyodide-maintenance.md b/docs/_internal/pyodide-maintenance.md index 62daf413..ad790048 100644 --- a/docs/_internal/pyodide-maintenance.md +++ b/docs/_internal/pyodide-maintenance.md @@ -31,7 +31,7 @@ In `cibuildwheel/resources/build-platforms.toml`, add an entry under `[pyodide]` { identifier = "cp315-pyodide_wasm32", version = "3.15", default_pyodide_version = "315.0.0a1", node_version = "v24", sha256 = "SHA256" }, ``` -`version` is the CPython version string, `default_pyodide_version` is the Pyodide release to use when the user does not pin one explicitly (use the latest available alpha/beta for a prerelease entry), `node_version` is the minimum Node.js major required by that Pyodide release, and `sha256` is the checksum of the Pyodide xbuildenv tarball — check the [pyodide-build FAQ](https://pyodide-build.readthedocs.io/en/latest/faq.html#what-node-js-version-do-i-need) for a rudimentary idea of what the correct value is. +`version` is the CPython version string, `default_pyodide_version` is the Pyodide release to use when the user does not pin one explicitly (use the latest available alpha/beta for a prerelease entry), and `node_version` is the minimum Node.js major required by that Pyodide release — check the [pyodide-build FAQ](https://pyodide-build.readthedocs.io/en/latest/faq.html#what-node-js-version-do-i-need) for a rudimentary idea of what the correct value is. `sha256` is the checksum of the Pyodide xbuildenv tarball. ### 2. Update the prerelease guards in the selector diff --git a/unit_test/python_build_standalone_test.py b/unit_test/python_build_standalone_test.py new file mode 100644 index 00000000..a8cac3a1 --- /dev/null +++ b/unit_test/python_build_standalone_test.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import hashlib + +from cibuildwheel.util import python_build_standalone + +TYPE_CHECKING = False +if TYPE_CHECKING: + from pathlib import Path + + import pytest + + +def test_download_or_get_from_cache_uses_valid_cached_file( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + cached_file = tmp_path / "python-build-standalone.tar.gz" + cached_bytes = b"cached archive" + cached_file.write_bytes(cached_bytes) + cached_sha256 = hashlib.sha256(cached_bytes).hexdigest() + + was_downloaded = False + + def fake_download(url: str, dest: Path, *, sha256: str | None = None) -> None: + nonlocal was_downloaded + was_downloaded = True + + monkeypatch.setattr(python_build_standalone, "download", fake_download) + + archive_path = python_build_standalone._download_or_get_from_cache( + asset_url="https://example.com/python-build-standalone.tar.gz", + asset_filename=cached_file.name, + cache_dir=tmp_path, + sha256=cached_sha256, + ) + + assert archive_path == cached_file + assert not was_downloaded + + +def test_download_or_get_from_cache_redownloads_invalid_cached_file( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + cached_file = tmp_path / "python-build-standalone.tar.gz" + cached_file.write_bytes(b"bad cache") + expected_bytes = b"good archive" + expected_sha256 = hashlib.sha256(expected_bytes).hexdigest() + + def fake_download(url: str, dest: Path, *, sha256: str | None = None) -> None: + assert sha256 == expected_sha256 + dest.write_bytes(expected_bytes) + + monkeypatch.setattr(python_build_standalone, "download", fake_download) + + archive_path = python_build_standalone._download_or_get_from_cache( + asset_url="https://example.com/python-build-standalone.tar.gz", + asset_filename=cached_file.name, + cache_dir=tmp_path, + sha256=expected_sha256, + ) + + assert archive_path == cached_file + assert cached_file.read_bytes() == expected_bytes