Repository navigation
210 lines (202 loc) · 9.92 KB
/
Copy pathci.yml
File metadata and controls
210 lines (202 loc) · 9.92 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
name: CI
# Every job runs inside the SAME pinned image the devcontainer uses, and
# invokes the SAME scripts/check.sh a developer runs via
# scripts/check-in-container.sh. Host OS, libc, and toolchain are therefore
# identical between a local check and a CI check — the divergence that made
# 999.47 cost hours to chase.
#
# BOTH `main` and `develop` require FOUR status contexts: `Test`, `Clippy`
# and `Format` from this file, plus `Build + test in devcontainer` from
# .github/workflows/devcontainer.yml. Renaming or removing one blocks every
# future merge to that branch, because the required check would never report.
#
# Check BOTH before touching a workflow's job name or existence — classic
# branch protection UNDER-REPORTS, because the contexts are declared in the
# `develop-merge-or-squash` / `main-squash-only` rulesets, a separate
# mechanism it does not read:
# gh api repos/denniyahh/devflow/branches/<branch>/protection
# gh api repos/denniyahh/devflow/rules/branches/<branch>
#
# `Sequential 2-CPU check` below is deliberately NOT among those four. It is
# advisory (46-CONTEXT.md D-05): it carries the local gate's load shape, which
# is the most timing-sensitive and therefore the most flake-prone thing CI
# runs, and making it a merge blocker on day one is the reverse of the point.
#
# The image tag is repeated per job because GitHub Actions does not expand
# `env` inside `jobs.<id>.container.image`. scripts/assert-image-parity.sh
# fails the Test job if these drift from .devcontainer/devcontainer.json,
# so the duplication cannot rot silently.
on:
push:
pull_request:
branches: [main, develop]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm
# --init: a job container's PID 1 is otherwise `tail -f /dev/null`, which
# never reaps orphans, so a killed agent's orphaned children stay
# zombies for the whole job. scripts/check-in-container.sh's PID 1 is
# bash, which reaps, and so does any real host. Without this the suite
# runs under a PID 1 neither of them has (PR #225: 18 failures, all
# zombies). The tests' liveness probes skip zombies too; this keeps CI
# matching the local gate rather than relying on that alone.
options: --init
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
# These jobs run as root inside a container while actions/checkout
# leaves the workspace owned by the runner user (uid 1001), so git
# refuses every command with "detected dubious ownership". checkout's
# own safe.directory entry is scoped to its step and is not visible
# here — GIT_CONFIG_GLOBAL is unset by the time tests run. Confirmed
# from git's own stderr in run 30214005214.
#
# Must precede assert-image-parity.sh: that script resolves the repo
# root via `git rev-parse` and would otherwise silently fall back to
# `pwd` instead of failing loudly.
- name: Trust the workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Assert CI image matches the devcontainer definition
run: scripts/assert-image-parity.sh "mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm"
- run: scripts/check.sh test
clippy:
name: Clippy
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- name: Trust the workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- run: scripts/check.sh clippy
fmt:
name: Format
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Trust the workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- run: scripts/check.sh fmt
# ADVISORY, not required (46-CONTEXT.md D-05) — see the header comment.
# This is the one job that reproduces the local pre-push gate's load shape:
# fmt -> clippy -> test sequentially, in ONE job, squeezed onto two CPUs.
# The three jobs above run in parallel on an unpinned runner, and have
# rejected 0 of the pushes the local gate rejected 2 of 2.
sequential:
name: Sequential 2-CPU check
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm
# --init: see the Test job.
options: --init
timeout-minutes: 45
# REQUIRED, not stylistic. A `run:` step in a CONTAINER job defaults to
# `sh -e {0}`, not `bash -e {0}` as it would on a bare runner — and this
# image's /bin/sh is dash, which has no `set -o pipefail`. Without this,
# the two `set -euo pipefail` steps below die on their FIRST line with
# `set: Illegal option -o pipefail` (exit 2) and `scripts/check.sh` never
# runs at all, while the job still reports as a real failing check rather
# than as a misconfiguration. Observed on PR #208, the job's first live
# run: 42s to red, having executed none of the suite.
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
# Must precede the parity step below; see the Test job above for why.
- name: Trust the workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Assert CI image matches the devcontainer definition
run: scripts/assert-image-parity.sh "mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm"
# Sourced, never parsed. A `sed -n ...p` extraction exits 0 whether or
# not it printed anything, and would run the suite unpinned while the
# log claimed nothing was wrong.
- name: Read the CPU pin from its single definition site
run: |
set -euo pipefail
. scripts/lib/ci-cpus.sh
echo "CPUS=$CPUS" >> "$GITHUB_ENV"
# D-04, corrected by 46-REVIEWS.md C-03. Echoing the CPU list alone
# proves nothing: a partially-invalid list narrows SILENTLY and still
# exits 0. This step used to say the unpinned count was the negative
# control for the pinned one and then never compare them -- three bare
# `echo`s that always exited 0. Documenting a gate is not implementing
# one. The comparison is now ASSERTED by scripts/assert-cpu-pin.sh, a
# decider that takes the measurements as ARGUMENTS so its failing
# direction is exercised by cargo test rather than only ever on a runner
# that happens to disagree with itself.
#
# The assertion is deliberately CONDITIONAL, and both skip paths print
# the reason they skipped (a silent skip reads as a pass in a job log,
# which is the same defect in another shape). It fires only when the pin
# is not the `all` override -- there is then no pin to narrow anything,
# see C-06 -- and when the host reports more than 2 CPUs, because a
# 2-CPU pin cannot narrow a 2-CPU machine and a failure there would be an
# artefact of the runner.
#
# WHAT THIS DOES NOT ESTABLISH: that rustc or the test-harness threads
# were actually confined, or that the runner was allocated 2 cores. It
# shows only that the pin narrowed the core count VISIBLE to one child
# process. The mechanism being relied on is that CPU affinity is
# inherited across fork/exec, and `nproc` does not measure inheritance.
# `nproc` here stays diagnostic, not the sole oracle.
#
# The pin prefix comes from cpu_pin_prefix() in scripts/lib/ci-cpus.sh,
# which is the ONE place the `all` override is handled. A shell function
# cannot cross a step boundary, so the fragment is sourced again here --
# that is the single definition site being read, not a second one.
- name: Record and assert the load shape this job actually got
run: |
set -euo pipefail
. scripts/lib/ci-cpus.sh
cpu_pin_prefix
unpinned="$(nproc)"
pinned="$("${CPU_PIN[@]}" nproc)"
echo "cpu_list=$CPUS"
echo "unpinned_nproc=$unpinned"
echo "pinned_nproc=$pinned"
scripts/assert-cpu-pin.sh "$CPUS" "$unpinned" "$pinned"
# CPU affinity is inherited across fork/exec, so rustc and the test
# harness threads all land under this mask. `--test-threads=N` is not a
# substitute: it throttles the harness only, leaving compilation free.
#
# Same re-source, same reason. With DEVFLOW_CI_CPUS=all the prefix is an
# empty array and this runs plain `scripts/check.sh all`; the previous
# literal `taskset -c "$CPUS"` died on that override
# (46-REVIEWS.md C-06). The array expansion needs the job's
# `defaults: run: shell: bash` above -- do not remove it.
- name: Run the full check suite under the CPU pin
run: |
set -euo pipefail
. scripts/lib/ci-cpus.sh
cpu_pin_prefix
"${CPU_PIN[@]}" scripts/check.sh all
# ADVISORY, not required — like `Sequential 2-CPU check`, it is not among the
# four required contexts in the header comment. `cargo deny` reads the live
# RustSec advisory database, so a newly published advisory can turn this red
# on a PR that changed nothing: worth seeing, not worth wedging a merge on.
# Neither tool ships in the pinned image, so the job installs the versions
# pinned in scripts/install-dep-tools.sh first.
deps:
name: Dependency checks
runs-on: ubuntu-24.04
container:
image: mcr.microsoft.com/devcontainers/rust:2.0.13-1-bookworm
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Trust the workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Install the pinned dependency tools
run: scripts/install-dep-tools.sh
- run: scripts/check.sh deps