This repository has no description
11 kB
285 lines
1name: CI
2
3on:
4 push:
5 branches: [master]
6 pull_request:
7 workflow_dispatch:
8
9# A newer run for the same ref replaces a running one.
10concurrency:
11 group: ci-${{ github.ref }}
12 cancel-in-progress: true
13
14# One job per check shows failing checks in the run overview, the same for
15# the NixOS machines, see the eval job.
16jobs:
17 list:
18 runs-on: ubuntu-latest
19 outputs:
20 checks: ${{ steps.list.outputs.checks }}
21 steps:
22 - uses: actions/checkout@v4
23 with:
24 # Fetch all history for git-based operations
25 fetch-depth: 0
26
27 - uses: cachix/install-nix-action@v31
28
29 - id: list
30 run: |
31 checks=$(nix eval --json .#checks.x86_64-linux --apply builtins.attrNames)
32 echo "checks=$checks" >> "$GITHUB_OUTPUT"
33
34 check:
35 needs: list
36 name: ${{ matrix.check }}
37 runs-on: ubuntu-latest
38 strategy:
39 fail-fast: false
40 matrix:
41 check: ${{ fromJSON(needs.list.outputs.checks) }}
42 steps:
43 - uses: actions/checkout@v4
44 with:
45 # Fetch all history for git-based operations
46 fetch-depth: 0
47
48 - uses: cachix/install-nix-action@v31
49
50 - run: nix build -L --no-link '.#checks.x86_64-linux.${{ matrix.check }}'
51
52 outputs:
53 name: packages and devShells
54 runs-on: ubuntu-latest
55 steps:
56 - uses: actions/checkout@v4
57 with:
58 # Fetch all history for git-based operations
59 fetch-depth: 0
60
61 - uses: cachix/install-nix-action@v31
62
63 - run: |
64 for output in packages devShells; do
65 echo "$output:"
66 # shellcheck disable=SC2016 # ${...} is Nix interpolation
67 nix eval --raw ".#$output.x86_64-linux" --apply 'xs: builtins.deepSeq (builtins.mapAttrs (_: x: x.drvPath) xs) "${toString (builtins.length (builtins.attrNames xs))} evaluated\n"'
68 done
69
70 # The NixOS machines, built on plain runners with hestia (the GitHub Actions
71 # cache) as binary cache next to cache.lassul.us:
72 #
73 # - eval: evaluates ciToplevels once (iets), uploads the drv closures to hestia,
74 # lists the machines whose toplevel is in neither cache and the
75 # derivations needed by more than one of them (.github/ci-shared.sh).
76 # - shared: builds those once, so the machine jobs substitute them instead
77 # of each building them; skipped when there are none.
78 # - one job per listed machine, builds its toplevel by drv path on a lazy
79 # /nix/store (.github/lazy-store.sh): the paths it would substitute are
80 # only downloaded once a file in them is opened.
81 #
82 # Every job pushes what it added to the store to cache.lassul.us
83 # (.github/ci-build.sh), for deploys.
84 eval:
85 runs-on: ubuntu-latest
86 permissions:
87 contents: read
88 actions: read # hestia: detect evicted cache entries upfront
89 id-token: write # niks3 push
90 outputs:
91 matrix: ${{ steps.matrix.outputs.matrix }}
92 any-jobs: ${{ steps.matrix.outputs.any-jobs }}
93 manifest-version: ${{ steps.matrix.outputs.manifest-version }}
94 shared: ${{ steps.plan.outputs.shared }}
95 niks3: ${{ steps.tools.outputs.niks3 }}
96 lazy-store: ${{ steps.tools.outputs.lazy-store }}
97 steps:
98 - uses: actions/checkout@v4
99 - uses: cachix/install-nix-action@v31
100 with:
101 extra_nix_config: |
102 trusted-users = root runner
103 extra-substituters = https://cache.lassul.us
104 extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0=
105 - uses: Mic92/hestia@v3
106 with:
107 upstream-cache-filter: true
108 upstream-cache-key-names: cache.nixos.org-1 cache.lassul.us-1
109 filter-drv-closures: true
110 # the niks3 client of all jobs and the lazy store of the machine jobs,
111 # the others substitute them; iets for the evaluation, its eval-jobs
112 # is nix-eval-jobs about 5 times faster (no import from derivation)
113 - id: tools
114 run: |
115 niks3=$(nix build --no-link --print-out-paths .#niks3)
116 lazy=$(nix build --no-link --print-out-paths .#lazy-store)
117 iets=$(nix build --no-link --print-out-paths .#iets)
118 {
119 echo "niks3=$niks3"
120 echo "lazy-store=$lazy"
121 echo "iets=$iets/bin/iets"
122 } >> "$GITHUB_OUTPUT"
123 PATH=$niks3/bin:$PATH .github/niks3-push.sh "$niks3" "$lazy" "$iets"
124 - id: matrix
125 uses: Mic92/hestia/matrix@v3
126 with:
127 flake: ".#ciToplevels"
128 nix-eval-jobs: "${{ steps.tools.outputs.iets }} eval-jobs --workers 4 --max-memory-size 3072"
129 - env:
130 MATRIX: ${{ steps.matrix.outputs.matrix }}
131 run: |
132 echo "not cached: $(jq -r '[.include[].name] | join(" ")' <<<"$MATRIX")" |
133 tee -a "$GITHUB_STEP_SUMMARY"
134 # the derivations the shared job builds and per machine the paths its
135 # lazy store registers
136 - id: plan
137 if: steps.matrix.outputs.any-jobs == 'true'
138 env:
139 MATRIX: ${{ steps.matrix.outputs.matrix }}
140 run: |
141 matrix=$RUNNER_TEMP/matrix.json
142 plans=$RUNNER_TEMP/plans
143 printf '%s' "$MATRIX" > "$matrix"
144 .github/ci-shared.sh "$matrix" "$plans"
145 {
146 for name in $(jq -r '.include[].name' "$matrix"); do
147 echo "- $name: $(wc -l < "$plans/$name.drvs") derivations to build"
148 done
149 echo "- shared (needed by >= 2 machines): $(wc -l < "$plans/shared.drvs")"
150 sed -E 's|^/nix/store/[^-]+-(.*)\.drv$| \1|' "$plans/shared.drvs"
151 } | tee -a "$GITHUB_STEP_SUMMARY"
152 if [ -s "$plans/shared.drvs" ]; then
153 echo "shared=true" >> "$GITHUB_OUTPUT"
154 fi
155 - if: steps.matrix.outputs.any-jobs == 'true'
156 uses: actions/upload-artifact@v4
157 with:
158 name: plans
159 path: |
160 ${{ runner.temp }}/plans/shared.drvs
161 ${{ runner.temp }}/plans/*.fetch
162
163 shared:
164 needs: eval
165 if: needs.eval.outputs.shared == 'true'
166 runs-on: ubuntu-latest
167 permissions:
168 contents: read
169 actions: read # hestia: detect evicted cache entries upfront
170 id-token: write # niks3 push
171 outputs:
172 manifest-version: ${{ steps.build.outputs.manifest-version }}
173 steps:
174 - uses: actions/checkout@v4
175 - uses: cachix/install-nix-action@v31
176 with:
177 extra_nix_config: |
178 trusted-users = root runner
179 extra-substituters = https://cache.lassul.us
180 extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0=
181 - uses: Mic92/hestia@v3
182 with:
183 upstream-cache-filter: true
184 upstream-cache-key-names: cache.nixos.org-1 cache.lassul.us-1
185 wait-manifest-version: ${{ needs.eval.outputs.manifest-version }}
186 - env:
187 NIKS3: ${{ needs.eval.outputs.niks3 }}
188 run: |
189 nix-store --realise "$NIKS3"
190 echo "$NIKS3/bin" >> "$GITHUB_PATH"
191 - uses: actions/download-artifact@v4
192 with:
193 name: plans
194 path: ${{ runner.temp }}/plans
195 - id: build
196 env:
197 EVAL_MANIFEST_VERSION: ${{ needs.eval.outputs.manifest-version }}
198 run: |
199 mapfile -t shared < "$RUNNER_TEMP/plans/shared.drvs"
200 "$HESTIA_BIN" prefetch "${shared[@]/%/^*}"
201 # a failed one fails the jobs of the machines that need it
202 .github/ci-build.sh "${shared[@]/%/^*}" ||
203 echo "::warning::some shared derivations failed to build"
204 # upload now instead of in the post step, so the machine jobs can
205 # wait for the manifest version that contains the shared outputs
206 "$HESTIA_BIN" drain --socket "$HESTIA_SOCKET" 2>&1 | tee "$RUNNER_TEMP/drain.txt"
207 drained=$(sed -nE 's/.*; manifest m[0-9]+#([0-9]+)$/\1/p' "$RUNNER_TEMP/drain.txt")
208 echo "manifest-version=${drained:-$EVAL_MANIFEST_VERSION}" >> "$GITHUB_OUTPUT"
209
210 build:
211 needs: [eval, shared]
212 # also when the shared job was skipped: nothing to share
213 if: >-
214 !cancelled() && needs.eval.result == 'success' && needs.eval.outputs.any-jobs == 'true'
215 && (needs.shared.result == 'success' || needs.shared.result == 'skipped')
216 name: ${{ matrix.name }}
217 runs-on: ${{ matrix.os }}
218 permissions:
219 contents: read
220 actions: read # hestia: detect evicted cache entries upfront
221 id-token: write # niks3 push
222 strategy:
223 fail-fast: false
224 matrix: ${{ fromJSON(needs.eval.outputs.matrix) }}
225 steps:
226 - uses: actions/checkout@v4
227 - uses: cachix/install-nix-action@v31
228 with:
229 extra_nix_config: |
230 trusted-users = root runner
231 extra-substituters = https://cache.lassul.us
232 extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0=
233 - uses: Mic92/hestia@v3
234 with:
235 upstream-cache-filter: true
236 upstream-cache-key-names: cache.nixos.org-1 cache.lassul.us-1
237 wait-manifest-version: ${{ needs.shared.outputs.manifest-version || needs.eval.outputs.manifest-version }}
238 - env:
239 NIKS3: ${{ needs.eval.outputs.niks3 }}
240 run: |
241 nix-store --realise "$NIKS3"
242 echo "$NIKS3/bin" >> "$GITHUB_PATH"
243 - uses: actions/download-artifact@v4
244 with:
245 name: plans
246 path: ${{ runner.temp }}/plans
247 - name: build ${{ matrix.name }}
248 env:
249 NAME: ${{ matrix.name }}
250 INSTALLABLES: ${{ matrix.installables }}
251 LAZY_STORE: ${{ needs.eval.outputs.lazy-store }}
252 run: |
253 read -ra installables <<<"$INSTALLABLES"
254 # the closure needs only the caches, so it runs while the drvs are
255 # fetched
256 .github/lazy-store.sh closure "$RUNNER_TEMP/plans/$NAME.fetch" &
257 closure=$!
258 "$HESTIA_BIN" prefetch "${installables[@]}"
259 wait "$closure"
260 .github/lazy-store.sh mount
261 .github/ci-build.sh "${installables[@]}"
262 - name: lazy store log
263 if: always()
264 run: |
265 dir=$RUNNER_TEMP/lazy-store
266 if [ -s "$dir/stats.json" ]; then
267 jq -r '"\(.nar_count // 0) NARs fetched, \((.nar_bytes // 0) / 1048576 | floor) MiB"' "$dir/stats.json"
268 fi
269 cat "$dir/fuse.log" 2>/dev/null || true
270
271 # Fixed-name summary of the jobs above, the required status check for
272 # merging into master (the matrix job names change with the hosts).
273 # Skipped: no machine to build, or a job it needs failed.
274 ci:
275 needs: [list, check, outputs, eval, shared, build]
276 if: always()
277 runs-on: ubuntu-latest
278 steps:
279 - env:
280 RESULTS: ${{ join(needs.*.result, ' ') }}
281 run: |
282 echo "results: $RESULTS"
283 for r in $RESULTS; do
284 [ "$r" = success ] || [ "$r" = skipped ] || exit 1
285 done