Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 783 – Emscripten Packaging

PEP 783 – Emscripten Packaging

Author:
Hood Chatham <roberthoodchatham at gmail.com>
Sponsor:
Łukasz Langa <lukasz at python.org>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Created:
28-Mar-2025
Post-History:
02-Apr-2025, 18-Mar-2025

Table of Contents

Abstract

This PEP proposes a new platform tag series pyemscripten for binary Python package distributions for the Pyodide Python runtime.

Emscripten is a complete open-source compiler toolchain. It compiles C/C++ code into WebAssembly/JavaScript executables, for use in JavaScript runtimes, including browsers and Node.js. The Rust language also maintains an Emscripten target. PEP 776 specifies Python’s support for Emscripten.

Motivation

Pyodide is a CPython distribution for use in the browser. A web browser is a universal computing platform, available on Windows, macOS, Linux, and every smartphone. Hundreds of thousands of students have learned Python through Pyodide via projects like Capytale and PyodideU. Pyodide is also increasingly being used by Python packages to provide interactive documentation.

Pyodide currently maintains ports of 255 different packages at the time of this writing, including major scientific Python packages like NumPy, SciPy, pandas, Polars, scikit-learn, OpenCV, PyArrow, and Pillow as well as general purpose packages like aiohttp, Requests, Pydantic, cryptography, and orjson.

About 60 packages are also testing against Pyodide in their CI, including NumPy, pandas, awkward-cpp, scikit-image, statsmodels, PyArrow, Hypothesis, and PyO3.

Python package projects cannot deploy binary distributions for Pyodide on PyPI. Instead they must use other options like anaconda.org or jsdelivr.com. This creates friction both for package maintainers and for users.

Rationale

When Emscripten builds an application, it builds it as a free-standing program, including the entire operating system. Emscripten primarily targets the use case of fully static programs. When dynamic linking is used, the primary target use case is bundle splitting and lazy loading, where the dynamic libraries are built at the same time as the application.

As a result of that, the Emscripten compiler makes no ABI stability guarantees between versions. Many Emscripten updates are ABI compatible by chance, and the Rust Emscripten target behaves as if the ABI were stable with only occasional negative consequences.

There are several linker flags that adjust the Emscripten ABI or system libraries. Python packages built to run with Emscripten must make sure to match the ABI-sensitive linker flags used to compile the interpreter to avoid load-time or run-time errors. The Emscripten compiler continuously fixes bugs and adds support for new web platform features. Thus, there is significant benefit to being able to update the ABI.

In order to balance the ABI stability needs of package maintainers with the ABI flexibility to allow the platform to move forward, Pyodide plans to adopt a new ABI for each feature release of Python which we call pyemscripten_${YEAR}_${PATCH}.

The Pyodide team also coordinates the ABI flags that Pyodide uses with the Emscripten ABI that Rust supports in order to ensure that we have support for the many popular Rust packages. Historically, most of the work for this has been related to unwinding ABIs. See for instance this Rust Major Change Proposal.

The pyemscripten platform has nothing specifically to do with Python and indeed can be used by any program that uses the appropriate version of Emscripten and the appropriate link flags. In particular, pyemscripten platform tags can be used by Python interpreters compiled and linked with the specified version of Emscripten and with the specified ABI-sensitive flags.

Specification

The platform tags will take the form:

pyemscripten_${YEAR}_${PATCH}_wasm32

Each one of these will be used with a specified Python version. For example, the platform tag pyemscripten_2026_0 will be used with Python 3.14.

Emscripten Wheel ABI

The specification of the pyemscripten_<abi> platform includes:

  • Which version of the Emscripten compiler is used
  • What libraries are statically linked with the interpreter
  • What stack unwinding ABI is to be used
  • How the loader handles dependency lookup
  • That libraries cannot use -pthread
  • That libraries should be linked with -sWASM_BIGINT

The ABI is selected by choosing the appropriate version of the Emscripten compiler and passing appropriate compiler and linker flags. It is possible for other people to build their own Python interpreter that is compatible with the Pyodide ABI, it is not necessary to use the Pyodide distribution itself.

The Pyodide ABIs are fully specified in the Pyodide Platform ABI documentation.

The pyodide build tool knows how to create wheels that match the Pyodide ABI. Unlike with manylinux wheels, there is no need for a Docker container to build the pyemscripten_<abi> wheels. All that is needed is a Linux machine and appropriate versions of Python, Node.js, and Emscripten.

It is possible to validate a wheel by installing and importing it into the Pyodide runtime. Because Pyodide can run in an environment with strong sandboxing guarantees, doing this produces no security risks.

Determining the ABI version

The ABI version is stored in the PYEMSCRIPTEN_ABI_VERSION config variable and can be determined via:

pyemscripten_abi_version = sysconfig.get_config_var("PYEMSCRIPTEN_ABI_VERSION")

To generate the list of compatible tags, one can use the following code:

from packaging.tags import cpython_tags, _generic_platforms

def _emscripten_platforms() -> Iterator[str]:
    pyemscripten_abi_version = sysconfig.get_config_var("PYEMSCRIPTEN_ABI_VERSION")
    if pyemscripten_abi_version:
        yield f"pyemscripten_{pyemscripten_abi_version}_wasm32"
    yield from _generic_platforms()

emscripten_tags = cpython_tags(platforms=_emscripten_platforms())

This code will be added to pypa/packaging.

Package Installers

Installers should use the _emscripten_platforms() function shown above to determine which platforms are compatible with an Emscripten build of CPython. In particular, the ABI version is exposed via sysconfig.get_config_var(" PYEMSCRIPTEN_ABI_VERSION").

Package Indexes

Package indexes SHOULD accept any wheel whose platform tag matches the regular expression pyemscripten_[0-9]+_[0-9]+_wasm32.

Dependency Specifier Markers

According to PEP 776, in Emscripten Python sys.platform returns "emscripten". To check for the Emscripten platform in a dependency specifier, one can use sys_platform == "emscripten" (or its negation).

Trove Classifier

Packages that build and test Emscripten wheels can declare this by adding the Environment :: WebAssembly :: Emscripten classifier. PyPI already accepts uploads of packages with this classifier.

Backwards Compatibility

There are no backwards compatibility concerns in this PEP.

Security Implications

There are no security implications in this PEP.

Rejected Ideas

A Custom Interpreter Tag For Pyodide

We don’t need a custom interpreter tag for Pyodide because Pyodide is CPython. While we do apply a few minor patches, they have no affect on the interpeter ABI and our long term goal is to upstream everything.

Alternative Options for the Platform Tag

emscripten_${EMSCRIPTEN_VERSION}
It is tempting to use emscripten as the platform tag because the pyemscripten platform has nothing specifically to do with Python and indeed can be used by any program that uses the appropriate version of Emscripten and the appropriate link flags. But emscripten_${EMSCRIPTEN_VERSION} is too vague by itself because the ABI also depends on various linker flags.

There are other communities which have similar problems and would also benefit from a centralized standard for “Long Term Service” ABIs that the whole ecosystem could use. However, the Emscripten team have so far not been willing to provide a this standard since they consider dynamic linking an unusual use case. Thus it is left for our ecosystem to solve the problem itself. The platform tag should contains some indication of this.

pyemscripten_${PYTHON_MAJOR_MINOR}_${PATCH}
This would make it clearer which Python version is meant for use with each ABI, but it leads to conceptual confusion since the platform has nothing to do with Python.
pyodide_...
For now the platform is defined by Pyodide so this connection would be made clearer by calling the platform pyodide. But the capabilities of the platform are tied to what Emscripten supports not on what Pyodide supports so the platform tag should be focused on Emscripten. The pyemscripten tag is also more forwards compatible to a future where the definition of the platform moves upstream of Pyodide.
No ABI patch version
We hope never to need the patch version, but it’s good to be prepared for unforseen problems.

How to Teach This

Fo Pyodide Users

We recommend the Pyodide documentation on installing packages. We will make a table showing which pyemscripten ABI each Pyodide version is compatible with.

For Package Maintainers

We recommend the Pyodide documentation on building and testing packages. The Scientific Python community is also working on a spec which describes to package maintainers how to maintain web-based interactive documentation using Emscripten-based Python.

Generally cibuildwheel is the easiest way to build and test a package for use with Pyodide. Maintainers can also use pyodide-build directly to build a package. Rust packages that use Maturin as their build system can be built directly with Maturin since it has native support for cross builds.

Reference Implementation

For building packages, pyodide build and cibuildwheel.

For installers to decide whether a wheel tag is compatible with a Pyodide interpreter, pypa/packaging#804.


Source: https://github.com/python/peps/blob/main/peps/pep-0783.rst

Last modified: 2026-03-04 11:42:51 GMT