-
Notifications
You must be signed in to change notification settings - Fork 1
343 lines (324 loc) · 15.8 KB
/
Copy pathci.yml
File metadata and controls
343 lines (324 loc) · 15.8 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
342
343
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# Supersede in-flight runs for the same PR; see .claude/CLAUDE.md "Superseding CI runs".
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
gazelle-check:
name: Gazelle BUILD file check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bazel-remote
with:
buildbuddy-api-key: ${{ secrets.BUILDBUDDY_API_KEY }}
- run: bazel run --config=ci //:gazelle -- -mode=diff
# MODULE.bazel.lock is the one derived file with no CI backstop of its own: builds run with
# `--lockfile_mode=update`, which rewrites the lock in memory and stays green, so a `bazel mod
# tidy` hook that never ran is invisible until it blocks someone's commit. Fail-only on
# purpose — the regenerated lock belongs in the authoring commit, not in a CI push.
#
# `bazel mod tidy` also rewrites MODULE.bazel's `use_repo` lines, so both files are diffed.
bazel-lock-check:
name: MODULE.bazel.lock freshness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bazel-remote
with:
buildbuddy-api-key: ${{ secrets.BUILDBUDDY_API_KEY }}
- run: |
set -euo pipefail
# `--config=ci` for consistency with gazelle-check next door. It changes nothing
# here — `mod tidy` executes no actions, so BES and remote execution are moot.
bazel mod tidy --config=ci
if ! git diff --exit-code -- MODULE.bazel MODULE.bazel.lock; then
MSG="stale — run 'bazel mod tidy' and commit the result."
echo "::error file=MODULE.bazel.lock::${MSG}"
exit 1
fi
echo "MODULE.bazel and MODULE.bazel.lock are what 'bazel mod tidy' produces."
# The repo's `meta/scripts/*.py` target Python 3.14 (matches the rules_python
# toolchain pin in MODULE.bazel and the `target-version` in pyproject.toml's
# [tool.ruff]). Ubuntu-24.04 still ships 3.12 as `python3`, so the language-
# toolchain pin needs to be applied explicitly on every job that runs a script
# outside Bazel. The `# renovate:` comment above each `python-version` is
# picked up by the regex matcher in renovate.json and grouped with the
# MODULE.bazel python_version under "Language toolchain SDKs".
# Polyglot module/project completeness check. Per language (Go, Python):
# config-file reachability + workflow matrix consistency. Python-only:
# root pyproject.toml has the required tool sections, every per-project
# pyproject is covered by [tool.uv.workspace].members, and
# requirements_lock.txt is fresh relative to uv.lock. uv is needed for the
# last check — setup-uv brings both uv and a 3.14 Python interpreter.
modules-check:
name: Module completeness check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# renovate: datasource=python-version depName=python
python-version: "3.14"
- run: python3 meta/scripts/check_modules.py
go-work-check:
name: go.work consistency/completeness check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# renovate: datasource=python-version depName=python
python-version: "3.14"
- run: python3 meta/scripts/check_go_work.py
adr-numbers-check:
name: ADR number uniqueness check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# renovate: datasource=python-version depName=python
python-version: "3.14"
- run: python3 meta/scripts/check_adr_numbers.py
secrets-check:
name: Secrets check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# renovate: datasource=python-version depName=python
python-version: "3.14"
- run: python3 meta/scripts/check_secrets_dir.py
no-cgo-check:
name: No-cgo policy check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
# renovate: datasource=python-version depName=python
python-version: "3.14"
# `go list -deps` is what surfaces transitive cgo, so the runner needs Go in PATH.
# Use go.work as the source-of-truth — it's the workspace-level Go version directive
# and matches the version Bazel installs in MODULE.bazel (per-module go.mod files
# only declare each module's minimum, which can drift below the workspace version).
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.work
# This step only runs `go list -deps` via check_no_cgo.py — no `go build`
# of our code, no `go mod download`. With cache enabled (the default),
# setup-go searches for go.mod/go.sum at the repo root to build a cache
# key and warns when it can't find them (this is a Bazel monorepo with
# per-module go.mod files, none at the root).
cache: false
- run: python3 meta/scripts/check_no_cgo.py
# TEND(lang-expand): this job lints Go only. When a new language is adopted, add a sibling
# job (e.g. ruff for Python) — do not extend this matrix, the linter is Go-specific.
golangci-lint:
name: golangci-lint (${{ matrix.go_module }})
runs-on: ubuntu-latest
needs: [modules-check]
strategy:
fail-fast: false
matrix:
go_module:
- tools/network_infrastructure_maintenance
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
working-directory: ${{ matrix.go_module }}
# Python's sibling of golangci-lint. Format + lint in one job; config lives in
# `[tool.ruff]` in //:pyproject.toml. Renovate's regex manager tracks the
# version pin via the comment above (matches the Dockerfile pattern).
# TEND(project-expand): repo-wide today because there is no per-project
# tuning. When real Python projects land and want per-project ruff config,
# convert this to a matrix-per-project job (mirroring golangci-lint) and
# add a matching matrix-completeness callsite to check_modules.py keyed off
# a Python matrix key (matrix.module: is currently Go-only).
ruff:
name: ruff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
with:
# renovate: datasource=pypi depName=ruff
version: "0.16.4"
args: format --check
- run: ruff check
# shellcheck: the enforcing half. The editor surfaces the same findings inline on save
# (timonwong.shellcheck, configured in .vscode/settings.json), but nothing blocks a commit on
# them, so this job is where shell lint is actually gated. Scoped to *.sh, which today means
# .devcontainer/'s host stub and lifecycle hooks plus meta/devcontainer-base/scripts/ —
# privileged writes against /etc and host-absolute paths, where `set -e` foot-guns (errexit
# suspension inside `||` callees, most recently) are worth catching mechanically rather than
# in review.
# The version is the devcontainer's, read out of its Dockerfile rather than restated here —
# the runner image ships its own shellcheck, and using it would make the gating job and the
# editor separately versioned, with a finding's presence depending on where you looked.
shellcheck:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install the pinned shellcheck
run: |
set -euo pipefail
version="$(sed -n 's/^ARG SHELLCHECK_VERSION=//p' .devcontainer/Dockerfile)"
# An ARG that moved or got renamed must fail here, not silently fall through to
# whatever the runner image happens to ship.
if [ -z "${version}" ]; then
echo "no SHELLCHECK_VERSION in .devcontainer/Dockerfile" >&2
exit 1
fi
curl -fsSL \
"https://github.com/koalaman/shellcheck/releases/download/${version}/shellcheck-${version}.linux.x86_64.tar.xz" \
| tar -xJ -C /tmp
# Ahead of the runner's own copy on PATH.
sudo install -m 0755 "/tmp/shellcheck-${version}/shellcheck" /usr/local/bin/shellcheck
# That ordering is an assumption about the runner image, and the whole point of the
# pin is that the gate and the editor cannot disagree — so a shadowed binary has to
# fail here rather than lint green under some other version.
on_path="$(shellcheck --version | sed -n 's/^version: //p')"
if [ "${on_path}" != "${version#v}" ]; then
echo "shellcheck on PATH is ${on_path}, expected ${version#v}" >&2
exit 1
fi
- run: |
set -euo pipefail
shellcheck --version | sed -n '2p'
mapfile -t files < <(git ls-files '*.sh')
# shellcheck itself exits 1 on an empty argument list; can't happen today, but a
# future .sh-free tree should report "nothing to check", not a red job.
if [ "${#files[@]}" -eq 0 ]; then
echo "no shell scripts tracked; nothing to check"
exit 0
fi
printf 'checking %d file(s)\n' "${#files[@]}"
shellcheck "${files[@]}"
# ty: Astral's static type checker. Config lives in `[tool.ty]` in
# //:pyproject.toml — same single-source-of-truth posture as ruff. No
# dedicated GitHub Action exists yet (ty is still alpha), so we install uv
# and run `uvx ty@<pin> check`. The pin is Renovate-tracked via the comment
# above TY_VERSION.
# TEND(project-expand): see the ruff job's TEND — same reasoning. When
# per-project ty config is needed, convert to a matrix-per-project job and
# extend check_modules.py with the matching matrix-completeness callsite.
ty:
name: ty
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
# `uv sync` materializes `.venv` from uv.lock so ty can resolve
# third-party imports (e.g. the smoke target's `requests`). Without it,
# ty's only search paths are first-party + stdlib and any non-stdlib
# import fails with `unresolved-import`.
- run: uv sync
- name: ty check
env:
# renovate: datasource=pypi depName=ty
TY_VERSION: "0.0.74"
run: uvx "ty@${TY_VERSION}" check
# Build and test runs once per supported target platform, on a runner whose host matches
# the target. (Running tests natively per platform is the only way (without an emulation
# layer we do not have) to actually exercise platform-specific code paths and catch regressions
# early.) A failure on any matrix entry blocks the merge because build-and-test-all is a
# required status check.
#
# If a future supported target lacks a matching GitHub-hosted runner, that entry would
# build-only (drop `test`); none of our current targets are in that situation.
build-and-test-per-target:
name: Build and test (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
needs: [
gazelle-check,
modules-check,
go-work-check,
secrets-check,
no-cgo-check,
golangci-lint,
ruff,
ty,
]
strategy:
fail-fast: false
matrix:
include:
- platform: linux_x86_64
runner: ubuntu-latest
- platform: linux_arm64
runner: ubuntu-24.04-arm
- platform: darwin_arm64
runner: macos-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bazel-remote
with:
buildbuddy-api-key: ${{ secrets.BUILDBUDDY_API_KEY }}
# `--config=ci` brings in remote cache + BES; `--config=<platform>` pins the target
# platform explicitly. Each row's runner matches its target arch — required for
# Python (`@pypi` wheel resolution is host-bound; see
# docs/adr/0001-go-builds-are-pure-python-is-not.md). The matching runner also keeps
# test execution native: BB
# executors of the same arch are registered as candidates via `--config=<platform>`,
# darwin executes on the runner itself since BB has no macOS executors.
- run: bazel test --config=ci --config=${{ matrix.platform }} //...
build-and-test-all:
name: Build and test (all targets)
runs-on: ubuntu-latest
needs: [build-and-test-per-target]
if: always()
steps:
- name: Verify all build-and-test-per-target jobs passed
run: |
if [ "${{ needs.build-and-test-per-target.result }}" != "success" ]; then
echo "build-and-test-all result: ${{ needs.build-and-test-per-target.result }}"
exit 1
fi
echo "All build-and-test-per-target jobs passed"
coverage:
name: Coverage
runs-on: ubuntu-latest
needs: [build-and-test-all]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/setup-bazel-remote
with:
buildbuddy-api-key: ${{ secrets.BUILDBUDDY_API_KEY }}
# Coverage-specific flags (--combined_report, --experimental_fetch_all_coverage_outputs,
# --strategy=CoverageReport=local under :remote_bb) live in .bazelrc under the `coverage`
# command scope, so `bazel coverage //...` produces an identical report locally and in CI.
#
# Coverage runs on a single platform (linux_x86_64) by deliberate choice: it measures
# source-line coverage, which is platform-independent for pure-Go code without build
# tags. If we ever introduce platform-conditional code that needs per-target coverage
# measurement, this job would become a matrix like build-and-test.
#
# TEND(lang-expand): `bazel coverage //...` picks up every language whose rules emit
# coverage (rules_go does automatically; rules_python is wired via
# `configure_coverage_tool = True` on `python.toolchain` in MODULE.bazel). When adopting
# a new language, confirm its ruleset is wired into Bazel's coverage collection — e.g.
# rules_java needs JaCoCo. If coverage output is silently missing for a language,
# that's the thing to check.
- run: bazel coverage --config=ci --config=linux_x86_64 //...
- name: Locate merged lcov
id: lcov
run: echo "path=$(bazel info output_path)/_coverage/_coverage_report.dat" >> "$GITHUB_OUTPUT"
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ${{ steps.lcov.outputs.path }}
disable_search: true
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}