build(deps): bump the wasm-cargo-dependencies group across 1 directory with 3 updates #976
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # The PR/master gate, organized as ONE plan-gated orchestrator (the pattern | |
| # uv, cargo, zed and wgpu converged on independently): a ~10 s `plan` job | |
| # classifies the event's diff into AREAS, every gate job runs only when its | |
| # area fired, and a final `ci-ok` fan-in job is the single branch-protection | |
| # context. | |
| # | |
| # The gate jobs themselves live in four workflow_call reusables, one caller job | |
| # each: core.yml (the root workspace), repo.yml (the whole-tree scanners), | |
| # gui.yml and wasm.yml (the sibling crates). A caller fires when ANY of the | |
| # areas its inner jobs consume fired, and passes the plan's outputs through as | |
| # INPUTS so each inner job keeps its own per-area `if:`; the check runs render | |
| # prefixed (`core / build + test (…)`, `repo / lint YAML (action-validator + | |
| # ryl)`). The three mutation jobs stay HERE rather than inside a reusable: a | |
| # failure inside a called workflow reddens its caller and so reaches ci-ok, | |
| # which would silently promote an advisory job to a gating one. Their shared | |
| # body is the .github/actions/mutate-crate COMPOSITE action — a composite's | |
| # failure surfaces on the calling job itself, preserving that posture. | |
| # | |
| # A job fires if and only if the diff could change that job's verdict, so the | |
| # classifier asks what KIND of input a file is before it asks which directory | |
| # holds it — see .github/scripts/classify-diff.ps1, which owns the rules, the | |
| # mechanism behind each one, and a self-test that refuses any tracked path no | |
| # rule claims. That self-test is its own job here, running beside the gate | |
| # jobs: it makes an unclassified file a failure rather than a silent skip, and | |
| # reaching ci-ok is what gives it teeth. So gating rests on the rules | |
| # themselves; sweep.yml | |
| # re-runs this orchestrator UNGATED on master daily so that environment drift | |
| # (runner images, toolchain point releases, link rot) is caught as well. | |
| # | |
| # WHY plan + `if:`, never `on.paths:`, on anything required: a job skipped via | |
| # `if:` still reports a check-run with conclusion `skipped`, which satisfies a | |
| # required status check — but a workflow filtered out by `on.paths:` never | |
| # reports at all and leaves the PR's required check "Expected" forever. One | |
| # nuance (observed live): a skipped MATRIX job reports once, UNexpanded — its | |
| # per-leg names never appear — so per-leg contexts cannot survive gating; | |
| # only the job-level result feeds ci-ok, which is what branch protection | |
| # requires. For the same reason, never rename or delete a job whose `name:` | |
| # string is a live required context (contexts match on that string). | |
| # | |
| # A master push first asks whether its run is needed at all. master takes | |
| # only squash merges of branches required to be up to date, so the pushed | |
| # commit's TREE is byte-identical to the PR head tree a successful | |
| # pull_request run already verified — and no required context reads a push | |
| # run's verdict. The plan job compares the pushed tree id against the head | |
| # trees of recent successful pull_request runs and on a match emits | |
| # should_run=false, which every downstream job's `if:` consumes, ending the | |
| # run with plan alone. Tree identity also pins the workflow definition | |
| # itself (the tree contains .github), so the matched run executed exactly | |
| # this gate. Any API failure fails open to should_run=true, and sweep.yml | |
| # still runs everything on master daily, so environment drift stays caught. | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| # sweep.yml invokes the whole orchestrator daily (and on demand) with every | |
| # area forced — the completeness backstop that makes the PR-path gating | |
| # provably non-weakening. | |
| workflow_call: | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # Two knobs that exist to keep the SAVED target/ small, set here rather than | |
| # in a tracked Cargo profile so local dev loops keep both. A hosted job builds | |
| # from a restored cache, near enough from scratch that incremental | |
| # compilation buys no rebuild it can amortise — it only writes | |
| # dependency-tracking metadata into the tree rust-cache then archives. | |
| # Debuginfo is the other bulk contributor; `line-tables-only` rather than `0` | |
| # so a failing test still prints file:line backtraces. | |
| # | |
| # Why size is the thing being optimised: a repository's Actions cache store | |
| # is capped (10 GB at the time of writing) and evicted least-recently-used | |
| # across ALL entries, so oversized Rust archives push out the few-MB | |
| # compiled-tool caches, whose miss costs minutes of `cargo install` on the | |
| # pull-request path. Smaller archives also restore faster, on every job. | |
| # | |
| # Jobs running cargo-mutants override CARGO_INCREMENTAL back to 1 — the | |
| # mutate-crate composite action does it for all of them. | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: line-tables-only | |
| concurrency: | |
| # Namespaced by event so that, should a workflow_call invocation from | |
| # sweep.yml ever be subject to this group (the caller's concurrency is the | |
| # one that governs a called run), a sweep could still never queue against — | |
| # and supersede — a master-push run on the same ref. | |
| group: ci-${{ github.event_name }}-${{ github.ref }} | |
| # PR-only: rapid sequential squash-merges must each keep their master run. | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Classify the event's changed files into areas. The rules, the mechanism | |
| # behind each one and the self-test that holds them honest all live in | |
| # .github/scripts/classify-diff.ps1; this job only decides WHICH files to | |
| # hand it. | |
| # | |
| # Fail-safes: if the diff cannot be computed (missing before-SHA, | |
| # force-push, an API error, a files list at an endpoint's cap) every area | |
| # fires; so does the `ci-full` PR label. Labels are read from the event | |
| # payload, so apply the label BEFORE a push or a close/reopen — a bare | |
| # re-run replays the old payload without it. | |
| plan: | |
| name: plan (classify the diff into areas) | |
| runs-on: ubuntu-latest | |
| # The default token carries contents: read only; the tree-identity skip | |
| # lists workflow runs (actions) and the classifier reads the PR's files | |
| # (pull-requests). | |
| # | |
| # COUPLED to sweep.yml's `orchestrator` job, which calls this workflow: a | |
| # called job may only request permissions its caller holds, so widening | |
| # this set without widening that one fails the whole sweep run at startup. | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| # 'false' only when the tree-identity step proved this pushed tree | |
| # already passed a pull_request run; defaults to 'true' on every other | |
| # event, where the step is skipped and its output empty. | |
| should_run: ${{ steps.skip.outputs.should_run || 'true' }} | |
| core: ${{ steps.classify.outputs.core }} | |
| gui: ${{ steps.classify.outputs.gui }} | |
| gui-full: ${{ steps.classify.outputs.gui-full }} | |
| wasm: ${{ steps.classify.outputs.wasm }} | |
| fuzz: ${{ steps.classify.outputs.fuzz }} | |
| deps: ${{ steps.classify.outputs.deps }} | |
| yaml: ${{ steps.classify.outputs.yaml }} | |
| workflows: ${{ steps.classify.outputs.workflows }} | |
| canary: ${{ steps.classify.outputs.canary }} | |
| dist: ${{ steps.classify.outputs.dist }} | |
| pkg: ${{ steps.classify.outputs.pkg }} | |
| toml: ${{ steps.classify.outputs.toml }} | |
| links: ${{ steps.classify.outputs.links }} | |
| typos: ${{ steps.classify.outputs.typos }} | |
| test: ${{ steps.classify.outputs.test }} | |
| test-os: ${{ steps.classify.outputs.test-os }} | |
| test-os-pr: ${{ steps.classify.outputs.test-os-pr }} | |
| steps: | |
| # Depth 1, .github only: the diff comes from the API, so the checkout | |
| # exists to run the classifier. The fixture discs never materialize | |
| # here. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: .github | |
| # Tree identity, not commit identity (see the header): a pull_request | |
| # run's head_sha is the merged branch's tip — still API-reachable after | |
| # the merge — and only the TREE survives the squash, so each | |
| # candidate's tree id comes from its commit object. Bounded at the last | |
| # 20 successful runs; no match, an empty list, or any API failure falls | |
| # open to should_run=true. | |
| - name: Skip a push whose tree a pull_request run already verified | |
| id: skip | |
| if: github.event_name == 'push' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PUSHED: ${{ github.sha }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $should = 'true' | |
| $matched = '' | |
| $pushedTree = gh api "repos/$env:REPO/commits/$env:PUSHED" --jq '.commit.tree.sha' | |
| if ($LASTEXITCODE -eq 0 -and $pushedTree) { | |
| $heads = @(gh api "repos/$env:REPO/actions/workflows/ci.yml/runs?event=pull_request&status=success&per_page=20" --jq '.workflow_runs[].head_sha') | |
| if ($LASTEXITCODE -ne 0) { $heads = @() } | |
| foreach ($sha in $heads) { | |
| $tree = gh api "repos/$env:REPO/commits/$sha" --jq '.commit.tree.sha' | |
| if ($LASTEXITCODE -eq 0 -and $tree -eq $pushedTree) { | |
| $should = 'false' | |
| $matched = $sha | |
| break | |
| } | |
| } | |
| } | |
| "should_run=$should" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| if ($should -eq 'false') { | |
| Write-Host "tree $pushedTree already verified by the pull_request run on $matched — skipping" | |
| } | |
| else { | |
| Write-Host 'no successful pull_request run with an identical tree — running everything' | |
| } | |
| # A gh failure handled fail-open above leaves a non-zero exit code | |
| # that the pwsh step wrapper would otherwise propagate as a step | |
| # failure. | |
| exit 0 | |
| - name: Classify the changed files into areas | |
| id: classify | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| EVENT: ${{ github.event_name }} | |
| PR: ${{ github.event.pull_request.number }} | |
| BEFORE: ${{ github.event.before }} | |
| AFTER: ${{ github.event.after }} | |
| CI_FULL: ${{ contains(github.event.pull_request.labels.*.name, 'ci-full') }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $mode = 'diff' | |
| $reason = '' | |
| $changed = @() | |
| # Each TSV line is one changed file: new path, TAB, previous path | |
| # (empty unless renamed). Both endpoints report a rename as one | |
| # entry carrying previous_filename, so feeding both sides keeps a | |
| # pure move-out-of-an-area firing the old area's gates — fail-open, | |
| # it can only classify MORE areas, never fewer. | |
| $fileTsv = '[.filename, .previous_filename // ""] | @tsv' | |
| function ConvertTo-Paths { | |
| param([string[]] $TsvLines) | |
| return @(foreach ($line in $TsvLines) { | |
| $new, $old = $line -split "`t", 2 | |
| if ($new) { $new } | |
| if ($old) { $old } | |
| }) | |
| } | |
| if ($env:CI_FULL -eq 'true') { | |
| $mode = 'all'; $reason = 'ci-full label' | |
| } | |
| elseif ($env:EVENT -eq 'pull_request') { | |
| # The PR files endpoint diffs head against the merge base — the | |
| # changes the PR itself introduces, which is what the merge | |
| # commit this run tests adds to master. The endpoint stops at | |
| # 3000 files, so a count that reaches that cap may be truncated | |
| # and fails safe. | |
| $lines = @(gh api "repos/$env:REPO/pulls/$env:PR/files?per_page=100" --paginate --jq ".[] | $fileTsv") | |
| if ($LASTEXITCODE -ne 0) { $mode = 'all'; $reason = 'PR files API failed' } | |
| elseif ($lines.Count -ge 3000) { $mode = 'all'; $reason = 'at the PR files cap (3000 files), possibly truncated' } | |
| else { $changed = ConvertTo-Paths $lines } | |
| } | |
| elseif ($env:EVENT -eq 'push') { | |
| # before...after is exactly the squash-merge's diff. A zero or | |
| # missing before (branch creation, history rewrite) fails safe. | |
| if (-not $env:BEFORE -or $env:BEFORE -eq ('0' * 40)) { | |
| $mode = 'all'; $reason = 'no usable before SHA' | |
| } | |
| else { | |
| # The compare endpoint pages its files list at up to 300 per | |
| # page; --paginate follows the pages, and the same 3000-file | |
| # guard as the PR path bounds the walk. | |
| $lines = @(gh api "repos/$env:REPO/compare/$($env:BEFORE)...$($env:AFTER)?per_page=250" --paginate --jq ".files[]? | $fileTsv") | |
| if ($LASTEXITCODE -ne 0) { $mode = 'all'; $reason = 'compare API failed' } | |
| elseif ($lines.Count -ge 3000) { $mode = 'all'; $reason = 'at the compare files cap (3000 files), possibly truncated' } | |
| else { $changed = ConvertTo-Paths $lines } | |
| } | |
| } | |
| else { | |
| # workflow_call from sweep.yml (schedule / workflow_dispatch). | |
| $mode = 'all'; $reason = "$($env:EVENT) event — the sweep runs everything" | |
| } | |
| $areas = if ($mode -eq 'all') { | |
| ./.github/scripts/classify-diff.ps1 -All | |
| } | |
| else { | |
| $changed | ./.github/scripts/classify-diff.ps1 | |
| } | |
| $areas | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "mode: $mode$(if ($reason) { " ($reason)" })" | |
| if ($mode -eq 'diff') { | |
| Write-Host 'changed files:' | |
| $changed | ForEach-Object { Write-Host " $_" } | |
| } | |
| Write-Host 'areas:' | |
| $areas | ForEach-Object { Write-Host " $_" } | |
| # A gh failure handled fail-open above leaves a non-zero exit code | |
| # that the pwsh step wrapper would otherwise propagate as a step | |
| # failure. | |
| exit 0 | |
| # Incremental mutation testing on the PR diff: every line a pull request | |
| # changes must be killed by a test that FAILS when the line is mutated. The | |
| # full-tree sweep (~hours) lives in sweep.yml (sharded, daily + release | |
| # tags); this PR-scoped slice (cargo mutants --in-diff, via the mutate-crate | |
| # composite) is fast. It reads the tracked .cargo/mutants.toml policy and | |
| # the `mutants` nextest profile (.config/nextest.toml) — both published, so | |
| # CI mutates exactly as the local gate does. | |
| # | |
| # ADVISORY, by design: this job is intentionally NOT in branch protection's | |
| # required status checks AND NOT in ci-ok's `needs`. cargo-mutants --in-diff | |
| # can false-TIMEOUT on the 4-core hosted runners (a hang-prone loop-guard | |
| # mutant runs in ceil(n/cores) nextest-terminate waves), so gating merges on | |
| # it would let an environment artifact block an unrelated PR. The | |
| # AUTHORITATIVE "0 missed mutants" gate is sweep.yml's sharded full sweep | |
| # (daily when the workspace changed + release tags); this surfaces | |
| # regressions early but does not block. Keep it out of the required set, | |
| # out of ci-ok, and out of core.yml — a failure inside a called workflow | |
| # reddens its caller, which is in ci-ok's `needs`. | |
| mutants: | |
| name: mutation testing (PR diff) | |
| needs: plan | |
| if: github.event_name == 'pull_request' && needs.plan.outputs.should_run != 'false' && needs.plan.outputs.core == 'true' | |
| # x64: taiki-e/install-action's cargo-mutants manifest carries no aarch64 | |
| # Linux asset (x86_64-unknown-linux-gnu, x86_64 macOS and x86_64 Windows | |
| # only), and `fallback: none` turns a missing prebuilt into a hard failure. | |
| # Same for gui-mutants and wasm-mutants below. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 # need the base branch to diff the PR against | |
| persist-credentials: false | |
| # No marker inputs: the root workspace has no PR-side short-circuit, so | |
| # every run diffs and mutates. | |
| - uses: ./.github/actions/mutate-crate | |
| with: | |
| mode: pr-diff | |
| # Advisory in-diff mutation over the GUI crate on the PR path: mirrors the | |
| # local gui gate's default mt scope. When the `gui` area fired via the core | |
| # edge only, the crate's own diff is empty and the composite exits with | |
| # nothing to mutate. Same ADVISORY posture and false-TIMEOUT rationale as | |
| # `mutants` above: not required, not in ci-ok's needs, not inside a | |
| # reusable. The authoritative full-crate 0-missed bar lives in sweep.yml | |
| # (daily when the crate changed + release tags). | |
| # | |
| # The sweep's tree-hash marker short-circuits this job too, RESTORE-ONLY: a | |
| # marker hit means this byte-identical crate tree (+ toolchain) already | |
| # passed a green FULL sweep, which subsumes any diff slice of it — mutating | |
| # the slice again would re-verify unchanged inputs. Only a green full sweep | |
| # ever writes the marker; this job must never save one (a diff slice proves | |
| # nothing about the rest of the crate), so it leaves `save-marker` off. The | |
| # marker prefix + paths are byte-identical to sweep.yml's gui-mutants call | |
| # ON PURPOSE — the composite builds the key in one place, and matching | |
| # inputs are what let a green sweep short-circuit this job. The guard is | |
| # what keeps a crate-sized PR diff (e.g. the branch that introduces a | |
| # crate) from re-paying the ~50-min sweep on every push that leaves the | |
| # crate untouched. | |
| gui-mutants: | |
| name: gui mutation testing (PR diff) | |
| needs: plan | |
| if: github.event_name == 'pull_request' && needs.plan.outputs.should_run != 'false' && needs.plan.outputs.gui == 'true' | |
| runs-on: ubuntu-latest # no aarch64 cargo-mutants prebuilt — see `mutants` | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 # need the base branch to diff the PR against | |
| persist-credentials: false | |
| - uses: ./.github/actions/mutate-crate | |
| with: | |
| mode: pr-diff | |
| working-directory: crates/bdinfo-rs-gui | |
| marker-prefix: gui-mutants-ok | |
| marker-paths: crates/bdinfo-rs-gui rust-toolchain.toml | |
| # --in-place: the ../bdinfo-rs-core path dependency does not survive | |
| # cargo-mutants' temp copy; --workspace: a diff touching the | |
| # assets-gen member is mutated too (cargo-mutants otherwise scopes | |
| # to the root package). | |
| mutants-args: --in-place --workspace | |
| # The suite the mutants face includes the iced_test snapshot ties; | |
| # the deterministic software backend is what makes them run on a | |
| # GPU-less runner at all (same as gui.yml). | |
| extra-env: ICED_TEST_BACKEND=tiny-skia | |
| cache-workspaces: crates/bdinfo-rs-gui | |
| # The wasm twin of gui-mutants: advisory in-diff mutation over the wasm | |
| # crate's own PR diff (--in-place for the same path-dep reason; the | |
| # crate-local .cargo/mutants.toml carries the cfg(wasm32) exclusions), with | |
| # the same restore-only full-sweep marker short-circuit and the same | |
| # byte-identical marker inputs as sweep.yml's wasm-mutants call. Not | |
| # required, not in ci-ok's needs, not inside a reusable; the full-crate bar | |
| # lives in sweep.yml. | |
| wasm-mutants: | |
| name: wasm mutation testing (PR diff) | |
| needs: plan | |
| if: github.event_name == 'pull_request' && needs.plan.outputs.should_run != 'false' && needs.plan.outputs.wasm == 'true' | |
| runs-on: ubuntu-latest # no aarch64 cargo-mutants prebuilt — see `mutants` | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 # need the base branch to diff the PR against | |
| persist-credentials: false | |
| - uses: ./.github/actions/mutate-crate | |
| with: | |
| mode: pr-diff | |
| working-directory: crates/bdinfo-rs-wasm | |
| marker-prefix: wasm-mutants-ok | |
| marker-paths: crates/bdinfo-rs-wasm rust-toolchain.toml | |
| mutants-args: --in-place | |
| cache-workspaces: crates/bdinfo-rs-wasm | |
| # Prove the classifier still holds its own rules: every golden case, and no | |
| # tracked path that matches no rule (which is what stops a newly added file | |
| # from silently gating nothing). | |
| # | |
| # It runs BESIDE the gate jobs rather than inside `plan` because it does not | |
| # have to run before the classification to protect it — it only has to fail | |
| # the RUN, and ci-ok below turns this job's failure into a red verdict that | |
| # blocks the merge whatever the areas said. Inside `plan` it was pure | |
| # latency: `plan` gates every other job, so its ~90 s (2026-08-15, before | |
| # the exhaustiveness pass early-exited) was paid by every pull request | |
| # before any gate job could start. Here it hides entirely behind gate jobs | |
| # that run for minutes. | |
| classifier: | |
| name: classifier self-test | |
| needs: plan | |
| if: needs.plan.outputs.should_run != 'false' | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Sparse like `plan`'s: `git ls-files` reads the index, which a sparse | |
| # checkout does not filter, so the exhaustiveness pass still sees every | |
| # tracked path without materializing the fixture discs. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: .github | |
| - name: Self-test the classifier | |
| shell: pwsh | |
| run: ./.github/scripts/classify-diff.ps1 -SelfTest | |
| # The root-workspace gate: build + test, the quality gate, coverage, the | |
| # install + scan and FROM-scratch scans, the .deb/.rpm smoke, the two | |
| # dependency audits, the release-workflow drift guard and the corpus replay. | |
| # The `if:` is the UNION of the areas those jobs consume — a caller that | |
| # never starts cannot skip its inner jobs individually — and every area it | |
| # unions is passed through, so each inner job keeps its own per-area `if:`. | |
| core: | |
| name: core | |
| needs: plan | |
| if: >- | |
| needs.plan.outputs.should_run != 'false' && | |
| (needs.plan.outputs.core == 'true' || | |
| needs.plan.outputs.deps == 'true' || | |
| needs.plan.outputs.dist == 'true' || | |
| needs.plan.outputs.fuzz == 'true' || | |
| needs.plan.outputs.pkg == 'true' || | |
| needs.plan.outputs.test == 'true') | |
| uses: ./.github/workflows/core.yml | |
| with: | |
| core: ${{ needs.plan.outputs.core }} | |
| deps: ${{ needs.plan.outputs.deps }} | |
| dist: ${{ needs.plan.outputs.dist }} | |
| fuzz: ${{ needs.plan.outputs.fuzz }} | |
| pkg: ${{ needs.plan.outputs.pkg }} | |
| test: ${{ needs.plan.outputs.test }} | |
| # Pull requests take the classifier's shrunk matrix — no aarch64 | |
| # Linux/Windows legs; those run on master pushes and the daily sweep, | |
| # which pass the full list here. A matrix shrunk by expression still | |
| # reports each remaining leg normally; nothing here is a required | |
| # context, so the absent legs hang no PR. | |
| test-os: ${{ github.event_name == 'pull_request' && needs.plan.outputs.test-os-pr || needs.plan.outputs.test-os }} | |
| # The whole-tree scanners: YAML, TOML, workflow security, spelling, links. | |
| # Same caller shape as `core` above. | |
| repo: | |
| name: repo | |
| needs: plan | |
| if: >- | |
| needs.plan.outputs.should_run != 'false' && | |
| (needs.plan.outputs.yaml == 'true' || | |
| needs.plan.outputs.toml == 'true' || | |
| needs.plan.outputs.workflows == 'true' || | |
| needs.plan.outputs.typos == 'true' || | |
| needs.plan.outputs.links == 'true') | |
| uses: ./.github/workflows/repo.yml | |
| with: | |
| yaml: ${{ needs.plan.outputs.yaml }} | |
| toml: ${{ needs.plan.outputs.toml }} | |
| workflows: ${{ needs.plan.outputs.workflows }} | |
| typos: ${{ needs.plan.outputs.typos }} | |
| links: ${{ needs.plan.outputs.links }} | |
| # The sibling-crate gates, invoked as workflow_call reusables so their job | |
| # content stays with their crates while the plan gates them here. Their | |
| # check-runs render prefixed (`gui / gui build + test (…)`, | |
| # `wasm / wasm gate (…)`); a red anywhere inside reddens the caller job and | |
| # therefore ci-ok. When every consumed area is skipped, the single caller | |
| # job reports `skipped` — the inner jobs never appear, which is fine because | |
| # only the caller jobs feed ci-ok and no inner name is a required context. | |
| # Same caller shape as `core` above: the `if:` is the UNION of the areas the | |
| # inner jobs consume, each passed through as an input. `gui` alone (a | |
| # bdinfo-rs-core edit reaching the crate through the path-dependency edge) | |
| # runs the gui build + test and checks jobs; `gui-full` (a gui-path or | |
| # toolchain change) adds the drive, packaging and mosaic jobs; `deps` runs | |
| # both crates' msrv jobs. | |
| gui: | |
| name: gui | |
| needs: plan | |
| if: >- | |
| needs.plan.outputs.should_run != 'false' && | |
| (needs.plan.outputs.gui == 'true' || | |
| needs.plan.outputs.gui-full == 'true' || | |
| needs.plan.outputs.deps == 'true') | |
| uses: ./.github/workflows/gui.yml | |
| with: | |
| gui: ${{ needs.plan.outputs.gui }} | |
| full: ${{ needs.plan.outputs.gui-full }} | |
| deps: ${{ needs.plan.outputs.deps }} | |
| wasm: | |
| name: wasm | |
| needs: plan | |
| if: >- | |
| needs.plan.outputs.should_run != 'false' && | |
| (needs.plan.outputs.wasm == 'true' || | |
| needs.plan.outputs.deps == 'true') | |
| uses: ./.github/workflows/wasm.yml | |
| with: | |
| wasm: ${{ needs.plan.outputs.wasm }} | |
| deps: ${{ needs.plan.outputs.deps }} | |
| # The fan-in: the ONE result branch protection requires (plus the three | |
| # standalone checks outside this workflow). Green = every gate job either | |
| # succeeded or was skipped by the plan; red = anything failed, was | |
| # cancelled, or never ran because a `needs` upstream failed. | |
| # | |
| # A skipped job is considered a success by GitHub, so overwrite `if:`. | |
| # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! | |
| # (cargo's warning, kept verbatim: a job missing from `needs` silently stops | |
| # gating merges.) With the gate jobs inside the four called workflows, that | |
| # means every CALLER job — a red inside a reusable reddens its caller. The | |
| # deliberate exceptions are the ADVISORY mutation jobs (`mutants`, | |
| # `gui-mutants`, `wasm-mutants` — see their headers: false-TIMEOUTs on | |
| # 4-core runners must not block unrelated PRs), which stay visible on the PR | |
| # but do not gate. `!cancelled()` rather than `always()`: a manually- | |
| # cancelled run must not end green. | |
| # | |
| # `should_run != 'false'` rather than `== 'true'`, here and on every job | |
| # that consumes it: when plan itself fails its outputs are EMPTY, and an | |
| # equality test would skip ci-ok — a skipped required check satisfies | |
| # branch protection, so a plan failure would open the merge gate instead | |
| # of blocking it. The inequality skips only on the one value the | |
| # tree-identity step deliberately emits. | |
| ci-ok: | |
| name: ci-ok | |
| needs: [plan, classifier, core, repo, gui, wasm] | |
| if: ${{ !cancelled() && needs.plan.outputs.should_run != 'false' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Fail unless every gate job succeeded or was skipped | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| echo "$NEEDS_JSON" | jq . | |
| failing=$(echo "$NEEDS_JSON" | jq -r 'to_entries[] | select(.value.result != "success" and .value.result != "skipped") | "\(.key): \(.value.result)"') | |
| if [ -n "$failing" ]; then | |
| echo "not green:" | |
| echo "$failing" | |
| exit 1 | |
| fi | |
| echo "ci-ok: every gate job succeeded or was skipped" |