-
Notifications
You must be signed in to change notification settings - Fork 20
582 lines (544 loc) · 21.7 KB
/
Copy pathmain.yml
File metadata and controls
582 lines (544 loc) · 21.7 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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
name: main
permissions:
contents: read
pull-requests: read
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
# Emits per-tier `--exclude NAME ...` args (modified/affected/required) to scope
# downstream `cargo --workspace` jobs. PRs snapshot base vs. the merge commit
# (same SHA downstream jobs build); pushes emit empty (full backstop).
# `skip=true` when no crates are impacted (e.g. doc-only PR).
delta:
runs-on: ubuntu-latest
outputs:
exclude_not_modified: ${{ steps.compute.outputs.exclude_not_modified }}
exclude_not_affected: ${{ steps.compute.outputs.exclude_not_affected }}
exclude_not_required: ${{ steps.compute.outputs.exclude_not_required }}
skip: ${{ steps.compute.outputs.skip }}
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
if: github.event_name == 'pull_request'
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_DELTA_VERSION
- name: Compute Impact
id: compute
shell: bash
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
# push to main (or any non-PR trigger) -> full backstop: no excludes.
{
echo "exclude_not_modified="
echo "exclude_not_affected="
echo "exclude_not_required="
echo "skip=false"
} >> "$GITHUB_OUTPUT"
exit 0
fi
# Snapshots written outside the working tree so `git checkout` can't trip on them.
baseline="$RUNNER_TEMP/delta-baseline.json"
current="$RUNNER_TEMP/delta-current.json"
head_sha=$(git rev-parse HEAD)
git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"
git checkout "origin/$BASE_REF"
cargo delta -c .delta.toml snapshot > "$baseline"
git checkout "$head_sha"
cargo delta -c .delta.toml snapshot > "$current"
# `cargo-excludes` emits the workspace complement of the selected tier as
# `--exclude NAME ...`, scoped via downstream `cargo --workspace`. `--exclude`
# only matches workspace members, avoiding `-p` ambiguity with same-named
# registry deps.
impact() {
cargo delta -c .delta.toml impact --baseline "$baseline" --current "$current" "$@"
}
exclude_not_modified=$(impact -f cargo-excludes --modified)
exclude_not_affected=$(impact -f cargo-excludes --affected)
exclude_not_required=$(impact -f cargo-excludes --required)
# `required` is the superset (required ⊇ affected ⊇ modified). If it
# has no impacted crates, nothing is impacted and downstream
# source-scoped jobs can be skipped entirely.
if [[ -z "$(impact -f names --required)" ]]; then
skip=true
else
skip=false
fi
{
echo "exclude_not_modified=$exclude_not_modified"
echo "exclude_not_affected=$exclude_not_affected"
echo "exclude_not_required=$exclude_not_required"
echo "skip=$skip"
} | tee -a "$GITHUB_OUTPUT"
testing:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_MSRV, RUST_NIGHTLY
cargo-tools: CARGO_NEXTEST_VERSION, CARGO_HACK_VERSION, JUST_VERSION
# execute
- name: Build
run: cargo hack build --each-feature --workspace ${{ needs.delta.outputs.exclude_not_affected }} --verbose --locked --color always
- name: Tests
if: success() || failure()
run: cargo nextest run --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features
- name: Docs
if: success() || failure()
env:
RUSTDOCFLAGS: "--cfg docsrs -D warnings"
run: cargo +${{ env.RUST_NIGHTLY }} doc --no-deps --verbose --workspace ${{ needs.delta.outputs.exclude_not_required }} --all-features
- name: Doc Tests
if: success() || failure()
run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }} --all-features
- name: Doc Tests (default features)
if: success() || failure()
run: cargo test --doc --verbose --workspace ${{ needs.delta.outputs.exclude_not_affected }}
- name: Examples
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} -Zscript ./scripts/run-examples.rs --cargo-profile dev ${{ needs.delta.outputs.exclude_not_affected }}
static-analysis:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm, windows-11-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
rust-components: clippy
cargo-tools: CARGO_DOC2README_VERSION, CARGO_WORKSPACES_VERSION, CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION, CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION, CARGO_DENY_VERSION, CARGO_SORT_VERSION
# execute
- name: Clippy
run: cargo clippy --workspace ${{ needs.delta.outputs.exclude_not_modified }} --all-targets --all-features -- -D warnings
- name: Install Nightly Rustfmt
if: success() || failure()
run: rustup toolchain install ${{ env.RUST_NIGHTLY }} --profile minimal --component rustfmt
- name: Source Format (nightly)
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} fmt --all -- --config-path ./unstable-rustfmt.toml --check
- name: Cargo.toml Format
if: success() || failure()
run: cargo sort --check --grouped --workspace
- name: Readmes
if: success() || failure()
run: cargo workspaces exec --ignore-private cargo doc2readme --check --lib --template ../README.j2
env:
# Disable sccache: macro expansion (rustc -Zunpretty=expanded) does not
# produce the artifact files sccache expects to cache, causing it to fail.
RUSTC_WRAPPER: ""
- name: Default Features
if: success() || failure()
run: cargo ensure-no-default-features
- name: Cyclic Dependencies
if: success() || failure()
run: cargo ensure-no-cyclic-deps
- name: Dependency Validation
if: success() || failure()
run: cargo deny --all-features --workspace --color always check all
spell-check:
runs-on: ubuntu-slim
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 1
- name: Setup
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_SPELLCHECK_VERSION, JUST_VERSION
- name: Check Spelling
run: just spellcheck
semver:
needs: [delta]
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_SEMVER_CHECKS_VERSION
# execute
- name: Semver Compatibility
id: semver
continue-on-error: true
run: |
set -o pipefail
cargo semver-checks --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --color never --baseline-rev origin/${{ github.base_ref }} 2>&1 | tee semver-output.txt
# report
- name: Prepare Semver Comment
if: steps.semver.outcome == 'failure'
run: |
echo "## ⚠️ Breaking Changes Detected" > semver-comment.txt
echo "" >> semver-comment.txt
echo '```' >> semver-comment.txt
grep -v "^ *Cloning " semver-output.txt | grep -v "^ *Building " | grep -v "^ *Built \[" | grep -v "^ *Parsing " | grep -v "^ *Parsed \[" | grep -v "^ *Checking" | grep -v "^ *Checked \[" | grep -v "^ *Finished \[" | grep -v "^ *Summary " >> semver-comment.txt
echo '```' >> semver-comment.txt
echo "" >> semver-comment.txt
echo "If the breaking changes are intentional then everything is fine - this message is merely informative." >> semver-comment.txt
echo "" >> semver-comment.txt
echo "Remember to apply a version number bump with the correct severity when publishing a version with breaking changes (\`1.x.x -> 2.x.x\` or \`0.1.x -> 0.2.x\`)." >> semver-comment.txt
- name: Post Semver Failure Comment
if: steps.semver.outcome == 'failure' && github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v3.0.4
with:
header: semver-check
path: semver-comment.txt
- name: Remove Semver Comment on Success
if: steps.semver.outcome == 'success' && github.event.pull_request.head.repo.full_name == github.repository
uses: marocchino/sticky-pull-request-comment@v3.0.4
with:
header: semver-check
delete: true
extended-analysis:
name: extended-analysis (${{ matrix.os }})
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing, static-analysis]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: windows-11-arm
target: aarch64-pc-windows-msvc
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
rust-components: miri
cargo-tools: CARGO_UDEPS_VERSION, CARGO_CAREFUL_VERSION, CARGO_NEXTEST_VERSION
# execute
- name: Udeps
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
# Without --all-targets: enforce that every [dependencies] entry is used
# by the library/bins (catches normal deps used only by tests/benches/
# examples -- i.e. misplaced deps that should be [dev-dependencies]).
cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always
# With --all-targets: enforce that every [dev-dependencies] entry is used
# by some target (catches unused dev-dependencies).
cargo +${{ env.RUST_NIGHTLY }} udeps --locked --all-features --all-targets --workspace ${{ needs.delta.outputs.exclude_not_modified }} --color always
- name: Careful
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} careful nextest run --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --color always --target ${{ matrix.target }}
- name: Miri (stacked borrows)
if: success() || failure()
run: cargo +${{ env.RUST_NIGHTLY }} miri test --all-features --workspace ${{ needs.delta.outputs.exclude_not_affected }} --lib --tests
model-checking:
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing]
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_LATEST
cargo-tools: JUST_VERSION
# execute
- name: Loom
run: just loom
fuzz-testing:
if: github.event_name == 'pull_request' && needs.delta.outputs.skip != 'true'
needs: [delta, testing]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
cargo-tools: CARGO_BOLERO_VERSION, JUST_VERSION
# execute
- name: Bolero
run: just bolero 60s
mutation-testing:
if: github.event_name == 'pull_request'
needs: [testing]
runs-on: ubuntu-latest
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
enable-wild-linker: true
rust-toolchain: RUST_NIGHTLY
cargo-tools: CARGO_MUTANTS_VERSION
# execute
- name: Generate PR Diff
run: git diff origin/main...HEAD >diff.txt
- name: Mutate Diff
timeout-minutes: 240
run: cargo +${{ env.RUST_NIGHTLY }} -Zscript scripts/mutants.rs --in-place --in-diff diff.txt
coverage:
needs: [delta]
# Coverage always runs (no `delta.skip` gate) so the externally-posted
# `codecov/project` status check is always reported. Codecov posts that
# status in reaction to a coverage upload from this job; if `coverage`
# is skipped, `codecov/project` stays in
# `Expected — Waiting for status to be reported` and blocks merge. The
# `required-checks` fan-in only solves stuck contexts for jobs we own,
# so we eat the cost of running coverage on doc-only PRs here to keep
# the externally-posted check unblocked.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Note: windows-11-arm excluded due to LLVM coverage instrumentation bugs
# causing "malformed instrumentation profile data: symbol name is empty" errors
# Note: when changing the number of OS entries here, update
# `codecov.notify.after_n_builds` in codecov.yml to match, otherwise
# Codecov reports the project status before all platforms have uploaded.
os: [ubuntu-latest, windows-latest, ubuntu-24.04-arm]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY
rust-components: llvm-tools-preview
cargo-tools: CARGO_LLVM_COV_VERSION
# execute
- name: Generate Coverage (all-features)
run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --all-features --workspace --lcov --output-path lcov-all.info
- name: Generate Coverage (no-default-features)
run: cargo +${{ env.RUST_NIGHTLY }} llvm-cov --no-default-features --workspace --lcov --output-path lcov-no-def.info
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov-all.info,lcov-no-def.info
fail_ci_if_error: true
pr-title:
runs-on: ubuntu-slim
if: github.event_name == 'pull_request'
steps:
- name: Check PR Title
uses: amannn/action-semantic-pull-request@v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
build
chore
ci
doc
docs
feat
fix
misc
miscellaneous
perf
refactor
style
task
test
license-headers:
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 1
- name: Setup
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
cargo-tools: CARGO_HEATHER_VERSION
- name: Check License Headers
run: cargo heather
external-type-exposure:
needs: [delta]
if: needs.delta.outputs.skip != 'true'
# Runs `cargo check-external-types --all-features` sequentially over every
# workspace crate, which exceeds the 15-minute `ubuntu-slim` execution cap as
# the crate/dependency graph grows. Use a standard runner with an explicit
# timeout so the job has headroom.
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
enable-sccache: true
rust-toolchain: RUST_NIGHTLY_EXTERNAL_TYPES
cargo-tools: CARGO_CHECK_EXTERNAL_TYPES_VERSION
# execute
- name: Check External Type Exposure
run: cargo +${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }} -Zscript scripts/check-external-types.rs ${{ env.RUST_NIGHTLY_EXTERNAL_TYPES }}
script-tests:
# Pester test suite for the release-related PowerShell scripts
# (release-packages.ps1, lib/releasing.ps1, lib/release-flow.ps1).
# Always runs (no delta gate) because script-level changes don't move any
# Rust crates so delta would mark the PR skippable, but the scripts
# themselves may still need validation. Cross-platform matrix mirrors the
# platforms developers run the scripts on locally.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
# prep
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup
# The synthetic-workspace fixture builder shells out to `cargo metadata`,
# so Rust must be available even though no Rust code is compiled.
uses: ./.github/actions/setup
with:
rust-toolchain: RUST_LATEST
- name: Install Pester
shell: pwsh
run: |
$existing = Get-Module Pester -ListAvailable |
Sort-Object Version -Descending |
Select-Object -First 1
if ($null -eq $existing -or $existing.Version -lt [version]'5.7.0') {
Install-Module -Name Pester -MinimumVersion 5.7.1 -Force -Scope CurrentUser -SkipPublisherCheck
} else {
Write-Host "Pester $($existing.Version) already installed; skipping."
}
# execute
- name: Run Pester Suite
shell: pwsh
run: ./scripts/tests/Pester/Run-Tests.ps1
# Single aggregating ("fan-in") status check. Branch protection should
# require ONLY this context for the workflow jobs below: it succeeds when
# every dependency either succeeded or was skipped, and fails if any
# dependency failed or was cancelled. This avoids the GitHub Actions
# quirk where a matrix job gated by a job-level `if:` posts only a single
# skipped check under the bare job name, leaving matrix-expanded required
# contexts (`testing (ubuntu-latest)` etc.) stuck on
# `Expected — Waiting for status to be reported`.
#
# Maintenance — when to add a job to `needs:` below:
#
# * Do NOT add jobs that are intentionally optional / advisory; this
# fan-in is only for jobs that must pass before merge.
#
# * MUST add if the job has BOTH a `strategy.matrix` AND a job-level
# `if:` that can evaluate to false (e.g. gated on
# `needs.delta.outputs.skip`, `github.event_name`, etc.). Such jobs
# trigger the stuck-context bug above and can only be made required
# safely via this fan-in.
#
# * SHOULD add any other job that must pass before merge. Funnelling
# every required job through this single context keeps branch
# protection simple: one workflow context to require here, plus
# externally-posted statuses (`license/cla`, `codecov/project`).
#
# When a new job is added below, also remove any direct entry for it
# from the branch protection ruleset (only `required-checks` should be
# listed for jobs defined in this workflow).
required-checks:
name: required-checks
if: always()
needs:
- delta
- testing
- static-analysis
- spell-check
- semver
- extended-analysis
- model-checking
- fuzz-testing
- mutation-testing
- coverage
- pr-title
- license-headers
- external-type-exposure
- script-tests
runs-on: ubuntu-latest
steps:
- name: Validate Dependency Results
shell: bash
env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
set -euo pipefail
echo "$NEEDS_JSON" | jq -r 'to_entries[] | " \(.key): \(.value.result)"'
# Pass when every dependency is success or skipped; fail otherwise
# (failure, cancelled, or any unexpected state).
bad=$(echo "$NEEDS_JSON" | jq -r '
to_entries
| map(select(.value.result != "success" and .value.result != "skipped"))
| .[] | "\(.key): \(.value.result)"
')
if [[ -n "$bad" ]]; then
printf '::error::One or more required jobs did not succeed or skip:\n%s\n' "$bad"
exit 1
fi
echo "All required jobs succeeded or were skipped."