Skip to content

Switch PyPI distribution to WASM + wasmtime for v0.9.0#1

Merged
libraz merged 1 commit into
mainfrom
develop
May 11, 2026
Merged

Switch PyPI distribution to WASM + wasmtime for v0.9.0#1
libraz merged 1 commit into
mainfrom
develop

Conversation

@libraz
Copy link
Copy Markdown
Owner

@libraz libraz commented May 11, 2026

Summary

Switch the Python distribution from ctypes-based (libformulon.{so,dylib,dll}) to a pure-Python py3-none-any wheel that ships a standalone WebAssembly module driven by wasmtime. One wheel works on every platform wasmtime supports.

Why

The previous v0.9.0 release failed PyPI upload because plain linux_x86_64 / linux_aarch64 wheels are rejected; PyPI requires manylinux tags. cibuildwheel + manylinux containers would have added significant CI complexity. The WASM approach removes the per-platform build entirely.

Changes

  • New WASM variant in cmake/FormulonWasm.cmake: FM_WASM_VARIANT option switches between embind (npm) and capi (PyPI). The capi variant emits a standalone reactor WASM with the curated fm_* export list from tools/wasm/capi_exports.txt.
  • Python binding rewrite: _c.py now drives the WASM through wasmtime; workbook.py uses explicit linear-memory alloc/free.
  • Wheel becomes py3-none-any: pyproject.toml adds wasmtime>=20,<45 runtime dep; setup.py marks the build as pure-Python.
  • release.yml: replaced 3-runner python-wheels matrix with a single ubuntu job. Fixed pre-existing publish-npm bug where CMAKE=/usr/bin/cmake override was missing on make npm-package / make npm-test.

Test plan

  • make wasm — embind WASM builds, make npm-test passes (7 tests)
  • make wasm-capiformulon_capi.wasm builds at 1.5 MiB / 414 KiB Brotli
  • make python-wheel — produces formulon-0.9.0-py3-none-any.whl (558 KB)
  • pip install in fresh venv + 22 unittest smoke tests pass
  • make build + ctest -LE "SLOW|LOAD|BENCH": 10114 tests passed, 0 failed
  • CI: format-check on develop-ci.yml (push trigger)
  • CI: main ci.yml (coverage + WASM size) on PR
  • CI: prebuild.yml on PR (3-platform N-API addon)

Next steps after merge

  1. Delete the stale v0.9.0 tag from origin
  2. Re-tag v0.9.0 on the merged main
  3. Push the new tag; release.yml drives the full pipeline (npm + CLI + PyPI + GitHub Release)

…asmtime

Replace the ctypes-based Python binding (which loaded a per-platform
libformulon.{so,dylib,dll}) with a pure-Python py3-none-any wheel that
ships a standalone WebAssembly module driven by the wasmtime runtime.
This removes the manylinux build complexity that blocked PyPI publishing
and collapses the 3-platform wheel matrix to a single artifact.

WASM build variant (cmake/FormulonWasm.cmake):
- Add FM_WASM_VARIANT cache variable: embind (default, npm) | capi (PyPI)
- capi variant: compiles src/c_api/wasm_entry.cpp + formulon_static,
  links with --no-entry + -sSTANDALONE_WASM=1, exports the curated fm_*
  list from tools/wasm/capi_exports.txt plus malloc / free; no -pthread
  (scheduler degrades to serial; acceptable for the Python distribution)
- Add tools/wasm/capi_exports.txt with 37 fm_* symbols used by the
  Python binding
- New make wasm-capi target; outputs build-wasm-capi/formulon_capi.wasm
  (1.5 MiB / 414 KiB Brotli)

Python binding rewrite:
- packages/python/formulon/_c.py: replace ctypes CDLL / Structure / Union
  with a wasmtime-driven _WasmInstance class; lazy-init on first use,
  shared process-wide; every WASM call serialised by an internal RLock
- _WasmInstance exposes read_bytes / write_bytes / read_u32 / read_i32 /
  read_f64 / read_cstr / alloc / free / alloc_utf8 / alloc_bytes for
  explicit linear-memory management
- packages/python/formulon/workbook.py: replace ctypes Structure / POINTER
  byref patterns with explicit WASM scratch-buffer alloc/free; Value._from_c
  becomes Value._from_wasm reading fm_value_t directly from WASM memory via
  struct.unpack_from; Workbook._handle changes from fm_workbook_t_p to int

Packaging:
- pyproject.toml: add wasmtime>=20,<45 runtime dependency; update packages
  list from formulon._lib to formulon._wasm; set OS Independent classifier
- setup.py: root_is_pure = True; get_tag() returns ("py3", "none", "any")
- MANIFEST.in: swap recursive-include from _lib *.so/*.dylib/*.dll/*.lib
  to _wasm *.wasm
- Delete packages/python/formulon/_lib/ directory and platform_tag.py
- Simplify scripts/stage.py from a cmake-invoking build script to a
  single shutil.copy2 of formulon_capi.wasm into formulon/_wasm/
- Remove FM_BUILD_PYTHON_SHARED cmake option from CMakeLists.txt

CI (.github/workflows/release.yml):
- Replace 3-runner python-wheels matrix (darwin-arm64 / linux-x64 /
  linux-arm64) with a single python-wheel job on ubuntu-latest
- python-wheel job installs Emscripten 3.1.74, runs make wasm-capi then
  make python-wheel, and verifies with python -m unittest discover
- Fix pre-existing publish-npm bug: add CMAKE=/usr/bin/cmake to npm-package
  and npm-test steps to prevent emsdk's PATH-shadowing non-executable cmake
  from causing EACCES failures
- publish-pypi: download single formulon-wheel artifact (no merge-multiple)

Documentation:
- CHANGELOG.md: document the three distribution channels (npm / PyPI / CLI)
  and describe the py3-none-any WASM-based wheel approach
- packages/python/README.md: update install, architecture, and
  "Building from source" sections to reflect the new WASM transport

Local verification:
- make wasm: embind WASM still builds, npm smoke test passes
- make wasm-capi: formulon_capi.wasm builds at 1.5 MiB / 414 KiB Brotli
- make python-wheel: produces formulon-0.9.0-py3-none-any.whl (558 KB)
- pip install in fresh venv: 22 unittest smoke tests pass
- make build + ctest -LE "SLOW|LOAD|BENCH": 10114 tests passed, 0 failed
@libraz libraz merged commit c07ac53 into main May 11, 2026
7 checks passed
@codecov-commenter
Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

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