From 59a4ef98d6e319eeca09093fa02721e9430f2008 Mon Sep 17 00:00:00 2001
From: David Plankensteiner
Date: Tue, 6 Oct 2026 14:28:01 +0200
Subject: [PATCH 1/6] ci(release): publish bloqade-ppvm wheels to PyPI on tag
Add a release workflow that builds abi3 wheels for linux x86_64/aarch64,
macOS x86_64/arm64 and windows x86_64 plus an sdist, runs the Python test
suite against each installed wheel, and publishes to PyPI via trusted
publishing on `v*` tags. Manual runs default to a build-and-test dry run.
Rename the distribution to `bloqade-ppvm` (`ppvm` is taken on PyPI; the
import name is unchanged), fill in the package metadata, and take the
version from ppvm-python-native's Cargo.toml.
Enable AES for aarch64 Linux in .cargo/config.toml: gxhash refuses to
compile there otherwise, which would break the linux-aarch64 wheel.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
.cargo/config.toml | 5 +
.github/workflows/release.yml | 179 ++++++++++++++++++++++++++++++++++
ppvm-python/pyproject.toml | 20 +++-
ppvm-python/uv.lock | 57 ++++++-----
4 files changed, 229 insertions(+), 32 deletions(-)
create mode 100644 .github/workflows/release.yml
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.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..85d15fc36
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,179 @@
+name: Release
+
+# Build `bloqade-ppvm` wheels + sdist and publish them to PyPI.
+#
+# - Push a `v*` tag (matching the version in
+# `crates/ppvm-python-native/Cargo.toml`) to build, test and publish.
+# - Run manually ("Run workflow") for a dry run: builds and tests only;
+# publishing is skipped unless the run is on a tag with `dry_run` unchecked.
+#
+# Publishing uses PyPI trusted publishing (OIDC), so there is no API token.
+# Requires a `pypi` environment in the repo settings and a matching trusted
+# publisher on PyPI (workflow `release.yml`, environment `pypi`).
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+ inputs:
+ dry_run:
+ description: "Build and test only; skip publishing"
+ type: boolean
+ default: true
+
+permissions:
+ contents: read
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ # One abi3 wheel per platform (pyo3 `abi3-py310`), so no Python matrix.
+ build:
+ name: Wheel (${{ matrix.target }})
+ 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
+ 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]
+ 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
+ if: ${{ startsWith(github.ref, 'refs/tags/') && !inputs.dry_run }}
+ needs: [build, sdist, test]
+ 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
+
+ # Guard against tagging a commit whose Cargo.toml version wasn't bumped.
+ - name: Check tag matches package version
+ run: |
+ expected="dist/bloqade_ppvm-${GITHUB_REF_NAME#v}.tar.gz"
+ if [ ! -f "$expected" ]; then
+ echo "::error::Tag $GITHUB_REF_NAME does not match the built version:" dist/*.tar.gz
+ exit 1
+ fi
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
+
+ github-release:
+ name: GitHub Release
+ needs: [publish-to-pypi]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ path: dist
+ merge-multiple: true
+
+ - name: Create release with notes and artifacts
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: >-
+ gh release create "$GITHUB_REF_NAME" dist/*
+ --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME"
+ --generate-notes --verify-tag
diff --git a/ppvm-python/pyproject.toml b/ppvm-python/pyproject.toml
index 094a98fca..dd54a4672 100644
--- a/ppvm-python/pyproject.toml
+++ b/ppvm-python/pyproject.toml
@@ -1,18 +1,32 @@
[project]
-name = "ppvm"
-version = "0.1.0"
-description = "Add your description here"
+name = "bloqade-ppvm"
+# Taken from crates/ppvm-python-native/Cargo.toml by maturin.
+dynamic = ["version"]
+description = "Fast Pauli propagation and stabilizer-tableau quantum circuit simulator, with a Rust core."
readme = "README.md"
+license = "Apache-2.0"
authors = [
{ name = "David Plankensteiner", email = "dplankensteiner@quera.com" }
]
requires-python = ">=3.10"
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Science/Research",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Rust",
+ "Topic :: Scientific/Engineering :: Physics",
+]
dependencies = [
"bloqade-circuit>=0.14.1",
"kirin-toolchain~=0.22.2",
"numpy>=2.2.6",
]
+[project.urls]
+Homepage = "https://queracomputing.github.io/ppvm/"
+Repository = "https://github.com/QuEraComputing/ppvm"
+Issues = "https://github.com/QuEraComputing/ppvm/issues"
+
[build-system]
requires = ["maturin>=1.10,<2.0"]
build-backend = "maturin"
diff --git a/ppvm-python/uv.lock b/ppvm-python/uv.lock
index a94a638df..7677981cd 100644
--- a/ppvm-python/uv.lock
+++ b/ppvm-python/uv.lock
@@ -85,6 +85,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/a7f60dacebc1bb03293546849569764698be197fad6b785f08c6276f96f0/bloqade_decoders-0.5.1-py3-none-any.whl", hash = "sha256:ac58437519160548fd7b8ea8895be3f7d81d1d2097d60c0f3fd5d5442d499bf3", size = 17015, upload-time = "2026-03-04T15:57:30.912Z" },
]
+[[package]]
+name = "bloqade-ppvm"
+source = { editable = "." }
+dependencies = [
+ { name = "bloqade-circuit" },
+ { name = "kirin-toolchain" },
+ { name = "numpy" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-benchmark" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "bloqade-circuit", specifier = ">=0.14.1" },
+ { name = "kirin-toolchain", specifier = "~=0.22.2" },
+ { name = "numpy", specifier = ">=2.2.6" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=9.0.2" },
+ { name = "pytest-benchmark", specifier = ">=5.2.3" },
+]
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -899,35 +927,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
-[[package]]
-name = "ppvm"
-version = "0.1.0"
-source = { editable = "." }
-dependencies = [
- { name = "bloqade-circuit" },
- { name = "kirin-toolchain" },
- { name = "numpy" },
-]
-
-[package.dev-dependencies]
-dev = [
- { name = "pytest" },
- { name = "pytest-benchmark" },
-]
-
-[package.metadata]
-requires-dist = [
- { name = "bloqade-circuit", specifier = ">=0.14.1" },
- { name = "kirin-toolchain", specifier = "~=0.22.2" },
- { name = "numpy", specifier = ">=2.2.6" },
-]
-
-[package.metadata.requires-dev]
-dev = [
- { name = "pytest", specifier = ">=9.0.2" },
- { name = "pytest-benchmark", specifier = ">=5.2.3" },
-]
-
[[package]]
name = "py-cpuinfo"
version = "9.0.0"
From ecf3a8a375b65c88d4517920377818fedc13de2f Mon Sep 17 00:00:00 2001
From: David Plankensteiner
Date: Tue, 6 Oct 2026 14:33:35 +0200
Subject: [PATCH 2/6] build(python): ship LICENSE, NOTICE and AUTHORS in the
wheel and sdist
Apache-2.0 requires redistributions to carry the license text and the
NOTICE file. Packaging can't reference files outside ppvm-python/, so
copy the repo-root files there and list them in `license-files`.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
ppvm-python/AUTHORS | 14 +++
ppvm-python/LICENSE | 204 +++++++++++++++++++++++++++++++++++++
ppvm-python/NOTICE | 6 ++
ppvm-python/pyproject.toml | 2 +
4 files changed, 226 insertions(+)
create mode 100644 ppvm-python/AUTHORS
create mode 100644 ppvm-python/LICENSE
create mode 100644 ppvm-python/NOTICE
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
+# or
+# Organization
+#
+# Please keep the list sorted.
+
+QuEra Computing Inc.
diff --git a/ppvm-python/LICENSE b/ppvm-python/LICENSE
new file mode 100644
index 000000000..806a6e9a6
--- /dev/null
+++ b/ppvm-python/LICENSE
@@ -0,0 +1,204 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for describing the origin of the Work and
+ reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2026 The PPVM Authors.
+
+ For a list of copyright holders, see the AUTHORS file at the root of
+ this repository.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/ppvm-python/NOTICE b/ppvm-python/NOTICE
new file mode 100644
index 000000000..a71e1c3e4
--- /dev/null
+++ b/ppvm-python/NOTICE
@@ -0,0 +1,6 @@
+PPVM - Pauli Propagation and Virtual Machine
+Copyright 2026 The PPVM Authors.
+
+This product was initially developed at QuEra Computing Inc.
+(https://www.quera.com/). See the AUTHORS file for the full list of
+copyright holders.
diff --git a/ppvm-python/pyproject.toml b/ppvm-python/pyproject.toml
index dd54a4672..8ba65ccc8 100644
--- a/ppvm-python/pyproject.toml
+++ b/ppvm-python/pyproject.toml
@@ -5,6 +5,8 @@ dynamic = ["version"]
description = "Fast Pauli propagation and stabilizer-tableau quantum circuit simulator, with a Rust core."
readme = "README.md"
license = "Apache-2.0"
+# Copies of the repo-root files; packaging can't reference paths outside this dir.
+license-files = ["LICENSE", "NOTICE", "AUTHORS"]
authors = [
{ name = "David Plankensteiner", email = "dplankensteiner@quera.com" }
]
From c2a4d56606ac28a2fc27a91e4eeef67230bdc612 Mon Sep 17 00:00:00 2001
From: David Plankensteiner
Date: Tue, 6 Oct 2026 14:55:26 +0200
Subject: [PATCH 3/6] build(cargo): share one workspace version and add
crates.io metadata
Move version, edition, license and repository into [workspace.package]
and inherit them in every crate, so the Rust crates and the bloqade-ppvm
Python package (via ppvm-python-native) release under one version.
Add a description to the 12 crates that will be published, and mark the
root examples package, ppvm-traits-2 and ppvm-python-native
`publish = false`. Symlink LICENSE and NOTICE into each published crate
so the crates.io packages carry them, as Apache-2.0 requires.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
Cargo.toml | 14 ++++++++++++--
crates/ppvm-cli/Cargo.toml | 7 +++++--
crates/ppvm-cli/LICENSE | 1 +
crates/ppvm-cli/NOTICE | 1 +
crates/ppvm-pauli-sum/Cargo.toml | 7 +++++--
crates/ppvm-pauli-sum/LICENSE | 1 +
crates/ppvm-pauli-sum/NOTICE | 1 +
crates/ppvm-pauli-word/Cargo.toml | 7 +++++--
crates/ppvm-pauli-word/LICENSE | 1 +
crates/ppvm-pauli-word/NOTICE | 1 +
crates/ppvm-python-native/Cargo.toml | 7 +++++--
crates/ppvm-stim/Cargo.toml | 7 +++++--
crates/ppvm-stim/LICENSE | 1 +
crates/ppvm-stim/NOTICE | 1 +
crates/ppvm-sym/Cargo.toml | 7 +++++--
crates/ppvm-sym/LICENSE | 1 +
crates/ppvm-sym/NOTICE | 1 +
crates/ppvm-tableau-sum/Cargo.toml | 7 +++++--
crates/ppvm-tableau-sum/LICENSE | 1 +
crates/ppvm-tableau-sum/NOTICE | 1 +
crates/ppvm-tableau/Cargo.toml | 7 +++++--
crates/ppvm-tableau/LICENSE | 1 +
crates/ppvm-tableau/NOTICE | 1 +
crates/ppvm-traits-2/Cargo.toml | 7 +++++--
crates/ppvm-traits/Cargo.toml | 7 +++++--
crates/ppvm-traits/LICENSE | 1 +
crates/ppvm-traits/NOTICE | 1 +
crates/ppvm-tui/Cargo.toml | 7 +++++--
crates/ppvm-tui/LICENSE | 1 +
crates/ppvm-tui/NOTICE | 1 +
crates/ppvm-vihaco/Cargo.toml | 7 +++++--
crates/ppvm-vihaco/LICENSE | 1 +
crates/ppvm-vihaco/NOTICE | 1 +
crates/stim-parser/Cargo.toml | 7 +++++--
crates/stim-parser/LICENSE | 1 +
crates/stim-parser/NOTICE | 1 +
crates/vihaco-circuit-isa/Cargo.toml | 7 +++++--
crates/vihaco-circuit-isa/LICENSE | 1 +
crates/vihaco-circuit-isa/NOTICE | 1 +
39 files changed, 106 insertions(+), 30 deletions(-)
create mode 120000 crates/ppvm-cli/LICENSE
create mode 120000 crates/ppvm-cli/NOTICE
create mode 120000 crates/ppvm-pauli-sum/LICENSE
create mode 120000 crates/ppvm-pauli-sum/NOTICE
create mode 120000 crates/ppvm-pauli-word/LICENSE
create mode 120000 crates/ppvm-pauli-word/NOTICE
create mode 120000 crates/ppvm-stim/LICENSE
create mode 120000 crates/ppvm-stim/NOTICE
create mode 120000 crates/ppvm-sym/LICENSE
create mode 120000 crates/ppvm-sym/NOTICE
create mode 120000 crates/ppvm-tableau-sum/LICENSE
create mode 120000 crates/ppvm-tableau-sum/NOTICE
create mode 120000 crates/ppvm-tableau/LICENSE
create mode 120000 crates/ppvm-tableau/NOTICE
create mode 120000 crates/ppvm-traits/LICENSE
create mode 120000 crates/ppvm-traits/NOTICE
create mode 120000 crates/ppvm-tui/LICENSE
create mode 120000 crates/ppvm-tui/NOTICE
create mode 120000 crates/ppvm-vihaco/LICENSE
create mode 120000 crates/ppvm-vihaco/NOTICE
create mode 120000 crates/stim-parser/LICENSE
create mode 120000 crates/stim-parser/NOTICE
create mode 120000 crates/vihaco-circuit-isa/LICENSE
create mode 120000 crates/vihaco-circuit-isa/NOTICE
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 c286a6973..746161608 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 5a30cbeb6..3355e5f6d 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 d88298f30..b2dab1862 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 04697b147..fcf0a7d06 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
From 92fad0ee67f07ebd54f5189aca45e6a9c4dfe04d Mon Sep 17 00:00:00 2001
From: David Plankensteiner
Date: Tue, 6 Oct 2026 15:04:26 +0200
Subject: [PATCH 4/6] ci(release): release crates and wheels in lockstep with
release-plz
Replace the tag-triggered release.yml with release-plz.yml, modelled on
vihaco. On each push to main, release-plz maintains a release PR and,
once it is merged, publishes the crates to crates.io and creates a single
`v0.x.y` tag and GitHub release. When it released, the same run builds,
tests and publishes the bloqade-ppvm wheels to PyPI. Manual runs do a
wheel dry run, with an opt-in PyPI publish for bootstrap and recovery.
release-plz.toml keeps all published crates in one version group with
`ppvm-cli` owning the tag and the root CHANGELOG.md. Pin release-plz in
mise.toml for local previews, and document the release process, including
the one-time 0.1.0 bootstrap, in the Developer Guide.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
.../{release.yml => release-plz.yml} | 132 +++++++++++-------
docs/src/pages/develop.astro | 107 +++++++++++++-
mise.toml | 2 +
release-plz.toml | 86 ++++++++++++
4 files changed, 279 insertions(+), 48 deletions(-)
rename .github/workflows/{release.yml => release-plz.yml} (54%)
create mode 100644 release-plz.toml
diff --git a/.github/workflows/release.yml b/.github/workflows/release-plz.yml
similarity index 54%
rename from .github/workflows/release.yml
rename to .github/workflows/release-plz.yml
index 85d15fc36..7258acf05 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release-plz.yml
@@ -1,37 +1,92 @@
-name: Release
+name: Release-plz
-# Build `bloqade-ppvm` wheels + sdist and publish them to PyPI.
+# Lockstep release of the Rust crates (crates.io) and bloqade-ppvm (PyPI).
#
-# - Push a `v*` tag (matching the version in
-# `crates/ppvm-python-native/Cargo.toml`) to build, test and publish.
-# - Run manually ("Run workflow") for a dry run: builds and tests only;
-# publishing is skipped unless the run is on a tag with `dry_run` unchecked.
+# 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.
#
-# Publishing uses PyPI trusted publishing (OIDC), so there is no API token.
-# Requires a `pypi` environment in the repo settings and a matching trusted
-# publisher on PyPI (workflow `release.yml`, environment `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:
- tags:
- - "v*"
+ branches: [main]
workflow_dispatch:
inputs:
- dry_run:
- description: "Build and test only; skip publishing"
+ publish_pypi:
+ description: "Publish the current version to PyPI (main only)"
type: boolean
- default: true
+ default: false
-permissions:
- contents: read
+permissions: {}
env:
CARGO_TERM_COLOR: always
jobs:
+ release-plz-release:
+ name: Release-plz release
+ if: ${{ github.event_name == 'push' }}
+ runs-on: ubuntu-latest
+ 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:
@@ -70,6 +125,12 @@ jobs:
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
@@ -92,6 +153,9 @@ jobs:
test:
name: Test (${{ matrix.target }}, py${{ matrix.python }})
needs: [build]
+ if: ${{ !cancelled() && needs.build.result == 'success' }}
+ permissions:
+ contents: read
strategy:
fail-fast: false
matrix:
@@ -133,8 +197,11 @@ jobs:
publish-to-pypi:
name: Publish to PyPI
- if: ${{ startsWith(github.ref, 'refs/tags/') && !inputs.dry_run }}
- needs: [build, sdist, test]
+ 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
@@ -147,33 +214,4 @@ jobs:
path: dist
merge-multiple: true
- # Guard against tagging a commit whose Cargo.toml version wasn't bumped.
- - name: Check tag matches package version
- run: |
- expected="dist/bloqade_ppvm-${GITHUB_REF_NAME#v}.tar.gz"
- if [ ! -f "$expected" ]; then
- echo "::error::Tag $GITHUB_REF_NAME does not match the built version:" dist/*.tar.gz
- exit 1
- fi
-
- uses: pypa/gh-action-pypi-publish@release/v1
-
- github-release:
- name: GitHub Release
- needs: [publish-to-pypi]
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - uses: actions/download-artifact@v4
- with:
- path: dist
- merge-multiple: true
-
- - name: Create release with notes and artifacts
- env:
- GH_TOKEN: ${{ github.token }}
- run: >-
- gh release create "$GITHUB_REF_NAME" dist/*
- --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME"
- --generate-notes --verify-tag
diff --git a/docs/src/pages/develop.astro b/docs/src/pages/develop.astro
index 6beaa457b..49da590af 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" },
];
---
@@ -499,7 +500,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.
§ 3Architecture
@@ -751,6 +755,107 @@ chore: restore lockfile consistency
ppvm-python/test/
+ § 8Releasing
+
+
+ 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.
+
+
+ 8.1 The normal flow
+
+ - Merge PRs to
main with conventional-commit titles (PRs are squash-merged, so the title is the commit message).
+ - The
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.
+ -
+ Merging it triggers
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.
+
+
+ 8.2 Versions and breaking changes
+
+ 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] 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.
+
+
+ 8.3 Dry runs and recovery
+
+ 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.
+
+
+ 8.4 Auth
+
+ 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.
+
+
+ 8.5 Adding a published crate
+
+ - Inherit
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.
+ - In
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.
+ - Trusted publishing only works for crates that already exist, so a crate owner publishes the first version by hand (
cargo publish -p <crate>) and then configures its trusted publisher.
+
+
+ 8.6 First release (one-time)
+
+ Until the crates exist on crates.io, release-plz-release
+ fails on every push to main. Bootstrap 0.1.0 by hand:
+
+
+ - From an up-to-date
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>).
+ - Configure the trusted publisher of each of the 12 crates (§ 8.4).
+ - Tag the published commit so the next changelog starts from it:
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.
+ - Run the workflow manually on
main with publish_pypi checked to publish bloqade-ppvm 0.1.0.
+ - Close the stale "release v0.1.0" PR if release-plz opened one before the crates were published.
+
+
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/release-plz.toml b/release-plz.toml
new file mode 100644
index 000000000..1efec2dd6
--- /dev/null
+++ b/release-plz.toml
@@ -0,0 +1,86 @@
+# release-plz configuration: https://release-plz.dev/docs/config
+#
+# All crates (and bloqade-ppvm, via ppvm-python-native) share the workspace
+# version and release in lockstep: one `v0.x.y` tag, one CHANGELOG.md, one
+# GitHub release. See the "Releasing" section of the Developer Guide.
+
+[workspace]
+semver_check = true
+git_tag_name = "v{{ version }}"
+# Separate key; does not inherit from git_tag_name.
+git_release_name = "v{{ version }}"
+# Disabled here and re-enabled on the single tag owner below, so the
+# packages don't all try to create the same tag.
+git_tag_enable = false
+git_release_enable = false
+changelog_update = false
+
+# Tag owner. Must be a published crate in the version group, or no tag is
+# ever created. Published last, as it depends on most other crates.
+[[package]]
+name = "ppvm-cli"
+git_tag_enable = true
+git_release_enable = true
+changelog_update = true
+changelog_path = "CHANGELOG.md"
+# Without this, commits touching only another crate miss the release notes.
+# A new published crate must be added here and given a [[package]] entry.
+changelog_include = [
+ "ppvm-traits",
+ "ppvm-pauli-word",
+ "ppvm-pauli-sum",
+ "ppvm-sym",
+ "ppvm-tableau",
+ "ppvm-tableau-sum",
+ "ppvm-stim",
+ "stim-parser",
+ "vihaco-circuit-isa",
+ "ppvm-vihaco",
+ "ppvm-tui",
+]
+version_group = "workspace"
+
+# Every published crate joins the group so they all bump together.
+[[package]]
+name = "ppvm-traits"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-pauli-word"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-pauli-sum"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-sym"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-tableau"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-tableau-sum"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-stim"
+version_group = "workspace"
+
+[[package]]
+name = "stim-parser"
+version_group = "workspace"
+
+[[package]]
+name = "vihaco-circuit-isa"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-vihaco"
+version_group = "workspace"
+
+[[package]]
+name = "ppvm-tui"
+version_group = "workspace"
From fbd8829ab7bd89082abaeb678866c30d065cf979 Mon Sep 17 00:00:00 2001
From: David Plankensteiner
Date: Tue, 6 Oct 2026 16:06:17 +0200
Subject: [PATCH 5/6] fix(release): serialize releases and correct release docs
Give the release job a concurrency group so two quick merges can't race
to publish the same version. In the Developer Guide, add the internal
dependency requirements and Cargo.lock refresh to the manual bump recipe
(a minor bump otherwise fails to resolve), and use the new
`bloqade-ppvm` name in the reinstall command.
Co-Authored-By: Claude Opus 5.5 (1M context)
---
.github/workflows/release-plz.yml | 4 ++++
docs/src/pages/develop.astro | 12 ++++++++----
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml
index 7258acf05..fb14222ce 100644
--- a/.github/workflows/release-plz.yml
+++ b/.github/workflows/release-plz.yml
@@ -33,6 +33,10 @@ jobs:
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
diff --git a/docs/src/pages/develop.astro b/docs/src/pages/develop.astro
index 49da590af..396396c8e 100644
--- a/docs/src/pages/develop.astro
+++ b/docs/src/pages/develop.astro
@@ -220,9 +220,9 @@ uv run --project ppvm-python --group dev pytest ppvm-python/test/test_basics.py
uv run --project ppvm-python --group dev pytest ppvm-python/test/ -k test_ghz
- 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.
@@ -803,8 +803,12 @@ chore: restore lockfile consistency
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] and adds the
- notes to CHANGELOG.md by hand. Once it is merged,
+ 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.
From d915318511f6f1583a3a65ba9588d81383b79b09 Mon Sep 17 00:00:00 2001
From: Roger-luo
Date: Sat, 10 Oct 2026 22:13:03 +0100
Subject: [PATCH 6/6] fix(release): use portable CPU defaults for distributed
builds
---
.cargo/config.toml | 10 -------
.github/workflows/ci.yml | 6 ++---
crates/ppvm-pauli-sum/benches/gate.rs | 15 +++++++++++
.../ppvm-pauli-sum/benches/random-circuit.rs | 15 +++++++++++
crates/ppvm-pauli-sum/benches/trotter.rs | 15 +++++++++++
docs/src/pages/develop.astro | 27 +++++++++----------
6 files changed, 60 insertions(+), 28 deletions(-)
diff --git a/.cargo/config.toml b/.cargo/config.toml
index 9e701f3ba..536d4915d 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,13 +1,3 @@
-# gxhash requires AES/SSE2 on x86_64; set this once so local cargo
-# build/test commands match CI without per-command RUSTFLAGS.
-[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/ci.yml b/.github/workflows/ci.yml
index a1f269b13..2967d369f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,10 +7,8 @@ on:
env:
CARGO_TERM_COLOR: always
- # gxhash's AES/SSE2 target features are set arch-scoped in `.cargo/config.toml`
- # (`cfg(target_arch = "x86_64")`), so we do NOT set a global RUSTFLAGS here:
- # `-C target-feature=+aes,+sse2` is invalid on aarch64 (macos-latest) and
- # would break that runner. The config applies the flags only on x86_64.
+ # Use each target's baseline CPU features so CI exercises the portable
+ # hashing fallback used by Linux/Windows wheels and crates.io consumers.
# The `pre-commit` job runs the full prek hook suite (rustfmt, clippy,
# cargo check, ruff, ty, hawkeye, and the file hygiene hooks). Every other
diff --git a/crates/ppvm-pauli-sum/benches/gate.rs b/crates/ppvm-pauli-sum/benches/gate.rs
index 7cc28813a..461143932 100644
--- a/crates/ppvm-pauli-sum/benches/gate.rs
+++ b/crates/ppvm-pauli-sum/benches/gate.rs
@@ -172,12 +172,22 @@ pub fn micro_benchmark(c: &mut Criterion) {
.build_global()
.unwrap();
println!("Using {} threads", current_num_threads());
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite::>(c, "ByteF64GxHashMap<2>");
benchmark_suite::>(c, "ByteF64FxHashMap<2>");
benchmark_suite::>(
c,
"ByteF64FxDashMap<2>",
);
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite::>(
c,
"ByteF64GxDashMap<2>",
@@ -186,6 +196,11 @@ pub fn micro_benchmark(c: &mut Criterion) {
c,
"ByteF64FxIndexMap<2>",
);
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite::>(
c,
"ByteF64GxIndexMap<2>",
diff --git a/crates/ppvm-pauli-sum/benches/random-circuit.rs b/crates/ppvm-pauli-sum/benches/random-circuit.rs
index 0f82687a9..b20dc7e96 100644
--- a/crates/ppvm-pauli-sum/benches/random-circuit.rs
+++ b/crates/ppvm-pauli-sum/benches/random-circuit.rs
@@ -65,11 +65,26 @@ pub fn random_circuit_benchmarks(c: &mut Criterion) {
.build_global()
.unwrap();
println!("Using {} threads", current_num_threads());
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_random_circuit::>(c, "ByteF64GxHashMap<2>");
benchmark_suite_random_circuit::>(c, "ByteF64FxHashMap<2>");
benchmark_suite_random_circuit::>(c, "ByteF64FxDashMap<2>");
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_random_circuit::>(c, "ByteF64GxDashMap<2>");
benchmark_suite_random_circuit::>(c, "ByteF64FxIndexMap<2>");
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_random_circuit::>(c, "ByteF64GxIndexMap<2>");
}
diff --git a/crates/ppvm-pauli-sum/benches/trotter.rs b/crates/ppvm-pauli-sum/benches/trotter.rs
index a487175e3..d2878b2f6 100644
--- a/crates/ppvm-pauli-sum/benches/trotter.rs
+++ b/crates/ppvm-pauli-sum/benches/trotter.rs
@@ -100,6 +100,11 @@ pub fn trotter_benchmarks(c: &mut Criterion) {
.build_global()
.unwrap();
println!("Using {} threads", current_num_threads());
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_trotter::<
config::gxhash::ByteF64<2, CombinedStrategy>,
>(
@@ -118,6 +123,11 @@ pub fn trotter_benchmarks(c: &mut Criterion) {
c,
"ByteF64FxDashMap<2, CombinedStrategy>",
);
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_trotter::<
config::dashmap::ByteGxHashF64<2, CombinedStrategy>,
>(
@@ -130,6 +140,11 @@ pub fn trotter_benchmarks(c: &mut Criterion) {
c,
"ByteF64FxIndexMap<2, CombinedStrategy>",
);
+ #[cfg(all(
+ feature = "gxhash",
+ target_feature = "aes",
+ any(target_arch = "x86_64", target_arch = "aarch64")
+ ))]
benchmark_suite_trotter::<
config::indexmap::ByteGxHashF64<2, CombinedStrategy>,
>(
diff --git a/docs/src/pages/develop.astro b/docs/src/pages/develop.astro
index 27afe5008..65bccd997 100644
--- a/docs/src/pages/develop.astro
+++ b/docs/src/pages/develop.astro
@@ -161,10 +161,12 @@ cargo bench --bench micro -- "gates/single-qubit/h"
Rust edition 2024. The fast hasher (gxhash) only compiles with AES
- enabled at compile time; this repo enables it for x86_64 in
- .cargo/config.toml (Apple Silicon has it by default), and CI
- does the same. Without AES, the gxhash dependency is pruned
- and fxhash is used instead (see the WebAssembly notes below).
+ enabled at compile time. Builds use each target's baseline CPU features
+ (Apple Silicon includes AES); without AES, the gxhash
+ dependency is pruned and fxhash is used instead. On a machine with AES,
+ opt into accelerated local benchmarks with
+ RUSTFLAGS="-C target-feature=+aes" cargo bench.
+ Keep those flags out of distributed wheels and source packages.
WebAssembly (wasm32)
@@ -497,16 +499,13 @@ npm run build # extract everything, then `astro build` → dist/
cargo build for the whole workspace three times.
- No global RUSTFLAGS. gxhash's
- +aes,+sse2 target features are set arch-scoped in
- .cargo/config.toml
- (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. 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.
+ Portable CPU defaults. Neither the shared Cargo
+ configuration nor the release workflow forces AES. Linux and Windows
+ wheels use fxhash on their baseline targets, so they also run on CPUs
+ without AES. The same fallback works for crates.io consumers without
+ any repository-local configuration. Apple Silicon targets include AES in their
+ baseline and can use gxhash. AES-only benchmarks are compiled only
+ when the target enables AES.
§ 3Architecture