Skip to content

Support PEP 783 pyemscripten_*_wasm32 wheel platform tag#3163

Merged
messense merged 5 commits into
PyO3:mainfrom
messense:feat/pep-783-emscripten
May 9, 2026
Merged

Support PEP 783 pyemscripten_*_wasm32 wheel platform tag#3163
messense merged 5 commits into
PyO3:mainfrom
messense:feat/pep-783-emscripten

Conversation

@messense
Copy link
Copy Markdown
Member

The Emscripten platform-tag branch in get_platform_tag now resolves the wheel tag through a priority cascade so maturin produces installable wheels across the full Pyodide release spectrum:

  1. pyemscripten_<year>_<patch>_wasm32 (PEP 783, Pyodide >= 0.30 / Python 3.14+), driven by MATURIN_PYEMSCRIPTEN_PLATFORM_VERSION / PYEMSCRIPTEN_PLATFORM_VERSION or pyodide config get pyemscripten_platform_version.
  2. pyodide_<year>_<patch>_wasm32 (Pyodide 0.28 / 0.29), driven by MATURIN_PYODIDE_ABI_VERSION / PYODIDE_ABI_VERSION or pyodide config get pyodide_abi_version.
  3. Legacy emscripten_<emcc-version>_wasm32 (Pyodide <= 0.27), still driven by MATURIN_EMSCRIPTEN_VERSION / emcc -dumpversion. A warning notes that wheels in this format are not installable on PEP 783-compliant runtimes.

The cascade is implemented as a pure function over an EmscriptenVersionInputs struct so the per-Pyodide-version behaviour can be covered deterministically by unit tests, and version segments are validated against the PEP 783 [0-9]+_[0-9]+ regex so malformed inputs fail loudly instead of producing a tag that no installer accepts.

The generated GitHub Actions workflow (generate-ci) now also exports PYEMSCRIPTEN_PLATFORM_VERSION and PYODIDE_ABI_VERSION from pyodide config get, defaulting to empty so older Pyodide releases keep producing the legacy tag.

For real-Pyodide coverage, noxfile.py learns to parse pyodide-lock.json (info.platform, info.abi_version, and a future info.pyemscripten_platform_version) into the matching env vars plus an EXPECTED_PLATFORM_TAG that test-emscripten asserts against the produced wheel filename. The CI test-emscripten job is now a matrix over Pyodide 0.28 and 0.29, and tests/emscripten_runner.js was updated to match all three Emscripten tag families when locating the built wheel.

messense added 2 commits May 9, 2026 19:36
The Emscripten platform-tag branch in `get_platform_tag` now resolves the
wheel tag through a priority cascade so maturin produces installable wheels
across the full Pyodide release spectrum:

1. `pyemscripten_<year>_<patch>_wasm32` (PEP 783, Pyodide >= 0.30 / Python
   3.14+), driven by `MATURIN_PYEMSCRIPTEN_PLATFORM_VERSION` /
   `PYEMSCRIPTEN_PLATFORM_VERSION` or `pyodide config get
   pyemscripten_platform_version`.
2. `pyodide_<year>_<patch>_wasm32` (Pyodide 0.28 / 0.29), driven by
   `MATURIN_PYODIDE_ABI_VERSION` / `PYODIDE_ABI_VERSION` or `pyodide config
   get pyodide_abi_version`.
3. Legacy `emscripten_<emcc-version>_wasm32` (Pyodide <= 0.27), still
   driven by `MATURIN_EMSCRIPTEN_VERSION` / `emcc -dumpversion`. A warning
   notes that wheels in this format are not installable on PEP 783-compliant
   runtimes.

The cascade is implemented as a pure function over an
`EmscriptenVersionInputs` struct so the per-Pyodide-version behaviour can be
covered deterministically by unit tests, and version segments are validated
against the PEP 783 `[0-9]+_[0-9]+` regex so malformed inputs fail loudly
instead of producing a tag that no installer accepts.

The generated GitHub Actions workflow (`generate-ci`) now also exports
`PYEMSCRIPTEN_PLATFORM_VERSION` and `PYODIDE_ABI_VERSION` from `pyodide
config get`, defaulting to empty so older Pyodide releases keep producing
the legacy tag.

For real-Pyodide coverage, `noxfile.py` learns to parse `pyodide-lock.json`
(`info.platform`, `info.abi_version`, and a future
`info.pyemscripten_platform_version`) into the matching env vars plus an
`EXPECTED_PLATFORM_TAG` that `test-emscripten` asserts against the produced
wheel filename. The CI `test-emscripten` job is now a matrix over Pyodide
0.28 and 0.29, and `tests/emscripten_runner.js` was updated to match all
three Emscripten tag families when locating the built wheel.
The previous `emit_emscripten_setup` step in `generate-ci` queried
`pyodide config get pyemscripten_platform_version`, but that key does
not exist in `pyodide-build`'s `PYODIDE_CLI_CONFIGS`. `pyodide
config get` for an unknown key prints the error message
`Config variable X not found.` to **stdout** (not stderr) and exits 1,
so the existing `2>/dev/null || true` pattern silently captured the
error string into `$GITHUB_ENV`. The downstream maturin build would
then trip the `[0-9]+_[0-9]+` validator with a confusing error.

Replace the two queries with a single robust pattern:

    if v=$(pyodide config get pyodide_abi_version 2>/dev/null); then
        echo PYODIDE_ABI_VERSION=$v >> $GITHUB_ENV
    fi

This only writes the env var when the key is actually recognised. The
PEP 783 `pyemscripten_<year>_<patch>_wasm32` tag is then selected by
the existing cascade in `src/target/platform_tag.rs` based on
`PYTHON_VERSION >= 3.14`. `PYEMSCRIPTEN_PLATFORM_VERSION` /
`MATURIN_PYEMSCRIPTEN_PLATFORM_VERSION` env-var overrides remain
supported for users who want to set the value manually.

Pyodide 314.0.0a1's `pyodide-lock.json` only exposes
`info.abi_version = 2026_0` and `info.python = 3.14.0` — there is
no `info.pyemscripten_platform_version` field. The
`_resolve_pyodide_platform_inputs` cascade already maps that
combination to `pyemscripten_2026_0_wasm32`, matching what
`pyodide_build.build_env.wheel_platform()` produces for the same
ABI.

Pyodide >= 314.0.0a1 also ships its Emscripten output as
`pyodide.asm.mjs` instead of `pyodide.asm.js`. Make the prettier
step tolerant of either filename so `nox -s setup-pyodide` does not
fail before parsing the lock file.

Finally, add Pyodide 314.0.0-alpha.1 / Python 3.14 to the
`test-emscripten` matrix so the new `pyemscripten_*_wasm32` tag
family is exercised on CI alongside the existing `pyodide_*` tag
covered by 0.28.3 / 0.29.0.
@messense messense force-pushed the feat/pep-783-emscripten branch from b892cad to bc3d67d Compare May 9, 2026 11:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for the PEP 783 pyemscripten_*_wasm32 wheel platform tag by introducing a prioritized Emscripten/Pyodide tag resolution cascade, and updates CI/test tooling to validate wheels across Pyodide versions.

Changes:

  • Implement Emscripten platform tag resolution cascade (pyemscripten_*pyodide_* → legacy emscripten_*) with input validation and unit tests.
  • Update Emscripten/Pyodide CI and nox setup to export/derive the right env vars and assert the produced wheel filename tag.
  • Expand wheel discovery logic in the JS runner and document new env vars.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/emscripten_runner.js Expands wheel filename matching to cover all three platform-tag families.
src/target/platform_tag.rs Introduces the tag-resolution cascade, validation, and unit tests.
src/ci/github/render.rs Updates generated CI setup to export PYODIDE_ABI_VERSION when available.
noxfile.py Derives platform tag inputs from pyodide-lock.json and asserts wheel tag correctness.
guide/src/environment-variables.md Documents new env vars for Pyodide/PEP 783 tagging and legacy behavior.
AGENTS.md Updates contributor guidance related to changelog modifications.
.github/workflows/test.yml Changes the Emscripten CI job into a matrix over Pyodide versions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/emscripten_runner.js Outdated
Comment thread src/target/platform_tag.rs
Comment thread src/ci/github/render.rs
Comment thread noxfile.py Outdated
Comment thread .github/workflows/test.yml Outdated
Comment thread AGENTS.md Outdated
messense added 2 commits May 9, 2026 21:36
Four small follow-ups based on Copilot's review of PyO3#3163:

1. `tests/emscripten_runner.js`: anchor the platform-tag regex to
   the trailing `-{platform tag}.whl` segment of the wheel filename so
   project names that happen to contain `pyodide_`, `emscripten_`,
   etc. cannot match. Wheel filenames are
   `{name}-{ver}-{python tag}-{abi tag}-{platform tag}.whl`, and the
   platform-tag field never contains `-`, so
   `-{family}_[^-]+_wasm32\.whl$` is unambiguous. Verified against
   all three Emscripten tag families plus `linux_x86_64` and a
   project named `my_pyodide_2025_0_wasm32_pkg`.

2. `noxfile.py`: only emit `EMSCRIPTEN_VERSION` from
   `_resolve_pyodide_platform_inputs` when we actually resolved one.
   The previous code unconditionally inserted
   `EMSCRIPTEN_VERSION=""`, which would clobber a previously-set
   value in `$GITHUB_ENV` and break `setup-emsdk` if the lock file
   ever omitted `info.platform`.

3. `.github/workflows/test.yml`: the matrix comment claimed three
   platform-tag families but only two are exercised end-to-end
   (`pyodide_*` via 0.29.0, `pyemscripten_*` via 314.0.0-alpha.1).
   Update the comment to match and note that the legacy
   `emscripten_<emcc-version>_wasm32` family is no longer used by any
   maintained Pyodide release and remains covered by the cascade unit
   tests in `src/target/platform_tag.rs`.

4. `AGENTS.md`: drop the duplicate `Do not modify Changelog by hand`
   bullet — the next bullet already says
   `Do not edit Changelog.md by hand; it is generated from git history
   by git-cliff (see cliff.toml)`.

The `eprintln!`-with-emoji warning in `emscripten_platform_tag` is
intentionally left as-is: it matches the existing user-facing warning
style elsewhere in maturin (`🍹`, `📦`, `❌`) and using
`tracing::warn!` here would be inconsistent.
The PEP 783 cascade in `emscripten_platform_tag` was over-engineered for
a 25-line decision tree:

- Three near-identical env-var helpers (`pyemscripten_platform_version`,
  `pyodide_abi_version`, `pyodide_python_version`) all duplicated the
  same trim/empty/fallback dance and returned `Result<Option<String>>`
  despite never producing an `Err`. Collapsed into one
  `first_non_empty_env(&[...])` helper.

- `EmscriptenVersionInputs` + the injected `emcc_lookup` closure existed
  purely as a test seam: `emcc_version` was never populated in
  production code, and the wrapper function was unused outside tests.
  Inlined the cascade directly into `emscripten_platform_tag`.

- `validate_version_segment` (regex + `Lazy` + `bail!`) was defensive
  validation against trusted Pyodide-supplied inputs (sysconfig /
  `pyodide config get`); a malformed override would fail loudly at
  install/upload time anyway. Removed.

- `python_version_at_least(_, 3, 14)` was generic over (major, minor)
  but only ever called with one constant. Renamed to
  `is_python_3_14_or_later` and switched parsing from `u64` to `u32`.

The 8 cascade unit tests + 2 validation tests (~140 lines of
scaffolding) tested little beyond format-string interpolation; end-to-end
coverage is provided by the new `EXPECTED_PLATFORM_TAG` assertion in
`noxfile.py` running across the Pyodide 0.29 / 314.0.0a1 CI matrix.

Net effect: -262 lines from `src/target/platform_tag.rs` with no
behavior change.

Also tidies `noxfile.py` (replaces the asm-file lookup loop with
`Path(...).glob(...)`) and adds a docstring cross-reference to keep
`_resolve_pyodide_platform_inputs` aligned with the Rust cascade.
@messense messense marked this pull request as ready for review May 9, 2026 14:30
@messense messense merged commit 8058c01 into PyO3:main May 9, 2026
45 checks passed
@messense messense deleted the feat/pep-783-emscripten branch May 9, 2026 14:30
greateggsgreg added a commit to greateggsgreg/pydantic that referenced this pull request May 10, 2026
The previous `requires = ['maturin @ git+...@main']` pin forced every PEP
517 build (including the MSRV job's `uv sync`) to compile maturin from
source, which needs rustc 1.89 — failing under the workspace MSRV of
1.88. Revert that pin to `maturin>=1.10,<2` and instead pre-install
maturin `main` into the pyemscripten CI build env, with the Makefile
target switching to `pyodide build --no-isolation` so PEP 517 picks up
the pre-installed maturin (which carries the PEP 783
`pyemscripten_*_wasm32` tag from PyO3/maturin#3163).

Also reformat tests/test_pickle.py to satisfy `ruff format --check`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
greateggsgreg added a commit to greateggsgreg/pydantic that referenced this pull request May 10, 2026
Adds a `core-build-pyemscripten` CI job that builds `pydantic-core` for
the `pyemscripten_*_wasm32` target and runs the test suite under Pyodide,
mirroring the conventions of the other `core-build-*` jobs (working
directory, `twine check`, `upload-artifact` layout).

- `.github/workflows/ci.yml`: new pyemscripten build/test job; pre-installs
  `maturin` from `main` so PEP 517 builds pick up the PEP 783
  `pyemscripten_*_wasm32` tag (PyO3/maturin#3163) without forcing every
  other PEP 517 build to compile maturin from source under the workspace
  MSRV.
- `.github/scripts/pyemscripten-run-tests.sh`: runner script driving
  `pyodide` for the build + test invocation.
- `pydantic-core/Makefile`: switch to `pyodide build --no-isolation` so
  the pre-installed maturin is used.
- `pydantic-core/pyproject.toml`, `pyproject.toml`: declare pyemscripten
  build/test extras.
- `pydantic-core/tests/conftest.py`, `tests/conftest.py`: skip tests that
  cannot run under pyemscripten.
- `tests/test_docs.py`, `tests/test_pickle.py`: gate cases that rely on
  capabilities unavailable in the wasm runtime; reformat `test_pickle.py`
  to satisfy `ruff format`.
sinaatalay added a commit to sinaatalay/typst-py that referenced this pull request May 12, 2026
The original commit produced a wheel tagged `emscripten_3_1_58_wasm32`.
PyPI's warehouse validator (live since 2026-04-21) only accepts
`pyemscripten_<year>_<patch>_wasm32` per PEP 783, so that wheel would
have been rejected at upload time. Local `emfs://` verification did
not catch this because it bypasses index-side tag validation.

Updates:

- Bump Pyodide to 0.29.4, which natively installs `pyemscripten_*`
  wheels (pyodide/pyodide#6180, #6203) and ships Python 3.13 as its
  bundled interpreter.
- Bump host Python on the runner to 3.13 to satisfy pyodide-build's
  xbuildenv compatibility check.
- Pin maturin to >= 1.13.2, which introduced the PEP 783 tag cascade
  (PyO3/maturin#3163), and export
  `MATURIN_PYEMSCRIPTEN_PLATFORM_VERSION` derived from `pyodide
  config get` so the cascade emits the `pyemscripten_*` tag even on
  Pyodide 0.29 (where it would otherwise fall back to `pyodide_*`).
- Verify step now asserts the wheel filename matches
  `pyemscripten_<year>_<patch>_wasm32.whl` before loading, so a
  PyPI-incompatible tag fails CI loudly instead of slipping through.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants