Release Process
Lightweight by design. Jacquard is a single-binary Rust project with
vendor/ submodules; releases are git tags + a CHANGELOG entry. No
crates.io publication, no pre-built binaries (until/unless that
demand surfaces).
When to release
Cut a release when:
- A user-visible feature or fix lands that you want consumers to be able to pin against.
- Schema or CLI changes happened (
--timing-reportJSON, CLI flags) and consumers need a stable reference point. - A meaningful chunk of work in
docs/plans/is closed (e.g. a Phase exits all criteria).
There is no fixed cadence.
Versioning
SemVer, starting once the first numbered
release ships. Pre-1.0 versions (0.x.0) carry the standard SemVer
caveat: minor bumps may include breaking changes; the public
contracts (--timing-report schema, IR layout) are documented in their
own ADRs and follow stricter rules.
Stable contracts (additive-only, breaking changes require a major bump and a deprecation window):
--timing-reportJSON schema —src/timing_report.rs::SCHEMA_VERSION, governed by ADR 0008.- Timing IR FlatBuffers schema —
crates/timing-ir/schemas/timing_ir.fbs, governed by ADR 0002.
CLI flags, log message formats, and --timing-summary text output are
not stable parseable contracts; consumers that need to script
against them should use --timing-report JSON.
Steps
For maintainers cutting a release:
-
Verify CI is green on
mainfor all three GPU backends (Metal, CUDA, HIP) plus the unit-test, opensta-to-ir, and lint jobs. If any GPU runner is offline, hold the release until it's restored — see.github/workflows/ci.yml. Do not ship a binary the CI hasn't built. -
Roll the
[Unreleased]section inCHANGELOG.mdinto a numbered version block. Format follows Keep a Changelog. Update the link references at the bottom of the file. Leave a fresh empty[Unreleased]section at the top. -
Bump the Rust crate version to match:
python3 scripts/bump_version.py <X.Y.Z>. The three first-party Rust crates (jacquard,opensta-to-ir,timing-ir) ship together in one tarball and carry a single shared version, so this script sets all three at once — never edit their[package].versionby hand. Thencargo buildto updateCargo.lock. (netlist-graphversions independently — see its ownnetlist-graph-v*tag flow.) The release workflow re-runsbump_version.py --check <tag>as a verify-guard and aborts before publishing if the tag and the crates disagree. -
Commit:
chore: release v<X.Y.Z>with the standardCo-developed-bytrailer. -
Tag:
git tag -a v<X.Y.Z> -m "v<X.Y.Z>"thengit push --tags. -
Create a GitHub release from the tag. Body = the CHANGELOG section for that version. No artefacts attached unless someone has asked for them.
Homebrew tap (automated)
The Homebrew formula is auto-bumped by release.yml — no manual step
(this closes the drift that had left the tap stale at 0.2.3). On every
release tag the bump-tap job rewrites packaging/homebrew/jacquard.rb's
url/version/sha256 from the just-published tarball + .sha256 and
pushes Formula/jacquard.rb to the tap:
- final release →
gpu-eda/homebrew-tap(brew install gpu-eda/tap/jacquard); - prerelease (RC) →
gpu-eda/homebrew-tap-prerelease(brew install gpu-eda/tap-prerelease/jacquard), so RCs arebrew install-able for staging without touching the stable channel.
packaging/homebrew/jacquard.rb is the template — edit it only to change
the formula's structure (deps, install, test); its version pin is a
placeholder the job overwrites. Requires the one-time org setup:
secrets.HOMEBREW_TAP_TOKEN (a token with contents: write on both tap
repos) and the gpu-eda/homebrew-tap-prerelease repo.
Staging validation (release candidates)
Optional but recommended before a user-facing release: prove the documented install commands work against a staging artifact before promoting. There is no "test crates registry", so a GitHub prerelease is the staging tier for the binary channels.
Sequence matters — do NOT roll main to the final version before the RC validates. During the RC window
mainis the candidate: it stays atX.Y.Z-rc.Nwith the changelog still under[Unreleased]. Only the Promote step (below) rolls[Unreleased] → [X.Y.Z]and bumps to the finalX.Y.Z. Rolling the release ontomainfirst leavesmainadvertising a version that has no release — the version pin and dated changelog claimX.Y.Zis shipped while only the RC tag exists, which also breakscargo binstall --git(it readsmain's version and fetches a non-existentvX.Y.Ztarball). Each new RC is amaincommit bumped to the next-rc.N;main == the latest RC tagthroughout.
-
Cut an RC. On
mainatX.Y.Z-rc.N(changelog under[Unreleased]),python3 scripts/bump_version.py <X.Y.Z>-rc.<N>, commit, tagv<X.Y.Z>-rc.<N>, push the tag.release.ymldetects the SemVer pre-release suffix and publishes a GitHub prerelease (never shown as "Latest") with the Metal tarball attached, andbump-tappushes the formula togpu-eda/homebrew-tap-prerelease. -
Validate. Dispatch the Validate install (staging) workflow (
validate-install.yml) with that tag. It runs the real install commands against the prerelease asset on macOS:cargo binstall(asset-fetch via the[package.metadata.binstall]override, compile fallback disabled so a missing asset fails hard);brew installof an RC formula (the source-of-truth formula repointed at the prerelease tarball + its.sha256, installed from a throwaway local tap).
-
Promote. A green run means the channels work. Bump to the final
<X.Y.Z>(drop the-rc.<N>), commit, tagv<X.Y.Z>, push — the same flow as a normal release below.
The netlist-graph (PyPI) channel validates separately via its own
workflow_dispatch → TestPyPI path (see publish-netlist-graph.yml); it
versions independently of the Rust crates.
What does NOT need to change at release time
- Submodule pins (unless deliberately bumping a vendored dep).
- The
vendor/opensta/submodule pin is the version named incrates/opensta-to-ir::MIN_TESTED_OPENSTA_VERSION. If you bump the submodule, also bump the constant and the version-probe test — see WS-RH.1 indocs/plans/post-phase-0-roadmap.md. LICENSE(unless re-licensing).
Pre-release checklist (one-time, before the first numbered release)
These items are tracked in docs/plans/post-phase-0-roadmap.md
§ Release hardening; this section is the visible punch-list:
- Phase 1 (ADR 0008 required outputs) closed.
- WS-RH.1 (OpenSTA detection + version check) shipped.
-
Metal CI on
macos-runner-1green (re-enabled in commit12e98df, 2026-05-12). -
CUDA CI on
nvidia-runner-1green on main. Currently disabled in.github/workflows/ci.yml(if: ${{ false }}, ~line 268). Re-enable when hardware lands. -
HIP CI on the AMD runner green on main. Currently disabled
in
.github/workflows/ci.yml(if: ${{ false }}, ~line 357). Re-enable when the AMD runner is online. -
Prebuilt CUDA/HIP binaries (ADR 0018 Phase 4), when produced, must
build with
JACQUARD_CUDA_ARCH=all-majorso the kernel ships portable SASS for every major arch (sm_50…sm_120on CUDA ≥ 12.8, Blackwell included) plus PTX for the newest — see the README § CUDA target architecture. Local dev usesJACQUARD_CUDA_ARCH=nativeinstead. Thenvidia1.localBlackwell box (sm_120, CUDA 12.8) is a candidate self-hosted CUDA release runner. - Vendored-dep license posture confirmed (gzz2000/eda-infra-rs#2 — sverilogparse AGPL declaration acknowledged as a typo; workspace Apache-2.0 governs).
-
Cargo.toml::license = "Apache-2.0"set. -
NOTICEfile enumerating vendored deps + their licenses. -
Bump
vendor/eda-infra-rssubmodule once upstream pushes the sverilogparseCargo.tomlcorrection; remove the corresponding footnote inNOTICE. Maintainer acknowledged the typo on 2026-05-02 but hasn't pushed the fix as of 2026-05-13. Verify withgit -C vendor/eda-infra-rs fetch && git log origin/master --oneline. -
CUDA / HIP runtime violation routing through
process_events— done (commit24723b5, issue #104).sim_cuda/sim_hipnow dispatch the timed-batched path and drain violation events, so--timing-report/--timing-summary/--timedare no longer Metal-only. -
Bounded violations array (
--timing-report-max-violations, default 100k). -
End-to-end
--timing-reporttest on Metal CI. Theinv_chain_pnrsim step uses--timing-ir(pre-generated.jtirchecked in) +--timing-report+--timing-summary; a follow-up step validates the JSON shape (top-level keys, semver schema version, metadata, stats, arrays). -
GF180MCU enablement (Phases 0–6) shipped. See
docs/plans/gf180mcu-enablement.md. Phase 7 (wafer.space test-run-1 design integration) deferred pending design availability; not release-blocking.
License posture
Project license is Apache-2.0 (LICENSE). Vendored-dep posture is
enumerated in NOTICE. Summary:
vendor/eda-infra-rs/— Apache-2.0 (workspace). Thesverilogparsesub-crate's staleAGPL-3.0-onlydeclaration inCargo.tomlis a typo per upstream maintainer (gzz2000/eda-infra-rs#2); governed by the workspace LICENSE. Submodule pin will be bumped when upstream pushes the correction.vendor/sky130_fd_sc_hd/— Apache-2.0.vendor/opensta/— GPL-3 (subprocess only per ADR 0001 + ADR 0006 § Amendment; never linked, never bundled).
Cross-references
CHANGELOG.md— release log.docs/adr/0008-structured-timing-output.md—--timing-reportstability contract.docs/adr/0002-timing-ir.md— IR schema versioning.docs/adr/0006-sdf-preprocessing-model.md— OpenSTA bundling rules.docs/project-scope.md— license posture contract.