feat: Pyodide improvements: version setting, standalone environments (#2002)
* Fix a typo: pyoodide ➡️ pyodide * Add `pyodide_build_version` attribute * Add version to xbuildenv log step * Add version to Emscripten log step * Use `pyodide-build`'s version for updating constraints * Bump Pyodide constraints by updating `pyodide-build` * Add a schema for `pyodide-version` * Update Pyodide constraints * Bump `pyodide-build` to new 0.29.0 * Test out another Pyodide identifier * Update outdated Pyodide constraints * Add Pyodide version to temp directory name * Remove Pyodide 0.26.1 from build configurations * Retrieve + validate + install specific xbuildenvs * Test wheel builds with Pyodide 0.26.2 * Add correct Pyodide version to identifier temp dir * Don't pre-call Pyodide xbuildenv search * Fetch just the stable Pyodide versions * Refactor search + validation + install into one step * Move all of it under a lock * Reorder xbuildenv installation * Add env and cwd to xbuildenv search call * Temporarily lower to 0.26.2 target * Separate out search, validate, install; again * Run xbuildenv search in `CIBW_CACHE_PATH` * Remove prior `PYODIDE_ROOT` env vars, copy envs * Validate doesn't need to depend on searching * Add file lock when searching xbuildenvs * Test the original version: 0.26.1 * Update Pyodide constraints * Update constraints for `pyodide-build` 0.29.0 again * Bump Pyodide from version 0.26.1 ➡️ version 0.26.4 * Add note on compatibility for macOS + other archs * Note Pyodide version for Pyodide identifier * Docs about `CIBW_PYODIDE_VERSION` * Don't fetch just the stable versions * Discard a variable that's not used later * Rename `search_xbuildenv` ➡️ `get_xbuildenv_versions` * `validate_xbuildenv` ➡️ `validate_xbuildenv_version` * Replace ordered comment, add newline * Replace sentence on macOS support Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com> * Capitalise: "pyodide" ➡️ "Pyodide" Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com> * "work" ➡️ "may succeed" Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com> * Add another job to test a custom Pyodide version * Handle "v"-prefixed + non-prefixed versions * Convert to a proper toml-able option, and remove some hardcoded versions This removes the enscripten and pyodide-build version specs from pyproject.toml - pyodide-build is spec'd in the constraints file, and the emscripten version can be read from the pyodide-build output. * Add a schema entry * Add docs for CIBW_PYODIDE_VERSION * Rephrase * Add tests for pyodide-version * Apply suggestions from code review * Add python_build_standalone util * Hook up to python-build-standalone, removing dependency on host python * Remove python hard-code in action.yml * Add log step * Add workaround for https://github.com/pyodide/pyodide-build/issues/143 * Add emscripten pytest test * Remove unneeded checks * Fix a pytest invoke for emscripten * Generate pyodide-build constraints from the pinned pyodide version * Remove pyodide python-build-standalone workaround * Fixup paths from newer version of pyodide-build * Fix/skip some failing tests * Use `python -m pytest` on pyodide, even on Linux * Docs fixes * Don't call the github API at runtime, cache the release assets instead * Ignore pylint false positive * Add version auto-updating for pyodide * Add support for pyodide 3.13. * Fix tests for multiple pyodide wheels * Remove workaround for unreleased pyodide-build * Rename to "test_pyodide" * Fix pathname confusion * Remove extra github actions job * Fix expectation for test_abi_none * Fix the custom_repair_wheel test to actually have clashing names * Fix pinned version test * Document test-command limitation * Remove pyodide 0.28.0a1 for now * Update constraints files * Docs/test fixes post removing pyodide cp313 * Fix ABI test expectation * Docs improvements * Improve some comments * Remove logic duplication * remove pyodide special casing * Refactor constraints code to use a utility script, circumventing import issues * chore: nicer nox env Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: typo in variable name found by copilot Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Apply suggestions from code review * Some more Pyodide version updates in the docs section * We haven't released Pyodide v0.27.6 yet * Back to the Github URL for cross-build-environments * `pyodide-build`, not `emsdk` for Windows skips Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com> * Move to a separate `_json_request` function Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com> * Rename "retries" ➡️ "retry_count" * Add some type hints * Copy env vars before `UV_CUSTOM_COMPILE_COMMAND` * Use `HTTPError.headers.get` instead * Remove extra end quote * Change download tests URL to `https://badssl.com/` Co-Authored-By: Joe Rickerby <1244307+joerick@users.noreply.github.com> * Download size changes, too * Use jsdelivr for Github asset mirroring * Bump to Pyodide v0.27.6 * Fix unit tests * Move to pyodide v0.27.6 again * Bump to pyodide-build 0.30.4 * Use new URL for cross-build environments metadata Co-authored-by: Joe Rickerby <joerick@mac.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com> Co-authored-by: Joe Rickerby <joerick@mac.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Joe Rickerby <1244307+joerick@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Hood Chatham
Joe Rickerby
Joe Rickerby
Henry Schreiner
pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
parent
c3c260dff7
commit
163864158d
@@ -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
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
Executable
+69
@@ -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()
|
||||
@@ -197,6 +197,9 @@ properties:
|
||||
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
|
||||
|
||||
Executable
+59
@@ -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()
|
||||
+51
-2
@@ -19,7 +19,7 @@ from packaging.version import Version
|
||||
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 @@ class CPythonIOSVersions:
|
||||
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 @@ class AllVersions:
|
||||
|
||||
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 @@ class AllVersions:
|
||||
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
|
||||
|
||||
+70
-1
@@ -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")))
|
||||
|
||||
@@ -112,6 +112,7 @@ class BuildOptions:
|
||||
build_frontend: BuildFrontendConfig | None
|
||||
config_settings: str
|
||||
container_engine: OCIContainerEngineConfig
|
||||
pyodide_version: str | None
|
||||
|
||||
@property
|
||||
def package_dir(self) -> Path:
|
||||
@@ -850,6 +851,8 @@ class Options:
|
||||
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,
|
||||
@@ -871,6 +874,7 @@ class Options:
|
||||
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:
|
||||
|
||||
@@ -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 @@ from ..util.file import (
|
||||
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 @@ IS_WIN: Final[bool] = sys.platform.startswith("win")
|
||||
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,
|
||||
)
|
||||
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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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": [
|
||||
@@ -718,6 +723,9 @@
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/properties/xbuild-tools"
|
||||
},
|
||||
"pyodide-version": {
|
||||
"$ref": "#/properties/pyodide-version"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/properties/repair-wheel-command"
|
||||
},
|
||||
@@ -830,6 +838,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": [
|
||||
@@ -900,6 +911,9 @@
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/properties/xbuild-tools"
|
||||
},
|
||||
"pyodide-version": {
|
||||
"$ref": "#/properties/pyodide-version"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/properties/repair-wheel-command"
|
||||
},
|
||||
@@ -957,6 +971,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": [
|
||||
@@ -1027,6 +1044,9 @@
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/properties/xbuild-tools"
|
||||
},
|
||||
"pyodide-version": {
|
||||
"$ref": "#/properties/pyodide-version"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/properties/repair-wheel-command"
|
||||
},
|
||||
@@ -1084,6 +1104,9 @@
|
||||
"xbuild-tools": {
|
||||
"$ref": "#/properties/xbuild-tools"
|
||||
},
|
||||
"pyodide-version": {
|
||||
"$ref": "#/properties/pyodide-version"
|
||||
},
|
||||
"repair-wheel-command": {
|
||||
"$ref": "#/properties/repair-wheel-command"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,6 +28,8 @@ test-environment = {}
|
||||
|
||||
container-engine = "docker"
|
||||
|
||||
pyodide-version = ""
|
||||
|
||||
manylinux-x86_64-image = "manylinux_2_28"
|
||||
manylinux-i686-image = "manylinux2014"
|
||||
manylinux-aarch64-image = "manylinux_2_28"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -16,6 +16,7 @@ BUILD_PLATFORMS: Final[Path] = PATH / "build-platforms.toml"
|
||||
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
|
||||
|
||||
+55
-18
@@ -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:
|
||||
|
||||
<div class="build-id-table-marker"></div>
|
||||
|
||||
| | macOS | Windows | Linux Intel | Linux Other | iOS |
|
||||
|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| Python 3.8 | cp38-macosx_x86_64<br/>cp38-macosx_universal2<br/>cp38-macosx_arm64 | cp38-win_amd64<br/>cp38-win32 | cp38-manylinux_x86_64<br/>cp38-manylinux_i686<br/>cp38-musllinux_x86_64<br/>cp38-musllinux_i686 | cp38-manylinux_aarch64<br/>cp38-manylinux_ppc64le<br/>cp38-manylinux_s390x<br/>cp38-manylinux_armv7l<br/>cp38-manylinux_riscv64<br/>cp38-musllinux_aarch64<br/>cp38-musllinux_ppc64le<br/>cp38-musllinux_s390x<br/>cp38-musllinux_armv7l<br/>cp38-musllinux_riscv64 | |
|
||||
| Python 3.9 | cp39-macosx_x86_64<br/>cp39-macosx_universal2<br/>cp39-macosx_arm64 | cp39-win_amd64<br/>cp39-win32<br/>cp39-win_arm64 | cp39-manylinux_x86_64<br/>cp39-manylinux_i686<br/>cp39-musllinux_x86_64<br/>cp39-musllinux_i686 | cp39-manylinux_aarch64<br/>cp39-manylinux_ppc64le<br/>cp39-manylinux_s390x<br/>cp39-manylinux_armv7l<br/>cp39-manylinux_riscv64<br/>cp39-musllinux_aarch64<br/>cp39-musllinux_ppc64le<br/>cp39-musllinux_s390x<br/>cp39-musllinux_armv7l<br/>cp39-musllinux_riscv64 | |
|
||||
| Python 3.10 | cp310-macosx_x86_64<br/>cp310-macosx_universal2<br/>cp310-macosx_arm64 | cp310-win_amd64<br/>cp310-win32<br/>cp310-win_arm64 | cp310-manylinux_x86_64<br/>cp310-manylinux_i686<br/>cp310-musllinux_x86_64<br/>cp310-musllinux_i686 | cp310-manylinux_aarch64<br/>cp310-manylinux_ppc64le<br/>cp310-manylinux_s390x<br/>cp310-manylinux_armv7l<br/>cp310-manylinux_riscv64<br/>cp310-musllinux_aarch64<br/>cp310-musllinux_ppc64le<br/>cp310-musllinux_s390x<br/>cp310-musllinux_armv7l<br/>cp310-musllinux_riscv64 | |
|
||||
| Python 3.11 | cp311-macosx_x86_64<br/>cp311-macosx_universal2<br/>cp311-macosx_arm64 | cp311-win_amd64<br/>cp311-win32<br/>cp311-win_arm64 | cp311-manylinux_x86_64<br/>cp311-manylinux_i686<br/>cp311-musllinux_x86_64<br/>cp311-musllinux_i686 | cp311-manylinux_aarch64<br/>cp311-manylinux_ppc64le<br/>cp311-manylinux_s390x<br/>cp311-manylinux_armv7l<br/>cp311-manylinux_riscv64<br/>cp311-musllinux_aarch64<br/>cp311-musllinux_ppc64le<br/>cp311-musllinux_s390x<br/>cp311-musllinux_armv7l<br/>cp311-musllinux_riscv64 | |
|
||||
| Python 3.12 | cp312-macosx_x86_64<br/>cp312-macosx_universal2<br/>cp312-macosx_arm64 | cp312-win_amd64<br/>cp312-win32<br/>cp312-win_arm64 | cp312-manylinux_x86_64<br/>cp312-manylinux_i686<br/>cp312-musllinux_x86_64<br/>cp312-musllinux_i686 | cp312-manylinux_aarch64<br/>cp312-manylinux_ppc64le<br/>cp312-manylinux_s390x<br/>cp312-manylinux_armv7l<br/>cp312-manylinux_riscv64<br/>cp312-musllinux_aarch64<br/>cp312-musllinux_ppc64le<br/>cp312-musllinux_s390x<br/>cp312-musllinux_armv7l<br/>cp312-musllinux_riscv64 | |
|
||||
| Python 3.13 | cp313-macosx_x86_64<br/>cp313-macosx_universal2<br/>cp313-macosx_arm64 | cp313-win_amd64<br/>cp313-win32<br/>cp313-win_arm64 | cp313-manylinux_x86_64<br/>cp313-manylinux_i686<br/>cp313-musllinux_x86_64<br/>cp313-musllinux_i686 | cp313-manylinux_aarch64<br/>cp313-manylinux_ppc64le<br/>cp313-manylinux_s390x<br/>cp313-manylinux_armv7l<br/>cp313-manylinux_riscv64<br/>cp313-musllinux_aarch64<br/>cp313-musllinux_ppc64le<br/>cp313-musllinux_s390x<br/>cp313-musllinux_armv7l<br/>cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos<br/>cp313-ios_arm64_iphonesimulator<br/>cp313-ios_x86_64_iphonesimulator |
|
||||
| Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64 | |
|
||||
| PyPy3.8 v7.3 | pp38-macosx_x86_64<br/>pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64<br/>pp38-manylinux_i686 | pp38-manylinux_aarch64 | |
|
||||
| PyPy3.9 v7.3 | pp39-macosx_x86_64<br/>pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64<br/>pp39-manylinux_i686 | pp39-manylinux_aarch64 | |
|
||||
| PyPy3.10 v7.3 | pp310-macosx_x86_64<br/>pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64<br/>pp310-manylinux_i686 | pp310-manylinux_aarch64 | |
|
||||
| PyPy3.11 v7.3 | pp311-macosx_x86_64<br/>pp311-macosx_arm64 | pp311-win_amd64 | pp311-manylinux_x86_64<br/>pp311-manylinux_i686 | pp311-manylinux_aarch64 | |
|
||||
| | macOS | Windows | Linux Intel | Linux Other | iOS | pyodide (WASM) |
|
||||
|---------------|------------------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|----------------------|
|
||||
| Python 3.8 | cp38-macosx_x86_64<br/>cp38-macosx_universal2<br/>cp38-macosx_arm64 | cp38-win_amd64<br/>cp38-win32 | cp38-manylinux_x86_64<br/>cp38-manylinux_i686<br/>cp38-musllinux_x86_64<br/>cp38-musllinux_i686 | cp38-manylinux_aarch64<br/>cp38-manylinux_ppc64le<br/>cp38-manylinux_s390x<br/>cp38-manylinux_armv7l<br/>cp38-manylinux_riscv64<br/>cp38-musllinux_aarch64<br/>cp38-musllinux_ppc64le<br/>cp38-musllinux_s390x<br/>cp38-musllinux_armv7l<br/>cp38-musllinux_riscv64 | | |
|
||||
| Python 3.9 | cp39-macosx_x86_64<br/>cp39-macosx_universal2<br/>cp39-macosx_arm64 | cp39-win_amd64<br/>cp39-win32<br/>cp39-win_arm64 | cp39-manylinux_x86_64<br/>cp39-manylinux_i686<br/>cp39-musllinux_x86_64<br/>cp39-musllinux_i686 | cp39-manylinux_aarch64<br/>cp39-manylinux_ppc64le<br/>cp39-manylinux_s390x<br/>cp39-manylinux_armv7l<br/>cp39-manylinux_riscv64<br/>cp39-musllinux_aarch64<br/>cp39-musllinux_ppc64le<br/>cp39-musllinux_s390x<br/>cp39-musllinux_armv7l<br/>cp39-musllinux_riscv64 | | |
|
||||
| Python 3.10 | cp310-macosx_x86_64<br/>cp310-macosx_universal2<br/>cp310-macosx_arm64 | cp310-win_amd64<br/>cp310-win32<br/>cp310-win_arm64 | cp310-manylinux_x86_64<br/>cp310-manylinux_i686<br/>cp310-musllinux_x86_64<br/>cp310-musllinux_i686 | cp310-manylinux_aarch64<br/>cp310-manylinux_ppc64le<br/>cp310-manylinux_s390x<br/>cp310-manylinux_armv7l<br/>cp310-manylinux_riscv64<br/>cp310-musllinux_aarch64<br/>cp310-musllinux_ppc64le<br/>cp310-musllinux_s390x<br/>cp310-musllinux_armv7l<br/>cp310-musllinux_riscv64 | | |
|
||||
| Python 3.11 | cp311-macosx_x86_64<br/>cp311-macosx_universal2<br/>cp311-macosx_arm64 | cp311-win_amd64<br/>cp311-win32<br/>cp311-win_arm64 | cp311-manylinux_x86_64<br/>cp311-manylinux_i686<br/>cp311-musllinux_x86_64<br/>cp311-musllinux_i686 | cp311-manylinux_aarch64<br/>cp311-manylinux_ppc64le<br/>cp311-manylinux_s390x<br/>cp311-manylinux_armv7l<br/>cp311-manylinux_riscv64<br/>cp311-musllinux_aarch64<br/>cp311-musllinux_ppc64le<br/>cp311-musllinux_s390x<br/>cp311-musllinux_armv7l<br/>cp311-musllinux_riscv64 | | |
|
||||
| Python 3.12 | cp312-macosx_x86_64<br/>cp312-macosx_universal2<br/>cp312-macosx_arm64 | cp312-win_amd64<br/>cp312-win32<br/>cp312-win_arm64 | cp312-manylinux_x86_64<br/>cp312-manylinux_i686<br/>cp312-musllinux_x86_64<br/>cp312-musllinux_i686 | cp312-manylinux_aarch64<br/>cp312-manylinux_ppc64le<br/>cp312-manylinux_s390x<br/>cp312-manylinux_armv7l<br/>cp312-manylinux_riscv64<br/>cp312-musllinux_aarch64<br/>cp312-musllinux_ppc64le<br/>cp312-musllinux_s390x<br/>cp312-musllinux_armv7l<br/>cp312-musllinux_riscv64 | | cp312-pyodide_wasm32 |
|
||||
| Python 3.13 | cp313-macosx_x86_64<br/>cp313-macosx_universal2<br/>cp313-macosx_arm64 | cp313-win_amd64<br/>cp313-win32<br/>cp313-win_arm64 | cp313-manylinux_x86_64<br/>cp313-manylinux_i686<br/>cp313-musllinux_x86_64<br/>cp313-musllinux_i686 | cp313-manylinux_aarch64<br/>cp313-manylinux_ppc64le<br/>cp313-manylinux_s390x<br/>cp313-manylinux_armv7l<br/>cp313-manylinux_riscv64<br/>cp313-musllinux_aarch64<br/>cp313-musllinux_ppc64le<br/>cp313-musllinux_s390x<br/>cp313-musllinux_armv7l<br/>cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos<br/>cp313-ios_arm64_iphonesimulator<br/>cp313-ios_x86_64_iphonesimulator | |
|
||||
| Python 3.14 | cp314-macosx_x86_64<br/>cp314-macosx_universal2<br/>cp314-macosx_arm64 | cp314-win_amd64<br/>cp314-win32<br/>cp314-win_arm64 | cp314-manylinux_x86_64<br/>cp314-manylinux_i686<br/>cp314-musllinux_x86_64<br/>cp314-musllinux_i686 | cp314-manylinux_aarch64<br/>cp314-manylinux_ppc64le<br/>cp314-manylinux_s390x<br/>cp314-manylinux_armv7l<br/>cp314-manylinux_riscv64<br/>cp314-musllinux_aarch64<br/>cp314-musllinux_ppc64le<br/>cp314-musllinux_s390x<br/>cp314-musllinux_armv7l<br/>cp314-musllinux_riscv64 | | |
|
||||
| PyPy3.8 v7.3 | pp38-macosx_x86_64<br/>pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64<br/>pp38-manylinux_i686 | pp38-manylinux_aarch64 | | |
|
||||
| PyPy3.9 v7.3 | pp39-macosx_x86_64<br/>pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64<br/>pp39-manylinux_i686 | pp39-manylinux_aarch64 | | |
|
||||
| PyPy3.10 v7.3 | pp310-macosx_x86_64<br/>pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64<br/>pp310-manylinux_i686 | pp310-manylinux_aarch64 | | |
|
||||
| PyPy3.11 v7.3 | pp311-macosx_x86_64<br/>pp311-macosx_arm64 | pp311-win_amd64 | pp311-manylinux_x86_64<br/>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:<br/>
|
||||
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
|
||||
|
||||
|
||||
+14
-6
@@ -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
|
||||
|
||||
|
||||
+21
-9
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,11 @@ from pathlib import Path
|
||||
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-")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
+10
-3
@@ -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)
|
||||
|
||||
@@ -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 @@ if __name__ == "__main__":
|
||||
@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)
|
||||
@@ -224,10 +224,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",
|
||||
},
|
||||
)
|
||||
|
||||
+10
-2
@@ -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"
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ manylinux-x86_64-image = "manylinux_2_28"
|
||||
|
||||
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:
|
||||
|
||||
@@ -12,6 +12,8 @@ from cibuildwheel.util.helpers import (
|
||||
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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user