diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92d2d659b..684c76cf4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -195,7 +195,7 @@ jobs: run: uv run --no-sync pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py test-pyodide: - name: Test cibuildwheel building Pyodide wheels + name: Test pyodide needs: lint runs-on: ubuntu-24.04 timeout-minutes: 180 @@ -222,6 +222,19 @@ jobs: env: CIBW_PLATFORM: pyodide + - name: Run a sample build (GitHub Action) for an overridden Pyodide version + uses: ./ + with: + package-dir: sample_proj + output-dir: wheelhouse + # In case this breaks at any point in time, switch to using the latest version + # available or any other version that is not the same as the default one set + # in cibuildwheel/resources/build-platforms.toml. + env: + CIBW_PLATFORM: pyodide + CIBW_BUILD: "cp312*" + CIBW_PYODIDE_VERSION: "0.27.6" + - name: Run tests with 'CIBW_PLATFORM' set to 'pyodide' run: | uv run --no-sync ./bin/run_tests.py diff --git a/action.yml b/action.yml index 4f8ff6796..3e19ab982 100644 --- a/action.yml +++ b/action.yml @@ -24,11 +24,10 @@ branding: runs: using: composite steps: - # Set up the version of Python that supports pyodide - uses: actions/setup-python@v5 id: python with: - python-version: "3.12" + python-version: "3.11 - 3.13" update-environment: false - id: cibw diff --git a/bin/generate_pyodide_constraints.py b/bin/generate_pyodide_constraints.py new file mode 100755 index 000000000..e378dd8ee --- /dev/null +++ b/bin/generate_pyodide_constraints.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import sys +import textwrap +from pathlib import Path + +import click + +from cibuildwheel.extra import get_pyodide_xbuildenv_info + + +@click.command() +@click.argument( + "pyodide-version", + type=str, +) +@click.option( + "--output-file", + type=click.Path(), + default=None, + help="Output file to write the constraints to. If not provided, the constraints will be printed to stdout.", +) +def generate_pyodide_constraints(pyodide_version: str, output_file: str | None = None) -> None: + """ + Generate constraints for a specific Pyodide version. The constraints are + generated based on the Pyodide version's xbuildenv info, which is retrieved + from the Pyodide repository. + + These constraints should then be 'pinned' using `uv pip compile`. + + Example usage: + + bin/generate_pyodide_constraints.py 0.27.0 + """ + xbuildenv_info = get_pyodide_xbuildenv_info() + try: + pyodide_version_xbuildenv_info = xbuildenv_info["releases"][pyodide_version] + except KeyError as e: + msg = f"Pyodide version {pyodide_version} not found in xbuildenv info. Versions available: {', '.join(xbuildenv_info['releases'].keys())}" + raise click.BadParameter(msg) from e + + pyodide_build_min_version = pyodide_version_xbuildenv_info.get("min_pyodide_build_version") + pyodide_build_max_version = pyodide_version_xbuildenv_info.get("max_pyodide_build_version") + + pyodide_build_specifier_parts: list[str] = [] + + if pyodide_build_min_version: + pyodide_build_specifier_parts.append(f">={pyodide_build_min_version}") + if pyodide_build_max_version: + pyodide_build_specifier_parts.append(f"<={pyodide_build_max_version}") + + pyodide_build_specifier = ",".join(pyodide_build_specifier_parts) + + constraints_txt = textwrap.dedent(f""" + pip + build[virtualenv] + pyodide-build{pyodide_build_specifier} + click<8.2 + """) + + if output_file is None: + print(constraints_txt) + else: + Path(output_file).write_text(constraints_txt) + print(f"Constraints written to {output_file}", file=sys.stderr) + + +if __name__ == "__main__": + generate_pyodide_constraints() diff --git a/bin/generate_schema.py b/bin/generate_schema.py index ac97f8e9b..3c99ded45 100755 --- a/bin/generate_schema.py +++ b/bin/generate_schema.py @@ -197,6 +197,9 @@ xbuild-tools: description: Binaries on the path that should be included in an isolated cross-build environment type: string_array + pyodide-version: + type: string + description: Specify the version of Pyodide to use repair-wheel-command: description: Execute a shell command to repair each built wheel. type: string_array diff --git a/bin/update_python_build_standalone.py b/bin/update_python_build_standalone.py new file mode 100755 index 000000000..516920144 --- /dev/null +++ b/bin/update_python_build_standalone.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import json + +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 + + +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"] + + assets: list[PythonBuildStandaloneAsset] = [] + + for github_asset in github_assets: + name = github_asset["name"] + if not name.endswith("install_only.tar.gz"): + continue + url = github_asset["browser_download_url"] + assets.append({"name": name, "url": url}) + + # 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() diff --git a/bin/update_pythons.py b/bin/update_pythons.py index 75de60a64..60b99f2aa 100755 --- a/bin/update_pythons.py +++ b/bin/update_pythons.py @@ -19,7 +19,7 @@ from rich.logging import RichHandler from rich.syntax import Syntax -from cibuildwheel.extra import dump_python_configurations +from cibuildwheel.extra import dump_python_configurations, get_pyodide_xbuildenv_info log = logging.getLogger("cibw") @@ -57,7 +57,14 @@ class ConfigApple(TypedDict): url: str -AnyConfig = ConfigWinCP | ConfigWinPP | ConfigWinGP | ConfigApple +class ConfigPyodide(TypedDict): + identifier: str + version: str + default_pyodide_version: str + node_version: str + + +AnyConfig = ConfigWinCP | ConfigWinPP | ConfigWinGP | ConfigApple | ConfigPyodide # The following set of "Versions" classes allow the initial call to the APIs to @@ -347,6 +354,39 @@ def update_version_ios(self, identifier: str, version: Version) -> ConfigApple | return None +class PyodideVersions: + def __init__(self) -> None: + xbuildenv_info = get_pyodide_xbuildenv_info() + self.releases = xbuildenv_info["releases"] + + def update_version_pyodide( + self, identifier: str, version: Version, spec: Specifier, node_version: str + ) -> ConfigPyodide | None: + # get releases that match the python version + releases = [ + r for r in self.releases.values() if spec.contains(Version(r["python_version"])) + ] + # sort by version, latest first + releases.sort(key=lambda r: Version(r["version"]), reverse=True) + + if not releases: + msg = f"Pyodide not found for {spec}!" + raise ValueError(msg) + + final_releases = [r for r in releases if not Version(r["version"]).is_prerelease] + + # prefer a final release if available, otherwise use the latest + # pre-release + release = final_releases[0] if final_releases else releases[0] + + return ConfigPyodide( + identifier=identifier, + version=str(version), + default_pyodide_version=release["version"], + node_version=node_version, + ) + + # This is a universal interface to all the above Versions classes. Given an # identifier, it updates a config dict. @@ -369,6 +409,8 @@ def __init__(self) -> None: self.graalpy = GraalPyVersions() + self.pyodide = PyodideVersions() + def update_config(self, config: MutableMapping[str, str]) -> None: identifier = config["identifier"] version = Version(config["version"]) @@ -407,6 +449,10 @@ def update_config(self, config: MutableMapping[str, str]) -> None: config_update = self.windows_arm64.update_version_windows(spec) elif "ios" in identifier: config_update = self.ios_cpython.update_version_ios(identifier, version) + elif "pyodide" in identifier: + config_update = self.pyodide.update_version_pyodide( + identifier, version, spec, config["node_version"] + ) assert config_update is not None, f"{identifier} not found!" config.update(**config_update) @@ -445,6 +491,9 @@ def update_pythons(force: bool, level: str) -> None: for config in configs["ios"]["python_configurations"]: all_versions.update_config(config) + for config in configs["pyodide"]["python_configurations"]: + all_versions.update_config(config) + result_toml = dump_python_configurations(configs) rich.print() # spacer diff --git a/cibuildwheel/extra.py b/cibuildwheel/extra.py index 774447c33..e13df6e0b 100644 --- a/cibuildwheel/extra.py +++ b/cibuildwheel/extra.py @@ -2,9 +2,16 @@ These are utilities for the `/bin` scripts, not for the `cibuildwheel` program. """ +import json +import time +import typing +import urllib.error +import urllib.request from collections.abc import Mapping, Sequence from io import StringIO -from typing import Protocol +from typing import Any, NotRequired, Protocol + +from cibuildwheel import __version__ as cibw_version __all__ = ("Printable", "dump_python_configurations") @@ -30,3 +37,65 @@ def dump_python_configurations( output.write("\n") # Strip the final newline, to avoid two blank lines at the end. return output.getvalue()[:-1] + + +def _json_request(request: urllib.request.Request, timeout: int = 30) -> dict[str, Any]: + with urllib.request.urlopen(request, timeout=timeout) as response: + return typing.cast(dict[str, Any], json.load(response)) + + +def github_api_request(path: str, *, max_retries: int = 3) -> dict[str, Any]: + """ + Makes a GitHub API request to the given path and returns the JSON response. + """ + api_url = f"https://api.github.com/{path}" + headers = { + "Accept": "application/vnd.github.v3+json", + "User-Agent": f"cibuildwheel/{cibw_version}", + } + request = urllib.request.Request(api_url, headers=headers) + + for retry_count in range(max_retries): + try: + return _json_request(request) + except (urllib.error.URLError, TimeoutError) as e: + # pylint: disable=E1101 + if ( + isinstance(e, urllib.error.HTTPError) + and (e.code == 403 or e.code == 429) + and e.headers.get("x-ratelimit-remaining") == "0" + ): + reset_time = int(e.headers.get("x-ratelimit-reset", 0)) + wait_time = max(0, reset_time - int(e.headers.get("date", 0))) + print(f"Github rate limit exceeded. Waiting for {wait_time} seconds.") + time.sleep(wait_time) + else: + print(f"Retrying GitHub API request due to error: {e}") + + if retry_count == max_retries - 1: + print(f"GitHub API request failed (Network error: {e}). Check network connection.") + raise e + + # Should never be reached but to keep the type checker happy + msg = "Unexpected execution path in github_api_request" + raise RuntimeError(msg) + + +class PyodideXBuildEnvRelease(typing.TypedDict): + version: str + python_version: str + emscripten_version: str + min_pyodide_build_version: NotRequired[str] + max_pyodide_build_version: NotRequired[str] + + +class PyodideXBuildEnvInfo(typing.TypedDict): + releases: dict[str, PyodideXBuildEnvRelease] + + +def get_pyodide_xbuildenv_info() -> PyodideXBuildEnvInfo: + xbuildenv_info_url = ( + "https://pyodide.github.io/pyodide/api/pyodide-cross-build-environments.json" + ) + with urllib.request.urlopen(xbuildenv_info_url) as response: + return typing.cast(PyodideXBuildEnvInfo, json.loads(response.read().decode("utf-8"))) diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py index fa1d1c046..62472e667 100644 --- a/cibuildwheel/options.py +++ b/cibuildwheel/options.py @@ -111,6 +111,7 @@ class BuildOptions: build_frontend: BuildFrontendConfig | None config_settings: str container_engine: OCIContainerEngineConfig + pyodide_version: str | None @property def package_dir(self) -> Path: @@ -840,6 +841,8 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions: msg = f"Failed to parse container config. {e}" raise errors.ConfigurationError(msg) from e + pyodide_version = self.reader.get("pyodide-version", env_plat=False) + return BuildOptions( globals=self.globals, test_command=test_command, @@ -860,6 +863,7 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions: build_frontend=build_frontend, config_settings=config_settings, container_engine=container_engine, + pyodide_version=pyodide_version or None, ) def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None: diff --git a/cibuildwheel/platforms/pyodide.py b/cibuildwheel/platforms/pyodide.py index 1c05dd0cd..ac26df3e6 100644 --- a/cibuildwheel/platforms/pyodide.py +++ b/cibuildwheel/platforms/pyodide.py @@ -1,13 +1,15 @@ import functools +import json import os import shutil import sys import tomllib +import typing from collections.abc import Set from dataclasses import dataclass from pathlib import Path from tempfile import TemporaryDirectory -from typing import Final +from typing import Final, TypedDict from filelock import FileLock @@ -28,8 +30,12 @@ extract_zip, move_file, ) -from ..util.helpers import prepare_command +from ..util.helpers import prepare_command, unwrap, unwrap_preserving_paragraphs from ..util.packaging import combine_constraints, find_compatible_wheel, get_pip_version +from ..util.python_build_standalone import ( + PythonBuildStandaloneError, + create_python_build_standalone_environment, +) from ..venv import constraint_flags, virtualenv IS_WIN: Final[bool] = sys.platform.startswith("win") @@ -39,12 +45,23 @@ class PythonConfiguration: version: str identifier: str - pyodide_version: str - pyodide_build_version: str - emscripten_version: str + default_pyodide_version: str node_version: str +class PyodideXBuildEnvInfoVersionRange(TypedDict): + min: str | None + max: str | None + + +class PyodideXBuildEnvInfo(TypedDict): + version: str + python: str + emscripten: str + pyodide_build: PyodideXBuildEnvInfoVersionRange + compatible: bool + + @functools.cache def ensure_node(major_version: str) -> Path: with resources.NODEJS.open("rb") as f: @@ -77,8 +94,6 @@ def ensure_node(major_version: str) -> Path: def install_emscripten(tmp: Path, version: str) -> Path: - # We don't need to match the emsdk version to the version we install, but - # we do for stability url = f"https://github.com/emscripten-core/emsdk/archive/refs/tags/{version}.zip" installation_path = CIBW_CACHE_PATH / f"emsdk-{version}" emsdk_path = installation_path / f"emsdk-{version}/emsdk" @@ -96,16 +111,82 @@ def install_emscripten(tmp: Path, version: str) -> Path: return emcc_path +def get_all_xbuildenv_version_info(env: dict[str, str]) -> list[PyodideXBuildEnvInfo]: + xbuildenvs_info_str = call( + "pyodide", + "xbuildenv", + "search", + "--json", + "--all", + env=env, + cwd=CIBW_CACHE_PATH, + capture_stdout=True, + ).strip() + + xbuildenvs_info = json.loads(xbuildenvs_info_str) + + if "environments" not in xbuildenvs_info: + msg = f"Invalid xbuildenvs info, got {xbuildenvs_info}" + raise ValueError(msg) + + return typing.cast(list[PyodideXBuildEnvInfo], xbuildenvs_info["environments"]) + + +def get_xbuildenv_version_info( + env: dict[str, str], version: str, pyodide_build_version: str +) -> PyodideXBuildEnvInfo: + xbuildenvs_info = get_all_xbuildenv_version_info(env) + for xbuildenv_info in xbuildenvs_info: + if xbuildenv_info["version"] == version: + return xbuildenv_info + + msg = unwrap(f""" + Could not find Pyodide cross-build environment version {version} in the available + versions as reported by pyodide-build v{pyodide_build_version}. + Available pyodide xbuildenv versions are: + {", ".join(e["version"] for e in xbuildenvs_info if e["compatible"])} + """) + raise errors.FatalError(msg) + + +# The default pyodide xbuildenv version that's specified in +# build-platforms.toml is compatible with the pyodide-build version that's +# pinned in the bundled constraints file. But if the user changes +# pyodide-version and/or dependency-constraints in the cibuildwheel config, we +# need to check if the xbuildenv version is compatible with the pyodide-build +# version. +def validate_pyodide_build_version( + xbuildenv_info: PyodideXBuildEnvInfo, pyodide_build_version: str +) -> None: + """ + Validate the Pyodide version is compatible with the installed + pyodide-build version. + """ + + pyodide_version = xbuildenv_info["version"] + + if not xbuildenv_info["compatible"]: + msg = unwrap_preserving_paragraphs(f""" + The Pyodide xbuildenv version {pyodide_version} is not compatible + with the pyodide-build version {pyodide_build_version}. Please use + the 'pyodide xbuildenv search --all' command to find a compatible + version. + + Set the pyodide-build version using the `dependency-constraints` + option, or set the Pyodide xbuildenv version using the + `pyodide-version` option. + """) + raise errors.FatalError(msg) + + def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_version: str) -> str: """Install a particular Pyodide xbuildenv version and set a path to the Pyodide root.""" - # Since pyodide-build was unvendored from Pyodide v0.27.0, the versions of pyodide-build are - # not guaranteed to match the versions of Pyodide or be in sync with them. Hence, we shall - # specify the pyodide-build version in the root path, which will set up the xbuildenv for - # the requested Pyodide version. - pyodide_root = ( - CIBW_CACHE_PATH - / f".pyodide-xbuildenv-{pyodide_build_version}/{pyodide_version}/xbuildenv/pyodide-root" - ) + # Since pyodide-build was unvendored from Pyodide v0.27.0, the versions of + # pyodide-build are uncoupled from the versions of Pyodide. So, we specify + # both the pyodide-build version and the Pyodide version in the temp path. + xbuildenv_cache_path = CIBW_CACHE_PATH / f"pyodide-build-{pyodide_build_version}" + pyodide_root = xbuildenv_cache_path / pyodide_version / "xbuildenv" / "pyodide-root" + with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"): if pyodide_root.exists(): return str(pyodide_root) @@ -114,31 +195,36 @@ def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_v # PYODIDE_ROOT so copy it first. env = dict(env) env.pop("PYODIDE_ROOT", None) + + # Install the xbuildenv call( "pyodide", "xbuildenv", "install", + "--path", + str(xbuildenv_cache_path), pyodide_version, env=env, cwd=CIBW_CACHE_PATH, ) - return str(pyodide_root) + assert pyodide_root.exists() + return str(pyodide_root) -def get_base_python(identifier: str) -> Path: - implementation_id = identifier.split("-")[0] - majorminor = implementation_id[len("cp") :] - version_info = (int(majorminor[0]), int(majorminor[1:])) - if version_info == sys.version_info[:2]: - return Path(sys.executable) - major_minor = ".".join(str(v) for v in version_info) - python_name = f"python{major_minor}" - which_python = shutil.which(python_name) - if which_python is None: - msg = f"CPython {major_minor} is not installed." - raise errors.FatalError(msg) - return Path(which_python) +def get_base_python(tmp: Path, python_configuration: PythonConfiguration) -> Path: + try: + return create_python_build_standalone_environment( + python_version=python_configuration.version, + temp_dir=tmp, + cache_dir=CIBW_CACHE_PATH, + ) + except PythonBuildStandaloneError as e: + msg = unwrap(f""" + Failed to create a Python build environment: + {e} + """) + raise errors.FatalError(msg) from e def setup_python( @@ -146,10 +232,13 @@ def setup_python( python_configuration: PythonConfiguration, constraints_path: Path | None, environment: ParsedEnvironment, + user_pyodide_version: str | None, ) -> dict[str, str]: - base_python = get_base_python(python_configuration.identifier) + log.step("Installing a base python environment...") + base_python = get_base_python(tmp / "base", python_configuration) log.step("Setting up build environment...") + pyodide_version = user_pyodide_version or python_configuration.default_pyodide_version venv_path = tmp / "venv" env = virtualenv(python_configuration.version, base_python, venv_path, None, use_uv=False) venv_bin_path = venv_path / "bin" @@ -201,15 +290,28 @@ def setup_python( env=env, ) - log.step(f"Installing Emscripten version: {python_configuration.emscripten_version} ...") - emcc_path = install_emscripten(tmp, python_configuration.emscripten_version) + pyodide_build_version = call( + "python", + "-c", + "from importlib.metadata import version; print(version('pyodide-build'))", + env=env, + capture_stdout=True, + ).strip() + + xbuildenv_info = get_xbuildenv_version_info(env, pyodide_version, pyodide_build_version) + validate_pyodide_build_version( + xbuildenv_info=xbuildenv_info, + pyodide_build_version=pyodide_build_version, + ) + + emscripten_version = xbuildenv_info["emscripten"] + log.step(f"Installing Emscripten version: {emscripten_version} ...") + emcc_path = install_emscripten(tmp, emscripten_version) env["PATH"] = os.pathsep.join([str(emcc_path.parent), env["PATH"]]) - log.step(f"Installing Pyodide xbuildenv version: {python_configuration.pyodide_version} ...") - env["PYODIDE_ROOT"] = install_xbuildenv( - env, python_configuration.pyodide_build_version, python_configuration.pyodide_version - ) + log.step(f"Installing Pyodide xbuildenv version: {pyodide_version} ...") + env["PYODIDE_ROOT"] = install_xbuildenv(env, pyodide_build_version, pyodide_version) return env @@ -259,6 +361,7 @@ def build(options: Options, tmp_path: Path) -> None: log.build_start(config.identifier) identifier_tmp_dir = tmp_path / config.identifier + built_wheel_dir = identifier_tmp_dir / "built_wheel" repaired_wheel_dir = identifier_tmp_dir / "repaired_wheel" identifier_tmp_dir.mkdir() @@ -270,10 +373,11 @@ def build(options: Options, tmp_path: Path) -> None: ) env = setup_python( - identifier_tmp_dir / "build", - config, - constraints_path, - build_options.environment, + tmp=identifier_tmp_dir / "build", + python_configuration=config, + constraints_path=constraints_path, + environment=build_options.environment, + user_pyodide_version=build_options.pyodide_version, ) pip_version = get_pip_version(env) # The Pyodide command line runner mounts all directories in the host diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml index 973154586..2875f9f24 100644 --- a/cibuildwheel/resources/build-platforms.toml +++ b/cibuildwheel/resources/build-platforms.toml @@ -220,7 +220,7 @@ python_configurations = [ [pyodide] python_configurations = [ - { identifier = "cp312-pyodide_wasm32", version = "3.12", pyodide_version = "0.27.0", pyodide_build_version = "0.29.2", emscripten_version = "3.1.58", node_version = "v20" }, + { identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.6", node_version = "v22" }, ] [ios] diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json index 8ffa0b75e..f3e64c9d1 100644 --- a/cibuildwheel/resources/cibuildwheel.schema.json +++ b/cibuildwheel/resources/cibuildwheel.schema.json @@ -424,6 +424,11 @@ ], "title": "CIBW_XBUILD_TOOLS" }, + "pyodide-version": { + "type": "string", + "description": "Specify the version of Pyodide to use", + "title": "CIBW_PYODIDE_VERSION" + }, "repair-wheel-command": { "description": "Execute a shell command to repair each built wheel.", "oneOf": [ @@ -697,6 +702,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "$ref": "#/properties/repair-wheel-command" }, @@ -806,6 +814,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "description": "Execute a shell command to repair each built wheel.", "oneOf": [ @@ -873,6 +884,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "$ref": "#/properties/repair-wheel-command" }, @@ -927,6 +941,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "description": "Execute a shell command to repair each built wheel.", "oneOf": [ @@ -994,6 +1011,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "$ref": "#/properties/repair-wheel-command" }, @@ -1048,6 +1068,9 @@ "xbuild-tools": { "$ref": "#/properties/xbuild-tools" }, + "pyodide-version": { + "$ref": "#/properties/pyodide-version" + }, "repair-wheel-command": { "$ref": "#/properties/repair-wheel-command" }, diff --git a/cibuildwheel/resources/constraints-pyodide312.txt b/cibuildwheel/resources/constraints-pyodide312.txt index 702ea12d2..e9116769d 100644 --- a/cibuildwheel/resources/constraints-pyodide312.txt +++ b/cibuildwheel/resources/constraints-pyodide312.txt @@ -4,7 +4,7 @@ annotated-types==0.7.0 # via pydantic anyio==4.9.0 # via httpx -auditwheel-emscripten==0.0.16 +auditwheel-emscripten==0.1.0 # via pyodide-build build==1.2.2.post1 # via @@ -21,8 +21,6 @@ click==8.1.8 # via # -r .nox/update_constraints/tmp/constraints-pyodide.in # typer -cmake==4.0.2 - # via pyodide-build distlib==0.3.9 # via virtualenv filelock==3.18.0 @@ -53,7 +51,9 @@ packaging==25.0 pip==25.1.1 # via -r .nox/update_constraints/tmp/constraints-pyodide.in platformdirs==4.3.8 - # via virtualenv + # via + # pyodide-build + # virtualenv pydantic==2.11.4 # via # pyodide-build @@ -62,9 +62,9 @@ pydantic-core==2.33.2 # via pydantic pygments==2.19.1 # via rich -pyodide-build==0.29.2 +pyodide-build==0.30.4 # via -r .nox/update_constraints/tmp/constraints-pyodide.in -pyodide-cli==0.2.4 +pyodide-cli==0.3.0 # via # auditwheel-emscripten # pyodide-build diff --git a/cibuildwheel/resources/defaults.toml b/cibuildwheel/resources/defaults.toml index 4d03394fd..ee4c2f6c7 100644 --- a/cibuildwheel/resources/defaults.toml +++ b/cibuildwheel/resources/defaults.toml @@ -27,6 +27,8 @@ test-groups = [] container-engine = "docker" +pyodide-version = "" + manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux_2_28" diff --git a/cibuildwheel/resources/python-build-standalone-releases.json b/cibuildwheel/resources/python-build-standalone-releases.json new file mode 100644 index 000000000..407b0318c --- /dev/null +++ b/cibuildwheel/resources/python-build-standalone-releases.json @@ -0,0 +1,425 @@ +{ + "releases": [ + { + "tag": "20250409", + "assets": [ + { + "name": "cpython-3.10.17+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.10.17+20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.10.17%2B20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.11.12+20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.11.12%2B20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.12.10+20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.12.10%2B20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.13.3+20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.14.0a6+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.14.0a6%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-aarch64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-aarch64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-armv7-unknown-linux-gnueabi-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-armv7-unknown-linux-gnueabihf-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-i686-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-i686-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-ppc64le-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-riscv64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-riscv64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-s390x-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-s390x-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64-apple-darwin-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64-apple-darwin-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v2-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v2-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v3-unknown-linux-musl-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v4-unknown-linux-gnu-install_only.tar.gz" + }, + { + "name": "cpython-3.9.22+20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.9.22%2B20250409-x86_64_v4-unknown-linux-musl-install_only.tar.gz" + } + ] + } + ] +} diff --git a/cibuildwheel/util/packaging.py b/cibuildwheel/util/packaging.py index f6619dd92..310778198 100644 --- a/cibuildwheel/util/packaging.py +++ b/cibuildwheel/util/packaging.py @@ -165,6 +165,9 @@ def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None: continue if not tag.platform.endswith(f"_{arch}"): continue + elif platform.startswith("pyodide"): + # each Pyodide version has its own platform tag + continue else: # Windows should exactly match if tag.platform != platform: diff --git a/cibuildwheel/util/python_build_standalone.py b/cibuildwheel/util/python_build_standalone.py new file mode 100644 index 000000000..95721ee54 --- /dev/null +++ b/cibuildwheel/util/python_build_standalone.py @@ -0,0 +1,181 @@ +import fnmatch +import functools +import json +import platform +import typing +from pathlib import Path + +from filelock import FileLock + +from cibuildwheel.util.file import download, extract_tar +from cibuildwheel.util.resources import PYTHON_BUILD_STANDALONE_RELEASES + + +class PythonBuildStandaloneAsset(typing.TypedDict): + name: str + url: str + + +class PythonBuildStandaloneRelease(typing.TypedDict): + tag: str + assets: list[PythonBuildStandaloneAsset] + + +class PythonBuildStandaloneReleaseData(typing.TypedDict): + releases: list[PythonBuildStandaloneRelease] + + +@functools.cache +def get_python_build_standalone_release_data() -> PythonBuildStandaloneReleaseData: + with open(PYTHON_BUILD_STANDALONE_RELEASES, "rb") as f: + return typing.cast(PythonBuildStandaloneReleaseData, json.load(f)) + + +class PythonBuildStandaloneError(Exception): + """Errors related to python-build-standalone.""" + + +def _get_platform_identifiers() -> tuple[str, str, str | None]: + """ + Detects the current platform and returns architecture, platform, and libc + identifiers. + """ + system = platform.system() + machine = platform.machine() + machine_lower = machine.lower() + + arch_identifier: str + platform_identifier: str + libc_identifier: str | None = None + + # Map Architecture + if machine_lower in ["x86_64", "amd64"]: + arch_identifier = "x86_64" + elif machine_lower in ["aarch64", "arm64"]: + arch_identifier = "aarch64" + else: + msg = f"Unsupported architecture: {system} {machine}. Cannot download appropriate Python build." + raise PythonBuildStandaloneError(msg) + + # Map OS + Libc + if system == "Linux": + platform_identifier = "unknown-linux" + libc_identifier = "musl" if "musl" in (platform.libc_ver() or ("", "")) else "gnu" + elif system == "Darwin": + platform_identifier = "apple-darwin" + elif system == "Windows": + platform_identifier = "pc-windows-msvc" + else: + msg = f"Unsupported operating system: {system}. Cannot download appropriate Python build." + raise PythonBuildStandaloneError(msg) + + print( + f"Detected platform: arch='{arch_identifier}', platform='{platform_identifier}', libc='{libc_identifier}'" + ) + return arch_identifier, platform_identifier, libc_identifier + + +def _get_pbs_asset( + *, + python_version: str, + arch_identifier: str, + platform_identifier: str, + libc_identifier: str | None, +) -> tuple[str, str, str]: + """Finds the asset, returning (tag, filename, url).""" + release_data = get_python_build_standalone_release_data() + + expected_suffix = f"{arch_identifier}-{platform_identifier}" + if libc_identifier: + expected_suffix += f"-{libc_identifier}" + expected_suffix += "-install_only.tar.gz" + + asset_pattern = f"cpython-{python_version}.*-{expected_suffix}" + print(f"Looking for file with pattern {asset_pattern}") + + for release in release_data["releases"]: + for asset in release["assets"]: + asset_name = asset["name"] + if not fnmatch.fnmatch(asset_name, asset_pattern): + continue + + asset_url = asset["url"] + return release["tag"], asset_url, asset_name + + # 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: + 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 + + print(f"Downloading python_build_standalone: {asset_url} to {asset_cache_path}") + download(asset_url, asset_cache_path) + return asset_cache_path + + +def _find_python_executable(extracted_dir: Path) -> Path: + """Finds the python executable within the extracted directory structure.""" + # Structure is typically 'python/bin/python' or 'python/python.exe' + base_install_dir = extracted_dir / "python" + + if platform.system() == "Windows": + executable_path = base_install_dir / "python.exe" + else: + executable_path = base_install_dir / "bin" / "python" + + if not executable_path.is_file(): + msg = f"Could not locate python executable at expected path {executable_path} within {extracted_dir}." + raise PythonBuildStandaloneError(msg) + + print(f"Found python executable: {executable_path}") + return executable_path.resolve() # Return absolute path + + +def create_python_build_standalone_environment( + python_version: str, temp_dir: Path, cache_dir: Path +) -> Path: + """ + Returns a Python environment from python-build-standalone, downloading it + if necessary using a cache, and expanding it into a fresh base path. + + Args: + python_version: The Python version string (e.g., "3.12"). + temp_dir: A directory where the Python environment will be created. + cache_dir: A directory to store/retrieve downloaded archives. + + Returns: + The absolute path to the python executable within the created environment (in temp_dir). + + Raises: + PythonBuildStandaloneError: If the platform is unsupported, the build cannot be found, + download/extraction fails, or configuration is invalid. + """ + + print(f"Creating python-build-standalone environment: version={python_version}") + + arch_id, platform_id, libc_id = _get_platform_identifiers() + + pbs_tag, asset_url, asset_filename = _get_pbs_asset( + python_version=python_version, + arch_identifier=arch_id, + platform_identifier=platform_id, + libc_identifier=libc_id, + ) + + 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 + ) + + python_base_dir = temp_dir / "pbs" + assert not python_base_dir.exists() + extract_tar(archive_path, python_base_dir) + + return _find_python_executable(python_base_dir) diff --git a/cibuildwheel/util/resources.py b/cibuildwheel/util/resources.py index 40e085d68..a3cef7815 100644 --- a/cibuildwheel/util/resources.py +++ b/cibuildwheel/util/resources.py @@ -16,6 +16,7 @@ CONSTRAINTS: Final[Path] = PATH / "constraints.txt" VIRTUALENV: Final[Path] = PATH / "virtualenv.toml" CIBUILDWHEEL_SCHEMA: Final[Path] = PATH / "cibuildwheel.schema.json" +PYTHON_BUILD_STANDALONE_RELEASES: Final[Path] = PATH / "python-build-standalone-releases.json" # this value is cached because it's used a lot in unit tests diff --git a/docs/options.md b/docs/options.md index d24db227b..4a51f9ede 100644 --- a/docs/options.md +++ b/docs/options.md @@ -17,7 +17,9 @@ Default: `auto` - For `linux`, you need [Docker or Podman](#container-engine) running, on Linux, macOS, or Windows. - For `macos` and `windows`, you need to be running on the respective system, with a working compiler toolchain installed - Xcode Command Line tools for macOS, and MSVC for Windows. - For `ios` you need to be running on macOS, with Xcode and the iOS simulator installed. -- For `pyodide` you need to be on an x86-64 linux runner and `python3.12` must be available in `PATH`. +- For `pyodide`, you need a Linux or macOS machine. + +Check the [platforms](platforms.md) page for more information on platform requirements. This option can also be set using the [command-line option](#command-line) `--platform`. This option is not available in the `pyproject.toml` config. @@ -49,20 +51,19 @@ When both options are specified, both conditions are applied and only builds wit When setting the options, you can use shell-style globbing syntax, as per [fnmatch](https://docs.python.org/3/library/fnmatch.html) with the addition of curly bracket syntax `{option1,option2}`, provided by [bracex](https://pypi.org/project/bracex/). All the build identifiers supported by cibuildwheel are shown below:
- -| | macOS | Windows | Linux Intel | Linux Other | iOS | -|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| -| Python 3.8 | cp38-macosx_x86_64