-
Notifications
You must be signed in to change notification settings - Fork 222
341 lines (303 loc) · 12.5 KB
/
ci.yml
File metadata and controls
341 lines (303 loc) · 12.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
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
name: CI
on:
pull_request: {}
push:
branches: [master]
# For pull requests only, cancel the previous build when a new commit is pushed. Since unfortunately
# it's not possible to only apply this to pull requests, for pull request events we use the ref
# (`refs/pulls/NUMBER/merge`, which gets reused across builds for the same PR), and for pushes we
# use the commit sha (which should never have two builds in the default branch running at a time).
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: true
# Define permissions at the job level.
permissions: {}
jobs:
dist:
name: ${{ matrix.name }}
permissions:
contents: read
id-token: write
attestations: write
strategy:
matrix:
include:
- os: ubuntu-latest
name: Linux
- os: windows-latest
name: Windows
uses: ./.github/workflows/build-boards.yml
with:
os: ${{ matrix.os }}
upload-artifacts: ${{ matrix.os == 'ubuntu-latest' }}
license:
name: Check licensing
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the source code
uses: actions/checkout@v6
- name: Check License Header
uses: apache/skywalking-eyes/header@501a28d2fb4a9b962661987e50cf0219631b32ff
tests:
name: Run tests
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the source code
uses: actions/checkout@v6
- name: Run tests
run: cargo test --verbose --workspace
env:
CARGO_TERM_COLOR: always
format:
name: Check formatting
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the source code
uses: actions/checkout@v6
- name: cargo fmt
run: cargo fmt --all --check
docs-build:
name: Build documentation
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the source code
uses: actions/checkout@v6
- name: Create output directories
run: |
mkdir -p output/reference
mkdir -p output/bugs
- name: Copy static website files
run: |
cp website/index.html output/index.html
cp website/style.css output/style.css
cp website/bugs/index.html output/bugs/index.html
- name: Generate reference
uses: tonynv/asciidoctor-action@master
with:
program: asciidoctor doc/index.adoc -o output/reference/index.html
- name: Upload content as an artifact
uses: actions/upload-pages-artifact@v4
with:
path: output/
retention-days: 90 # We might want to inspect this in a PR.
docs-deploy:
name: Deploy documentation
runs-on: ubuntu-slim
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs:
- docs-build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
id: deployment
# Hubris builds have to be reproducible, and we want to test that in CI. This job does a build of
# a few boards with the standard Ubuntu image and no interference, as the baseline to compire to.
reproducible-a:
name: Reproducibility (A, ${{ matrix.name }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix: &reproducibility-matrix
include:
- name: cosmo-b
image: default
app_toml: app/cosmo/rev-b.toml
- name: oxide-rot-1
image: a
app_toml: app/oxide-rot-1/app.toml
steps:
- name: Checkout the source code
uses: actions/checkout@v6
# We check explicitly to ensure the other job has a different one.
- name: Check that GCC is the system C toolchain
run: cc --version | grep -q "Free Software Foundation"
- name: Build a Hubris board
run: |
umask 0007 # We set the umask explicitly here to ensure the other job has a different one.
cargo xtask dist "$app_toml"
env:
app_toml: ${{ matrix.app_toml }}
- name: Upload the artifact to be later checked
uses: actions/upload-artifact@v6
with:
name: reproducible-${{ matrix.name }}-a
path: target/${{ matrix.name }}/dist/${{ matrix.image }}/build-${{ matrix.name }}-image-${{ matrix.image }}.zip
if-no-files-found: error
# Hubris builds have to be reproducible, and we want to test that in CI. This job does a build of
# a few boards trying to change the build environment as further as possible from reproducible-a.
# Each variability we introduce and its reasoning is documented in comments below.
#
# While this is not a guarantee that things are reproducible, this should catch most of the usual
# sources of nondeterminism within build systems and toolchains.
#
# More information on common variations are available on the reproducible-builds website:
# https://reproducible-builds.org/docs/env-variations/
reproducible-b:
name: Reproducibility (B, ${{ matrix.name }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix: *reproducibility-matrix
env:
CUSTOM_ROOT: /very/long/path/we/are/doing/the/build/in/to/check/for/issues/with/long/paths/or/different/paths
steps:
- name: Install Ubuntu dependencies
run: |
sudo apt-get update
sudo apt-get install -y disorderfs clang
sudo apt-get remove -y gcc
sudo apt-get autoremove -y
# In the Ubuntu dependencies installation step above we switched from GCC to Clang as the
# system C toolchain and linker. We are not using the system linker in the build process
# (Hubris uses the LLD copy bundled with Rust), so switching the system toolchain will catch
# us accidentally relying on it (and breaking reproducibility depending on which toolchain is
# installed on the system attempting to reproduce).
- name: Check that clang is the system C toolchain
run: |
! command -v gcc >/dev/null
cc --version | grep -q clang
- name: Checkout the source code in the standard GitHub Actions directory
uses: actions/checkout@v6
# We run the Hubris build in a different directory, to ensure that paths are not hardcoded. We
# also use a very long path, as a reproducibility issue Emily found in the wild in the past
# was a rust-lang/rust test failing when built in a path that was too long.
#
# We also use disorderfs to randomize the ordering of listing directories, to catch code
# assuming directory entries are always returned in the same order.
- name: Prepare a custom build root directory with disorderfs
run: |
sudo mkdir -p $CUSTOM_ROOT
sudo disorderfs --multi-user=yes --shuffle-dirents=yes $(pwd) $CUSTOM_ROOT
# The current time might be included in the built artifacts. To ensure reproducibility, move
# the time forward by a day and a few hours. This should be enough to expose differences in
# the build without messing with TLS certificate expiration.
#
# Note that this causes very funny behavior in GitHub Action's workflow UI, as apparently step
# duration estimates are based on time reported by the runner???
- name: Move forward in time to ensure a different build date
run: |
echo "Before time change: $(date)"
sudo timedatectl set-ntp false
# Changing the system time only works if NTP is disabled. We disable NTP in the command
# above, but the command exits as soon as systemd *begins* to stop the NTP daemon. We saw
# CI builds where shutting down NTP took longer than expected, causing set-time command to
# fail with this message:
#
# Failed to set time: Previous request is not finished, refusing.
#
# To prevent spurious failures, we repeatedly try to set the time with a backoff. The
# error message was added to systemd in https://github.com/systemd/systemd/pull/11424.
set_time() {
for backoff in 1 2 4 8 16; do
sudo timedatectl set-time "$1" && return 0
echo "retrying in $backoff seconds"
sleep $backoff
done
return 1
}
set_time "$(date -d '1 day 11 hours' "+%Y-%m-%d %H:%M:%S")"
echo "After time change: $(date)"
- name: Build a Hubris board
run: |
# Permissions of files created during the build process might leak into the artifacts.
# Changing the umask will let us test with different permissions than archives created in
# the reproducible-a job.
umask 0077
cd $CUSTOM_ROOT
cargo xtask dist "$app_toml"
env:
app_toml: ${{ matrix.app_toml }}
- name: Move back to the right time
run: sudo timedatectl set-ntp true
- name: Upload the artifact to be later checked
uses: actions/upload-artifact@v6
with:
name: reproducible-${{ matrix.name }}-b
path: ${{ env.CUSTOM_ROOT }}/target/${{ matrix.name }}/dist/${{ matrix.image }}/build-${{ matrix.name }}-image-${{ matrix.image }}.zip
if-no-files-found: error
reproducible-check:
name: Reproducibility check (${{ matrix.name }})
runs-on: ubuntu-slim
needs:
- reproducible-a
- reproducible-b
strategy:
fail-fast: false # We want all reports even if one fails.
matrix: *reproducibility-matrix
permissions: {}
steps:
- name: Install uv (Python package manager)
uses: astral-sh/setup-uv@v7
with:
enable-cache: false
ignore-empty-workdir: true
- name: Download reproducible artifacts
uses: actions/download-artifact@v7
with:
pattern: reproducible-${{ matrix.name }}-*
# Diffoscope is a tool built by the reproducible-builds people to do a rich format-aware diff
# of two files. For example, it understands both zip archives and ELF objects, so it can point
# out the member of archive or the ELF section containing the difference.
#
# We are pulling diffoscope from PyPI transparently through `uvx`, instead of installing it
# from the Ubuntu archives with `apt-get`. We're doing this because due to (mostly sensible)
# packaging choice installing the Ubuntu package takes 6+ minutes, compared to the sub-second
# installation time `uvx` provides. The fact we get a newer version doesn't hurt either.
#
# If you are curious, the reason why the Ubuntu package takes so long to install is because
# diffoscope can produce better diffs the more CLI tools are installed, and the Ubuntu package
# depends on all of those tools. We don't really care about all of them, and the barebones
# version installed through PyPI is enough for us.
- name: Compare the two reproducible artifacts
run: uvx diffoscope --html report.html "reproducible-$name-a/build-$name-image-$image.zip" "reproducible-$name-b/build-$name-image-$image.zip"
env:
name: ${{ matrix.name }}
image: ${{ matrix.image }}
- name: Upload the diffoscope report
if: failure() # Only upload the report if the previous step failed.
id: diffoscope-report
uses: actions/upload-artifact@v6
with:
name: reproducible-diffoscope-report-${{ matrix.name }}
path: report.html
if-no-files-found: error
- name: Add a job summary to point folks to diffoscope
if: failure()
run: echo "Non-reproducibility was detected by CI in $name. [Download the diffoscope report]($report_url) to learn more" >> $GITHUB_STEP_SUMMARY
env:
name: ${{ matrix.name }}
report_url: ${{ steps.diffoscope-report.outputs.artifact-url }}
finish:
name: CI finished
runs-on: ubuntu-slim
permissions: {}
needs:
- dist
- license
- tests
- format
- docs-build
- docs-deploy
- reproducible-check
if: "${{ !cancelled() }}"
steps:
- name: Calculate the correct exit status
run: echo $needs | jq --exit-status 'all(.result == "success" or .result == "skipped")'
env:
needs: ${{ toJson(needs) }}