-
Notifications
You must be signed in to change notification settings - Fork 0
341 lines (328 loc) · 15.9 KB
/
Copy pathrelease.yml
File metadata and controls
341 lines (328 loc) · 15.9 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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# Release automation: build prebuilt pitty binaries and publish them to a
# GitHub Release, then move the floating major/minor tags (`v1`, `v1.2`, ...)
# and publish matching asset sets so action users can pin either stability level
# while still getting the prebuilt fast path. After the one-time Marketplace
# publication has been done in the GitHub UI, these published GitHub Releases are
# the Marketplace update path; GitHub does not expose a separate Marketplace
# publish API for workflows.
#
# Trigger: this workflow wakes up on any `v*` tag push but only publishes for a
# full patch tag (`vMAJOR.MINOR.PATCH`, e.g. `v1.2.0`). Floating tag moves (`v1`
# and `v1.2`) re-trigger the workflow, and the parse job deliberately turns all
# release/upload jobs into no-ops for those refs. A release job has
# `contents: write` and the GITHUB_TOKEN in scope; restricting it to tag pushes
# from this repo keeps that token away from untrusted fork code (a fork can open
# a PR but cannot push a tag here), which is why there is deliberately no
# `pull_request`/`pull_request_target` trigger.
#
# Asset-name contract (the load-bearing detail): action.yml downloads
# pitty-${ref}-${RUNNER_OS}-${RUNNER_ARCH}.tar.gz
# so the archive names produced here MUST use GitHub's runner OS/arch labels,
# not `uname` values or Rust target triples. In particular Apple Silicon is
# `macOS-ARM64` (RUNNER_OS/RUNNER_ARCH), even though we build the
# `aarch64-apple-darwin` triple; Windows x64 is `Windows-X64`, even though the
# executable inside the archive is `pitty.exe`. A mismatch makes the action's
# fast path 404 and silently fall back to a slow `cargo install`, so the
# `release_asset_name_contract.rs` test pins both sides to one source of truth.
#
# Action pinning: every action is pinned to an immutable commit SHA (with a
# version comment), and ci.yml gates those pins/comments with pinact. This
# workflow has a write-scoped GITHUB_TOKEN in scope, so a compromised mutable tag
# here could publish tampered release assets. Bump the SHAs deliberately.
name: Release
on:
push:
tags:
- "v*"
# Least privilege: the only write this workflow needs is creating the GitHub
# Release, moving version tags, and uploading assets (`contents: write`). It does
# not need packages, id-token, or any other scope.
permissions:
contents: write
jobs:
# Normalize the pushed ref once, then make every publishing job depend on this
# single decision. Why not keep a simple expression guard: GitHub Actions has
# no regex operator, and `contains(github.ref_name, '.')` would also match the
# floating minor tag (`v1.2`) that this workflow force-pushes.
release-version:
name: parse release tag
runs-on: ubuntu-latest
outputs:
should_publish: ${{ steps.parse.outputs.should_publish }}
major: ${{ steps.parse.outputs.major }}
minor: ${{ steps.parse.outputs.minor }}
steps:
- name: Parse release ref
id: parse
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
if [[ "$tag" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
{
echo "should_publish=true"
echo "major=v${BASH_REMATCH[1]}"
echo "minor=v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
} >> "$GITHUB_OUTPUT"
echo "Release tag ${tag}; floating refs are v${BASH_REMATCH[1]} and v${BASH_REMATCH[1]}.${BASH_REMATCH[2]}."
else
{
echo "should_publish=false"
echo "major="
echo "minor="
} >> "$GITHUB_OUTPUT"
echo "Skipping ${tag}; release automation only publishes vMAJOR.MINOR.PATCH tags."
fi
# Create the GitHub Release for the pushed patch tag FIRST.
# upload-rust-binary-action only attaches assets to an existing release — if
# none exists it retries and then fails with "release not found". The four
# parallel upload jobs would all race that failure, so one job creates the
# release up front and they depend on it. If the release already exists, edit
# it back to a published/latest release so reruns preserve the Marketplace-
# visible patch version instead of letting floating releases become Latest.
create-release:
name: create release
needs: [release-version]
if: ${{ needs.release-version.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Create or refresh the release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
if gh release view "$tag" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
echo "Release $tag already exists; marking it published and Latest."
gh release edit "$tag" --repo "${GITHUB_REPOSITORY}" \
--draft=false --prerelease=false --latest
else
echo "Creating release $tag"
gh release create "$tag" --repo "${GITHUB_REPOSITORY}" \
--title "$tag" --generate-notes --latest --verify-tag
fi
# Build one prebuilt binary per OS x arch and upload it under the pushed patch
# tag (e.g. v1.2.0). Separate jobs below rebuild the same targets for the
# floating major/minor releases because upload-rust-binary-action's `ref`
# input selects which release/tag the asset attaches to and feeds the `$tag`
# template variable.
upload-assets:
name: ${{ matrix.os_name }}-${{ matrix.arch_name }} (${{ matrix.target }})
needs: [release-version, create-release]
if: ${{ needs.release-version.outputs.should_publish == 'true' }}
strategy:
# Build every target even if one fails, so a single broken cross build
# does not hide the status of the others.
fail-fast: false
matrix:
include:
# Linux x64: RUNNER_OS = Linux, RUNNER_ARCH = X64. Native build.
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: X64
# Linux ARM64: RUNNER_ARCH = ARM64 on a Linux ARM host. Cross-built
# from an x64 runner via upload-rust-binary-action's `build-tool:
# cross` (which invokes the `cross` tool under the hood; no separate
# cross action is used).
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: ARM64
# Apple Silicon: RUNNER_OS = macOS, RUNNER_ARCH = ARM64, even though
# the Rust triple is aarch64-apple-darwin. The archive must be named
# with runner labels to match action.yml.
#
# No macOS Intel (x86_64-apple-darwin) target: GitHub's macos-13 Intel
# runners are unreliably scheduled (queued indefinitely), which blocked
# the whole release. Intel Macs fall back to `cargo install` in the
# composite action instead. Re-add a `- target: x86_64-apple-darwin`
# entry (and the RUNNER_PAIRS row in the contract test) if a dependable
# Intel runner returns.
- target: aarch64-apple-darwin
runner: macos-14
os_name: macOS
arch_name: ARM64
# Windows x64: the archive is still `.tar.gz` for one action-side
# extraction path; the binary inside is `pitty.exe`.
- target: x86_64-pc-windows-msvc
runner: windows-latest
os_name: Windows
arch_name: X64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# Publish the asset under the pushed tag (e.g. v1.2.0). `archive` is a
# literal name embedding the GitHub runner OS/arch labels (not $target) so
# it matches action.yml's download URL exactly; `leading-dir: false` places
# the binary at the tarball root so the action's `tar -xzf -C
# $HOME/.local/bin` yields `$HOME/.local/bin/pitty` or `pitty.exe`.
# Why not rely on the action default: a future upload-rust-binary-action
# release could flip the default to a leading `pitty-.../` directory,
# which would make action.yml's chmod target hit "No such file"; pinning
# it here fixes the tarball layout regardless.
- name: Upload assets for the release tag
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 # v1.30.2
with:
bin: pitty
target: ${{ matrix.target }}
# Cross-build Linux aarch64 via `cross`; native build for the rest.
build-tool: ${{ matrix.target == 'aarch64-unknown-linux-gnu' && 'cross' || 'cargo' }}
archive: pitty-${{ github.ref_name }}-${{ matrix.os_name }}-${{ matrix.arch_name }}
leading-dir: false
tar: all
zip: none
checksum: sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Move both floating semver tags to this release commit and create matching
# non-Latest GitHub Releases so their prebuilt assets have release targets.
# `v1` supports the default `uses: kexi/pitty@v1`; `v1.2` lets users pin to a
# minor line per GitHub's action-versioning recommendation.
move-floating-tags:
name: move floating tags and publish floating releases
needs: [release-version, upload-assets]
if: ${{ needs.release-version.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Full history so the tag moves point at real commit objects.
fetch-depth: 0
# Force-move the floating major/minor tags to the release commit so
# `uses: kexi/pitty@v1`, `uses: kexi/pitty@v1.2`, and the cargo fallback
# all resolve to the same released source. The push uses the workflow
# GITHUB_TOKEN (contents: write), so no PAT is needed.
- name: Move floating tags to the release commit
env:
MAJOR_REF: ${{ needs.release-version.outputs.major }}
MINOR_REF: ${{ needs.release-version.outputs.minor }}
run: |
set -euo pipefail
for ref in "$MAJOR_REF" "$MINOR_REF"; do
echo "Moving floating tag ${ref} to ${GITHUB_SHA}"
git tag -f "${ref}" "${GITHUB_SHA}"
done
git push -f origin "refs/tags/${MAJOR_REF}" "refs/tags/${MINOR_REF}"
# Create (or refresh) the floating releases so the asset upload jobs have
# releases to attach to. If a floating release exists from a prior patch,
# delete it first so its assets/notes are regenerated for the new commit;
# the moved tags are preserved (we only delete releases, not tags).
- name: Create floating releases
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAJOR_REF: ${{ needs.release-version.outputs.major }}
MINOR_REF: ${{ needs.release-version.outputs.minor }}
run: |
set -euo pipefail
for ref in "$MAJOR_REF" "$MINOR_REF"; do
if gh release view "$ref" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
# `gh release delete` keeps the tag by default (only --cleanup-tag
# removes it), so the freshly force-moved tag is preserved.
echo "Deleting stale $ref release (keeping the tag) before recreating"
gh release delete "$ref" --repo "${GITHUB_REPOSITORY}" --yes
fi
echo "Creating $ref release at ${GITHUB_SHA}"
gh release create "$ref" --repo "${GITHUB_REPOSITORY}" \
--title "$ref" --latest=false \
--notes "Floating release. Tracks the latest ${ref}.x compatible patch; assets mirror the latest tagged release." \
--verify-tag
done
# Build the same targets again, but attach the archives to the floating major
# release under major-named files. A separate matrix job (rather than reusing
# upload-assets) because `ref` must point at the floating tag, which only
# exists after move-floating-tags has run.
upload-major-assets:
name: floating major ${{ matrix.os_name }}-${{ matrix.arch_name }} (${{ matrix.target }})
needs: [release-version, move-floating-tags]
if: ${{ needs.release-version.outputs.should_publish == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: X64
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: ARM64
# No macOS Intel target — see the upload-assets matrix for why.
- target: aarch64-apple-darwin
runner: macos-14
os_name: macOS
arch_name: ARM64
- target: x86_64-pc-windows-msvc
runner: windows-latest
os_name: Windows
arch_name: X64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# `ref` attaches these assets to the floating major release and the
# literal `archive` name embeds that same major ref plus runner OS/arch
# labels, matching the action's default `version: v1` download URL.
- name: Upload assets for the floating major tag
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 # v1.30.2
with:
bin: pitty
target: ${{ matrix.target }}
build-tool: ${{ matrix.target == 'aarch64-unknown-linux-gnu' && 'cross' || 'cargo' }}
ref: refs/tags/${{ needs.release-version.outputs.major }}
archive: pitty-${{ needs.release-version.outputs.major }}-${{ matrix.os_name }}-${{ matrix.arch_name }}
leading-dir: false
tar: all
zip: none
checksum: sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Build the same targets for the floating minor release (`v1.2`, `v2.0`, ...).
# This gives Marketplace/Actions users the standard "stay within this minor
# line" pin without falling back to a source build.
upload-minor-assets:
name: floating minor ${{ matrix.os_name }}-${{ matrix.arch_name }} (${{ matrix.target }})
needs: [release-version, move-floating-tags]
if: ${{ needs.release-version.outputs.should_publish == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: X64
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
os_name: Linux
arch_name: ARM64
# No macOS Intel target — see the upload-assets matrix for why.
- target: aarch64-apple-darwin
runner: macos-14
os_name: macOS
arch_name: ARM64
- target: x86_64-pc-windows-msvc
runner: windows-latest
os_name: Windows
arch_name: X64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# `ref` attaches these assets to the floating minor release and the
# literal `archive` name embeds that same minor ref plus runner OS/arch
# labels, so `with: version: v1.2` gets the prebuilt fast path too.
- name: Upload assets for the floating minor tag
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 # v1.30.2
with:
bin: pitty
target: ${{ matrix.target }}
build-tool: ${{ matrix.target == 'aarch64-unknown-linux-gnu' && 'cross' || 'cargo' }}
ref: refs/tags/${{ needs.release-version.outputs.minor }}
archive: pitty-${{ needs.release-version.outputs.minor }}-${{ matrix.os_name }}-${{ matrix.arch_name }}
leading-dir: false
tar: all
zip: none
checksum: sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}