-
Notifications
You must be signed in to change notification settings - Fork 0
260 lines (250 loc) · 12.3 KB
/
Copy pathrolling-release.yml
File metadata and controls
260 lines (250 loc) · 12.3 KB
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Rolling release: publish a calendar-versioned GitHub Release on EVERY push to
# main, so "install IronCache" is "grab the latest release". This is the
# CONTINUOUS channel; the formal `v*` channel (release.yml) keeps the curated,
# tagged releases (changelog gate, minisign, CycloneDX export). The two-channel
# model mirrors IronBus (see RELEASING.md).
#
# Each rolling build ships the four reproducible cross-built binaries (x86_64 +
# aarch64, musl + glibc-2.17, via cargo-zigbuild, reusing release.yml's proven
# build VERBATIM), one consolidated SHA256SUMS, and a keyless Sigstore
# build-provenance attestation over the binaries + SHA256SUMS. The calendar
# version is stamped into the binary via IRONCACHE_BUILD_VERSION (read by
# option_env! in cli::BUILD_VERSION), so `ironcache --version` reports the
# published build without touching Cargo.lock (which pins every crate at 0.0.0).
#
# The version is YYYY.MMDD.N (UTC date + per-day build number); the first push of
# a day is .1, the next .2, the next day resets. A NORMAL release (not draft, not
# prerelease) so GitHub's releases/latest points at the newest rolling build.
# Skip a build by putting `[skip release]` in the head commit message.
name: rolling-release
on:
push:
branches: [main]
workflow_dispatch:
# Serialize close-together pushes so two builds never collide on the per-day
# build number: max+1 is computed against published releases, so two near
# simultaneous runs must not race between "compute N" and "create the release".
concurrency:
group: rolling-release
cancel-in-progress: false
permissions:
contents: read
jobs:
guard:
name: guard (skip until a Cargo project exists)
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
has_cargo: ${{ steps.check.outputs.has_cargo }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- id: check
shell: bash
run: |
if [ -f Cargo.toml ] && [ -f Cargo.lock ]; then
echo "has_cargo=true" >> "$GITHUB_OUTPUT"
else
echo "has_cargo=false" >> "$GITHUB_OUTPUT"
echo "::notice::repo is docs-only (no Cargo.toml/Cargo.lock); rolling-release is a no-op"
fi
version:
name: compute calendar version
needs: guard
# Run on workflow_dispatch always; on push, skip when the head commit opts
# out with [skip release]. Both gated on a real Cargo project existing.
if: >-
needs.guard.outputs.has_cargo == 'true' &&
( github.event_name == 'workflow_dispatch' ||
!contains(github.event.head_commit.message, '[skip release]') )
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.compute.outputs.version }}
steps:
- id: compute
name: compute YYYY.MMDD.N
env:
GH_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
# The UTC calendar date with leading-zero month/day, e.g. 2026.0615.
# This is a deliberate CalVer string used only as a tag and a printed
# version, never semver-parsed, so the leading zeros are fine.
DATE="$(date -u +%Y.%m%d)"
# N is the per-day build number: the MAX of the existing same-day build
# numbers plus one, NOT count + one. Counting is gap-fragile: a deleted
# tag, or an operator-created tag that jumped ahead, would make count+1
# collide with an existing tag and `gh release create` would fail,
# shipping nothing. max+1 is robust against gaps. The next day, with no
# prior "$DATE." releases, max is 0 so the first build is 1 (each day
# resets). `startswith` + `split(".")` avoids escaping the date's dots.
maxn="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1000 --json tagName \
--jq "[.[].tagName | select(startswith(\"${DATE}.\")) | split(\".\")[-1] | tonumber] | max // 0")"
build=$((maxn + 1))
VERSION="${DATE}.${build}"
echo "computed rolling version: $VERSION (highest existing $DATE.* build: $maxn)"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
build:
name: build ${{ matrix.target }}
needs: version
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# `target` is the internal cargo/cross-build triple; `plat` is the
# human-friendly platform tag (CPU-arch + libc). The `unknown` vendor
# field in a Rust Linux triple confuses non-Rust operators, so the
# published asset drops the triple: ironcache-<version>-linux-<arch>-<libc>.
include:
- target: x86_64-unknown-linux-musl
plat: linux-amd64-musl
- target: aarch64-unknown-linux-musl
plat: linux-arm64-musl
# glibc-version-pinned gnu fallback (pin the minimum glibc per target).
- target: x86_64-unknown-linux-gnu.2.17
plat: linux-amd64-glibc
- target: aarch64-unknown-linux-gnu.2.17
plat: linux-arm64-glibc
env:
# Reproducibility contract (same as release.yml): pinned Cargo.lock is the
# input base; CARGO_INCREMENTAL=0 and a fixed SOURCE_DATE_EPOCH keep the
# artifact bytes stable. The only intended per-build difference is the
# stamped version string, set below.
SOURCE_DATE_EPOCH: "1"
CARGO_INCREMENTAL: "0"
RUSTFLAGS: "--remap-path-prefix=${{ github.workspace }}=/build -C strip=symbols"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install Rust + zig + cargo-zigbuild + cargo-auditable
env:
TARGET: ${{ matrix.target }}
run: |
rustup toolchain install stable --profile minimal
# Strip any glibc version suffix (e.g. .2.17) before `rustup target add`;
# cargo-zigbuild itself consumes the dotted suffix on --target below.
rustup target add "${TARGET%%.*}" || true
cargo install --locked cargo-zigbuild cargo-auditable
# cargo-zigbuild shells out to `zig cc` as the cross-linker, so zig must
# be present (the cargo plugin alone is not enough). Install a pinned zig
# via the ziglang wheel so the cross-build stays reproducible;
# cargo-zigbuild discovers it through `python3 -m ziglang`. Pin 0.14.x to
# stay before zig 0.15+'s clang-18 requirement. --break-system-packages:
# the runner's system python is PEP 668 externally-managed.
python3 -m pip install --break-system-packages ziglang==0.14.1
python3 -m ziglang version
- name: Cross-build (locked, reproducible, embedded SBOM, stamped version)
env:
# Stamp the calendar version into the binary; option_env! in
# cli::BUILD_VERSION reads it, so `ironcache --version` reports the
# published rolling build. Compile-time only; Cargo.lock stays at 0.0.0
# so --locked still holds. build.rs's rerun-if-env-changed makes this
# re-stamp reliably even on a warm target.
IRONCACHE_BUILD_VERSION: ${{ needs.version.outputs.version }}
run: |
cargo auditable zigbuild --release --locked --target "${{ matrix.target }}"
- name: Package the tarball
shell: bash
env:
VERSION: ${{ needs.version.outputs.version }}
run: |
set -euo pipefail
target="${{ matrix.target }}"
# cargo-zigbuild writes to the BARE rustc target dir; the `.2.17` glibc
# pin is a zigbuild linker hint, not part of the target/ path (so a
# `gnu.2.17` build lands in target/<triple-without-suffix>/release).
# Strip the suffix for the directory (the same ${target%%.*} transform
# the install step uses); musl has no suffix, so it is unchanged.
tdir="target/${target%%.*}/release"
# Friendly, versioned asset name: ironcache-<version>-linux-<arch>-<libc>
# (no `unknown` Rust triple).
out="ironcache-${VERSION}-${{ matrix.plat }}.tar.gz"
# Deterministic tar (sorted, no owner/uid/gid, fixed mtime) so a re-run
# of the same inputs yields a byte-identical archive.
tar --sort=name --owner=0 --group=0 --numeric-owner \
--mtime="@${SOURCE_DATE_EPOCH}" -C "$tdir" -czf "$out" ironcache
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
# A UNIQUE artifact name per platform (no shared SHA256SUMS file), so the
# publish job's merge-multiple download cannot have two same-named files
# overwrite each other. The consolidated SHA256SUMS is built once, in
# publish, over the four downloaded tarballs.
with:
name: ${{ matrix.plat }}
path: ironcache-*.tar.gz
if-no-files-found: error
publish:
name: publish rolling release
needs: [version, build]
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write # create the tag + GitHub Release
id-token: write # keyless Sigstore signing via OIDC
attestations: write # store the build-provenance attestation
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
path: artifacts
merge-multiple: true
- name: Build the consolidated SHA256SUMS over the four tarballs
shell: bash
run: |
set -euo pipefail
cd artifacts
# Build SHA256SUMS from the tarballs themselves (not from per-job files),
# so all four arch tarballs are always covered. Self-check before it
# ships, so a bad upload can never produce a SHA256SUMS that does not
# match its own artifacts.
sha256sum ironcache-*.tar.gz > SHA256SUMS
sha256sum -c SHA256SUMS
ls -l
- uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2
with:
# Keyless Sigstore build-provenance over the binaries + SHA256SUMS, so a
# verifier can anchor the whole set to one provenance (verify with
# `gh attestation verify <asset> --repo <owner>/<repo>`).
subject-path: |
artifacts/ironcache-*.tar.gz
artifacts/SHA256SUMS
- name: Publish the rolling GitHub release
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
VERSION: ${{ needs.version.outputs.version }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
# Heredoc keeps the markdown readable; no changelog gate (rolling builds
# are continuous, not curated). A NORMAL release (no --prerelease, no
# --draft) so releases/latest resolves to this newest build. --target
# pins the tag to this exact commit.
notes="$(cat <<EOF
Automatic rolling build \`${VERSION}\` from commit \`${SHA}\`.
Reproducible cross-built binaries for four Linux targets (x86_64 and
aarch64, each as a static \`musl\` build and a glibc-2.17-pinned \`gnu\`
build). Each carries an embedded cargo-auditable SBOM. Pick the tarball
for your platform, extract, and run \`ironcache --version\` to confirm
it reports \`${VERSION}\`.
| Platform | Asset |
| --- | --- |
| x86_64, static (no libc needed) | \`ironcache-${VERSION}-linux-amd64-musl.tar.gz\` |
| aarch64, static (no libc needed) | \`ironcache-${VERSION}-linux-arm64-musl.tar.gz\` |
| x86_64, glibc >= 2.17 | \`ironcache-${VERSION}-linux-amd64-glibc.tar.gz\` |
| aarch64, glibc >= 2.17 | \`ironcache-${VERSION}-linux-arm64-glibc.tar.gz\` |
Verify integrity with \`sha256sum -c SHA256SUMS\`, and provenance with
\`gh attestation verify <asset> --repo ${REPO}\`. The formal, tagged
\`v*\` channel (with the changelog gate, minisign signatures, and the
standalone CycloneDX SBOM) is described in RELEASING.md.
EOF
)"
gh release create "$VERSION" \
--repo "$REPO" \
--title "$VERSION" \
--target "$SHA" \
--notes "$notes" \
artifacts/ironcache-*.tar.gz \
artifacts/SHA256SUMS