Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the use of arbitrary Pyodide versions #2002

Draft
wants to merge 53 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
2ae389d
Fix a typo: pyoodide ➡️ pyodide
agriyakhetarpal Sep 10, 2024
0e3b3f1
Add `pyodide_build_version` attribute
agriyakhetarpal Sep 10, 2024
394459f
Add version to xbuildenv log step
agriyakhetarpal Sep 10, 2024
dff7bf2
Add version to Emscripten log step
agriyakhetarpal Sep 10, 2024
16057bc
Use `pyodide-build`'s version for updating constraints
agriyakhetarpal Sep 10, 2024
f167c50
Bump Pyodide constraints by updating `pyodide-build`
agriyakhetarpal Sep 10, 2024
31a6be9
Add a schema for `pyodide-version`
agriyakhetarpal Sep 12, 2024
cbca40b
Merge branch 'main' into feat/distinct-pyodide-build
agriyakhetarpal Sep 12, 2024
9003067
Update Pyodide constraints
agriyakhetarpal Sep 12, 2024
d8a8d5e
Bump `pyodide-build` to new 0.29.0
agriyakhetarpal Sep 24, 2024
cf5dd3e
Test out another Pyodide identifier
agriyakhetarpal Sep 24, 2024
55459cb
Merge branch 'main' into feat/distinct-pyodide-build
agriyakhetarpal Sep 24, 2024
4fe86e0
Update outdated Pyodide constraints
agriyakhetarpal Sep 24, 2024
b6830ee
Add Pyodide version to temp directory name
agriyakhetarpal Sep 24, 2024
aaf32e5
Remove Pyodide 0.26.1 from build configurations
agriyakhetarpal Sep 24, 2024
bb6e0d6
Retrieve + validate + install specific xbuildenvs
agriyakhetarpal Sep 24, 2024
735d5bb
Test wheel builds with Pyodide 0.26.2
agriyakhetarpal Sep 24, 2024
b8ac6c0
Add correct Pyodide version to identifier temp dir
agriyakhetarpal Sep 24, 2024
7796311
Don't pre-call Pyodide xbuildenv search
agriyakhetarpal Sep 24, 2024
97e22c8
Fetch just the stable Pyodide versions
agriyakhetarpal Sep 24, 2024
d30eb6a
Refactor search + validation + install into one step
agriyakhetarpal Sep 24, 2024
ce2a3f0
Move all of it under a lock
agriyakhetarpal Sep 24, 2024
13fbf66
Reorder xbuildenv installation
agriyakhetarpal Sep 24, 2024
14ec071
Add env and cwd to xbuildenv search call
agriyakhetarpal Sep 24, 2024
aae64bb
Temporarily lower to 0.26.2 target
agriyakhetarpal Sep 24, 2024
6956121
Separate out search, validate, install; again
agriyakhetarpal Sep 24, 2024
e5d8443
Run xbuildenv search in `CIBW_CACHE_PATH`
agriyakhetarpal Sep 24, 2024
95c3681
Remove prior `PYODIDE_ROOT` env vars, copy envs
agriyakhetarpal Sep 24, 2024
09af46f
Validate doesn't need to depend on searching
agriyakhetarpal Sep 24, 2024
66999cb
Add file lock when searching xbuildenvs
agriyakhetarpal Sep 24, 2024
1af1e5d
Test the original version: 0.26.1
agriyakhetarpal Sep 24, 2024
ad3a203
Merge branch 'main' into feat/distinct-pyodide-build
agriyakhetarpal Oct 22, 2024
d344548
Update Pyodide constraints
agriyakhetarpal Oct 22, 2024
b3143b4
Merge remote-tracking branch 'upstream/main' into feat/distinct-pyodi…
agriyakhetarpal Nov 21, 2024
738ed1f
Update constraints for `pyodide-build` 0.29.0 again
agriyakhetarpal Nov 21, 2024
41e466a
Bump Pyodide from version 0.26.1 ➡️ version 0.26.4
agriyakhetarpal Nov 21, 2024
2a66ddd
Add note on compatibility for macOS + other archs
agriyakhetarpal Nov 21, 2024
dff72ca
Note Pyodide version for Pyodide identifier
agriyakhetarpal Nov 21, 2024
9a78845
Docs about `CIBW_PYODIDE_VERSION`
agriyakhetarpal Nov 21, 2024
e551021
Don't fetch just the stable versions
agriyakhetarpal Nov 21, 2024
1fe8a04
Discard a variable that's not used later
agriyakhetarpal Nov 21, 2024
8a8177c
Rename `search_xbuildenv` ➡️ `get_xbuildenv_versions`
agriyakhetarpal Nov 21, 2024
fffb705
`validate_xbuildenv` ➡️ `validate_xbuildenv_version`
agriyakhetarpal Nov 21, 2024
0df3c45
Replace ordered comment, add newline
agriyakhetarpal Nov 21, 2024
057e542
Replace sentence on macOS support
agriyakhetarpal Nov 21, 2024
29b5eff
Capitalise: "pyodide" ➡️ "Pyodide"
agriyakhetarpal Nov 21, 2024
321e0de
"work" ➡️ "may succeed"
agriyakhetarpal Nov 21, 2024
92babdb
Add another job to test a custom Pyodide version
agriyakhetarpal Nov 21, 2024
d73f71f
Handle "v"-prefixed + non-prefixed versions
agriyakhetarpal Nov 21, 2024
0f52a4b
Merge remote-tracking branch 'upstream/main' into feat/distinct-pyodi…
agriyakhetarpal Nov 22, 2024
0b09321
Merge branch 'main' into feat/distinct-pyodide-build
joerick Mar 21, 2025
1b4f911
Convert to a proper toml-able option, and remove some hardcoded versions
joerick Mar 21, 2025
9d0f6bc
Add a schema entry
joerick Mar 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,13 @@ jobs:
run: uv run pytest --run-emulation ${{ matrix.arch }} test/test_emulation.py

test-pyodide:
name: Test cibuildwheel building Pyodide wheels
name: Test cibuildwheel building Pyodide wheels (${{ matrix.pyodide-version }} version)
needs: lint
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
pyodide-version: ["default", "custom"]
timeout-minutes: 180
steps:
- uses: actions/checkout@v4
Expand All @@ -199,12 +203,26 @@ jobs:
uv run -m test.test_projects test.test_0_basic.basic_project sample_proj

- name: Run a sample build (GitHub Action)
if: matrix.pyodide-version == 'default'
uses: ./
with:
package-dir: sample_proj
output-dir: wheelhouse
env:
CIBW_PLATFORM: pyodide

- name: Run a sample build (GitHub Action) for an overridden Pyodide version
if: matrix.pyodide-version == 'custom'
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_PYODIDE_VERSION: "0.27.0a2"

- name: Run tests with 'CIBW_PLATFORM' set to 'pyodide'
run: |
Expand Down
3 changes: 3 additions & 0 deletions bin/generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@
musllinux-x86_64-image:
type: string
description: Specify alternative manylinux / musllinux container images
pyodide-version:
type: string
description: Specify the version of Pyodide to use
repair-wheel-command:
type: string_array
description: Execute a shell command to repair each built wheel.
Expand Down
4 changes: 4 additions & 0 deletions cibuildwheel/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class BuildOptions:
build_frontend: BuildFrontendConfig | None
config_settings: str
container_engine: OCIContainerEngineConfig
pyodide_version: str | None

@property
def package_dir(self) -> Path:
Expand Down Expand Up @@ -825,6 +826,8 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions:
msg = f"Failed to parse container config. {e}"
raise errors.ConfigurationError(msg) from e

pyodide_version = self.reader.get("pyodide-version", env_plat=False)

return BuildOptions(
globals=self.globals,
test_command=test_command,
Expand All @@ -844,6 +847,7 @@ def _compute_build_options(self, identifier: str | None) -> BuildOptions:
build_frontend=build_frontend,
config_settings=config_settings,
container_engine=container_engine,
pyodide_version=pyodide_version or None,
)

def check_for_invalid_configuration(self, identifiers: Iterable[str]) -> None:
Expand Down
132 changes: 115 additions & 17 deletions cibuildwheel/pyodide.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import functools
import json
import os
import shutil
import sys
import tomllib
import typing
from collections.abc import Sequence, 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

Expand All @@ -29,7 +31,7 @@
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 .venv import virtualenv

Expand All @@ -40,12 +42,23 @@
class PythonConfiguration:
version: str
identifier: str
pyodide_version: str
pyodide_build_version: str
emscripten_version: str
default_pyodide_version: str
node_version: str


class PyodideXBuildEnvInfoVersionRange(TypedDict):
min: str | None
max: str | None


class PyodideXBuildEnvInfo(TypedDict):
version: str
python: str
emscripten: str
pyodide_build: PyodideXBuildEnvInfoVersionRange
compatible: bool


@functools.cache
def ensure_node(major_version: str) -> Path:
with resources.NODEJS.open("rb") as f:
Expand Down Expand Up @@ -78,8 +91,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"
Expand All @@ -97,6 +108,73 @@ 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 xbuildenv version {version} in the available
versions as reported by pyodide-build v{pyodide_build_version}.
Available pyodide xbuild versions are:
{", ".join(e["version"] for e in xbuildenvs_info if e["compatible"])}
""")
raise errors.FatalError(msg)


# The xbuildenv version is brought in sync with the pyodide-build version in
# the constraints file, which will always be compatible with the version in
# build-platforms.toml. Hence, this condition really checks only for the case
# where the version is supplied manually through a CIBW_PYODIDE_VERSION
# environment variable and raises an error as appropriate.
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
Expand All @@ -107,6 +185,7 @@ def install_xbuildenv(env: dict[str, str], pyodide_build_version: str, pyodide_v
CIBW_CACHE_PATH
/ f".pyodide-xbuildenv-{pyodide_build_version}/{pyodide_version}/xbuildenv/pyodide-root"
)

with FileLock(CIBW_CACHE_PATH / "xbuildenv.lock"):
if pyodide_root.exists():
return str(pyodide_root)
Expand All @@ -115,6 +194,8 @@ 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",
Expand Down Expand Up @@ -147,8 +228,10 @@ def setup_python(
python_configuration: PythonConfiguration,
dependency_constraint_flags: Sequence[PathOrStr],
environment: ParsedEnvironment,
user_pyodide_version: str | None,
) -> dict[str, str]:
base_python = get_base_python(python_configuration.identifier)
pyodide_version = user_pyodide_version or python_configuration.default_pyodide_version

log.step("Setting up build environment...")
venv_path = tmp / "venv"
Expand Down Expand Up @@ -202,15 +285,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,
)

enscripten_version = xbuildenv_info["emscripten"]
log.step(f"Installing Emscripten version: {enscripten_version} ...")
emcc_path = install_emscripten(tmp, enscripten_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

Expand Down Expand Up @@ -259,6 +355,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()
Expand All @@ -273,10 +370,11 @@ def build(options: Options, tmp_path: Path) -> None:
)

env = setup_python(
identifier_tmp_dir / "build",
config,
dependency_constraint_flags,
build_options.environment,
tmp=identifier_tmp_dir / "build",
python_configuration=config,
dependency_constraint_flags=dependency_constraint_flags,
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
Expand Down
2 changes: 1 addition & 1 deletion cibuildwheel/resources/build-platforms.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,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.0", node_version = "v22" },
]

[ios]
Expand Down
31 changes: 30 additions & 1 deletion cibuildwheel/resources/cibuildwheel.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,11 @@
"description": "Specify alternative manylinux / musllinux container images",
"title": "CIBW_MUSLLINUX_X86_64_IMAGE"
},
"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": [
Expand Down Expand Up @@ -658,6 +663,9 @@
"musllinux-x86_64-image": {
"$ref": "#/properties/musllinux-x86_64-image"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
Expand Down Expand Up @@ -758,6 +766,9 @@
"musllinux-x86_64-image": {
"$ref": "#/properties/musllinux-x86_64-image"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"description": "Execute a shell command to repair each built wheel.",
"oneOf": [
Expand Down Expand Up @@ -822,6 +833,9 @@
"environment": {
"$ref": "#/properties/environment"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
Expand Down Expand Up @@ -873,6 +887,9 @@
"environment": {
"$ref": "#/properties/environment"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"description": "Execute a shell command to repair each built wheel.",
"oneOf": [
Expand Down Expand Up @@ -937,6 +954,9 @@
"environment": {
"$ref": "#/properties/environment"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
Expand Down Expand Up @@ -988,6 +1008,9 @@
"environment": {
"$ref": "#/properties/environment"
},
"pyodide-version": {
"$ref": "#/properties/pyodide-version"
},
"repair-wheel-command": {
"$ref": "#/properties/repair-wheel-command"
},
Expand All @@ -997,10 +1020,16 @@
"test-extras": {
"$ref": "#/properties/test-extras"
},
"test-sources": {
"$ref": "#/properties/test-sources"
},
"test-groups": {
"$ref": "#/properties/test-groups"
},
"test-requires": {
"$ref": "#/properties/test-requires"
}
}
}
}
}
}
Loading
Loading