cd38ee1548
* feat: add SHA256 verification for direct downloads
Store SHA256 hashes when running update scripts and verify them
when downloading files at build time. This improves security by
detecting unexpected changes to downloaded artifacts.
Platforms covered: macOS (CPython, PyPy, GraalPy), iOS, Android,
virtualenv, and python-build-standalone. Windows (nuget) and
Linux (Docker) are excluded.
SHA256 sources per platform:
- macOS/iOS/Android CPython (python.org): sha256_sum from API
- GraalPy: .sha256 sidecar assets from GitHub releases
- python-build-standalone: SHA256SUMS file in release
- PyPy, BeeWare iOS, Maven (Chaquopy): stream-download and compute
Changes:
- cibuildwheel/util/file.py: add sha256 param to download()
- cibuildwheel/platforms/{macos,ios,android}.py: add sha256 to
PythonConfiguration and pass to download()
- cibuildwheel/venv.py: read sha256 from toml and pass to download()
- cibuildwheel/util/python_build_standalone.py: add sha256 to
PythonBuildStandaloneAsset and pass to download()
- cibuildwheel/resources/build-platforms.toml: add sha256 fields
- cibuildwheel/resources/virtualenv.toml: add sha256 field
- cibuildwheel/resources/python-build-standalone-releases.json: add sha256
- bin/update_pythons.py: compute/store sha256 per source strategy
- bin/update_virtualenv.py: compute sha256 by streaming download
- bin/update_python_build_standalone.py: parse SHA256SUMS file
Closes #908
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: copilot-cli:claude-sonnet-4.6
* fix: populate sha256 in resource files and fix Windows PythonConfiguration
- Add sha256 field to Windows PythonConfiguration (PyPy/GraalPy have
direct download URLs on Windows too)
- Pass sha256 to install_pypy() and install_graalpy() in windows.py
- Fix update_pythons.py: handle empty sha256 from CPython API (older
versions) by streaming download to compute it; fix condition to
check 'not sha256' rather than 'not in dict'
- Fix update_virtualenv.py: compute sha256 even when version unchanged
but sha256 is empty (first-time population)
- Fix update_python_build_standalone.py: resolve file path relative to
the script itself (not the installed package) so writes go to source
checkout, not the uv cache
- Populate actual sha256 values by running all three update scripts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: copilot-cli:claude-sonnet-4.6
* fix: also include pyodide
Assisted-by: CopilotCLI:gpt-5.3-codex
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
* fix: PR review comments for cache verification and docs wording
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
* fix: require sha256 for download configs
Require sha256 for URL-backed Python and virtualenv download configs. Update the GraalPy updater to refresh macOS x86_64 entries by selecting the latest release that still has a matching asset, and fill the two missing GraalPy checksums in build-platforms.toml.
Assisted-by: CopilotCLI:gpt-5.4
* ci: remove unit test for bin item
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
* refactor: combine sha256 unit tests into test_sha256.py
Merge pyodide_test.py and python_build_standalone_test.py into a
single unit_test/test_sha256.py since both test sha256-related
behaviour.
Assisted-by: opencode:glm-5
---------
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: henryiii <4616906+henryiii@users.noreply.github.com>
99 lines
2.9 KiB
Python
Executable File
99 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env -S uv run --script
|
|
|
|
# /// script
|
|
# dependencies = [
|
|
# "cibuildwheel",
|
|
# "requests",
|
|
# ]
|
|
#
|
|
# [tool.uv.sources]
|
|
# cibuildwheel = { path = ".." }
|
|
# ///
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import requests
|
|
|
|
from cibuildwheel.extra import github_api_request
|
|
from cibuildwheel.util.python_build_standalone import (
|
|
PythonBuildStandaloneAsset,
|
|
PythonBuildStandaloneReleaseData,
|
|
)
|
|
|
|
# Resolve path relative to this script so writes go to the source checkout,
|
|
# not the uv-installed copy of the package.
|
|
DIR: Final[Path] = Path(__file__).parent.parent.resolve()
|
|
PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = (
|
|
DIR / "cibuildwheel/resources/python-build-standalone-releases.json"
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""
|
|
This script updates the vendored list of release assets to the latest
|
|
version of astral-sh/python-build-standalone.
|
|
"""
|
|
|
|
# Get the latest release tag from the GitHub API
|
|
latest_release = github_api_request("repos/astral-sh/python-build-standalone/releases/latest")
|
|
latest_tag = latest_release["tag_name"]
|
|
|
|
# Get the list of assets for the latest release
|
|
github_assets = github_api_request(
|
|
f"repos/astral-sh/python-build-standalone/releases/tags/{latest_tag}"
|
|
)["assets"]
|
|
|
|
# Build a sha256 map from the SHA256SUMS file in the release
|
|
sha256_sums_urls = [
|
|
ga["browser_download_url"] for ga in github_assets if ga["name"] == "SHA256SUMS"
|
|
]
|
|
name_to_sha256: dict[str, str] = {}
|
|
if sha256_sums_urls:
|
|
response = requests.get(sha256_sums_urls[0])
|
|
response.raise_for_status()
|
|
for line in response.text.splitlines():
|
|
parts = line.split()
|
|
if len(parts) == 2:
|
|
sha256_hex, filename = parts
|
|
# The filename may have a leading "./" or spaces - strip it
|
|
name_to_sha256[filename.lstrip("./")] = sha256_hex
|
|
|
|
assets = [
|
|
PythonBuildStandaloneAsset(
|
|
name=ga["name"],
|
|
url=ga["browser_download_url"],
|
|
sha256=name_to_sha256.get(ga["name"], ""),
|
|
)
|
|
for ga in github_assets
|
|
if ga["name"].endswith("install_only.tar.gz")
|
|
]
|
|
|
|
# Try to keep output order stable
|
|
assets = sorted(assets, key=lambda x: x["name"])
|
|
|
|
# Write the assets to the JSON file. One day, we might need to support
|
|
# multiple releases, but for now, we only support the latest one
|
|
json_file_contents = PythonBuildStandaloneReleaseData(
|
|
releases=[
|
|
{
|
|
"tag": latest_tag,
|
|
"assets": assets,
|
|
}
|
|
]
|
|
)
|
|
|
|
with PYTHON_BUILD_STANDALONE_RELEASES.open("w", encoding="utf-8") as f:
|
|
json.dump(json_file_contents, f, indent=2)
|
|
# Add a trailing newline, our pre-commit hook requires it
|
|
f.write("\n")
|
|
|
|
print(
|
|
f"Updated {PYTHON_BUILD_STANDALONE_RELEASES.name} with {len(assets)} assets for tag {latest_tag}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|