Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion .github/workflows/publish-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,52 @@ jobs:
name: pypi_files-${{ matrix.os }}-${{ matrix.target }}-${{ matrix.manylinux || 'manylinux' }}
path: crates/bashkit-python/dist

# Reduced-feature Pyodide/Emscripten wheel (browser / JupyterLite).
# Separate job: distinct toolchain (nightly Rust + emsdk + pyodide-build) and a
# single Python version dictated by the pyodide-build ABI lockstep.
# See specs/emscripten-wheels.md.
build-emscripten:
name: Build wheel - emscripten (Pyodide)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

# Python 3.13 -> pyodide-build's modern config (Pyodide 0.29.x, Emscripten
# 4.0.9), whose binaryen understands modern LLVM's wasm target-features and
# whose runtime supports wasm exception handling. See specs/emscripten-wheels.md.
- uses: actions/setup-python@v6
with:
python-version: "3.13"

# nightly satisfies our deps' MSRV (monty needs 1.95) + edition 2024;
# its LLVM matches Emscripten 4.0.9's binaryen. pyodide-build manages its
# own matching emsdk via the cross-build env (no setup-emsdk needed).
- name: Install nightly Rust with the Emscripten target
uses: dtolnay/rust-toolchain@nightly
with:
targets: wasm32-unknown-emscripten

- name: Install pyodide-build
run: pip install pyodide-build

- name: Install pyodide cross-build environment
run: pyodide xbuildenv install

- name: Build Pyodide wheel
working-directory: crates/bashkit-python
env:
RUSTUP_TOOLCHAIN: nightly
run: pyodide build --outdir dist

- uses: actions/upload-artifact@v7
with:
name: pypi_files-emscripten
path: crates/bashkit-python/dist

# Verify built artifacts
inspect:
name: Inspect artifacts
needs: [build, build-sdist]
needs: [build, build-sdist, build-emscripten]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
Expand Down
79 changes: 77 additions & 2 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,19 +181,94 @@ jobs:
path: crates/bashkit-python/dist
retention-days: 5

# Build the reduced-feature Pyodide/Emscripten wheel and smoke-test it in a
# Pyodide venv. See specs/emscripten-wheels.md for the feature matrix and the
# pyodide-build <-> Emscripten <-> Python <-> Rust version lockstep.
wasm:
name: Build wheel (Pyodide/Emscripten)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

# Python 3.13 selects pyodide-build's modern config (Pyodide 0.29.x,
# Emscripten 4.0.9), whose binaryen understands the wasm target-features
# modern LLVM emits and whose runtime supports wasm exception handling.
- uses: actions/setup-python@v6
with:
python-version: "3.13"

# Pyodide passes `-Z link-native-libraries=no` (nightly-only). The nightly
# must satisfy our deps' MSRV (monty needs rustc 1.95) and edition 2024,
# so it has to be recent; its LLVM (19+) matches Emscripten 4.0.9's
# binaryen, which is what avoids the wasm-opt target-feature skew that
# older Emscripten (3.1.x) hit. See specs/emscripten-wheels.md.
- name: Install nightly Rust with the Emscripten target
uses: dtolnay/rust-toolchain@nightly
with:
targets: wasm32-unknown-emscripten

- uses: Swatinem/rust-cache@v2

# pyodide-build manages its own matching emsdk via the cross-build env, so
# no separate setup-emsdk step is needed.
- name: Install pyodide-build
run: pip install pyodide-build

- name: Install pyodide cross-build environment
run: pyodide xbuildenv install

- name: Build Pyodide wheel
working-directory: crates/bashkit-python
env:
RUSTUP_TOOLCHAIN: nightly
run: pyodide build --outdir dist

- name: Smoke test in a Pyodide venv
working-directory: crates/bashkit-python
# Run the import test from a scratch dir: the crate's own `bashkit/`
# source package would otherwise shadow the installed extension module.
run: |
pyodide venv .venv-pyodide
.venv-pyodide/bin/pip install dist/*.whl
venv_python="$(pwd)/.venv-pyodide/bin/python"
cd "$(mktemp -d)"
"$venv_python" -c "
from bashkit import Bash
b = Bash(python=True)
r = b.execute_sync('echo hello && echo 1 | jq .')
print(r.stdout)
assert r.exit_code == 0, r
assert r.stdout == 'hello\n1\n', r.stdout
# Reduced-feature build: unavailable config must fail loudly.
try:
Bash(sqlite=True)
except RuntimeError:
pass
else:
raise AssertionError('expected sqlite=True to raise on wasm')
print('wasm smoke test OK')
"

- uses: actions/upload-artifact@v7
with:
name: python-wheel-emscripten
path: crates/bashkit-python/dist
retention-days: 5

# Gate job for branch protection
python-check:
name: Python Check
if: always()
needs: [lint, test, examples, build-wheel]
needs: [lint, test, examples, build-wheel, wasm]
runs-on: ubuntu-latest
steps:
- name: Verify all jobs passed
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] || \
[[ "${{ needs.test.result }}" != "success" ]] || \
[[ "${{ needs.examples.result }}" != "success" ]] || \
[[ "${{ needs.build-wheel.result }}" != "success" ]]; then
[[ "${{ needs.build-wheel.result }}" != "success" ]] || \
[[ "${{ needs.wasm.result }}" != "success" ]]; then
echo "One or more Python CI jobs failed"
exit 1
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ node_modules/

# WASM build artifacts
*.wasm

# Pyodide / Emscripten cross-build environment (downloaded by `pyodide xbuildenv`)
.pyodide-xbuildenv/
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Fix root cause. Unsure: read more code; if stuck, ask w/ short options. Unrecogn
| coreutils-args-port | Port uutils `uu_app()` clap definitions (args mode) and platform-clean uucore modules (module mode, manifest-driven) into bashkit via codegen |
| credential-injection | Transparent per-host credential injection for outbound HTTP requests, without exposing secrets to sandboxed scripts |
| performance-results | Benchmark/eval result locations and `/benches` site aggregation contract |
| emscripten-wheels | Reduced-feature Pyodide/Emscripten (`wasm32-unknown-emscripten`) Python wheel: feature gating, toolchain, CI/publish |

### Documentation

Expand Down
31 changes: 23 additions & 8 deletions crates/bashkit-python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,34 @@ name = "bashkit"
doc = false # Python extension, no Rust docs needed

[dependencies]
# Bashkit core
# realfs: always-on so Python callers can use mount_real_* APIs
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "realfs", "jq", "interop", "http_client", "sqlite"] }

# PyO3 native extension
pyo3 = { workspace = true }
pyo3-async-runtimes = { workspace = true }

# Async runtime
tokio = { workspace = true, features = ["rt-multi-thread"] }

# Serialization
serde_json = { workspace = true }

# Big-integer support for py_to_monty BigInt extraction
num-bigint = "^0.4.6" # must be compatible with the version pulled in by bashkit core

# Native (non-wasm) targets: full feature set.
# realfs: always-on so Python callers can use mount_real_* APIs.
# http_client/sqlite/interop pull mio/tokio-net/rt-multi-thread/tokio::fs,
# none of which compile on wasm32-unknown-emscripten — see the wasm block
# below and specs/emscripten-wheels.md.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "realfs", "jq", "interop", "http_client", "sqlite"] }
tokio = { workspace = true, features = ["rt-multi-thread"] }
# pyo3-async-runtimes hard-pulls tokio `rt-multi-thread` + `net` (mio), which
# `compile_error!`s on wasm. Native-only; the wasm build exposes `execute_sync`
# (current-thread runtime) and omits the async `execute()` coroutine bridge.
pyo3-async-runtimes = { workspace = true }

# wasm32 (Pyodide/Emscripten) target: reduced feature set.
# Drops http_client, sqlite, realfs, interop — each requires threads, sockets,
# or host filesystem access that Pyodide does not provide. Keeps the in-VFS
# shell plus the embedded jq and Monty Python interpreters. tokio stays on its
# wasm-safe base features (sync, macros, io-util, rt, time) — current-thread
# runtime only. See specs/emscripten-wheels.md.
[target.'cfg(target_arch = "wasm32")'.dependencies]
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "jq"] }
tokio = { workspace = true }
Loading
Loading