1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
name: Release
# Triggered by pushing an annotated tag matching `v*` (e.g. v0.1.0,
# v0.2.0-rc1). Also exposes a `workflow_dispatch` button so a
# release-engineer can rebuild artifacts for an existing tag without
# re-pushing it (useful when a runner died mid-matrix).
on:
push:
tags:
workflow_dispatch:
inputs:
ref:
description: "Git ref / tag to build (e.g. v0.1.0)"
required: true
type: string
# Cancel a duplicate run targeting the same tag (e.g. tag re-pushed).
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
PYTHON_VERSION: "3.12"
jobs:
# ──────────────────────────────────────────────────────────────────
# Linux wheels: native runners (x86_64 + aarch64), Zig as the linker
# for a glibc 2.17 floor. `--compatibility manylinux2014` tells maturin
# to tag the wheel accordingly and run its built-in auditwheel
# validation. No QEMU, no manylinux container.
# ──────────────────────────────────────────────────────────────────
linux:
name: linux · ${{ matrix.target }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
runs-on: ubuntu-latest
rust-target: x86_64-unknown-linux-gnu
- target: aarch64
runs-on: ubuntu-24.04-arm
rust-target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.ref || github.ref }}
- uses: ./.github/actions/setup-toolchain
with:
rust-target: ${{ matrix.rust-target }}
cache-key: release-linux-${{ matrix.target }}
python: "true"
python-version: ${{ env.PYTHON_VERSION }}
- uses: mlugg/setup-zig@v2
# Use hardcoded version numbers for now - skipping manually setting version numbers
# - name: Sync workspace + pyproject version to tag
# run: python3 tools/set_version_from_tag.py "${{ inputs.ref || github.ref }}"
- name: Build wheel
run: >
uvx maturin build --release --zig
--target ${{ matrix.rust-target }}
--compatibility manylinux2014
--out dist
- uses: actions/upload-artifact@v7
with:
name: wheel-linux-${{ matrix.target }}
path: dist/*.whl
if-no-files-found: error
# ──────────────────────────────────────────────────────────────────
# macOS: x86_64 (Intel) + arm64 (Apple Silicon). The `macos-13`
# runner is x86_64, `macos-14` is arm64. Both are native — no
# cross-compile needed.
# ──────────────────────────────────────────────────────────────────
macos:
name: macos · ${{ matrix.target }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64
runs-on: macos-14
rust-target: aarch64-apple-darwin
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.ref || github.ref }}
- uses: ./.github/actions/setup-toolchain
with:
rust-target: ${{ matrix.rust-target }}
cache-key: release-macos-${{ matrix.target }}
python: "true"
python-version: ${{ env.PYTHON_VERSION }}
# Use hardcoded version numbers for now - skipping manually setting version numbers
# - name: Sync workspace + pyproject version to tag
# run: python3 tools/set_version_from_tag.py "${{ inputs.ref || github.ref }}"
- name: Build wheel
run: >
uvx maturin build --release
--target ${{ matrix.rust-target }}
--out dist
- uses: actions/upload-artifact@v7
with:
name: wheel-macos-${{ matrix.target }}
path: dist/*.whl
if-no-files-found: error
# ──────────────────────────────────────────────────────────────────
# Source distribution. Platform-independent, so just spin up the
# cheapest runner and let maturin package the source.
# ──────────────────────────────────────────────────────────────────
sdist:
name: sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.ref || github.ref }}
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
# Use hardcoded version numbers for now - skipping manually setting version numbers
# - name: Sync workspace + pyproject version to tag
# run: python3 tools/set_version_from_tag.py "${{ inputs.ref || github.ref }}"
- name: Build sdist
run: uvx maturin sdist --out dist
- uses: actions/upload-artifact@v7
with:
name: sdist
path: dist/*.tar.gz
if-no-files-found: error
# ──────────────────────────────────────────────────────────────────
# GitHub Release: attach every wheel + sdist as release assets.
# The release is created in `draft` mode by default — release-eng
# reviews the artifact list and publishes manually. Skips on a
# workflow_dispatch run because that's typically a re-build, not a
# re-publish.
# ──────────────────────────────────────────────────────────────────
github-release:
name: github release
needs:
# Only on real tag pushes — never on workflow_dispatch, even if dispatched
# from a tag ref. `event_name == 'push'` excludes the manual-run path.
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Create draft release with attachments
uses: softprops/action-gh-release@v3
with:
files: dist/*
draft: true
generate_release_notes: true
# ──────────────────────────────────────────────────────────────────
# crates.io publish.
# Single-crate workspace, so one `cargo publish` call is enough.
# ──────────────────────────────────────────────────────────────────
crates-io:
name: publish to crates.io
needs:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment:
name: crates-io
steps:
- uses: actions/checkout@v5
with:
ref: ${{ inputs.ref || github.ref }}
- uses: ./.github/actions/setup-toolchain
with:
cache-key: release-crates-io
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --locked
# ──────────────────────────────────────────────────────────────────
# PyPI publish (trusted publishing).
# Trusted publishing requires no API token: PyPI verifies
# the OIDC token GitHub mints and matches it against the configured
# repo + workflow + environment triple.
# ──────────────────────────────────────────────────────────────────
pypi:
name: publish to PyPI
needs:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/rust-arrs
permissions:
id-token: write # OIDC token for trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
skip-existing: true