Skip to content

feat(python): add Pyodide/Emscripten (wasm32) wheel#1811

Open
chaliy wants to merge 1 commit into
mainfrom
claude/emscripten-wheels-bashkit-t0GLw
Open

feat(python): add Pyodide/Emscripten (wasm32) wheel#1811
chaliy wants to merge 1 commit into
mainfrom
claude/emscripten-wheels-bashkit-t0GLw

Conversation

@chaliy
Copy link
Copy Markdown
Contributor

@chaliy chaliy commented May 31, 2026

What

Ship a reduced-feature wasm32-unknown-emscripten (Pyodide) Python wheel so bashkit runs in the browser / JupyterLite / other WASM hosts, alongside the existing native wheels. Mirrors the Rust + maturin + PyO3 recipe from pydantic's emscripten-wheels writeup.

Why

Pyodide is single-threaded with no sockets and no host filesystem. Several core deps hard-compile_error! on emscripten (mio/reqwest, tokio rt-multi-thread for turso/sqlite, tokio::fs for realfs, pyo3-async-runtimes). The core bashkit crate was already wasm-aware; the work is confined to crates/bashkit-python plus two small core wasm-cleanliness fixes.

How

  • Cargo.toml: split bashkit-python deps per target. Native keeps the full feature set; wasm32 builds scripted_tool + python + jq only, on wasm-safe tokio base features, without pyo3-async-runtimes.
  • lib.rs: #[cfg(not(target_arch = "wasm32"))] gates the async execute() family, network/credential config, host mounts, sqlite, the capsule interop bridge, and external_handler. WASM exposes execute_sync() plus sync/async custom-builtin callbacks via the private-loop fallback. Unavailable config kwargs raise RuntimeError instead of silently no-opping. CallerLoopLocals aliases TaskLocals (native) / Infallible (wasm) so caller-loop branches are statically dead on wasm.
  • core: gate two now-conditionally-dead bindings (SPAWN_BLOCKING_THRESHOLD, execution_timeout) so the wasm build is warning-clean under clippy -D warnings.
  • CI: python.yml gets a wasm job (build + Pyodide-venv smoke test, wired into the python-check gate); publish-python.yml gets a build-emscripten job feeding inspectpublish. Both use nightly Rust (Pyodide passes -Z link-native-libraries=no) and link at -O1 to skip emcc's binaryen pass (flag skew in some emsdk 3.1.46 snapshots).
  • Docs: new specs/emscripten-wheels.md (feature matrix, toolchain, gotchas); updates to python-package.md, release-process.md, AGENTS.md.

Tests verified

  • Pyodide wheel builds end-to-end locally: bashkit-0.8.0-cp311-cp311-emscripten_3_1_46_wasm32.whl (contains the Emscripten-compiled .so, correctly tagged).
  • Rust: full CI slice green (4600+ tests). The only failure is ssh_supabase_connects, a live-network test against an external host unreachable from the build sandbox (unrelated — diff touches no SSH code).
  • Python: 681 native pytest pass (3 skipped need the bashkit_random_fs fixture crate built separately in CI).
  • cargo fmt, clippy (native + wasm, -D warnings), ruff check/format all clean.
  • Native public-API smoke test (shell + jq + python + BashTool) passes.

See specs/emscripten-wheels.md.


Generated by Claude Code

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 31, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit c431bd1 Commit Preview URL

Branch Preview URL
May 31 2026, 02:12 AM

@chaliy chaliy force-pushed the claude/emscripten-wheels-bashkit-t0GLw branch from 8cc7b33 to ac3989b Compare May 31, 2026 01:13
Ship a reduced-feature `wasm32-unknown-emscripten` wheel so bashkit runs in
the browser / JupyterLite via Pyodide. Mirrors the Rust+maturin+PyO3 recipe
from pydantic's emscripten-wheels writeup.

Pyodide is single-threaded with no sockets or host FS, so several deps
hard-fail to compile on wasm (mio/reqwest, tokio rt-multi-thread for
turso+sqlite, tokio::fs for realfs, pyo3-async-runtimes). Split bashkit-python
deps per target: native keeps the full feature set; wasm builds
scripted_tool+python+jq only.

Gate the unbuildable surfaces behind cfg(not(target_arch = "wasm32")): the
async execute() family, network/credential config, host mounts, sqlite, the
capsule interop bridge, and external_handler. wasm exposes execute_sync() plus
sync/async custom-builtin callbacks via the private-loop fallback. Unavailable
config kwargs raise RuntimeError instead of silently no-opping.

CI: add a wasm build+smoke-test job to python.yml (wired into the gate) and a
build-emscripten publish job to publish-python.yml. Both use nightly Rust
(Pyodide passes -Z link-native-libraries=no) and link at -O1 to skip emcc's
binaryen pass (flag skew in some emsdk 3.1.46 snapshots).

See specs/emscripten-wheels.md.
@chaliy chaliy force-pushed the claude/emscripten-wheels-bashkit-t0GLw branch from ac3989b to c431bd1 Compare May 31, 2026 02:11
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.

1 participant