Simpl-Resultat/.forgejo/workflows/check-rust.yml
le king fu 71a4e1e88f
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m51s
PR Check — Rust / rust (pull_request) Successful in 4m46s
ci: move check-frontend, audit and release from Node 20 to Node 24
Node 20 reached end of life on 2026-04-30. check-frontend.yml, audit.yml
and release.yml now install Node.js 24 through NodeSource's setup_24.x,
as check-rust.yml has done since #375. Each step still prints
`node --version` in the job log.

The concurrency comment of check-rust.yml claimed the key cancels
obsolete runs on force-push. la-compagnie-maximus docs/forgejo-ops.md
measured the opposite on Forgejo 8.0.3: no pull_request run was
cancelled by a later one, and push runs are cancelled without the key.
The comment now states that measurement; the keys stay (harmless,
meaningful where honoured), and check-frontend.yml and audit.yml point
to it.

release.yml names the trust it places in NodeSource: the script runs as
root in the job container, whose Node.js then runs `npx tauri build`
with TAURI_SIGNING_PRIVATE_KEY. Kept on purpose, also documented in
docs/architecture.md, whose release.yml section described two jobs
(windows-latest, ubuntu-22.04) where there is one containerised job
cross-compiling Windows with cargo-xwin.

release.yml only runs on a tag, so Node 24 there is proven at the next
release: the /release skill now reads `node --version` from the release
run's log (step 9), and STATE.md lists it under the checks due at the
next release.

No CHANGELOG entry: CI only.

Resolves #376

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:40:28 -04:00

210 lines
10 KiB
YAML

name: PR Check — Rust
# Rust half of the former check.yml (split in #232).
#
# Runs on PRs that change Rust, plus once a week on main to refresh the shared
# cache (see the note on the triggers). The runner has capacity 1 — jobs queue
# instead of running in parallel, so every minute spent here is a minute the
# next PR waits.
#
# No `branches:` filter on purpose. `branches: [main]` never matched a PR
# stacked on top of another feature branch, which is what /autopilot produces:
# 4 of the 5 PRs in the feature-gating milestone ran no CI at all.
on:
pull_request:
paths:
- 'src-tauri/**'
- '.forgejo/workflows/check-rust.yml'
# Whole directory, not just audit.toml: a later .cargo/config.toml
# (rustflags, linker, target dir) would change the Rust build, and an
# exact-path entry would let it skip Rust CI unnoticed.
- '.cargo/**'
# The weekly run is what feeds the cache to pull requests (#234). The runner
# (forgejo-runner v12.7.0) isolates what a pull_request run writes to that
# PR's ref: later pushes to the same PR read it back, other PRs never see it.
# A PR run falls back to the shared, non-isolated entries only when nothing
# matches in its own scope. Scheduled and dispatched runs write to the shared
# scope, which makes them this workflow's only writers of it (a merged PR's
# `closed` event would be another, but this workflow does not subscribe to
# it).
#
# The runner deletes an entry unused for 7 days or created more than 30 days
# ago, and keeps only the newest entry per key, whatever its scope; the next
# Monday run saves a fresh one. Cold runs that do not mean #234 is back (a
# timeout would):
# - a new stable Rust (the toolchain floats on `stable`) or a change to this
# job's `env:`: the key hashes rustc and the CARGO*, RUST*, CC*, CFLAGS*,
# CXX* and CMAKE* variables. Cold until the next Monday;
# - a PR run that saves under the shared entry's key evicts it for every PR
# until the next Monday. It saves when its own scope holds an older entry
# for another Cargo.lock, served before the shared one: a PR that bumped a
# dependency, then went back to main's lockfile. Two open PRs on the same
# lockfile, different from main's, evict each other the same way;
# - PR runs once an entry that only got exact hits, hence never re-saved,
# reaches 30 days;
# - after a week without any cache request, the Monday restore can fail with
# a 404: the lookup does not refresh the entry, and the cleanup it triggers
# may delete it (forgejo/runner#1741). The run then saves a fresh entry.
# workflow_dispatch re-seeds by hand.
schedule:
# Mondays 06:30 UTC, after audit.yml (06:00 UTC).
- cron: '30 6 * * 1'
workflow_dispatch:
# What this key does on Forgejo 8.0.3 / runner v12.7.0, as measured on
# 2026-10-02 (la-compagnie-maximus docs/forgejo-ops.md, sections "Le schéma du
# workflow est validé par le runner" and "Un push annule le run du push
# précédent"):
# - it passes the runner's schema validation, but what it adds has not been
# isolated;
# - without it, Forgejo cancels a `push` run when a newer one of the same
# workflow is created on the same ref, whether the older run's job was still
# queued or already running;
# - no pull_request (or pull_request_sync) job has been cancelled by a later
# run: on a PR, a force-push does not cancel the obsolete run.
# Kept anyway (#376): harmless here, and it keeps its GitHub Actions meaning
# should these workflows run where it is honoured (GitHub, a later Forgejo).
# There, a group shared with the frontend workflow would make the two cancel
# each other on a PR that touches both, hence the distinct group.
concurrency:
group: ci-rust-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
rust:
runs-on: ubuntu
container: ubuntu:22.04
env:
PATH: /root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CARGO_TERM_COLOR: always
# rust-cache keeps the dependencies and drops this crate's own artifacts
# before saving, so incremental data would never be reused — pure
# overhead — and test debug info would only make the cached target/
# bigger.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
steps:
- name: Install system dependencies, Node.js and Rust
run: |
apt-get update
# zstd: the cache archives are compressed with it when present,
# faster and smaller than the gzip fallback.
apt-get install -y --no-install-recommends \
curl wget git ca-certificates build-essential pkg-config \
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libssl-dev \
libdbus-1-dev zstd
# Node.js runs the JavaScript actions: actions/checkout (node20) and
# Swatinem/rust-cache (node24 since v2.9). The runner starts them
# with whatever `node` the container has, so install the newest
# runtime they declare. taiki-e/install-action is a composite action.
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
apt-get install -y nodejs
# Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
node --version
rustc --version
cargo --version
- name: Checkout
uses: https://github.com/actions/checkout@v4
# Caches ~/.cargo (registry, git, bin) and src-tauri/target, keyed on the
# rustc version, the job's CARGO*/RUST* environment, the dependencies
# declared in the Cargo manifests and lockfile (not the app's own
# version), and this job. Saved only when the job succeeds and the key
# was not an exact hit. Served by
# the runner's cache proxy on port 28100, which ufw opens to the Docker
# job networks only; until 2026-10-03 every restore and save timed out
# there (#234, la-compagnie-maximus docs/forgejo-ops.md). What a PR run
# saves stays private to that PR: see the note on the triggers.
- name: Rust cache
uses: https://github.com/Swatinem/rust-cache@v2
with:
workspaces: "src-tauri -> target"
- name: cargo check
run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets
# Anti-rot guard for the suppressions in .cargo/audit.toml (#310). Each
# entry there is justified by its crate being absent from every shipped
# target's graph — a property of today's resolved graph, not a permanent
# one. rsa is currently reachable from nothing: its only parent in the
# lockfile is sqlx-mysql, which this SQLite project never compiles. If a
# dependency change ever pulls it into a shipped target, the suppression
# would silently hide a live advisory and the daily audit would stay
# green: the inverse of the permanent red #310 exists to kill.
#
# Such a change would itself touch src-tauri, which is exactly what
# triggers this workflow. Runs after cargo check so the registry index is
# already warm, and --locked so cargo tree cannot rewrite the lockfile the
# audit was taken against.
#
# CRATES must mirror the crates named in .cargo/audit.toml. Adding an
# entry there without adding its crate here leaves it unguarded.
#
# Every check echoes its result, including the passing ones. A guard that
# is silent on success cannot be told apart in the log from a guard that
# never ran — which is the same silent-skip failure mode this step exists
# to catch, one level up.
- name: Verify suppressed advisories are still unreachable
run: |
set -u
CRATES="rsa"
TARGETS="x86_64-unknown-linux-gnu x86_64-pc-windows-msvc"
rc=0
checks=0
for crate in $CRATES; do
for target in $TARGETS; do
# An absent crate exits 0 with empty stdout ("nothing to print"
# goes to stderr). A non-zero exit means cargo tree itself failed
# — treat that as a failure rather than as proof of absence.
out=$(cargo tree --manifest-path src-tauri/Cargo.toml --locked \
-i "$crate" --target "$target" 2>/dev/null) || {
echo "cargo tree failed for $crate / $target — cannot verify the suppression"
rc=1
continue
}
if [ -n "$out" ]; then
echo "FAIL: $crate is now compiled for $target — its .cargo/audit.toml suppression is no longer justified (see #310)."
rc=1
else
echo "ok: $crate absent from $target"
fi
checks=$((checks + 1))
done
done
# Canary: a crate known to be present. If this stops being found, the
# loop above is broken and its silence means nothing.
canary=$(cargo tree --manifest-path src-tauri/Cargo.toml --locked \
-i tar --target x86_64-unknown-linux-gnu 2>/dev/null) || true
if [ -z "$canary" ]; then
echo "FAIL: canary 'tar' was not found although it is a known dependency. The guard is not proving anything."
rc=1
else
echo "ok: canary 'tar' found for x86_64-unknown-linux-gnu"
fi
echo "Suppression guard: $checks checks, exit $rc"
exit $rc
- name: cargo test
run: cargo test --manifest-path src-tauri/Cargo.toml --all-targets
# Prebuilt binary. `cargo install --locked cargo-audit` recompiled the
# tool from source on every single run (~4m40s). No continue-on-error:
# a tooling failure should fail the job rather than be swallowed.
- name: Install cargo-audit
uses: https://github.com/taiki-e/install-action@v2
with:
tool: cargo-audit
# Advisories are informational — they can land on unrelated crates and
# would otherwise stall unrelated work — so the step is non-blocking.
# It no longer hides real failures behind `|| true` though. Daily
# RustSec coverage outside Rust PRs lives in audit.yml.
- name: cargo audit
continue-on-error: true
run: cargo audit --file src-tauri/Cargo.lock