Repository navigation
214 lines (205 loc) · 9.3 KB
/
Copy pathci.yml
File metadata and controls
214 lines (205 loc) · 9.3 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
# The main gate: fmt, clippy -D warnings, tests, rustdoc with warnings denied,
# cargo-deny, schema validation, and the pin/version/skill consistency
# scripts — identical to `just ci` locally, on runners with NO GPU. That a
# plain runner stays green is itself the no-GPU path's regression test.
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
# A push to an open pull request supersedes the run before it, and a gate
# result nobody will read is runner time nobody gets back. `install` and
# `stress` deliberately do not cancel; those two are worth finishing.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install pinned toolchain from rust-toolchain.toml
run: rustup show
- uses: taiki-e/install-action@4cef1412cce204788f482e778a0b9187f9626a29 # v2.87.21
with:
tool: just,cargo-deny
# The Metal path, executed rather than merely compiled.
#
# `run_metal` is the function behind the README's "On Apple Silicon it
# measures on Metal", and until now nothing called it outside the CLI:
# its crate had one test, about rewriting a constant in a source
# string, and the macOS leg compiled the `cfg(target_os = "macos")`
# code and then ran that. The only evidence the path works was
# `runs/reduce-stable-metal`, one sweep on one machine, committed and
# never re-run.
#
# A smoke test, deliberately: it asserts that a dispatch happens and
# produces a readable `results.v1`, not that the numbers are anything.
# Timings from a shared runner are not measurements and this does not
# pretend otherwise -- `--budget 60s` bounds it, and `report` reading
# the run back is the assertion.
#
# `--cc` is required by the parser and ignored on this path, which has
# no gate to run at any capability.
- name: Measure one kernel on Metal
if: runner.os == 'macOS'
run: |
set -euo pipefail
launchbound() { cargo run -q -p launchbound-cli --bin launchbound -- "$@"; }
launchbound tune corpus/reduce-stable --cc 8.6 --backend metal \
--out "${RUNNER_TEMP}/metal-smoke" --budget 60s
launchbound report "${RUNNER_TEMP}/metal-smoke" --json > "${RUNNER_TEMP}/metal.json"
python3 - "${RUNNER_TEMP}/metal.json" <<'PY'
import json, sys
report = json.load(open(sys.argv[1]))
chosen = report.get("chosen")
assert chosen, "the Metal sweep chose nothing"
assert chosen["summary"]["median_ms"] > 0, chosen
print("metal ok:", chosen["config"], chosen["summary"]["median_ms"], "ms")
PY
- run: just ci
env:
# Every screen a failing termlens wait embeds is also written here,
# so the report step below can put the picture in the job summary
# instead of leaving it in a log someone has to scroll (termlens
# 0.10). The dependency carries the `serde` feature, so these are
# `.screen.json` rather than `.screen.txt`.
TERMLENS_ARTIFACT_DIR: ${{ runner.temp }}/termlens
# A PTY failure is a picture, and a log is the worst place to read one.
# This renders every screen the suite left behind into the job summary
# and uploads the SVG/HTML. It installs termlens-cli itself, pinned to
# the version the lockfile names so the renderer and the library that
# wrote the file are one release — the same rule tests/cli.rs enforces.
- uses: vyncint/termlens/.github/actions/report@de7fe39df19fd2c9649a8a296a680ca6abe3b40b # v0.11.4
if: failure()
with:
name: termlens-report-ci-${{ matrix.os }}
cli-version: "0.11.0" # checked against the manifest by check-skill-version.sh
# The public API against the last published release.
#
# `--release-type` is FORCED, never inferred, and the reason is measured
# rather than assumed. Removing `Config::kernel` from launchbound-space
# while bumping the manifest to 3.0.0 in the same PR:
#
# inferred -> "0 checks: 0 pass, 254 skip", exit 0
# --release-type patch -> "1 major and 0 minor checks failed", exit 100
#
# cargo-semver-checks compares the manifest version to the baseline and
# runs only the lints that bump would not already excuse. Declare the major
# bump in the same commit as the break and it has nothing left to say. That
# is the hole: the gate agrees with whatever the PR claims about itself.
#
# `patch` means "no API change of any kind is excused", so every lint
# fires. It does NOT forbid additions -- cargo-semver-checks only reports
# breaking changes, so a new `pub fn` passes (verified: 223 checks, 223
# pass, with a `pub use` added).
#
# A deliberate break carries the `breaking` label, which switches this to
# `major`. Three of the eleven published crates are binary-only and have no
# library API; the eight that do are all covered by --workspace.
#
# `baseline-version` is a literal, moved in the release PR after publish --
# left at the old release it would compare against a version nobody can
# install, and moved before publish it would compare against one that does
# not exist yet.
semver:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install pinned toolchain from rust-toolchain.toml
run: rustup show
# A prebuilt binary, downloaded every run: never built from source, and
# never restored from a cache. A gate that reports on whatever a cache
# held the day it was filled is not reporting on this PR.
- uses: taiki-e/install-action@4cef1412cce204788f482e778a0b9187f9626a29 # v2.87.21
with:
tool: cargo-semver-checks
- name: Compare the public API against the published baseline
env:
RELEASE_TYPE: ${{ contains(github.event.pull_request.labels.*.name, 'breaking') && 'major' || 'patch' }}
run: |
echo "release-type: $RELEASE_TYPE"
cargo semver-checks --workspace \
--baseline-version 2.3.0 \
--release-type "$RELEASE_TYPE"
# MSRV applies to the crates that do not need the pinned nightly
# (CONTRIBUTING.md); checked against the committed lockfile.
msrv:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
RUSTUP_TOOLCHAIN: "1.88"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: rustup toolchain install 1.88 --profile minimal
- uses: taiki-e/install-action@4cef1412cce204788f482e778a0b9187f9626a29 # v2.87.21
with:
tool: just
- run: just msrv
# Workflow security, on the workflows and on the action this repository
# ships. It runs ONLINE, which is the half a local run cannot do: `zizmor
# --offline` checks that a `uses:` carries a hash, and only the API can say
# whether that hash is the one the version comment claims. Both the tool and
# the action installing it are pinned, because a linter deciding whether
# this repository is pinned properly is a poor place to float.
zizmor:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
inputs: .github/workflows/ action/
version: "1.29.0"
persona: regular
# No security tab upload: no Advanced Security here, and a failed
# upload would turn a clean audit into a red job. Annotations put
# each finding on the diff instead.
advanced-security: false
annotations: true
# One green check standing for every job in this workflow.
#
# `main`'s ruleset requires this rather than each job by name, so adding a
# job here protects `main` automatically instead of silently not being
# required until somebody remembers to edit the ruleset. That is the
# failure mode this exists to prevent: an unrequired gate is a gate that
# reports.
#
# `if: always()` plus an explicit scan, because `needs` alone would mark
# this skipped when a dependency fails — and a skipped required check does
# not block a merge. The three checks outside this workflow (`gate`,
# `check`, `termlens`) are required by name in the ruleset.
required-green:
name: required-green
if: always()
needs: [ci, semver, msrv, zizmor]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Verify every gate succeeded
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "gate results: $RESULTS"
for r in $RESULTS; do
if [ "$r" != "success" ]; then
echo "a required gate did not succeed" >&2
exit 1
fi
done