Plan — Python engine as a bundled binary wheel
Implementation plan for ADR 0020:
turn PR #53's subprocess API into a pip install jacquard self-contained
binary wheel built with cibuildwheel.
Deferred (2026-07-01). ADR 0020 is a draft — a native PyO3 binding is the preferred long-term direction, and whether we build this subprocess-wheel path at all is deferred (#161). P0 (adopt PR #53's API into the uv workspace) is worth doing regardless — it's the Python surface either approach exposes. P1–P3 (embed-and-delocate the subprocess binary) are on hold pending the subprocess-vs-PyO3 decision; note the shared hard part (per-platform wheels +
libc++/libompvendoring) carries over to a PyO3 extension either way.
Guiding constraints
- The Python surface is PR #53's —
config/runner/result/regression/errors. This plan changes packaging and adds a binary; it does not redesign the API. - Reuse, don't reinvent, the release machinery. The binary to embed is the
one
release.ymlalready builds (cargo build --release --features metal --bin jacquard, metallib embedded). The publish path ispublish-netlist-graph.yml's OIDC trusted-publishing shape. The smoke idea isscripts/ci/user_acceptance_smoke.sh(install relocated → runsim). - Every phase ends green in CI with an install-into-clean-venv smoke test,
and any platform the wheel does not cover is
log-ged, not silently dropped.
P0 — Land the API in the workspace (no binary yet)
Adopt PR #53 as a uv workspace member; ship nothing to PyPI yet.
- Move #53's
python/jacquard/in as the 5th workspace member; wire it into the rootpyproject.tomlworkspace list next tonetlist_graph/chipflow_harness/mcu_soc. - Keep
find_jacquard_binary()'s current env →PATH→whichchain (no embedded binary yet); it resolves a dev's localtarget/release/jacquard. - CI: a
python-apijob —uv sync,ruff,pytest— plus one integration test that buildsjacquardand drivessim()against a tiny fixture (guarded like the other Metal jobs; reuses themetal-buildartifact from the build/test split). - Exit: #53's tests green in CI;
from jacquard import simworks against a locally built binary. #53 can be closed/superseded by this branch.
P1 — macOS/arm64 + Metal binary wheel (the spike + the crux)
The whole ADR rests on "embed the binary + vendor its dylibs into a wheel that launches with no Homebrew LLVM." Prove it here on the platform we already release for.
- Embed step. Package build copies the release binary to
jacquard/_bin/jacquard.find_jacquard_binary()gains step 0: prefer the packaged_bin/jacquard; existing chain remains the fallback. - cibuildwheel + delocate. Configure
cibuildwheelformacos/arm64, withdelocaterepairing the wheel — copyinglibc++/libompin and rewriting install names. Confirm withotool -Lthat the repaired binary references@loader_path-relative dylibs, not/opt/homebrew. - Clean-environment smoke (the gate). In a runner without Homebrew LLVM
on the load path (or with it hidden),
pip installthe wheel into a fresh venv and run a realsim— thepip-user analogue of the relocated-tarball user-acceptance gate. This is the test that would have caught v0.2.1. - libomp duplicate-runtime check. Load jacquard in a venv that also imports a numpy/scipy stack (which ship their own libomp) and confirm no duplicate-OpenMP abort.
- Exit: a macOS/arm64 wheel that
pip installs and runssimon a clean box; TestPyPI upload validated viaworkflow_dispatch.
P2 — Linux/x86_64 CPU-fallback wheel
Make pip install jacquard work in a plain Linux CI container (no GPU).
- Build the cosim CPU backend (ADR 0017) for
manylinux;auditwheelrepairs the (smaller, no-GPU) dylib set. - Same clean-venv
sim/cosimsmoke, in a stockmanylinux/ubuntu container. - Document the backend the Linux wheel provides (CPU) vs. what needs P3.
- Exit: Linux CPU wheel installs and runs in a bare container.
P3 — CUDA / HIP (gated, likely extras)
- Gate: ADR 0018 Phase 4 (prebuilt CUDA/HIP binaries) must exist first — this reuses those binaries, it does not invent GPU CI.
- Decide with data: default manylinux wheel vs.
jacquard[cuda]extra vs. a separate package — driven by wheel size and CUDA-runtime bundling.logand document whichever platforms the default wheel omits. - Exit: a documented, working install path for at least one GPU backend on Linux, or a recorded decision to keep GPU on the tarball/binstall channel.
Publish pipeline (built across P1–P3)
- New
publish-jacquard-wheel.yml, modeled onpublish-netlist-graph.yml: cibuildwheel build matrix → artifact → TestPyPI onworkflow_dispatch(dry-run) → PyPI via OIDC trusted publishing on a release tag. No stored token; configure the trusted publisher once (mind the case-sensitivegpu-eda/Jacquardrepo claim — the same gotchanetlist-graphhit). - Version tracks the binary release via
scripts/bump_version.py(the embedded binary and the wheel are one artifact), notnetlist-graph's independent line. - Extend
docs/installation.md(apip install jacquardpath) anddocs/release-process.md(the wheel channel + its staging-validation gate).
Open questions (from the ADR)
- PyPI name
jacquard— confirm availability; fall back tojacquard-edawith an aliased import name if taken. Blocks only the first real publish. - CUDA wheel size / runtime — P3 decision, with data.
- libomp duplicate runtime — must be cleared in the P1 spike (step 4) before committing to delocate as the vendoring mechanism.