feat: pyemscripten platform tag (PEP 783), 314.0a1, pyodide-eol flag, and maintenance updates (#2812)

* Use `pyodide config get emscripten_dir`

* Update constraints

* Updates for `pyemscripten` platform tag

* Weird constraints stuff...

* Revert "Weird constraints stuff..."

This reverts commit 4bd785de872269c0b27362ee800f17288b2b4789.

* Add some special constraints for pyodide

* Wheel pyemscripten collision

* Put delocate back in constraints (not needed tho)

* Drop Pyodide 0.27.7 (cp312-pyodide_wasm32)

* Add Pyodide 314.0.0a1 (cp314-pyodide_wasm32)

* Delete no-longer-needed Pyodide 312 constraints

* Update Pyodide 313 and 314 constraints

* Add back prerelease stuff and update tests

* Fix `test_pyodide_on_windows`

* Update and fix tests some more

* Also enable pyodide-prerelease in GHA sample build

* Update tests yet again (`SINGLE_PYTHON_VERSION` changes)

* Add a Pyodide-specific maintenance guide

* Add suggestions from Hood and Gyeongjae

Co-Authored-By: Hood Chatham <roberthoodchatham@gmail.com>
Co-Authored-By: Gyeongjae Choi <def6488@gmail.com>

* Add "Last updated: May 2026" to Pyodide document

* Add a Pyodide EOL filter

* Fix typo

* Update maintenance docs about EoL Pyodide

* Add Pyodide 312 constraints back

* Update Pyodide 313 and 314 constraints

* Remove "experimental" note about Pyodide

* Remove another experimental Pyodide sentence

* Add docs about the `pyodide-eol` enable option

---------

Co-authored-by: Hood Chatham <roberthoodchatham@gmail.com>
Co-authored-by: Gyeongjae Choi <def6488@gmail.com>
Co-authored-by: Henry Schreiner <henryfs@princeton.edu>
This commit is contained in:
agriya khetarpal
2026-05-09 14:01:41 -04:00
committed by GitHub
co-authored by Hood Chatham Gyeongjae Choi Henry Schreiner
parent 542335fb4a
commit d2c0e59833
20 changed files with 343 additions and 99 deletions
+7 -2
View File
@@ -43,15 +43,20 @@ def test_abi3(tmp_path: Path) -> None:
project_dir,
add_env={
# free_threaded, GraalPy, and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
# pyodide uses cp313 (the stable version) which supports limited API / abi3
# also limit the number of builds for test performance reasons
"CIBW_BUILD": "cp39-* cp310-* pp310-* gp312_250-* cp312-* cp314t-*",
"CIBW_BUILD": (
"cp313-*"
if utils.get_platform() == "pyodide"
else "cp39-* cp310-* pp310-* gp312_250-* cp312-* cp314t-*"
),
"CIBW_ENABLE": "all",
},
)
# check that the expected wheels are produced
if utils.get_platform() == "pyodide":
# there's only 1 possible configuration for pyodide, cp312. It builds
# there's only 1 possible configuration for pyodide, cp313. It builds
# a wheel that is tagged abi3, compatible back to 3.10
expected_wheels = utils.expected_wheels(
"spam",
+3 -3
View File
@@ -18,10 +18,10 @@ 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
if platform.startswith("pyemscripten"):
# for the sake of this test, munge the pyemscripten platforms into one, it's
# not valid, but it does activate the uniqueness check
platform = "pyodide"
platform = "pyemscripten"
name = f"spam-0.1.0-py2-none-{platform}.whl"
dest = dest_dir / name
+17 -8
View File
@@ -77,13 +77,13 @@ def get_versions_from_constraint_file(constraint_file: Path) -> dict[str, str]:
return dict(re.findall(VERSION_REGEX, constraint_file_text))
@pytest.mark.parametrize("python_version", ["3.8", "3.12"])
@pytest.mark.parametrize("python_version", ["3.8", "3.13"])
def test_pinned_versions(
tmp_path: Path, python_version: str, build_frontend_env_nouv: dict[str, str]
) -> None:
if utils.get_platform() == "linux":
pytest.skip("linux doesn't pin individual tool versions, it pins manylinux images instead")
if python_version != "3.12" and utils.get_platform() == "pyodide":
if python_version != "3.13" and utils.get_platform() == "pyodide":
pytest.skip(f"pyodide does not support Python {python_version}")
if (
python_version == "3.8"
@@ -124,7 +124,7 @@ def test_pinned_versions(
expected_wheels = [
w
for w in utils.expected_wheels("spam", "0.1.0")
if f"-cp{version_no_dot}" in w or f"-pp{version_no_dot}" in w
if f"-cp{version_no_dot}-cp{version_no_dot}-" in w or f"-pp{version_no_dot}-" in w
]
assert set(actual_wheels) == set(expected_wheels)
@@ -140,11 +140,20 @@ def test_dependency_constraints(
project_dir = tmp_path / "project"
test_projects.new_c_project().generate(project_dir)
tool_versions = {
"pip": "23.1.2",
"build": "1.2.2",
"delocate": "0.10.3",
}
if utils.get_platform() == "pyodide":
# pyodide-build 0.34+ requires build~=1.4.0, so we must use a
# compatible version here. delocate is macOS-only and not used on pyodide.
tool_versions = {
"pip": "23.1.2",
"build": "1.4.2",
"delocate": "0.10.3",
}
else:
tool_versions = {
"pip": "23.1.2",
"build": "1.2.2",
"delocate": "0.10.3",
}
if method == "file":
constraints_file = tmp_path / "constraints file.txt"
+3 -3
View File
@@ -34,7 +34,7 @@ def test_cross_compiled_build(tmp_path: Path) -> None:
add_env={"CIBW_ARCHS": "x86_64, universal2, arm64"},
single_python=True,
)
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
python_tag = "cp{0}{1}-cp{0}{1}-".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -176,7 +176,7 @@ def test_universal2_testing_on_x86_64(
else:
assert warning_message in captured.err
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
python_tag = "cp{0}{1}-cp{0}{1}-".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w and "universal2" in w]
assert set(actual_wheels) == set(expected_wheels)
@@ -210,7 +210,7 @@ def test_universal2_testing_on_arm64(
assert "running tests on arm64 with pillow" in captured.out
assert "running tests on x86_64 with pillow" in captured.out
python_tag = "cp{}{}".format(*utils.SINGLE_PYTHON_VERSION)
python_tag = "cp{0}{1}-cp{0}{1}-".format(*utils.SINGLE_PYTHON_VERSION)
expected_wheels = [w for w in ALL_MACOS_WHEELS if python_tag in w and "universal2" in w]
assert set(actual_wheels) == set(expected_wheels)
+5 -5
View File
@@ -79,8 +79,8 @@ def test_pyodide_build(tmp_path: Path, use_pyproject_toml: bool) -> None:
# 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",
"spam-0.1.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl",
"spam-0.1.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl",
]
print("actual_wheels", actual_wheels)
@@ -144,8 +144,8 @@ def test_pyodide_build_and_test(tmp_path: Path, expect_failure: bool) -> None:
)
# 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",
"spam-0.1.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl",
"spam-0.1.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl",
]
assert set(actual_wheels) == set(expected_wheels)
@@ -170,6 +170,6 @@ def test_pyodide_repair_wheel(tmp_path: Path) -> None:
# 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-pyemscripten_2025_0_wasm32.whl",
]
assert set(actual_wheels) == set(expected_wheels)
+10 -9
View File
@@ -26,7 +26,7 @@ EMULATED_ARCHS: Final[list[str]] = sorted(
PYPY_ARCHS = ["x86_64", "i686", "AMD64", "aarch64", "arm64"]
GRAALPY_ARCHS = ["x86_64", "AMD64", "aarch64", "arm64"]
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 12)
SINGLE_PYTHON_VERSION: Final[tuple[int, int]] = (3, 13)
# temporary workaround: set by build_frontend_env fixture to skip graalpy
# when uv is the build frontend (compatibility issue between graalpy and uv)
@@ -263,11 +263,11 @@ def _expected_wheels(
if musllinux_versions is None:
musllinux_versions = ["musllinux_1_2"]
# To be kept in sync with Python versions for Pyodide identifiers in cibuildwheel/selector.py.
if platform == "pyodide" and python_abi_tags is None:
python_abi_tags = [
"cp312-cp312",
"cp313-cp313",
]
python_abi_tags = ["cp313-cp313"]
if EnableGroup.PyodidePrerelease in enable_groups:
python_abi_tags.append("cp314-cp314")
elif platform == "android" and python_abi_tags is None: # noqa: SIM114
python_abi_tags = [
"cp313-cp313",
@@ -412,13 +412,14 @@ def _expected_wheels(
elif platform == "pyodide":
platform_tags = {
"cp312-cp312": ["pyodide_2024_0_wasm32"],
"cp313-cp313": ["pyodide_2025_0_wasm32"],
"cp313-cp313": ["pyemscripten_2025_0_wasm32"],
"cp314-cp314": ["pyemscripten_2026_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"]
# for example if the python tag is `none` or `abi3`, the wheel
# is built by the stable cp313 Python version
platform_tags = ["pyemscripten_2025_0_wasm32"]
else:
msg = f"Unsupported platform {platform!r}"