diff --git a/.cargo/config.toml b/.cargo/config.toml
index 414c8c83e..9e701f3ba 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -3,6 +3,11 @@
[target.'cfg(target_arch = "x86_64")']
rustflags = ["-C", "target-feature=+aes,+sse2"]
+# Same for aarch64 Linux, whose default target features lack AES (Apple
+# targets already enable it). Needed for the linux-aarch64 release wheel.
+[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
+rustflags = ["-C", "target-feature=+aes,+neon"]
+
# On wasm32 (browser) `getrandom` has no default entropy source. Select its JS
# backend so `rand` (e.g. `rand::make_rng()` in the tableau simulators) reads
# from the Web Crypto API. Pairs with the `getrandom`/`wasm_js` feature enabled
diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml
new file mode 100644
index 000000000..fb14222ce
--- /dev/null
+++ b/.github/workflows/release-plz.yml
@@ -0,0 +1,221 @@
+name: Release-plz
+
+# Lockstep release of the Rust crates (crates.io) and bloqade-ppvm (PyPI).
+#
+# On every push to main:
+# - release-plz-pr opens/updates the release PR (version bump + CHANGELOG.md).
+# - release-plz-release publishes any unpublished crate versions, then
+# creates the `v0.x.y` tag and GitHub release (i.e. when the release PR
+# is merged). If it released, the wheels are built, tested and published.
+# Run manually for a build-and-test dry run of the wheels; `publish_pypi`
+# (main only) also publishes the current version to PyPI.
+#
+# Auth: crates.io and PyPI both use OIDC trusted publishing (no tokens). The
+# PyPI publisher points at this file and the `pypi` environment.
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+ inputs:
+ publish_pypi:
+ description: "Publish the current version to PyPI (main only)"
+ type: boolean
+ default: false
+
+permissions: {}
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ release-plz-release:
+ name: Release-plz release
+ if: ${{ github.event_name == 'push' }}
+ runs-on: ubuntu-latest
+ # One release at a time, so quick merges can't race to publish the same version.
+ concurrency:
+ group: release-plz-release-${{ github.ref }}
+ cancel-in-progress: false
+ permissions:
+ contents: write # tags + GitHub release
+ pull-requests: read # link merged PRs in the release notes
+ id-token: write # crates.io trusted publishing
+ outputs:
+ releases_created: ${{ steps.release-plz.outputs.releases_created }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - uses: dtolnay/rust-toolchain@1.98.0
+ - uses: Swatinem/rust-cache@v2
+ - name: Run release-plz
+ id: release-plz
+ uses: release-plz/action@v0.5.139
+ with:
+ command: release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ release-plz-pr:
+ name: Release-plz PR
+ if: ${{ github.event_name == 'push' }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ concurrency:
+ group: release-plz-${{ github.ref }}
+ cancel-in-progress: false
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - uses: dtolnay/rust-toolchain@1.98.0
+ - uses: Swatinem/rust-cache@v2
+ - name: Run release-plz
+ uses: release-plz/action@v0.5.139
+ with:
+ command: release-pr
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # One abi3 wheel per platform (pyo3 `abi3-py310`), so no Python matrix.
+ build:
+ name: Wheel (${{ matrix.target }})
+ needs: [release-plz-release]
+ if: >-
+ ${{ !cancelled() && (needs.release-plz-release.outputs.releases_created == 'true'
+ || github.event_name == 'workflow_dispatch') }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ target: x86_64-unknown-linux-gnu
+ - os: ubuntu-latest
+ target: aarch64-unknown-linux-gnu
+ - os: macos-14
+ target: x86_64-apple-darwin
+ - os: macos-14
+ target: aarch64-apple-darwin
+ - os: windows-latest
+ target: x86_64-pc-windows-msvc
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ # manylinux_2_28 = glibc 2.28+ (RHEL 8 / Ubuntu 20.04+); ignored off Linux.
+ - name: Build wheel
+ uses: PyO3/maturin-action@v1
+ with:
+ working-directory: ppvm-python
+ target: ${{ matrix.target }}
+ args: --release --out dist
+ manylinux: "2_28"
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: wheel-${{ matrix.target }}
+ path: ppvm-python/dist/*.whl
+
+ sdist:
+ name: sdist
+ needs: [release-plz-release]
+ if: >-
+ ${{ !cancelled() && (needs.release-plz-release.outputs.releases_created == 'true'
+ || github.event_name == 'workflow_dispatch') }}
+ permissions:
+ contents: read
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Build sdist
+ uses: PyO3/maturin-action@v1
+ with:
+ working-directory: ppvm-python
+ command: sdist
+ args: --out dist
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: sdist
+ path: ppvm-python/dist/*.tar.gz
+
+ # Install each wheel into a clean env and run the Python test suite against
+ # it, so a broken wheel never reaches PyPI. The x86_64 macOS wheel is not
+ # tested: there is no native runner for it.
+ test:
+ name: Test (${{ matrix.target }}, py${{ matrix.python }})
+ needs: [build]
+ if: ${{ !cancelled() && needs.build.result == 'success' }}
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ python: ["3.10", "3.13"]
+ target:
+ - x86_64-unknown-linux-gnu
+ - aarch64-unknown-linux-gnu
+ - aarch64-apple-darwin
+ - x86_64-pc-windows-msvc
+ include:
+ - target: x86_64-unknown-linux-gnu
+ os: ubuntu-latest
+ - target: aarch64-unknown-linux-gnu
+ os: ubuntu-24.04-arm
+ - target: aarch64-apple-darwin
+ os: macos-14
+ - target: x86_64-pc-windows-msvc
+ os: windows-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: astral-sh/setup-uv@v5
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: wheel-${{ matrix.target }}
+ path: dist
+
+ # Run from outside ppvm-python/ so the src/ tree can't shadow the wheel.
+ - name: Run tests against the installed wheel
+ shell: bash
+ working-directory: ${{ runner.temp }}
+ run: >-
+ uv run --no-project --isolated --python ${{ matrix.python }}
+ --with "$(ls "$GITHUB_WORKSPACE"/dist/*.whl)"
+ --with pytest --with pytest-benchmark
+ pytest -p no:cacheprovider "$GITHUB_WORKSPACE/ppvm-python/test"
+
+ publish-to-pypi:
+ name: Publish to PyPI
+ needs: [release-plz-release, sdist, test]
+ if: >-
+ ${{ !cancelled() && needs.sdist.result == 'success' && needs.test.result == 'success'
+ && (needs.release-plz-release.outputs.releases_created == 'true'
+ || (inputs.publish_pypi && github.ref == 'refs/heads/main')) }}
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/p/bloqade-ppvm
+ permissions:
+ id-token: write
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ path: dist
+ merge-multiple: true
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/Cargo.toml b/Cargo.toml
index 92cc72838..ce5e5b334 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,9 @@
[package]
name = "ppvm"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+# Examples-only package; not published.
+publish = false
[dependencies]
ppvm-pauli-sum = { version = "0.1.0", path = "crates/ppvm-pauli-sum" }
@@ -29,6 +31,14 @@ members = [
"skills/ppvm-usage/examples/rust",
]
+# Single version for every crate and the `bloqade-ppvm` Python package
+# (via ppvm-python-native); bumped by release-plz.
+[workspace.package]
+version = "0.1.0"
+edition = "2024"
+license = "Apache-2.0"
+repository = "https://github.com/QuEraComputing/ppvm"
+
# Keep function names and line numbers in release builds so profilers
# (cargo-flamegraph, samply, Instruments) produce readable output. Adds
# negligible binary bloat compared to full debug info.
diff --git a/crates/ppvm-cli/Cargo.toml b/crates/ppvm-cli/Cargo.toml
index 38290e612..ef5091d6b 100644
--- a/crates/ppvm-cli/Cargo.toml
+++ b/crates/ppvm-cli/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-cli"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Command-line tool and TUI for parsing, running and stepping through ppvm `.sst` programs."
[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
diff --git a/crates/ppvm-cli/LICENSE b/crates/ppvm-cli/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-cli/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-cli/NOTICE b/crates/ppvm-cli/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-cli/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-pauli-sum/Cargo.toml b/crates/ppvm-pauli-sum/Cargo.toml
index 9cd8e1a9f..ab620e808 100644
--- a/crates/ppvm-pauli-sum/Cargo.toml
+++ b/crates/ppvm-pauli-sum/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-pauli-sum"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Pauli-propagation engine for ppvm: `PauliSum`, truncation strategies and ready-made configs."
[dependencies]
ppvm-traits = { version = "0.1.0", path = "../ppvm-traits" }
diff --git a/crates/ppvm-pauli-sum/LICENSE b/crates/ppvm-pauli-sum/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-pauli-sum/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-pauli-sum/NOTICE b/crates/ppvm-pauli-sum/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-pauli-sum/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-pauli-word/Cargo.toml b/crates/ppvm-pauli-word/Cargo.toml
index c789944c5..3a0cb89c9 100644
--- a/crates/ppvm-pauli-word/Cargo.toml
+++ b/crates/ppvm-pauli-word/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-pauli-word"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Packed Pauli-string types (plain, phased, lossy and pattern) for the ppvm simulators."
[dependencies]
ppvm-traits = { version = "0.1.0", path = "../ppvm-traits" }
diff --git a/crates/ppvm-pauli-word/LICENSE b/crates/ppvm-pauli-word/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-pauli-word/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-pauli-word/NOTICE b/crates/ppvm-pauli-word/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-pauli-word/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-python-native/Cargo.toml b/crates/ppvm-python-native/Cargo.toml
index aaf14b584..9057573e6 100644
--- a/crates/ppvm-python-native/Cargo.toml
+++ b/crates/ppvm-python-native/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-python-native"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
diff --git a/crates/ppvm-stim/Cargo.toml b/crates/ppvm-stim/Cargo.toml
index 6f0329988..c6f53d4e6 100644
--- a/crates/ppvm-stim/Cargo.toml
+++ b/crates/ppvm-stim/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-stim"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Parse and run Stim circuits on the ppvm generalized stabilizer tableau."
[features]
default = []
diff --git a/crates/ppvm-stim/LICENSE b/crates/ppvm-stim/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-stim/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-stim/NOTICE b/crates/ppvm-stim/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-stim/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-sym/Cargo.toml b/crates/ppvm-sym/Cargo.toml
index cec8b2a1b..795149bcf 100644
--- a/crates/ppvm-sym/Cargo.toml
+++ b/crates/ppvm-sym/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-sym"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Symbolic, parametric Pauli propagation for ppvm."
[dependencies]
anyhow = "1.0.100"
diff --git a/crates/ppvm-sym/LICENSE b/crates/ppvm-sym/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-sym/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-sym/NOTICE b/crates/ppvm-sym/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-sym/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-tableau-sum/Cargo.toml b/crates/ppvm-tableau-sum/Cargo.toml
index 618bd6db0..fbd601e4c 100644
--- a/crates/ppvm-tableau-sum/Cargo.toml
+++ b/crates/ppvm-tableau-sum/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-tableau-sum"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Sums of generalized stabilizer tableaux with sampling, measurement and noise, for ppvm."
[dependencies]
bitvec = "1.0.1"
diff --git a/crates/ppvm-tableau-sum/LICENSE b/crates/ppvm-tableau-sum/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-tableau-sum/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-tableau-sum/NOTICE b/crates/ppvm-tableau-sum/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-tableau-sum/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-tableau/Cargo.toml b/crates/ppvm-tableau/Cargo.toml
index 6b5c4dc07..e53f4d461 100644
--- a/crates/ppvm-tableau/Cargo.toml
+++ b/crates/ppvm-tableau/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-tableau"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Generalized stabilizer-tableau simulator for ppvm."
[features]
default = []
diff --git a/crates/ppvm-tableau/LICENSE b/crates/ppvm-tableau/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-tableau/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-tableau/NOTICE b/crates/ppvm-tableau/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-tableau/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-traits-2/Cargo.toml b/crates/ppvm-traits-2/Cargo.toml
index 4a772596e..0110c292a 100644
--- a/crates/ppvm-traits-2/Cargo.toml
+++ b/crates/ppvm-traits-2/Cargo.toml
@@ -3,8 +3,11 @@
[package]
name = "ppvm-traits-2"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+publish = false
[dependencies]
num = "0.4"
diff --git a/crates/ppvm-traits/Cargo.toml b/crates/ppvm-traits/Cargo.toml
index 10a1e54a5..2a4c8a2bf 100644
--- a/crates/ppvm-traits/Cargo.toml
+++ b/crates/ppvm-traits/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-traits"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Core traits, the `Config` bundle and the single-qubit Pauli alphabet shared by the ppvm crates."
[dependencies]
bitvec = "1.0.1"
diff --git a/crates/ppvm-traits/LICENSE b/crates/ppvm-traits/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-traits/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-traits/NOTICE b/crates/ppvm-traits/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-traits/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-tui/Cargo.toml b/crates/ppvm-tui/Cargo.toml
index 22a4ffc06..2ba54d303 100644
--- a/crates/ppvm-tui/Cargo.toml
+++ b/crates/ppvm-tui/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-tui"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Composable ratatui components and app state for the ppvm terminal UI."
[dependencies]
eyre = "0.6.12"
diff --git a/crates/ppvm-tui/LICENSE b/crates/ppvm-tui/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-tui/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-tui/NOTICE b/crates/ppvm-tui/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-tui/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/ppvm-vihaco/Cargo.toml b/crates/ppvm-vihaco/Cargo.toml
index 40602edcf..61b4e1edd 100644
--- a/crates/ppvm-vihaco/Cargo.toml
+++ b/crates/ppvm-vihaco/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "ppvm-vihaco"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Run vihaco circuit programs on the ppvm simulators."
[features]
# Parallelism via rayon: shot-level (`run_shots`) and tableau-internal. Off by
diff --git a/crates/ppvm-vihaco/LICENSE b/crates/ppvm-vihaco/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/ppvm-vihaco/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/ppvm-vihaco/NOTICE b/crates/ppvm-vihaco/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/ppvm-vihaco/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/stim-parser/Cargo.toml b/crates/stim-parser/Cargo.toml
index 3941e8014..221d1d1b2 100644
--- a/crates/stim-parser/Cargo.toml
+++ b/crates/stim-parser/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "stim-parser"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Parser for the Stim circuit format, including an extended dialect."
[dependencies]
ariadne = "0.5"
diff --git a/crates/stim-parser/LICENSE b/crates/stim-parser/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/stim-parser/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/stim-parser/NOTICE b/crates/stim-parser/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/stim-parser/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/crates/vihaco-circuit-isa/Cargo.toml b/crates/vihaco-circuit-isa/Cargo.toml
index b64fc9109..69bae76df 100644
--- a/crates/vihaco-circuit-isa/Cargo.toml
+++ b/crates/vihaco-circuit-isa/Cargo.toml
@@ -1,7 +1,10 @@
[package]
name = "vihaco-circuit-isa"
-version = "0.1.0"
-edition = "2024"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+description = "Quantum-circuit instruction set for the vihaco virtual machine, used by ppvm."
[dependencies]
chumsky = "0.10"
diff --git a/crates/vihaco-circuit-isa/LICENSE b/crates/vihaco-circuit-isa/LICENSE
new file mode 120000
index 000000000..30cff7403
--- /dev/null
+++ b/crates/vihaco-circuit-isa/LICENSE
@@ -0,0 +1 @@
+../../LICENSE
\ No newline at end of file
diff --git a/crates/vihaco-circuit-isa/NOTICE b/crates/vihaco-circuit-isa/NOTICE
new file mode 120000
index 000000000..fb376cfaa
--- /dev/null
+++ b/crates/vihaco-circuit-isa/NOTICE
@@ -0,0 +1 @@
+../../NOTICE
\ No newline at end of file
diff --git a/docs/src/pages/develop.astro b/docs/src/pages/develop.astro
index 91dbbf5bf..27afe5008 100644
--- a/docs/src/pages/develop.astro
+++ b/docs/src/pages/develop.astro
@@ -73,6 +73,7 @@ const toc = [
{ label: "§ 5 Python bindings", href: "#python-bindings" },
{ label: "§ 6 Extending ppvm", href: "#extending" },
{ label: "§ 7 Where to look for X", href: "#where-to-look" },
+ { label: "§ 8 Releasing", href: "#releasing" },
];
---
- The compiled ppvm._core is part of the ppvm
+ The compiled ppvm._core is part of the bloqade-ppvm
wheel, so after changing Rust force a rebuild with uv sync
- --project ppvm-python --reinstall-package ppvm (or maturin
+ --project ppvm-python --reinstall-package bloqade-ppvm (or maturin
develop -m crates/ppvm-python-native/Cargo.toml). The Python
project is configured to use uv-managed Python installations, so a fresh
uv sync avoids linking PyO3 builds against a system Python.
@@ -502,7 +503,10 @@ npm run build # extract everything, then `astro build` → dist/
(cfg(target_arch = "x86_64")), not as a workflow-wide
RUSTFLAGS — those x86 features are invalid on the aarch64
macos-latest runner and would fail to compile there. Linux
- and Windows (x86_64) still pick them up from the config.
+ and Windows (x86_64) still pick them up from the config. aarch64 Linux
+ gets +aes,+neon the same way (its default target features
+ lack AES; Apple targets already enable it), which the linux-aarch64
+ release wheel needs.
ppvm-python/test/
+ Releases are automated with release-plz
+ and driven by the Conventional Commit messages on main
+ (§ 4.1). All published crates and the
+ bloqade-ppvm Python package share one version
+ ([workspace.package] in the root Cargo.toml;
+ maturin reads it through ppvm-python-native) and release
+ together under a single v0.x.y tag, one root
+ CHANGELOG.md and one GitHub release. Configuration lives in
+ release-plz.toml; the workflow is
+ .github/workflows/release-plz.yml.
+
main with conventional-commit titles (PRs are squash-merged, so the title is the commit message).release-plz-pr job keeps a release PR open that bumps the shared version and updates CHANGELOG.md. Edit it before merging if the version or notes look wrong.release-plz-release: it publishes
+ the crates to crates.io in dependency order, then creates the tag and
+ GitHub release. Because it released something, the same run builds
+ abi3 wheels (linux x86_64/aarch64, macOS x86_64/arm64, windows
+ x86_64) and an sdist, runs the Python test suite against each
+ installed wheel, and publishes to PyPI.
+
+ The release PR is opened with the default GITHUB_TOKEN, so
+ CI does not run on it. It only changes versions and the changelog.
+
+ Pre-1.0, feat:/fix: bump the patch version and
+ a breaking change (feat!:, or a BREAKING CHANGE:
+ footer) bumps the minor version. semver_check runs
+ cargo-semver-checks and reports Rust API breaks in the
+ release PR. It cannot see the Python API, so mark Python-facing breaking
+ changes with ! yourself and review the release PR with
+ that in mind.
+
+ Python-only changes don't open a release PR.
+ release-plz only tracks the published crates, so commits confined to
+ ppvm-python/ or ppvm-python-native are
+ invisible to it. To release them, open a PR that bumps
+ version in [workspace.package], updates the
+ internal version = "…" requirements on path
+ dependencies to match (for a minor bump they would otherwise reject the
+ new crates), runs cargo update --workspace to refresh
+ Cargo.lock, and adds the notes to CHANGELOG.md
+ by hand. Once it is merged,
+ release-plz-release sees an unpublished version and runs
+ the full release as above.
+
+ Running the Release-plz workflow manually (Actions → Run
+ workflow) builds and tests the wheels without publishing anything.
+ Checking publish_pypi on main also publishes
+ the current version to PyPI — use this if the Python half of a
+ release failed after the crates were already published. To preview
+ the next version bump and changelog locally, run
+ mise exec -- release-plz update; it rewrites files in
+ place, so inspect with git diff and then discard.
+
+ Published versions can't be deleted or replaced, only yanked
+ (cargo yank --version X.Y.Z <crate>, or "yank" in
+ the PyPI project settings). Fix a bad release with a new patch release.
+
+ crates.io and PyPI both use OIDC trusted publishing, so no tokens are
+ stored. Each crate's trusted publisher (crates.io → crate
+ settings → Trusted Publishing) and the PyPI publisher point at
+ owner QuEraComputing, repo ppvm, workflow
+ release-plz.yml; PyPI additionally uses the
+ pypi environment.
+
version, edition, license and repository from the workspace, add a description, and symlink LICENSE and NOTICE into the crate directory (ln -s ../../LICENSE). Internal-only crates get publish = false instead.release-plz.toml, give it a [[package]] entry with version_group = "workspace" and add it to ppvm-cli's changelog_include. Neither omission fails loudly: the crate would version on its own, or its commits would be missing from the release notes.cargo publish -p <crate>) and then configures its trusted publisher.
+ Until the crates exist on crates.io, release-plz-release
+ fails on every push to main. Bootstrap 0.1.0 by hand:
+
main, a crates.io account that will own the crates runs cargo login and cargo publish --workspace, then adds a GitHub team as co-owner of each crate (cargo owner --add github:QuEraComputing:<team> <crate>).git tag -a v0.1.0 -m v0.1.0 && git push origin v0.1.0, and optionally gh release create v0.1.0 --generate-notes.main with publish_pypi checked to publish bloqade-ppvm 0.1.0.
Found something out of date? Send a PR — this guide is the canonical
diff --git a/mise.toml b/mise.toml
index 16df94724..cb7a32d3d 100644
--- a/mise.toml
+++ b/mise.toml
@@ -6,3 +6,5 @@ prek = "0.4.14"
"github:korandoru/hawkeye" = "6.5.1"
# Unused-dependency checker
"cargo:cargo-machete" = "0.9.2"
+# Local release preview (`release-plz update`); same version the CI action uses
+release-plz = "0.3.169"
diff --git a/ppvm-python/AUTHORS b/ppvm-python/AUTHORS
new file mode 100644
index 000000000..04aa57314
--- /dev/null
+++ b/ppvm-python/AUTHORS
@@ -0,0 +1,14 @@
+# This is the list of PPVM authors for copyright purposes.
+#
+# This does not necessarily list everyone who has contributed code, since
+# in some cases their employer may be the copyright holder. To see the
+# full list of contributors, see the revision history in source control.
+#
+# Names should be added to this file as either:
+# Name