Migrating from Native Builds#
If you already build native wheels with python -m build and want to add Pyodide/WebAssembly support, this guide shows what to change and what to watch out for.
The short version#
For many packages, the migration is just:
pip install pyodide-build
pyodide build .
Your existing pyproject.toml, setup.py, CMakeLists.txt, or meson.build works as-is — pyodide-build handles the cross-compilation transparently. If the build succeeds, you’re done.
This page covers what to do when it doesn’t.
What works differently in WebAssembly#
WebAssembly (via Emscripten) is a different platform with different capabilities than Linux, macOS, or Windows. Some things your code may rely on are not available:
No threading#
pthread, std::thread, Python’s threading, and multiprocessing are not available. Code that uses them will fail at runtime if not handled properly.
You need to guard threaded code paths like this:
import sys
if sys.platform != "emscripten":
import threading
# threaded implementation
else:
# single-threaded fallback
No networking#
socket, http.client, urllib.request (with network I/O), and similar modules don’t work. Network access in Pyodide goes through the browser’s fetch API.
Fix — guard network code or provide alternative implementations:
import sys
if sys.platform == "emscripten":
from pyodide.http import pyfetch
# use pyfetch for HTTP
else:
import urllib.request
# standard networking
Note
Third party networking libraries such as requests, aiohttp, urllib3, httpx
has a Pyodide-specific code path that uses pyodide.http.pyfetch or similar alternatives.
Therefore, if you are using these libraries, your code should work for common use cases.
No subprocesses#
subprocess, os.system, os.popen, and related calls don’t work in WebAssembly.
Fix — skip or mock subprocess-dependent functionality on Emscripten.
32-bit integers#
WebAssembly is a 32-bit platform. Code that assumes 64-bit pointer sizes or uses pointer-to-int casts may behave differently.
Detecting Pyodide at build time#
pyodide-build sets the PYODIDE environment variable during the build. Use it to conditionally adjust your build configuration:
# setup.py or build script
import os
if os.environ.get("PYODIDE"):
# WebAssembly-specific build adjustments
...
For C/C++ code, use the Emscripten preprocessor macro:
#ifdef __EMSCRIPTEN__
// WebAssembly-specific code
#else
// Native code
#endif
Detecting Pyodide at runtime#
import sys
if sys.platform == "emscripten":
# Running on Pyodide
...
Common migration patterns#
Skipping unsupported tests#
Mark tests that require threads, networking, subprocesses, or platform-specific behavior:
import sys
import pytest
@pytest.mark.skipif(sys.platform == "emscripten", reason="No threads on Emscripten")
def test_concurrent_access():
...
@pytest.mark.skipif(sys.platform == "emscripten", reason="No sockets on Emscripten")
def test_http_client():
...
Adding Pyodide to your CI#
Once your build works locally, add it to CI alongside your native builds:
CI with cibuildwheel — add Pyodide as a platform in your existing cibuildwheel config
CI without cibuildwheel — add a separate GitHub Actions job
What’s next?#
Tutorial: C Extension — detailed walkthrough for C extension packages
Debugging Build Failures — systematic troubleshooting
Customizing Compiler Flags — fine-tuning the build