Repository navigation
263 lines (244 loc) · 10.5 KB
/
Copy pathrelease.yml
File metadata and controls
263 lines (244 loc) · 10.5 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
# The release. T-18.
#
# ⛔ NOTHING PUBLISHES ON A RED SUITE. The evidence table runs first, on the
# two architectures that have runners, on real silicon for aarch64, and the
# build does not start until it is green.
#
# ⚠ The evidence table covers x86_64 and aarch64 and NOTHING ELSE: the three
# cross-compiled architectures have no runner and no demo AppImages, so their
# artefacts are released on the strength of the cross build plus
# scripts/verify-artifacts.sh, with the trampolines run under qemu-user and
# nothing else. That is recorded, not assumed, in
# docs/report/10-measured-versus-assumed.md 10 and docs/limits.md. A red
# evidence table still blocks them: it blocks the whole release.
#
# ⛔ NOTHING PUBLISHES FROM A BRANCH THAT HAS NOT BEEN THROUGH A PULL REQUEST.
# A tag can be pushed at any commit, so that rule is checked rather than
# trusted: the tagged commit must be an ancestor of the default branch, and
# the default branch is what pull requests land on.
#
# ⛔ THE BODY IS GENERATED. scripts/release-notes.sh reads build-manifest.json,
# which scripts/verify-artifacts.sh wrote from the objects themselves. Nothing
# in the body is typed at release time, so the release and the manifest cannot
# disagree about a checksum, a floor or a symbol version.
#
# ⚠ A workflow_dispatch run BUILDS and uploads for inspection and stops there.
# Only a tag publishes, so a manual run cannot create a release named after a
# branch.
#
# PIN EVERY THIRD-PARTY ACTION TO A COMMIT, NOT A TAG.
name: release
# ⭐ A pull request that touches the release path RUNS it, up to but not
# including the publish. A release workflow first exercised on the day of the
# first release is a release workflow nobody has tested, and workflow_dispatch
# cannot reach a workflow that is not yet on the default branch, so a pull
# request is the only place this can be proven before it matters.
# ⚠ `paths` keeps it off every unrelated pull request: it is a five-architecture
# build and it is not a fast gate.
on:
push:
tags: ['v*']
pull_request:
paths:
- '.github/workflows/release.yml'
- 'scripts/build.sh'
- 'scripts/build-in-env.sh'
- 'scripts/verify-artifacts.sh'
- 'scripts/package-release.sh'
- 'scripts/release-notes.sh'
- 'src/**'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
# ---------------------------------------------------------------- the gate --
# ⭐ aarch64 runs on ubuntu-24.04-arm, which is real silicon. Publishing an
# aarch64 artefact whose suite has only ever run under emulation would be
# publishing a claim nobody measured.
gate:
name: evidence table (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: true
matrix:
include:
- arch: x86_64
runner: ubuntu-latest
- arch: aarch64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run the evidence table
run: sh scripts/run-evidence.sh
env:
CLD_ENGINE: docker
# --------------------------------------------------------------- the build --
build:
name: build and package ${{ matrix.arch }}
needs: gate
# ⛔ EVERY architecture builds on ubuntu-latest, and that is deliberate.
# scripts/build.sh CROSS-compiles aarch64 and the three newer targets
# inside an x86-64 floor image (trixie for loongarch64, whose gcc-14 cross
# compiler is the first to exist).
# `podman run --platform linux/<arch>` would pull a tag for another
# platform, REPLACE the cached image for that tag, and leave the next
# x86-64 job with "Exec format error". docs/building.md says what it cost.
runs-on: ubuntu-latest
timeout-minutes: 30
# ⭐ One variant ships. It reads CROSS_LIBC_DLOPEN_ROOT with APPDIR as the
# fallback, because an AppImage runtime exports APPDIR on its own; the
# strict build that reads only this project's name stays a build-time
# choice (`cd src && make portable`), which is where the choice belongs.
# src/cld-env.h has the argument; E87 and E88 measure the strict arm.
strategy:
fail-fast: false
matrix:
arch: [x86_64, aarch64, riscv64, ppc64le, loongarch64]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: what is available
run: sh scripts/build.sh --check --arch ${{ matrix.arch }}
# ⛔ The floor rule. No --floor-glibc override here: a release is built
# on the oldest glibc this project preaches, which is the script's
# default, and an override at release time would be how that silently
# stops being true.
- name: build on the floor
run: |
set -eu
sh scripts/build.sh --engine docker --arch ${{ matrix.arch }}
echo "BUILD_DIR=${{ matrix.arch }}" >> "$GITHUB_ENV"
- name: the manifest
run: cat "build/$BUILD_DIR/build-manifest.json"
# ⚠ The variant is read out of the manifest by the packager, not passed
# in, so a mislabelled directory cannot produce a mislabelled asset.
- name: package
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends zip
sh scripts/package-release.sh ${{ matrix.arch }} "build/$BUILD_DIR"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-${{ matrix.arch }}
path: |
dist/
build/*/build-manifest.json
if-no-files-found: error
retention-days: 7
# ------------------------------------------------------------- the publish --
publish:
name: publish
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
# Only a tag publishes.
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
attestations: write
id-token: write
steps:
# fetch-depth: 0 because the body's changelog is `git log` between this
# tag and the one before it, and a shallow clone has neither.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# ⛔ THE PULL REQUEST RULE, CHECKED. A tag pushed at a commit that never
# reached the default branch never went through review, and this is the
# only place that can still be caught.
- name: the tagged commit reached the default branch
run: |
set -eu
base="origin/${{ github.event.repository.default_branch }}"
git fetch -q origin "${{ github.event.repository.default_branch }}"
sha=$(git rev-parse HEAD)
if ! git merge-base --is-ancestor "$sha" "$base"; then
echo "$sha is not an ancestor of $base."
echo "A tag on a commit that never landed on the default branch has"
echo "not been through a pull request. Refusing to publish."
exit 1
fi
echo "ok: $sha is on $base"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: staged
# download-artifact writes one directory per uploaded artifact, so the
# five architectures arrive as staged/release-x86_64/... through
# staged/release-loongarch64/.... Flatten them into the layout the two
# scripts expect, and fail if ANY is missing rather than publishing
# half a release. The first loop is the other direction: a matrix row
# added above without widening this list would ship silently as a
# release without that architecture, which is exactly the defect this
# step exists to refuse.
- name: collect every architecture
run: |
set -eu
arches="x86_64 aarch64 riscv64 ppc64le loongarch64"
for d in staged/release-*; do
[ -d "$d" ] || { echo "no build artifacts were downloaded"; exit 1; }
a=${d#staged/release-}; a=${a%-*}
case " $arches " in
*" $a "*) ;;
*) echo "$a produced artifacts but is not in the collect list"; exit 1 ;;
esac
done
mkdir -p dist build
for a in $arches; do
src=staged/release-$a
[ -d "$src/dist" ] || { echo "no dist/ for $a"; exit 1; }
cp -a "$src/dist/." dist/
cp -a "$src/build/." build/
done
ls -la dist
echo "--- manifests ---"
for m in build/*/build-manifest.json; do
echo "$m: arch=$(jq -r .arch "$m") variant=$(jq -r '.variant // "default"' "$m")"
done
# ⛔ The checksums in the body have to be the ones in the manifest, and
# the files have to match them. package-release.sh asserted that at build
# time; this asserts it again on the artifacts as DOWNLOADED, because an
# upload and a download sit between the two.
- name: every asset still matches its manifest
run: |
set -eu
fail=0
# ⚠ The default build ships loose files, and there is one variant
# now; every downloaded manifest has its loose set checked here.
for a in x86_64 aarch64 riscv64 ppc64le loongarch64; do
man=build/$a/build-manifest.json
for f in $(jq -r '.artifacts | keys[]' "$man"); do
want=$(jq -r --arg f "$f" '.artifacts[$f].sha256' "$man")
got=$(sha256sum "dist/$a-$f" | awk '{print $1}')
if [ "$want" != "$got" ]; then
echo "MISMATCH $a/$f: manifest $want, asset $got"; fail=1
else
echo "ok $a/$f $want"
fi
done
done
exit $fail
- name: generate the body
run: |
set -eu
sh scripts/release-notes.sh "${GITHUB_REF#refs/tags/}" dist build > /tmp/notes.md
echo "--- generated body ---"
cat /tmp/notes.md
- name: attest what is being published
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/*
- name: create the release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
tag="${GITHUB_REF#refs/tags/}"
gh release create "$tag" \
--repo "$GITHUB_REPOSITORY" \
--title "$tag" \
--notes-file /tmp/notes.md \
--verify-tag \
dist/*