Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
892871796a | ||
|
|
39b605f6e0 | ||
|
|
3f6f9b3b1f | ||
|
|
f925e8ccef | ||
|
|
b597cb5aff | ||
|
|
97537fe937 |
@@ -3,6 +3,7 @@
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "cibuildwheel",
|
||||
# "requests",
|
||||
# ]
|
||||
#
|
||||
# [tool.uv.sources]
|
||||
@@ -10,13 +11,23 @@
|
||||
# ///
|
||||
|
||||
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,
|
||||
)
|
||||
from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES
|
||||
|
||||
# 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:
|
||||
@@ -34,8 +45,27 @@ def main() -> None:
|
||||
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"])
|
||||
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")
|
||||
]
|
||||
|
||||
+49
-4
@@ -15,12 +15,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import difflib
|
||||
import hashlib
|
||||
import logging
|
||||
import operator
|
||||
import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Any, Final, Literal, TypedDict
|
||||
from typing import Any, Final, Literal, NotRequired, TypedDict
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import click
|
||||
@@ -56,11 +57,13 @@ class Config(TypedDict):
|
||||
|
||||
class ConfigUrl(Config):
|
||||
url: str
|
||||
sha256: NotRequired[str]
|
||||
|
||||
|
||||
class ConfigPyodide(Config):
|
||||
default_pyodide_version: str
|
||||
node_version: str
|
||||
sha256: str
|
||||
|
||||
|
||||
# The following set of "Versions" classes allow the initial call to the APIs to
|
||||
@@ -179,10 +182,23 @@ class GraalPyVersions:
|
||||
]
|
||||
if urls:
|
||||
(url,) = urls
|
||||
# Fetch sha256 from the ".sha256" sidecar asset in the same release
|
||||
sha256 = ""
|
||||
sha256_asset_name = url.rsplit("/", 1)[-1] + ".sha256"
|
||||
sha256_urls = [
|
||||
rf["browser_download_url"]
|
||||
for rf in release["assets"]
|
||||
if rf["name"] == sha256_asset_name
|
||||
]
|
||||
if sha256_urls:
|
||||
sha256_response = requests.get(sha256_urls[0])
|
||||
sha256_response.raise_for_status()
|
||||
sha256 = sha256_response.text.strip().split()[0]
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{version.major}.{version.minor}",
|
||||
url=url,
|
||||
sha256=sha256,
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -298,12 +314,14 @@ class CPythonVersions:
|
||||
uri = self.versions_dict[new_version]
|
||||
files = [rf for rf in self.files_info if rf["release"] == uri]
|
||||
|
||||
urls = [rf["url"] for rf in files if file_ident in rf["url"]]
|
||||
if urls:
|
||||
matching = [rf for rf in files if file_ident in rf["url"]]
|
||||
if matching:
|
||||
rf = matching[0]
|
||||
return ConfigUrl(
|
||||
identifier=identifier,
|
||||
version=f"{new_version.major}.{new_version.minor}",
|
||||
url=urls[0],
|
||||
url=rf["url"],
|
||||
sha256=rf.get("sha256_sum", ""),
|
||||
)
|
||||
|
||||
return None
|
||||
@@ -420,6 +438,7 @@ class PyodideVersions:
|
||||
version=str(version),
|
||||
default_pyodide_version=release["version"],
|
||||
node_version=node_version,
|
||||
sha256=release["sha256"],
|
||||
)
|
||||
|
||||
|
||||
@@ -448,6 +467,16 @@ class AllVersions:
|
||||
|
||||
self.pyodide = PyodideVersions()
|
||||
|
||||
def _stream_sha256(self, url: str) -> str:
|
||||
"""Download a file (streaming) and return its SHA256 hex digest."""
|
||||
log.debug("Computing sha256 for %s by streaming download...", url)
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
hasher = hashlib.sha256()
|
||||
for chunk in response.iter_content(65536):
|
||||
hasher.update(chunk)
|
||||
return hasher.hexdigest()
|
||||
|
||||
def update_config(self, config: MutableMapping[str, str]) -> None:
|
||||
identifier = config["identifier"]
|
||||
version = Version(config["version"])
|
||||
@@ -505,6 +534,22 @@ class AllVersions:
|
||||
)
|
||||
|
||||
assert config_update is not None, f"{identifier} not found!"
|
||||
|
||||
# Fill in sha256 for URL-based configs if not already provided by the
|
||||
# update_version_* method (e.g. PyPy, BeeWare iOS, Maven have no sidecar).
|
||||
# Also fills in sha256 when the CPython API doesn't return a sha256_sum
|
||||
# (e.g. for older releases).
|
||||
# Widen the type to allow arbitrary key access on the underlying dict.
|
||||
config_update_dict: dict[str, str] = config_update # type: ignore[assignment]
|
||||
if "url" in config_update_dict and not config_update_dict.get("sha256"):
|
||||
url = config_update_dict["url"]
|
||||
existing_sha256 = config.get("sha256", "")
|
||||
if url == config.get("url") and existing_sha256:
|
||||
# URL unchanged — preserve the existing sha256
|
||||
config_update_dict["sha256"] = existing_sha256
|
||||
else:
|
||||
config_update_dict["sha256"] = self._stream_sha256(url)
|
||||
|
||||
if config_update != config:
|
||||
log.info(" Updated %s to %s", config, config_update)
|
||||
config.clear()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# dependencies = [
|
||||
# "click",
|
||||
# "packaging",
|
||||
# "requests",
|
||||
# "rich",
|
||||
# "cibuildwheel",
|
||||
# ]
|
||||
@@ -15,12 +16,14 @@
|
||||
|
||||
import dataclasses
|
||||
import difflib
|
||||
import hashlib
|
||||
import logging
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import click
|
||||
import requests
|
||||
import rich
|
||||
from packaging.version import Version
|
||||
from rich.logging import RichHandler
|
||||
@@ -92,16 +95,29 @@ def update_virtualenv(force: bool, level: str) -> None:
|
||||
if latest_release.version > Version(local_version):
|
||||
version = latest_release.name
|
||||
url = latest_release.download_url
|
||||
sha256 = "" # recomputed below
|
||||
else:
|
||||
version = local_version
|
||||
url = default["url"]
|
||||
sha256 = default.get("sha256", "")
|
||||
|
||||
# Compute sha256 if not already stored (new version or first-time population)
|
||||
if not sha256:
|
||||
log.info("Computing sha256 for %s...", url)
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
hasher = hashlib.sha256()
|
||||
for chunk in response.iter_content(65536):
|
||||
hasher.update(chunk)
|
||||
sha256 = hasher.hexdigest()
|
||||
|
||||
configurations["default"] = {
|
||||
"version": version,
|
||||
"url": url,
|
||||
"sha256": sha256,
|
||||
}
|
||||
result_toml = "".join(
|
||||
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}" }}\n'
|
||||
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}", sha256 = "{value.get("sha256", "")}" }}\n'
|
||||
for key, value in configurations.items()
|
||||
)
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ def github_api_request(path: str, *, max_retries: int = 3) -> dict[str, Any]:
|
||||
|
||||
class PyodideXBuildEnvRelease(typing.TypedDict):
|
||||
version: str
|
||||
sha256: str
|
||||
python_version: str
|
||||
emscripten_version: str
|
||||
min_pyodide_build_version: NotRequired[str]
|
||||
|
||||
@@ -72,6 +72,7 @@ class PythonConfiguration:
|
||||
version: str
|
||||
identifier: str
|
||||
url: str
|
||||
sha256: str = ""
|
||||
|
||||
@property
|
||||
def arch(self) -> str:
|
||||
@@ -184,7 +185,7 @@ def setup_target_python(config: PythonConfiguration, build_path: Path) -> Path:
|
||||
python_tgz = CIBW_CACHE_PATH / config.url.rpartition("/")[-1]
|
||||
with FileLock(f"{python_tgz}.lock"):
|
||||
if not python_tgz.exists():
|
||||
download(config.url, python_tgz)
|
||||
download(config.url, python_tgz, sha256=config.sha256 or None)
|
||||
|
||||
python_dir = build_path / "python"
|
||||
python_dir.mkdir()
|
||||
|
||||
@@ -46,6 +46,7 @@ class PythonConfiguration:
|
||||
identifier: str
|
||||
url: str
|
||||
build_url: str
|
||||
sha256: str = ""
|
||||
|
||||
@property
|
||||
def sdk(self) -> str:
|
||||
@@ -133,7 +134,7 @@ def install_target_cpython(tmp: Path, config: PythonConfiguration, free_threadin
|
||||
with FileLock(str(installation_path) + ".lock"):
|
||||
if not installation_path.exists():
|
||||
downloaded_tar_gz = tmp / ios_python_tar_gz
|
||||
download(config.url, downloaded_tar_gz)
|
||||
download(config.url, downloaded_tar_gz, sha256=config.sha256 or None)
|
||||
installation_path.mkdir(parents=True, exist_ok=True)
|
||||
call("tar", "-C", installation_path, "-xf", downloaded_tar_gz)
|
||||
downloaded_tar_gz.unlink()
|
||||
|
||||
@@ -88,6 +88,7 @@ class PythonConfiguration:
|
||||
version: str
|
||||
identifier: str
|
||||
url: str
|
||||
sha256: str = ""
|
||||
|
||||
|
||||
def all_python_configurations() -> list[PythonConfiguration]:
|
||||
@@ -135,7 +136,9 @@ def get_python_configurations(
|
||||
return python_configurations
|
||||
|
||||
|
||||
def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) -> Path:
|
||||
def install_cpython(
|
||||
_tmp: Path, version: str, url: str, free_threading: bool, sha256: str = ""
|
||||
) -> Path:
|
||||
ft = "T" if free_threading else ""
|
||||
installation_path = Path(f"/Library/Frameworks/Python{ft}.framework/Versions/{version}")
|
||||
with FileLock(CIBW_CACHE_PATH / f"cpython{version}.lock"):
|
||||
@@ -161,7 +164,7 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
|
||||
python_filename = url.rsplit("/", maxsplit=1)[-1]
|
||||
pkg_path = CIBW_CACHE_PATH / "cpython-installer" / python_filename
|
||||
if not pkg_path.exists():
|
||||
download(url, pkg_path)
|
||||
download(url, pkg_path, sha256=sha256 or None)
|
||||
args = []
|
||||
if version.startswith("3.14"):
|
||||
args += ["-applyChoiceChangesXML", str(resources.FREE_THREAD_ENABLE_314.resolve())]
|
||||
@@ -185,7 +188,7 @@ def install_cpython(_tmp: Path, version: str, url: str, free_threading: bool) ->
|
||||
return installation_path / "bin" / (f"python{version}t" if free_threading else "python3")
|
||||
|
||||
|
||||
def install_pypy(tmp: Path, url: str) -> Path:
|
||||
def install_pypy(tmp: Path, url: str, sha256: str = "") -> Path:
|
||||
pypy_tar_bz2 = url.rsplit("/", 1)[-1]
|
||||
extension = ".tar.bz2"
|
||||
assert pypy_tar_bz2.endswith(extension)
|
||||
@@ -193,14 +196,14 @@ def install_pypy(tmp: Path, url: str) -> Path:
|
||||
with FileLock(str(installation_path) + ".lock"):
|
||||
if not installation_path.exists():
|
||||
downloaded_tar_bz2 = tmp / pypy_tar_bz2
|
||||
download(url, downloaded_tar_bz2)
|
||||
download(url, downloaded_tar_bz2, sha256=sha256 or None)
|
||||
installation_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
call("tar", "-C", installation_path.parent, "-xf", downloaded_tar_bz2)
|
||||
downloaded_tar_bz2.unlink()
|
||||
return installation_path / "bin" / "pypy3"
|
||||
|
||||
|
||||
def install_graalpy(tmp: Path, url: str) -> Path:
|
||||
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
|
||||
graalpy_archive = url.rsplit("/", 1)[-1]
|
||||
extension = ".tar.gz"
|
||||
assert graalpy_archive.endswith(extension)
|
||||
@@ -208,7 +211,7 @@ def install_graalpy(tmp: Path, url: str) -> Path:
|
||||
with FileLock(str(installation_path) + ".lock"):
|
||||
if not installation_path.exists():
|
||||
downloaded_archive = tmp / graalpy_archive
|
||||
download(url, downloaded_archive)
|
||||
download(url, downloaded_archive, sha256=sha256 or None)
|
||||
installation_path.mkdir(parents=True)
|
||||
# GraalPy top-folder name is inconsistent with archive name
|
||||
call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive)
|
||||
@@ -232,13 +235,17 @@ def setup_python(
|
||||
if implementation_id.startswith("cp"):
|
||||
free_threading = "t-macos" in python_configuration.identifier
|
||||
base_python = install_cpython(
|
||||
tmp, python_configuration.version, python_configuration.url, free_threading
|
||||
tmp,
|
||||
python_configuration.version,
|
||||
python_configuration.url,
|
||||
free_threading,
|
||||
python_configuration.sha256,
|
||||
)
|
||||
|
||||
elif implementation_id.startswith("pp"):
|
||||
base_python = install_pypy(tmp, python_configuration.url)
|
||||
base_python = install_pypy(tmp, python_configuration.url, python_configuration.sha256)
|
||||
elif implementation_id.startswith("gp"):
|
||||
base_python = install_graalpy(tmp, python_configuration.url)
|
||||
base_python = install_graalpy(tmp, python_configuration.url, python_configuration.sha256)
|
||||
else:
|
||||
msg = "Unknown Python implementation"
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -55,6 +55,7 @@ class PythonConfiguration:
|
||||
identifier: str
|
||||
default_pyodide_version: str
|
||||
node_version: str
|
||||
sha256: str = ""
|
||||
|
||||
|
||||
class PyodideXBuildEnvInfoVersionRange(TypedDict):
|
||||
|
||||
@@ -73,6 +73,7 @@ class PythonConfiguration:
|
||||
version: str
|
||||
identifier: str
|
||||
url: str | None = None
|
||||
sha256: str = ""
|
||||
|
||||
@property
|
||||
def arch(self) -> str:
|
||||
@@ -131,7 +132,7 @@ def install_cpython(configuration: PythonConfiguration, arch: str | None = None)
|
||||
return installation_path / "python.exe"
|
||||
|
||||
|
||||
def install_pypy(tmp: Path, arch: str, url: str) -> Path:
|
||||
def install_pypy(tmp: Path, arch: str, url: str, sha256: str = "") -> Path:
|
||||
assert arch == "64"
|
||||
assert "win64" in url
|
||||
# Inside the PyPy zip file is a directory with the same name
|
||||
@@ -142,13 +143,13 @@ def install_pypy(tmp: Path, arch: str, url: str) -> Path:
|
||||
with FileLock(str(installation_path) + ".lock"):
|
||||
if not installation_path.exists():
|
||||
pypy_zip = tmp / zip_filename
|
||||
download(url, pypy_zip)
|
||||
download(url, pypy_zip, sha256=sha256 or None)
|
||||
# Extract to the parent directory because the zip file still contains a directory
|
||||
extract_zip(pypy_zip, installation_path.parent)
|
||||
return installation_path / "python.exe"
|
||||
|
||||
|
||||
def install_graalpy(tmp: Path, url: str) -> Path:
|
||||
def install_graalpy(tmp: Path, url: str, sha256: str = "") -> Path:
|
||||
zip_filename = url.rsplit("/", 1)[-1]
|
||||
extension = ".zip"
|
||||
assert zip_filename.endswith(extension)
|
||||
@@ -156,7 +157,7 @@ def install_graalpy(tmp: Path, url: str) -> Path:
|
||||
with FileLock(str(installation_path) + ".lock"):
|
||||
if not installation_path.exists():
|
||||
graalpy_zip = tmp / zip_filename
|
||||
download(url, graalpy_zip)
|
||||
download(url, graalpy_zip, sha256=sha256 or None)
|
||||
# Extract to the parent directory because the zip file still contains a directory
|
||||
extract_zip(graalpy_zip, installation_path.parent)
|
||||
return installation_path / "bin" / "graalpy.exe"
|
||||
@@ -272,9 +273,13 @@ def setup_python(
|
||||
base_python = install_cpython(python_configuration, arch=native_arch)
|
||||
elif implementation_id.startswith("pp"):
|
||||
assert python_configuration.url is not None
|
||||
base_python = install_pypy(tmp, python_configuration.arch, python_configuration.url)
|
||||
base_python = install_pypy(
|
||||
tmp, python_configuration.arch, python_configuration.url, python_configuration.sha256
|
||||
)
|
||||
elif implementation_id.startswith("gp"):
|
||||
base_python = install_graalpy(tmp, python_configuration.url or "")
|
||||
base_python = install_graalpy(
|
||||
tmp, python_configuration.url or "", python_configuration.sha256
|
||||
)
|
||||
else:
|
||||
msg = "Unknown Python implementation"
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -143,43 +143,43 @@ python_configurations = [
|
||||
|
||||
[macos]
|
||||
python_configurations = [
|
||||
{ identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
|
||||
{ identifier = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
|
||||
{ identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg" },
|
||||
{ identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
|
||||
{ identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
|
||||
{ identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" },
|
||||
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
|
||||
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
|
||||
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg" },
|
||||
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
|
||||
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
|
||||
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg" },
|
||||
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
|
||||
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
|
||||
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg" },
|
||||
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg" },
|
||||
{ identifier = "cp315-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg" },
|
||||
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2" },
|
||||
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
|
||||
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2" },
|
||||
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2" },
|
||||
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_x86_64.tar.bz2" },
|
||||
{ identifier = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_arm64.tar.bz2" },
|
||||
{ identifier = "gp311_242-macosx_x86_64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-amd64.tar.gz" },
|
||||
{ identifier = "gp311_242-macosx_arm64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-aarch64.tar.gz" },
|
||||
{ identifier = "gp312_250-macosx_x86_64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.1/graalpy-25.0.1-macos-amd64.tar.gz" },
|
||||
{ identifier = "gp312_250-macosx_arm64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-macos-aarch64.tar.gz" },
|
||||
{ identifier = "cp39-macosx_x86_64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
|
||||
{ identifier = "cp39-macosx_arm64", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
|
||||
{ identifier = "cp39-macosx_universal2", version = "3.9", url = "https://www.python.org/ftp/python/3.9.13/python-3.9.13-macos11.pkg", sha256 = "351fe18f4fb03be7afac5e4012fc0a51345f43202af43ef620cf1eee5ee36578" },
|
||||
{ identifier = "cp310-macosx_x86_64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
|
||||
{ identifier = "cp310-macosx_arm64", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
|
||||
{ identifier = "cp310-macosx_universal2", version = "3.10", url = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg", sha256 = "767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" },
|
||||
{ identifier = "cp311-macosx_x86_64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
|
||||
{ identifier = "cp311-macosx_arm64", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
|
||||
{ identifier = "cp311-macosx_universal2", version = "3.11", url = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg", sha256 = "b6cfdee2571ca56ee895043ca1e7110fb78a878cee3eb0c21accb2de34d24b55" },
|
||||
{ identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
|
||||
{ identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
|
||||
{ identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg", sha256 = "8373e58da4ea146b3eb1c1f9834f19a319440b6b679b06050b1f9ee3237aa8e4" },
|
||||
{ identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
|
||||
{ identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
|
||||
{ identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.13/python-3.13.13-macos11.pkg", sha256 = "a909cb655af5db67d5a90b3603437a1d58bec3446d624e4034e278ac62023cc9" },
|
||||
{ identifier = "cp314-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp314-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp314-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp314t-macosx_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp314t-macosx_arm64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp314t-macosx_universal2", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-macos11.pkg", sha256 = "b28a8dc33c456dd06c97024697d63ca916cfb494594c06fa3e4ef4d41fa82335" },
|
||||
{ identifier = "cp315-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "cp315-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "cp315-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "cp315t-macosx_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "cp315t-macosx_arm64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "cp315t-macosx_universal2", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-macos11.pkg", sha256 = "d9e6de70d8bb02c9300b9d8e8b56aadb8dd426073d97e6225ac5319b3eb6d84e" },
|
||||
{ identifier = "pp39-macosx_x86_64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2", sha256 = "fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c" },
|
||||
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2", sha256 = "88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe" },
|
||||
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_x86_64.tar.bz2", sha256 = "2c5e5c2a33ac882551d7f28b98d19d486b8995aa50824a73b4edcc6aeec35c63" },
|
||||
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-macos_arm64.tar.bz2", sha256 = "3db8a03fc496164801646844d7f3b12baa0adb3ad9a0b7cb124521bc2e168a36" },
|
||||
{ identifier = "pp311-macosx_x86_64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_x86_64.tar.bz2", sha256 = "c95363c4e87235d11a6cec8128239c291b1eb67a752778fbcfe029a71da82b5e" },
|
||||
{ identifier = "pp311-macosx_arm64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-macos_arm64.tar.bz2", sha256 = "4747b3aceba4c1c6104cddc0fe5ea302101d32955f0957347b9ecc4fbd7aed05" },
|
||||
{ identifier = "gp311_242-macosx_x86_64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-amd64.tar.gz", sha256 = "" },
|
||||
{ identifier = "gp311_242-macosx_arm64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-aarch64.tar.gz", sha256 = "f4a2ae01bae0fa53ec0d19f86d73c6dcc2a162d245552030183b84bfdd8f7635" },
|
||||
{ identifier = "gp312_250-macosx_x86_64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.1/graalpy-25.0.1-macos-amd64.tar.gz", sha256 = "" },
|
||||
{ identifier = "gp312_250-macosx_arm64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-macos-aarch64.tar.gz", sha256 = "387d9f5b376860842bea4d55aae5820974f3f3b68fc36c77ae863a061a888857" },
|
||||
]
|
||||
|
||||
[windows]
|
||||
@@ -211,39 +211,39 @@ python_configurations = [
|
||||
{ identifier = "cp314t-win_arm64", version = "3.14.5" },
|
||||
{ identifier = "cp315-win_arm64", version = "3.15.0-b1" },
|
||||
{ identifier = "cp315t-win_arm64", version = "3.15.0-b1" },
|
||||
{ identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
|
||||
{ identifier = "pp310-win_amd64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip" },
|
||||
{ identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-win64.zip" },
|
||||
{ identifier = "gp311_242-win_amd64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip" },
|
||||
{ identifier = "gp312_250-win_amd64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-windows-amd64.zip" },
|
||||
{ identifier = "pp39-win_amd64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip", sha256 = "06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323" },
|
||||
{ identifier = "pp310-win_amd64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.19-win64.zip", sha256 = "c0d07bba6c8fb4e5804f4a8b3f8ef07cc3d89f6ad1db42a45ffb9be60bbb7cc2" },
|
||||
{ identifier = "pp311-win_amd64", version = "3.11", url = "https://downloads.python.org/pypy/pypy3.11-v7.3.23-win64.zip", sha256 = "948b8ea58dea5b9917210fe4afd242c788fbfaba1c3f1a25e696a404f703389a" },
|
||||
{ identifier = "gp311_242-win_amd64", version = "3.11", url = "https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-windows-amd64.zip", sha256 = "9606134284d4d95b2f9d69c3087cd3e9e488f46355b419f5e66588a3281df6a3" },
|
||||
{ identifier = "gp312_250-win_amd64", version = "3.12", url = "https://github.com/oracle/graalpython/releases/download/graal-25.0.3/graalpy-25.0.3-windows-amd64.zip", sha256 = "2ae5c42b93e08d9e106a017634a3442b272ccae6d08ace157244de0449e937d4" },
|
||||
]
|
||||
|
||||
[pyodide]
|
||||
python_configurations = [
|
||||
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22" },
|
||||
{ identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.4", node_version = "v22" },
|
||||
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24" },
|
||||
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.7", node_version = "v22", sha256 = "0c2e7db42efa5d1dac38b50f8b3d659a1e3885d0a233494831b8206281307d52" },
|
||||
{ identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.29.4", node_version = "v22", sha256 = "a29fc4a076408a18fc29eb4b280f80c6dddc95c19514c874c29e590d0931c02a" },
|
||||
{ identifier = "cp314-pyodide_wasm32", version = "3.14", default_pyodide_version = "314.0.0a2", node_version = "v24", sha256 = "ac7bbcdf289ab3ae25621c436b1d013099ef6bec644ad0538dae5d65552c5c95" },
|
||||
]
|
||||
|
||||
[android]
|
||||
python_configurations = [
|
||||
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-aarch64-linux-android.tar.gz" },
|
||||
{ identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-x86_64-linux-android.tar.gz" },
|
||||
{ identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-aarch64-linux-android.tar.gz" },
|
||||
{ identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-x86_64-linux-android.tar.gz" },
|
||||
{ identifier = "cp315-android_arm64_v8a", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-aarch64-linux-android.tar.gz" },
|
||||
{ identifier = "cp315-android_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-x86_64-linux-android.tar.gz" },
|
||||
{ identifier = "cp313-android_arm64_v8a", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-aarch64-linux-android.tar.gz", sha256 = "a21250c246b36eb704be096be51e40ab24cafa3ae1d7ca4c396e6bf780cf87cf" },
|
||||
{ identifier = "cp313-android_x86_64", version = "3.13", url = "https://repo.maven.apache.org/maven2/com/chaquo/python/python/3.13.13/python-3.13.13-x86_64-linux-android.tar.gz", sha256 = "b021b76df2c8d64e41c76d18a2d91f27652356bd932c06f9076bac1b664c0e37" },
|
||||
{ identifier = "cp314-android_arm64_v8a", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-aarch64-linux-android.tar.gz", sha256 = "f008321abf837fcaec569df143283ece0e764b18d8c75763200160553f906af1" },
|
||||
{ identifier = "cp314-android_x86_64", version = "3.14", url = "https://www.python.org/ftp/python/3.14.5/python-3.14.5-x86_64-linux-android.tar.gz", sha256 = "36184e31464b2b959d92c81ded8d025834342cc080623bced282a2a9a57bd47c" },
|
||||
{ identifier = "cp315-android_arm64_v8a", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-aarch64-linux-android.tar.gz", sha256 = "9a25e5499d639d4f59bc766ad36c9eddd892232a9f9224c2fa4ef55dc1d49ff3" },
|
||||
{ identifier = "cp315-android_x86_64", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-x86_64-linux-android.tar.gz", sha256 = "86f492e92340028c8c7efedde6760b40f5e645062d0ef17af0d0bf4d18b127f2" },
|
||||
]
|
||||
|
||||
[ios]
|
||||
python_configurations = [
|
||||
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz" },
|
||||
{ identifier = "cp313-ios_x86_64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz" },
|
||||
{ identifier = "cp313-ios_arm64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz" },
|
||||
{ identifier = "cp314-ios_arm64_iphoneos", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz" },
|
||||
{ identifier = "cp314-ios_x86_64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz" },
|
||||
{ identifier = "cp314-ios_arm64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz" },
|
||||
{ identifier = "cp315-ios_arm64_iphoneos", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz" },
|
||||
{ identifier = "cp315-ios_x86_64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz" },
|
||||
{ identifier = "cp315-ios_arm64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz" },
|
||||
{ identifier = "cp313-ios_arm64_iphoneos", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
|
||||
{ identifier = "cp313-ios_x86_64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
|
||||
{ identifier = "cp313-ios_arm64_iphonesimulator", version = "3.13", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.13-b13/Python-3.13-iOS-support.b13.tar.gz", sha256 = "d1f95f95137a4b91dc0cbe9b99ddfc0a78918dde883c7fbe7147074a5e715274" },
|
||||
{ identifier = "cp314-ios_arm64_iphoneos", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
|
||||
{ identifier = "cp314-ios_x86_64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
|
||||
{ identifier = "cp314-ios_arm64_iphonesimulator", version = "3.14", url = "https://github.com/beeware/Python-Apple-support/releases/download/3.14-b9/Python-3.14-iOS-support.b9.tar.gz", sha256 = "8e4123b543bf17fdae2e2c6c033434487752438431014eb12e6d833aa35927a8" },
|
||||
{ identifier = "cp315-ios_arm64_iphoneos", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
|
||||
{ identifier = "cp315-ios_x86_64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
|
||||
{ identifier = "cp315-ios_arm64_iphonesimulator", version = "3.15", url = "https://www.python.org/ftp/python/3.15.0/python-3.15.0b1-iOS-XCframework.tar.gz", sha256 = "abd43fc362bb6e40a5233a499aa3fa96bf49306ca10b4cbcb27418778e429eb8" },
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
default = { version = "21.3.3", url = "https://github.com/pypa/get-virtualenv/releases/download/21.3.3/virtualenv.pyz" }
|
||||
default = { version = "21.3.3", url = "https://github.com/pypa/get-virtualenv/releases/download/21.3.3/virtualenv.pyz", sha256 = "d3a7f4ee4c820c4247fe14ec1a478e19ef8f63f716c0f15bc10fab4d70ad9b69" }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import ssl
|
||||
@@ -45,7 +46,7 @@ def ensure_cache_sentinel(cache_path: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def download(url: str, dest: Path) -> None:
|
||||
def download(url: str, dest: Path, *, sha256: str | None = None) -> None:
|
||||
print(f"+ Download {url} to {dest}")
|
||||
dest_dir = dest.parent
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -59,13 +60,20 @@ def download(url: str, dest: Path) -> None:
|
||||
try:
|
||||
with urllib.request.urlopen(url, context=context) as response:
|
||||
dest.write_bytes(response.read())
|
||||
return
|
||||
break
|
||||
|
||||
except OSError:
|
||||
if i == repeat_num - 1:
|
||||
raise
|
||||
time.sleep(3)
|
||||
|
||||
if sha256:
|
||||
computed = hashlib.sha256(dest.read_bytes()).hexdigest()
|
||||
if computed != sha256:
|
||||
dest.unlink(missing_ok=True)
|
||||
msg = f"SHA256 mismatch for {url}: expected {sha256!r}, got {computed!r}"
|
||||
raise FatalError(msg)
|
||||
|
||||
|
||||
def extract_zip(zip_src: Path, dest: Path) -> None:
|
||||
"""Extracts a zip and correctly sets permissions on extracted files.
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import typing
|
||||
@@ -19,6 +20,7 @@ if TYPE_CHECKING:
|
||||
class PythonBuildStandaloneAsset(typing.TypedDict):
|
||||
name: str
|
||||
url: str
|
||||
sha256: str
|
||||
|
||||
|
||||
class PythonBuildStandaloneRelease(typing.TypedDict):
|
||||
@@ -86,8 +88,8 @@ def _get_pbs_asset(
|
||||
arch_identifier: str,
|
||||
platform_identifier: str,
|
||||
libc_identifier: str | None,
|
||||
) -> tuple[str, str, str]:
|
||||
"""Finds the asset, returning (tag, filename, url)."""
|
||||
) -> tuple[str, str, str, str]:
|
||||
"""Finds the asset, returning (tag, url, filename, sha256)."""
|
||||
release_data = get_python_build_standalone_release_data()
|
||||
|
||||
expected_suffix = f"{arch_identifier}-{platform_identifier}"
|
||||
@@ -105,22 +107,36 @@ def _get_pbs_asset(
|
||||
continue
|
||||
|
||||
asset_url = asset["url"]
|
||||
return release["tag"], asset_url, asset_name
|
||||
asset_sha256 = asset.get("sha256", "")
|
||||
return release["tag"], asset_url, asset_name, asset_sha256
|
||||
|
||||
# If loop completes without finding a match
|
||||
msg = f"Could not find python-build-standalone release asset matching {asset_pattern!r}."
|
||||
raise PythonBuildStandaloneError(msg)
|
||||
|
||||
|
||||
def _download_or_get_from_cache(asset_url: str, asset_filename: str, cache_dir: Path) -> Path:
|
||||
def _download_or_get_from_cache(
|
||||
asset_url: str, asset_filename: str, cache_dir: Path, sha256: str = ""
|
||||
) -> Path:
|
||||
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)
|
||||
download(asset_url, asset_cache_path, sha256=sha256 or None)
|
||||
return asset_cache_path
|
||||
|
||||
|
||||
@@ -166,7 +182,7 @@ def create_python_build_standalone_environment(
|
||||
|
||||
arch_id, platform_id, libc_id = _get_platform_identifiers()
|
||||
|
||||
pbs_tag, asset_url, asset_filename = _get_pbs_asset(
|
||||
pbs_tag, asset_url, asset_filename, asset_sha256 = _get_pbs_asset(
|
||||
python_version=python_version,
|
||||
arch_identifier=arch_id,
|
||||
platform_identifier=platform_id,
|
||||
@@ -176,7 +192,7 @@ def create_python_build_standalone_environment(
|
||||
print(f"Using python-build-standalone release: {pbs_tag}")
|
||||
|
||||
archive_path = _download_or_get_from_cache(
|
||||
asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir
|
||||
asset_url=asset_url, asset_filename=asset_filename, cache_dir=cache_dir, sha256=asset_sha256
|
||||
)
|
||||
|
||||
python_base_dir = temp_dir / "pbs"
|
||||
|
||||
@@ -53,10 +53,11 @@ def _ensure_virtualenv(version: str) -> tuple[Path, Version]:
|
||||
configuration = loaded_file.get(key, loaded_file["default"])
|
||||
version = str(configuration["version"])
|
||||
url = str(configuration["url"])
|
||||
sha256 = str(configuration.get("sha256", ""))
|
||||
path = CIBW_CACHE_PATH / f"virtualenv-{version}.pyz"
|
||||
with FileLock(str(path) + ".lock"):
|
||||
if not path.exists():
|
||||
download(url, path)
|
||||
download(url, path, sha256=sha256 or None)
|
||||
return (path, Version(version))
|
||||
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ For example, consider a scenario when Pyodide ships a new `315.0.0a1` with cp315
|
||||
In `cibuildwheel/resources/build-platforms.toml`, add an entry under `[pyodide]`:
|
||||
|
||||
```toml
|
||||
{ identifier = "cp315-pyodide_wasm32", version = "3.15", default_pyodide_version = "315.0.0a1", node_version = "v24" },
|
||||
{ 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), 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.
|
||||
`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
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user