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
cp38-macosx_universal2
cp38-macosx_arm64 | cp38-win_amd64
cp38-win32 | cp38-manylinux_x86_64
cp38-manylinux_i686
cp38-musllinux_x86_64
cp38-musllinux_i686 | cp38-manylinux_aarch64
cp38-manylinux_ppc64le
cp38-manylinux_s390x
cp38-manylinux_armv7l
cp38-manylinux_riscv64
cp38-musllinux_aarch64
cp38-musllinux_ppc64le
cp38-musllinux_s390x
cp38-musllinux_armv7l
cp38-musllinux_riscv64 | | -| Python 3.9 | cp39-macosx_x86_64
cp39-macosx_universal2
cp39-macosx_arm64 | cp39-win_amd64
cp39-win32
cp39-win_arm64 | cp39-manylinux_x86_64
cp39-manylinux_i686
cp39-musllinux_x86_64
cp39-musllinux_i686 | cp39-manylinux_aarch64
cp39-manylinux_ppc64le
cp39-manylinux_s390x
cp39-manylinux_armv7l
cp39-manylinux_riscv64
cp39-musllinux_aarch64
cp39-musllinux_ppc64le
cp39-musllinux_s390x
cp39-musllinux_armv7l
cp39-musllinux_riscv64 | | -| Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-manylinux_armv7l
cp310-manylinux_riscv64
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x
cp310-musllinux_armv7l
cp310-musllinux_riscv64 | | -| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-manylinux_armv7l
cp311-manylinux_riscv64
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x
cp311-musllinux_armv7l
cp311-musllinux_riscv64 | | -| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-manylinux_armv7l
cp312-manylinux_riscv64
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x
cp312-musllinux_armv7l
cp312-musllinux_riscv64 | | -| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-manylinux_riscv64
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l
cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos
cp313-ios_arm64_iphonesimulator
cp313-ios_x86_64_iphonesimulator | -| Python 3.14 | cp314-macosx_x86_64
cp314-macosx_universal2
cp314-macosx_arm64 | cp314-win_amd64
cp314-win32
cp314-win_arm64 | cp314-manylinux_x86_64
cp314-manylinux_i686
cp314-musllinux_x86_64
cp314-musllinux_i686 | cp314-manylinux_aarch64
cp314-manylinux_ppc64le
cp314-manylinux_s390x
cp314-manylinux_armv7l
cp314-manylinux_riscv64
cp314-musllinux_aarch64
cp314-musllinux_ppc64le
cp314-musllinux_s390x
cp314-musllinux_armv7l
cp314-musllinux_riscv64 | | -| PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | | -| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | | -| PyPy3.10 v7.3 | pp310-macosx_x86_64
pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64
pp310-manylinux_i686 | pp310-manylinux_aarch64 | | -| PyPy3.11 v7.3 | pp311-macosx_x86_64
pp311-macosx_arm64 | pp311-win_amd64 | pp311-manylinux_x86_64
pp311-manylinux_i686 | pp311-manylinux_aarch64 | | +| | macOS | Windows | Linux Intel | Linux Other | iOS | pyodide (WASM) | +|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|----------------------| +| Python 3.8 | cp38-macosx_x86_64
cp38-macosx_universal2
cp38-macosx_arm64 | cp38-win_amd64
cp38-win32 | cp38-manylinux_x86_64
cp38-manylinux_i686
cp38-musllinux_x86_64
cp38-musllinux_i686 | cp38-manylinux_aarch64
cp38-manylinux_ppc64le
cp38-manylinux_s390x
cp38-manylinux_armv7l
cp38-manylinux_riscv64
cp38-musllinux_aarch64
cp38-musllinux_ppc64le
cp38-musllinux_s390x
cp38-musllinux_armv7l
cp38-musllinux_riscv64 | | | +| Python 3.9 | cp39-macosx_x86_64
cp39-macosx_universal2
cp39-macosx_arm64 | cp39-win_amd64
cp39-win32
cp39-win_arm64 | cp39-manylinux_x86_64
cp39-manylinux_i686
cp39-musllinux_x86_64
cp39-musllinux_i686 | cp39-manylinux_aarch64
cp39-manylinux_ppc64le
cp39-manylinux_s390x
cp39-manylinux_armv7l
cp39-manylinux_riscv64
cp39-musllinux_aarch64
cp39-musllinux_ppc64le
cp39-musllinux_s390x
cp39-musllinux_armv7l
cp39-musllinux_riscv64 | | | +| Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-manylinux_armv7l
cp310-manylinux_riscv64
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x
cp310-musllinux_armv7l
cp310-musllinux_riscv64 | | | +| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-manylinux_armv7l
cp311-manylinux_riscv64
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x
cp311-musllinux_armv7l
cp311-musllinux_riscv64 | | | +| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-manylinux_armv7l
cp312-manylinux_riscv64
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x
cp312-musllinux_armv7l
cp312-musllinux_riscv64 | | cp312-pyodide_wasm32 | +| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-manylinux_riscv64
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l
cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos
cp313-ios_arm64_iphonesimulator
cp313-ios_x86_64_iphonesimulator | | +| Python 3.14 | cp314-macosx_x86_64
cp314-macosx_universal2
cp314-macosx_arm64 | cp314-win_amd64
cp314-win32
cp314-win_arm64 | cp314-manylinux_x86_64
cp314-manylinux_i686
cp314-musllinux_x86_64
cp314-musllinux_i686 | cp314-manylinux_aarch64
cp314-manylinux_ppc64le
cp314-manylinux_s390x
cp314-manylinux_armv7l
cp314-manylinux_riscv64
cp314-musllinux_aarch64
cp314-musllinux_ppc64le
cp314-musllinux_s390x
cp314-musllinux_armv7l
cp314-musllinux_riscv64 | | | +| PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | | | +| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | | | +| PyPy3.10 v7.3 | pp310-macosx_x86_64
pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64
pp310-manylinux_i686 | pp310-manylinux_aarch64 | | | +| PyPy3.11 v7.3 | pp311-macosx_x86_64
pp311-macosx_arm64 | pp311-win_amd64 | pp311-manylinux_x86_64
pp311-manylinux_i686 | pp311-manylinux_aarch64 | | | The list of supported and currently selected build identifiers can also be retrieved by passing the `--print-build-identifiers` flag to cibuildwheel. The format is `python_tag-platform_tag`, with tags similar to those in [PEP 425](https://www.python.org/dev/peps/pep-0425/#details). @@ -70,9 +71,6 @@ The format is `python_tag-platform_tag`, with tags similar to those in [PEP 425] Windows arm64 platform support is experimental. Linux riscv64 platform support is experimental and requires an explicit opt-in through [CIBW_ENABLE](#enable). -For an experimental WebAssembly build with `--platform pyodide`, -`cp312-pyodide_wasm32` is the only platform identifier. - See the [cibuildwheel 2 documentation](https://cibuildwheel.pypa.io/en/2.x/) for past end-of-life versions of Python. #### Examples @@ -1223,6 +1221,45 @@ Platform-specific environment variables are also available:
dependency-versions = { packages = ["pyodide-build==0.29.1"] } ``` +### `CIBW_PYODIDE_VERSION` {: #pyodide-version} + +> Specify the Pyodide version to use for `pyodide` platform builds + +This option allows you to specify a specific version of Pyodide to be used when building wheels for the `pyodide` platform. If unset, cibuildwheel will use a pinned Pyodide version. + +This option is particularly useful for: + +- Testing against specific Pyodide alpha or older releases. +- Ensuring reproducibility by targeting a known Pyodide version. + +The available Pyodide versions are determined by the version of `pyodide-build` being used. You can list the compatible versions using the command `pyodide xbuildenv search --all` as described in the [Pyodide platform documentation](platforms.md#pyodide-choosing-a-version). + +!!! tip + You can set the version of `pyodide-build` using the [`CIBW_DEPENDENCY_VERSIONS`](#dependency-versions) option. + +#### Examples + +!!! tab examples "Environment variables" + + ```yaml + # Build Pyodide wheels using Pyodide version 0.27.6 + CIBW_PYODIDE_VERSION: 0.27.6 + + # Build Pyodide wheels using a specific alpha release + CIBW_PYODIDE_VERSION: 0.28.0a2 + ``` + +!!! tab examples "pyproject.toml" + + ```toml + [tool.cibuildwheel.pyodide] + # Build Pyodide wheels using Pyodide version 0.27.6 + pyodide-version = "0.27.6" + + [tool.cibuildwheel.pyodide] + # Build Pyodide wheels using a specific alpha release + pyodide-version = "0.28.0a2" + ``` ## Testing diff --git a/docs/platforms.md b/docs/platforms.md index 115e91455..c4ffb9af6 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -3,7 +3,7 @@ title: Platforms --- # Platforms -## Linux +## Linux {: #linux} ### System requirements @@ -31,7 +31,7 @@ Linux wheels are built in [`manylinux`/`musllinux` containers](https://github.co - Alternative Docker images can be specified with the `CIBW_MANYLINUX_*_IMAGE`/`CIBW_MUSLLINUX_*_IMAGE` options to allow for a custom, preconfigured build environment for the Linux builds. See [options](options.md#linux-image) for more details. -## macOS +## macOS {: #macos} ### System requirements @@ -138,7 +138,7 @@ Regarding testing, As a workaround, the tag can be fixed before running delocate to repair the wheel. The [`wheel tags`](https://wheel.readthedocs.io/en/stable/reference/wheel_tags.html) command is ideal for this. See [this workflow](https://gist.github.com/anderssonjohan/49f07e33fc5cb2420515a8ac76dc0c95#file-build-pendulum-wheels-yml-L39-L53) for an example usage of `wheel tags`. -## Windows +## Windows {: #windows} ### System requirements @@ -160,15 +160,23 @@ By default, `ARM64` is not enabled when running on non-ARM64 runners. Use [`CIBW Pyodide is offered as an experimental feature in cibuildwheel. -### Prerequisites +### System requirements -You need to have a matching host version of Python (unlike all other cibuildwheel platforms). Linux host highly recommended; macOS hosts may work (e.g. invoking `pytest` directly in [`CIBW_TEST_COMMAND`](options.md#test-command) is [currently failing](https://github.com/pyodide/pyodide/issues/4802)) and Windows hosts will not work. +Pyodide builds require a Linux or macOS machine. ### Specifying a pyodide build You must target pyodide with `--platform pyodide` (or use `--only` on the identifier). -## iOS +### Choosing a Pyodide version {: #pyodide-choosing-a-version} + +It is also possible to target a specific Pyodide version by setting the `CIBW_PYODIDE_VERSION` option to the desired version. Users are responsible for setting an appropriate Pyodide version according to the `pyodide-build` version. A list is available in Pyodide's [cross-build environments metadata file](https://github.com/pyodide/pyodide/blob/main/pyodide-cross-build-environments.json), which can be viewed more easily by installing `pyodide-build` from PyPI and using `pyodide xbuildenv search --all` to see a compatibility table. + +### Running tests + +Currently, it's recommended to run tests using a `python -m` entrypoint, rather than a command line entrypoint, or a shell script. This is because custom entrypoints have some issues in the Pyodide virtual environment. For example, `pytest` may not work as a command line entrypoint, but will work as a `python -m pytest` entrypoint. + +## iOS {: #ios} ### System requirements diff --git a/noxfile.py b/noxfile.py index ba3ce2ee8..a31c147f6 100755 --- a/noxfile.py +++ b/noxfile.py @@ -68,17 +68,20 @@ def update_constraints(session: nox.Session) -> None: Update the dependencies inplace. """ + session.install("-e.", "click") + resources = Path("cibuildwheel/resources") if session.venv_backend != "uv": session.install("uv>=0.1.23") + # CUSTOM_COMPILE_COMMAND is a pip-compile option that tells users how to + # regenerate the constraints files + env = os.environ.copy() + env["UV_CUSTOM_COMPILE_COMMAND"] = f"nox -s {session.name}" + for minor_version in range(8, 15): python_version = f"3.{minor_version}" - env = os.environ.copy() - # CUSTOM_COMPILE_COMMAND is a pip-compile option that tells users how to - # regenerate the constraints files - env["UV_CUSTOM_COMPILE_COMMAND"] = f"nox -s {session.name}" output_file = resources / f"constraints-python{python_version.replace('.', '')}.txt" session.run( "uv", @@ -100,12 +103,19 @@ def update_constraints(session: nox.Session) -> None: pyodides = build_platforms["pyodide"]["python_configurations"] for pyodide in pyodides: python_version = ".".join(pyodide["version"].split(".")[:2]) - pyodide_build_version = pyodide["pyodide_build_version"] - output_file = resources / f"constraints-pyodide{python_version.replace('.', '')}.txt" + pyodide_version = pyodide["default_pyodide_version"] + tmp_file = Path(session.create_tmp()) / "constraints-pyodide.in" - tmp_file.write_text( - f"pip\nbuild[virtualenv]\npyodide-build=={pyodide_build_version}\nclick<8.2" + + session.run( + "python", + "bin/generate_pyodide_constraints.py", + "--output-file", + tmp_file, + pyodide_version, ) + + output_file = resources / f"constraints-pyodide{python_version.replace('.', '')}.txt" session.run( "uv", "pip", @@ -121,7 +131,8 @@ def update_constraints(session: nox.Session) -> None: @nox.session(default=False, tags=["update"]) def update_pins(session: nox.Session) -> None: """ - Update the python, docker and virtualenv pins version inplace. + Update the python, docker, virtualenv, node, and python-build-standalone + version pins inplace. """ pyproject = nox.project.load_toml() session.install("-e.", *nox.project.dependency_groups(pyproject, "bin")) @@ -129,6 +140,7 @@ def update_pins(session: nox.Session) -> None: session.run("python", "bin/update_docker.py") session.run("python", "bin/update_virtualenv.py", "--force") session.run("python", "bin/update_nodejs.py", "--force") + session.run("python", "bin/update_python_build_standalone.py") @nox.session(default=False, reuse_venv=True, tags=["update"]) diff --git a/test/test_abi_variants.py b/test/test_abi_variants.py index 780f6d908..d3734ae64 100644 --- a/test/test_abi_variants.py +++ b/test/test_abi_variants.py @@ -48,8 +48,13 @@ def test_abi3(tmp_path): # check that the expected wheels are produced if utils.get_platform() == "pyodide": - # there's only 1 possible configuration for pyodide, cp312 - expected_wheels = utils.expected_wheels("spam", "0.1.0", python_abi_tags=["cp310-abi3"]) + # there's only 1 possible configuration for pyodide, cp312. It builds + # a wheel that is tagged abi3, compatible back to 3.10 + expected_wheels = utils.expected_wheels( + "spam", + "0.1.0", + python_abi_tags=["cp310-abi3"], + ) else: expected_wheels = utils.expected_wheels( "spam", @@ -189,15 +194,17 @@ def test_abi_none(tmp_path, capfd): }, ) - # check that the expected wheels are produced expected_wheels = utils.expected_wheels("ctypesexample", "1.0.0", python_abi_tags=["py3-none"]) + # check that the expected wheels are produced assert set(actual_wheels) == set(expected_wheels) - # check that each wheel was built once, and reused captured = capfd.readouterr() - assert "Building wheel..." in captured.out + if utils.get_platform() == "pyodide": - # there's only 1 possible configuration for pyodide, we won't see the message expected on following builds + # pyodide builds a different platform tag for each python version, so + # wheels are not reused assert "Found previously built wheel" not in captured.out else: + # check that each wheel was built once, and reused + assert "Building wheel..." in captured.out assert "Found previously built wheel" in captured.out diff --git a/test/test_custom_repair_wheel.py b/test/test_custom_repair_wheel.py index bd22d6343..5af012653 100644 --- a/test/test_custom_repair_wheel.py +++ b/test/test_custom_repair_wheel.py @@ -16,6 +16,11 @@ wheel = Path(sys.argv[1]) dest_dir = Path(sys.argv[2]) platform = wheel.stem.split("-")[-1] +if platform.startswith("pyodide"): + # for the sake of this test, munge the pyodide platforms into one, it's + # not valid, but it does activate the uniqueness check + platform = "pyodide" + name = f"spam-0.1.0-py2-none-{platform}.whl" dest = dest_dir / name dest_dir.mkdir(parents=True, exist_ok=True) @@ -48,11 +53,6 @@ def test(tmp_path, capfd): assert "Build failed because a wheel named" in captured.err assert exc_info.value.returncode == 6 else: - # We only produced one wheel (currently Pyodide) + # We only produced one wheel (perhaps Pyodide) # check that it has the right name - # - # As far as I can tell, this is the only full test coverage for - # CIBW_REPAIR_WHEEL_COMMAND so this is useful even in the case when no - # error is raised - assert "spam-0.1.0-py2-none-pyodide" in captured.out assert result[0].startswith("spam-0.1.0-py2-none-") diff --git a/test/test_dependency_versions.py b/test/test_dependency_versions.py index 6e29adef1..5aa84a506 100644 --- a/test/test_dependency_versions.py +++ b/test/test_dependency_versions.py @@ -70,7 +70,10 @@ def test_pinned_versions(tmp_path, python_version, build_frontend_env_nouv): version_no_dot = python_version.replace(".", "") build_environment = {} build_pattern = f"[cp]p{version_no_dot}-*" - constraint_filename = f"constraints-python{version_no_dot}.txt" + if utils.get_platform() == "pyodide": + constraint_filename = f"constraints-pyodide{version_no_dot}.txt" + else: + constraint_filename = f"constraints-python{version_no_dot}.txt" constraint_file = resources.PATH / constraint_filename constraint_versions = get_versions_from_constraint_file(constraint_file) diff --git a/test/test_environment.py b/test/test_environment.py index ee50155b5..30bab81f5 100644 --- a/test/test_environment.py +++ b/test/test_environment.py @@ -100,7 +100,12 @@ def test_overridden_path(tmp_path, capfd): "build_frontend", [ pytest.param("pip", marks=utils.skip_if_pyodide("No pip for pyodide")), - "build", + pytest.param( + "build", + marks=utils.skip_if_pyodide( + "pyodide doesn't support multiple values for PIP_CONSTRAINT" + ), + ), ], ) def test_overridden_pip_constraint(tmp_path, build_frontend): diff --git a/test/test_pep518.py b/test/test_pep518.py index d519cc297..702e7f185 100644 --- a/test/test_pep518.py +++ b/test/test_pep518.py @@ -60,12 +60,19 @@ def test_pep518(tmp_path, build_frontend_env): assert not (project_dir / "42").exists() assert not (project_dir / "4.1.2").exists() - # pypa/build creates a "build" folder & a "*.egg-info" folder for the wheel being built, - # this should be harmless so remove them + # pypa/build creates a "build" folder & a "*.egg-info" folder for the + # wheel being built, this should be harmless so remove them. pyodide-build + # creates a ".pyodide_build" folder, but this is gitignored with a + # .gitignore file inside. contents = [ item for item in project_dir.iterdir() - if item.name != "build" and not item.name.endswith(".egg-info") + if item.name != "build" + and not item.name.endswith(".egg-info") + and item.name != ".pyodide_build" ] + print("Project contents after build:") + print("\n".join(f" {f}" for f in contents)) + assert len(contents) == len(basic_project.files) diff --git a/test/test_emscripten.py b/test/test_pyodide.py similarity index 52% rename from test/test_emscripten.py rename to test/test_pyodide.py index 897cd67f1..e1dbbd56e 100644 --- a/test/test_emscripten.py +++ b/test/test_pyodide.py @@ -1,10 +1,10 @@ -import shutil +import contextlib +import subprocess import sys import textwrap import pytest -from cibuildwheel.ci import CIProvider, detect_ci_provider from cibuildwheel.util.file import CIBW_CACHE_PATH from . import test_projects, utils @@ -43,13 +43,7 @@ def check_node(): @pytest.mark.parametrize("use_pyproject_toml", [True, False]) def test_pyodide_build(tmp_path, use_pyproject_toml): if sys.platform == "win32": - pytest.skip("emsdk doesn't work correctly on Windows") - - if not shutil.which("python3.12"): - pytest.skip("Python 3.12 not installed") - - if detect_ci_provider() == CIProvider.travis_ci: - pytest.skip("Python 3.12 is just a non-working pyenv shim") + pytest.skip("pyodide-build doesn't work correctly on Windows") if use_pyproject_toml: basic_project.files["pyproject.toml"] = textwrap.dedent( @@ -84,3 +78,64 @@ def test_pyodide_build(tmp_path, use_pyproject_toml): print("expected_wheels", expected_wheels) assert set(actual_wheels) == set(expected_wheels) + + +def test_pyodide_version_incompatible(tmp_path, capfd): + if sys.platform == "win32": + pytest.skip("pyodide-build doesn't work correctly on Windows") + + basic_project.generate(tmp_path) + + with pytest.raises(subprocess.CalledProcessError): + utils.cibuildwheel_run( + tmp_path, + add_args=["--platform", "pyodide"], + add_env={ + "CIBW_DEPENDENCY_VERSIONS": "packages: pyodide-build==0.29.3", + "CIBW_PYODIDE_VERSION": "0.26.0a6", + }, + ) + + out, err = capfd.readouterr() + + assert "is not compatible with the pyodide-build version" in err + + +@pytest.mark.parametrize("expect_failure", [True, False]) +def test_pyodide_build_and_test(tmp_path, expect_failure): + if sys.platform == "win32": + pytest.skip("pyodide-build doesn't work correctly on Windows") + + if expect_failure: + basic_project.files["test/spam_test.py"] = textwrap.dedent(r""" + def test_filter(): + assert 0 == 1 + """) + else: + basic_project.files["test/spam_test.py"] = textwrap.dedent(r""" + import spam + def test_filter(): + assert spam.filter("spam") == 0 + """) + basic_project.generate(tmp_path) + + context = ( + pytest.raises(subprocess.CalledProcessError) if expect_failure else contextlib.nullcontext() + ) + with context: + # build the wheels + actual_wheels = utils.cibuildwheel_run( + tmp_path, + add_args=["--platform", "pyodide"], + add_env={ + "CIBW_TEST_REQUIRES": "pytest", + "CIBW_TEST_COMMAND": "python -m pytest", + }, + ) + # check that the expected wheels are produced + expected_wheels = [ + "spam-0.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl", + ] + print("actual_wheels", actual_wheels) + print("expected_wheels", expected_wheels) + assert set(actual_wheels) == set(expected_wheels) diff --git a/test/test_testing.py b/test/test_testing.py index 9b7364f99..a13c106fa 100644 --- a/test/test_testing.py +++ b/test/test_testing.py @@ -218,10 +218,10 @@ def test_test_sources(tmp_path): project_dir, add_env={ "CIBW_TEST_REQUIRES": "pytest", - "CIBW_TEST_COMMAND": "pytest", # pytest fails on GraalPy 24.2.0 on Windows so we skip it there # until https://github.com/oracle/graalpython/issues/490 is fixed "CIBW_TEST_COMMAND_WINDOWS": "where graalpy || pytest", + "CIBW_TEST_COMMAND": utils.invoke_pytest(), "CIBW_TEST_SOURCES": "test", }, ) diff --git a/test/utils.py b/test/utils.py index 028e88863..55b492a81 100644 --- a/test/utils.py +++ b/test/utils.py @@ -382,7 +382,15 @@ def _expected_wheels( raise Exception(msg) elif platform == "pyodide": - platform_tags = ["pyodide_2024_0_wasm32"] + platform_tags = { + "cp312-cp312": ["pyodide_2024_0_wasm32"], + "cp313-cp313": ["pyodide_2025_0_wasm32"], + }.get(python_abi_tag, []) + + if not platform_tags: + # for example if the python tag is `none` or `abi3`, all + # platform tags are built with that python tag + platform_tags = ["pyodide_2024_0_wasm32"] else: msg = f"Unsupported platform {platform!r}" @@ -425,7 +433,7 @@ def skip_if_pyodide(reason: str) -> Any: def invoke_pytest() -> str: # see https://github.com/pyodide/pyodide/issues/4802 - if get_platform() == "pyodide" and sys.platform.startswith("darwin"): + if get_platform() == "pyodide": return "python -m pytest" return "pytest" diff --git a/unit_test/options_test.py b/unit_test/options_test.py index fbf181754..9e5a1f089 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -34,6 +34,8 @@ environment-pass = ["EXAMPLE_ENV"] +pyodide-version = "0.27.6" + [tool.cibuildwheel.macos] test-requires = "else" @@ -88,6 +90,9 @@ def test_options_1(tmp_path, monkeypatch): assert local.test_sources == ["test", "other dir"] assert local.manylinux_images["x86_64"] == pinned_x86_64_container_image["manylinux_2_34"] + local = options.build_options("cp312-pyodide_wasm32") + assert local.pyodide_version == "0.27.6" + def test_passthrough(tmp_path, monkeypatch): with tmp_path.joinpath("pyproject.toml").open("w") as f: diff --git a/unit_test/utils_test.py b/unit_test/utils_test.py index 3c4a58859..8b0be33d3 100644 --- a/unit_test/utils_test.py +++ b/unit_test/utils_test.py @@ -12,6 +12,8 @@ format_safe, parse_key_value_string, prepare_command, + unwrap, + unwrap_preserving_paragraphs, ) from cibuildwheel.util.packaging import find_compatible_wheel @@ -363,3 +365,33 @@ def test_copy_test_sources_alternate_copy_into(sample_project): ], any_order=True, ) + + +def test_unwrap(): + assert ( + unwrap(""" + This is a + multiline + string + """) + == "This is a multiline string" + ) + + +def test_unwrap_preserving_paragraphs(): + assert ( + unwrap(""" + This is a + multiline + string + """) + == "This is a multiline string" + ) + assert ( + unwrap_preserving_paragraphs(""" + paragraph one + + paragraph two + """) + == "paragraph one\n\nparagraph two" + )