Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,33 @@ jobs:
- name: Tests
run: cargo test --locked --all-features --workspace

extra-platforms-gate:
name: Extra platforms
# A required status check is matched by name, so a name carrying a value makes
# the pull request that changes the value block itself: the job emits a new
# name, the ruleset still requires the old one, and nothing emits it. This job
# carries a name that cannot move, so a ruleset gates on "the extra-platform
# tests passed" without naming which platforms those are. Glyndor/podup#1552.
needs: test-extra
# `needs` on its own SKIPS this job when the dependency fails, and a skipped
# required check does not block a merge - which is the exact failure this job
# exists to prevent. `if: always()` plus an explicit result check is what makes
# it a gate rather than a decoration.
if: always()
runs-on: ubuntu-latest
steps:
- name: Reflect the extra-platform test result
env:
RESULT: ${{ needs.test-extra.result }}
run: |
set -euo pipefail
# `skipped` is a pass: test-extra is gated on `extra-test-os != '[]'`, so a
# caller that asks for no extra platforms has nothing to fail.
case "$RESULT" in
success | skipped) echo "extra-platform tests: $RESULT" ;;
*) echo "::error::extra-platform tests did not pass (result: $RESULT)"; exit 1 ;;
esac

coverage:
name: Coverage
if: inputs.coverage-threshold > 0
Expand Down Expand Up @@ -268,6 +295,27 @@ jobs:
- name: Build on the declared MSRV
run: cargo build --locked --all-features

msrv-gate:
name: MSRV
# Same reason as extra-platforms-gate: `MSRV (1.85)` moves the day the MSRV
# moves, and takes the gate with it. MSRV is the worse of the two because it is
# not a matrix - a single job interpolating an input into its own name. The
# number stays visible in the job log and in the `MSRV (x.y)` job itself.
needs: msrv
if: always()
runs-on: ubuntu-latest
steps:
- name: Reflect the MSRV result
env:
RESULT: ${{ needs.msrv.result }}
run: |
set -euo pipefail
# `skipped` is a pass: msrv is gated on `inputs.msrv != ''`.
case "$RESULT" in
success | skipped) echo "MSRV: $RESULT" ;;
*) echo "::error::MSRV job did not pass (result: $RESULT)"; exit 1 ;;
esac

package:
name: Package check
if: inputs.package-check
Expand Down
2 changes: 1 addition & 1 deletion docs/reusables/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ which blocks every pull request until someone works out why.
| [`python-ci`](python-ci.md) | 1 | 0 | 5 |
| [`release-verify`](release-verify.md) | 1 | 0 | 6 |
| [`rust-audit`](rust-audit.md) | 2 | 0 | 4 |
| [`rust-ci`](rust-ci.md) | 8 | 6 | 12 |
| [`rust-ci`](rust-ci.md) | 10 | 6 | 12 |
| [`rust-debian`](rust-debian.md) | 2 | 1 | 11 |
| [`rust-fuzz`](rust-fuzz.md) | 1 | 0 | 7 |
| [`rust-supply-chain`](rust-supply-chain.md) | 1 | 0 | 5 |
Expand Down
4 changes: 3 additions & 1 deletion docs/reusables/rust-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ a required check whose name nothing emits blocks every pull request.
| `example / Format & lint` | always |
| `example / Test` | always |
| `example / Test (${{ matrix.os }})` (one per matrix entry) | `inputs.extra-test-os != '[]'` |
| `example / Extra platforms` | always |
| `example / Coverage` | `inputs.coverage-threshold > 0` |
| `example / MSRV (${{ inputs.msrv }})` | `inputs.msrv != ''` |
| `example / MSRV` | always |
| `example / Package check` | `inputs.package-check` |
| `example / Semver checks` | `inputs.semver-check` |
| `example / Doc warnings` | `inputs.doc-warnings` |

6 of these 8 are conditional: they do not run,
6 of these 10 are conditional: they do not run,
and therefore emit no check, unless the input in the right-hand column says
so. Requiring one in a ruleset without setting its input is how a phantom
check is created.
Expand Down
12 changes: 11 additions & 1 deletion scripts/render-reusable-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,24 @@ def header_comment(path):
return " ".join(x for x in out if x).strip()


# Spellings of "this job always runs". A job with no `if:` runs unconditionally;
# so does one that spells that out, which a gate job must do to survive a failed
# dependency.
ALWAYS = {"always()", "${{ always() }}"}


def emitted_checks(spec):
"""One row per job: the name a caller will see, and the condition that
decides whether it appears at all."""
rows = []
for job_id, job in (spec.get("jobs") or {}).items():
name = job.get("name", job_id)
cond = job.get("if")
if cond is None:
# `if: always()` is the opposite of conditional: it is how a gate job
# keeps emitting its check even when the job it reports on failed or was
# skipped. Reading it as a condition would document exactly the jobs a
# ruleset CAN safely require as the ones that might not appear.
if cond is None or str(cond).strip() in ALWAYS:
when = "always"
else:
when = f"`{str(cond).strip()}`"
Expand Down
Loading