diff --git a/bin/generate_schema.py b/bin/generate_schema.py
index 6b98ce8ef..3ea67f378 100755
--- a/bin/generate_schema.py
+++ b/bin/generate_schema.py
@@ -33,11 +33,13 @@
description: How to inherit the parent's value.
enable:
enum:
+ - cpython-experimental-riscv64
- cpython-freethreading
- cpython-prerelease
+ - graalpy
+ - pyodide-prerelease
- pypy
- pypy-eol
- - cpython-experimental-riscv64
description: A Python version or flavor to enable.
additionalProperties: false
description: cibuildwheel's settings.
diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml
index 00c555794..0102ff5e7 100644
--- a/cibuildwheel/resources/build-platforms.toml
+++ b/cibuildwheel/resources/build-platforms.toml
@@ -221,6 +221,7 @@ python_configurations = [
[pyodide]
python_configurations = [
{ identifier = "cp312-pyodide_wasm32", version = "3.12", default_pyodide_version = "0.27.6", node_version = "v22" },
+ { identifier = "cp313-pyodide_wasm32", version = "3.13", default_pyodide_version = "0.28.0a3", node_version = "v22" },
]
[ios]
diff --git a/cibuildwheel/resources/cibuildwheel.schema.json b/cibuildwheel/resources/cibuildwheel.schema.json
index 92113e3d7..7d6aaf10d 100644
--- a/cibuildwheel/resources/cibuildwheel.schema.json
+++ b/cibuildwheel/resources/cibuildwheel.schema.json
@@ -13,11 +13,13 @@
},
"enable": {
"enum": [
+ "cpython-experimental-riscv64",
"cpython-freethreading",
"cpython-prerelease",
+ "graalpy",
+ "pyodide-prerelease",
"pypy",
- "pypy-eol",
- "cpython-experimental-riscv64"
+ "pypy-eol"
]
},
"description": "A Python version or flavor to enable."
diff --git a/cibuildwheel/selector.py b/cibuildwheel/selector.py
index 42f4104be..256e4fb04 100644
--- a/cibuildwheel/selector.py
+++ b/cibuildwheel/selector.py
@@ -29,12 +29,13 @@ class EnableGroup(StrEnum):
Groups of build selectors that are not enabled by default.
"""
+ CPythonExperimentalRiscV64 = "cpython-experimental-riscv64"
CPythonFreeThreading = "cpython-freethreading"
CPythonPrerelease = "cpython-prerelease"
+ GraalPy = "graalpy"
PyPy = "pypy"
PyPyEoL = "pypy-eol"
- CPythonExperimentalRiscV64 = "cpython-experimental-riscv64"
- GraalPy = "graalpy"
+ PyodidePrerelease = "pyodide-prerelease"
@classmethod
def all_groups(cls) -> frozenset["EnableGroup"]:
@@ -98,6 +99,10 @@ def __call__(self, build_id: str) -> bool:
return False
if EnableGroup.GraalPy not in self.enable and fnmatch(build_id, "gp*"):
return False
+ if EnableGroup.PyodidePrerelease not in self.enable and fnmatch(
+ build_id, "cp313-pyodide_*"
+ ):
+ return False
should_build = selector_matches(self.build_config, build_id)
should_skip = selector_matches(self.skip_config, build_id)
diff --git a/docs/options.md b/docs/options.md
index 3a9abf552..67439176f 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -58,7 +58,7 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat
| Python 3.10 | cp310-macosx_x86_64
cp310-macosx_universal2
cp310-macosx_arm64 | cp310-win_amd64
cp310-win32
cp310-win_arm64 | cp310-manylinux_x86_64
cp310-manylinux_i686
cp310-musllinux_x86_64
cp310-musllinux_i686 | cp310-manylinux_aarch64
cp310-manylinux_ppc64le
cp310-manylinux_s390x
cp310-manylinux_armv7l
cp310-manylinux_riscv64
cp310-musllinux_aarch64
cp310-musllinux_ppc64le
cp310-musllinux_s390x
cp310-musllinux_armv7l
cp310-musllinux_riscv64 | | |
| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-manylinux_armv7l
cp311-manylinux_riscv64
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x
cp311-musllinux_armv7l
cp311-musllinux_riscv64 | | |
| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-manylinux_armv7l
cp312-manylinux_riscv64
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x
cp312-musllinux_armv7l
cp312-musllinux_riscv64 | | cp312-pyodide_wasm32 |
-| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-manylinux_riscv64
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l
cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos
cp313-ios_arm64_iphonesimulator
cp313-ios_x86_64_iphonesimulator | |
+| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-manylinux_riscv64
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l
cp313-musllinux_riscv64 | cp313-ios_arm64_iphoneos
cp313-ios_arm64_iphonesimulator
cp313-ios_x86_64_iphonesimulator | cp313-pyodide_wasm32 |
| Python 3.14 | cp314-macosx_x86_64
cp314-macosx_universal2
cp314-macosx_arm64 | cp314-win_amd64
cp314-win32
cp314-win_arm64 | cp314-manylinux_x86_64
cp314-manylinux_i686
cp314-musllinux_x86_64
cp314-musllinux_i686 | cp314-manylinux_aarch64
cp314-manylinux_ppc64le
cp314-manylinux_s390x
cp314-manylinux_armv7l
cp314-manylinux_riscv64
cp314-musllinux_aarch64
cp314-musllinux_ppc64le
cp314-musllinux_s390x
cp314-musllinux_armv7l
cp314-musllinux_riscv64 | | |
| PyPy3.8 v7.3 | pp38-macosx_x86_64
pp38-macosx_arm64 | pp38-win_amd64 | pp38-manylinux_x86_64
pp38-manylinux_i686 | pp38-manylinux_aarch64 | | |
| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | | |
@@ -334,15 +334,18 @@ values are:
are disabled by default as they can't be uploaded to PyPI and a PEP will most likely
be required before this can happen.
- `graalpy`: Enable GraalPy.
+- `pyodide-prerelease`: Pyodide versions that haven't released yet, if one is
+ available. Safe if you are shipping a site with an early build, not for
+ general distribution.
- `all`: Enable all of the above.
!!! caution
`cpython-prerelease` is provided for testing purposes only. It is not
recommended to distribute wheels built with beta releases, such as
- uploading to PyPI. Please _do not_ upload these wheels to PyPI, as they are
- not guaranteed to work with the final Python release. Once Python is ABI
- stable and enters the release candidate phase, that version of Python will
- become available without this flag.
+ uploading to PyPI. Please _do not_ upload these wheels to PyPI (except for
+ pre-releases), as they are not guaranteed to work with the final Python
+ release. Once Python is ABI stable and enters the release candidate phase,
+ that version of Python will become available without this flag.
!!! note
Free threading is experimental: [What’s New In Python 3.13](https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython)
diff --git a/docs/platforms.md b/docs/platforms.md
index d57b1b864..438ba7607 100644
--- a/docs/platforms.md
+++ b/docs/platforms.md
@@ -170,7 +170,9 @@ You must target pyodide with `--platform pyodide` (or use `--only` on the identi
### Choosing a Pyodide version {: #pyodide-choosing-a-version}
-It is also possible to target a specific Pyodide version by setting the `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.
+It is also possible to target a specific Pyodide version by setting the [`pyodide-version`](options.md#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.
+
+If there are pre-releases available for a newer Python version, the `pyodide-prerelease` [`enable`](options.md#enable) can be used to include pre-release versions.
### Running tests
diff --git a/test/test_pyodide.py b/test/test_pyodide.py
index 39bc2be32..9240e8266 100644
--- a/test/test_pyodide.py
+++ b/test/test_pyodide.py
@@ -58,7 +58,7 @@ def test_pyodide_build(tmp_path, use_pyproject_toml):
basic_project.generate(project_dir)
# check for node in 1 case only to reduce CI load
- add_env = {}
+ add_env = {"CIBW_ENABLE": "pyodide-prerelease"}
if use_pyproject_toml:
add_env["CIBW_TEST_COMMAND"] = f"python {{project}}/check_node.py {CIBW_CACHE_PATH}"
@@ -72,6 +72,7 @@ def test_pyodide_build(tmp_path, use_pyproject_toml):
# check that the expected wheels are produced
expected_wheels = [
"spam-0.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl",
+ "spam-0.1.0-cp313-cp313-pyodide_2025_0_wasm32.whl",
]
print("actual_wheels", actual_wheels)
@@ -130,11 +131,13 @@ def test_filter():
add_env={
"CIBW_TEST_REQUIRES": "pytest",
"CIBW_TEST_COMMAND": "python -m pytest {project}",
+ "CIBW_ENABLE": "pyodide-prerelease",
},
)
# check that the expected wheels are produced
expected_wheels = [
"spam-0.1.0-cp312-cp312-pyodide_2024_0_wasm32.whl",
+ "spam-0.1.0-cp313-cp313-pyodide_2025_0_wasm32.whl",
]
print("actual_wheels", actual_wheels)
print("expected_wheels", expected_wheels)
diff --git a/test/utils.py b/test/utils.py
index 838e2179c..71071abe0 100644
--- a/test/utils.py
+++ b/test/utils.py
@@ -261,6 +261,8 @@ def _expected_wheels(
if platform == "pyodide" and python_abi_tags is None:
python_abi_tags = ["cp312-cp312"]
+ if EnableGroup.PyodidePrerelease in enable_groups:
+ python_abi_tags.append("cp313-cp313")
elif platform == "ios" and python_abi_tags is None:
python_abi_tags = ["cp313-cp313"]
elif python_abi_tags is None:
diff --git a/unit_test/build_selector_test.py b/unit_test/build_selector_test.py
index 1c87c4264..0b29b3887 100644
--- a/unit_test/build_selector_test.py
+++ b/unit_test/build_selector_test.py
@@ -89,6 +89,26 @@ def test_build_filter_pypy_all():
assert build_selector("pp39-manylinux_x86_64")
+def test_build_filter_pyodide_prerelease():
+ build_selector = BuildSelector(
+ build_config="*",
+ skip_config="",
+ enable=frozenset([EnableGroup.PyodidePrerelease]),
+ )
+ assert build_selector("cp312-pyodide_wasm32")
+ assert build_selector("cp313-pyodide_wasm32")
+
+
+def test_build_filter_pyodide():
+ build_selector = BuildSelector(
+ build_config="*",
+ skip_config="",
+ enable=frozenset(),
+ )
+ assert build_selector("cp312-pyodide_wasm32")
+ assert not build_selector("cp313-pyodide_wasm32")
+
+
def test_skip():
build_selector = BuildSelector(
build_config="*",