Skip to content

docs: provenance, licensing and attribution pass #1325

docs: provenance, licensing and attribution pass

docs: provenance, licensing and attribution pass #1325

Workflow file for this run

name: CI
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
schedule:
# Weekly drift net (v1.5.0 "Bedrock"): catches a regression in a dependency/toolchain/ROM-corpus
# change that landed without a matching code diff to trigger the push/pull_request triggers
# above. Offset from security.yml's own Monday-00:00-UTC cron to spread runner load.
- cron: "0 7 * * 1"
workflow_dispatch:
permissions:
contents: read
# Cancel a superseded run (a new push to the same PR/branch) instead of letting stale, already-
# obsolete runs burn minutes to completion — the single biggest per-PR cost lever besides scoping
# the full battery to tags/main/schedule (below).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# v1.5.0 "Bedrock": detect doc-only changes so the expensive jobs below can skip cleanly on a
# pure-docs PR/push instead of burning minutes proving nothing changed. NOT a strict inversion of
# security.yml's paths-ignore list (caught in PR review) — this is a curated, narrower positive
# list of the specific paths that can affect a Rust build/lint/test/CI outcome, so it stays
# correct even if security.yml's own ignore list grows for unrelated (e.g. asset) reasons.
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
id: filter
with:
filters: |
code:
- "crates/**"
- "tests/**"
- "scripts/**"
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain.toml"
- ".cargo/**"
- ".github/workflows/**"
- ".github/actions/**"
# v1.5.0 "Bedrock": one place that decides "light" (PR / feature-branch push: fast gate only) vs
# "full" (push to main, a release tag, the weekly cron, or a manual dispatch: the complete
# battery) — mirrors RustyNES's own `changes`/`setup` split. Always-scheduled/dispatched/tag/main
# runs are treated as "full" even on a doc-only diff (`always_full`), since a manual/scheduled
# run's whole point is a from-scratch verification, not an incremental one.
setup:
needs: changes
runs-on: ubuntu-latest
outputs:
mode: ${{ steps.mode.outputs.mode }}
steps:
- id: mode
run: |
always_full=false
if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
always_full=true
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then
always_full=true
elif [[ "${{ github.ref }}" == "refs/heads/main" && "${{ github.event_name }}" == "push" ]]; then
always_full=true
fi
if [[ "$always_full" == "true" ]]; then
echo "mode=full" >> "$GITHUB_OUTPUT"
elif [[ "${{ needs.changes.outputs.code }}" == "true" ]]; then
echo "mode=light" >> "$GITHUB_OUTPUT"
else
echo "mode=skip" >> "$GITHUB_OUTPUT"
fi
# Fast gate: every PR event and every push to `main` with code changes. Formatting + lint
# (Linux only) — the 3-OS matrix, the doc build's cross-platform legs, and the no_std/bench gates
# stay reserved for `full` mode (below) so PR iteration stays quick. The tradeoff (a broken PR is
# only caught by the FULL battery at merge-to-main/tag, not on every single push) is deliberate;
# `test-light` below closes the specific gap that tradeoff used to leave (PR-time `cargo test`
# never running at all, relying solely on CONTRIBUTING.md's manual pre-push checklist).
lint:
needs: setup
if: needs.setup.outputs.mode != 'skip'
env:
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
# fmt/clippy only ever check the HOST target here (no `--target` is ever passed to either
# command in this job) — installing the wasm32/thumbv7em cross targets bought nothing but
# setup time on every single PR push. Only `no_std` (which actually cross-compiles) needs
# thumbv7em-none-eabihf; nothing in ci.yml builds for wasm32 (that's `pages.yml`'s job).
- uses: ./.github/actions/rust-setup
with:
components: rustfmt, clippy
linux-frontend-deps: "true"
- run: cargo fmt --all --check
# The reviewer's comment-selection filter decides which PR comments the bot DELETES, and
# it has been wrong twice in ways nothing observed (it posted its review fine both times).
# This check is offline — fixtures and `jq`, no network, no `gh`, no self-hosted runner —
# so it runs here on every PR rather than only where `agy` is installed.
- run: bash scripts/agy-review-selftest.sh
# `--exclude rustysnes-android` (`v1.15.0 "Sideload"`): that crate's `ndk-sys` dependency
# hard-fails (`compile_error!`) on every non-Android target, unconditionally -- it's not
# feature-gated, so no `--features`/`--no-default-features` combination avoids it. Its own
# cross-compiled CI coverage is `android.yml`'s job (a later rung), not this host-target
# workspace gate.
- run: cargo clippy --workspace --exclude rustysnes-android --all-targets -- -D warnings
# Explicit flags-off byte-identical gate (`v0.8.0` T-81-004): `default` is currently
# exactly `["wasm-winit", "help-tui"]`, so this is redundant with the line above TODAY —
# its value is as a named regression guard. If a future change ever folds `debug-hooks`/
# `scripting`/`cheats`/`emu-thread` into `default` without updating this line too, this still
# locks a true flags-off build/lint as its own explicit, protected CI step, independent of
# whatever `default` becomes.
- run: cargo clippy --workspace --exclude rustysnes-android --all-targets --no-default-features --features wasm-winit,help-tui -- -D warnings
# Per-feature-combo clippy (`v0.8.0` T-81-004, extended `v0.9.0` T-82-004) — each Phase 8
# `rustysnes-frontend` flag individually, then combined; NEVER `--all-features` (the
# workspace-wide rule — `wasm-winit`/`wasm-canvas` are mutually exclusive, so an
# all-features build doesn't even make sense).
- run: cargo clippy -p rustysnes-frontend --all-targets --features debug-hooks -- -D warnings
- run: cargo clippy -p rustysnes-frontend --all-targets --features scripting -- -D warnings
- run: cargo clippy -p rustysnes-frontend --all-targets --features cheats -- -D warnings
- run: cargo clippy -p rustysnes-frontend --all-targets --features netplay -- -D warnings
- run: cargo clippy -p rustysnes-frontend --all-targets --features retroachievements -- -D warnings
# `emu-thread` itself was never actually gated here despite being referenced in the comment
# above (found in review, post-`v1.3.0`) — added now, plus its one functionally meaningful
# combo: `netplay` (post-`v1.3.0`, `EmuControl::netplay_paused` + `NetplayState::drive` now
# runs under `emu-thread` too, previously silently unreachable there). `emu-thread,scripting`
# is NOT added — see `full`'s own comment below for why that combo doesn't compile.
- run: cargo clippy -p rustysnes-frontend --all-targets --features emu-thread -- -D warnings
- run: cargo clippy -p rustysnes-frontend --all-targets --features emu-thread,netplay -- -D warnings
# `full` (`v1.0.0`, `cargo full-build`/`full-run`) IS this same combo (plus the no-op
# `hd-pack` placeholder) — testing it directly instead of re-listing the flags keeps this
# step and `full`'s own Cargo.toml definition from silently drifting apart.
- run: cargo clippy -p rustysnes-frontend --all-targets --features full -- -D warnings
# `rustysnes-test-harness` (T-CA-10): its ROM-oracle test targets are gated behind
# `#![cfg(feature = "test-roms")]`, so the `--workspace` line above — which never passes
# `test-roms` — compiles them to nothing and never lints them. Lint them explicitly. The harness
# always renders through `rustysnes-core`'s default (per-dot) compositor — it has no
# compositor feature of its own — so one `--features test-roms` step covers it. Without this the
# whole accuracysnes/undisbeliever oracle suite is invisible to clippy and drifts (it had — a
# backlog of pedantic findings that had never fired). NEVER `--all-features` (the workspace rule).
- run: cargo clippy -p rustysnes-test-harness --all-targets --features test-roms -- -D warnings
# `rustysnes-libretro` (`v1.2.0`): already covered by the `--workspace` clippy line above (a
# regular workspace member, no non-default features to combo over), but clippy doesn't
# necessarily exercise the FFI-crate-specific bit that matters most for a `cdylib`/
# `staticlib`: does it actually LINK. A build-only step (not `cargo test` -- this crate has
# no tests of its own; its logic lives in and is tested by `rustysnes-core`) is cheap and
# catches a broken libretro C-ABI export/link before it reaches a release artifact.
- run: cargo build -p rustysnes-libretro
# `fuzz/` is a SEPARATE workspace (its manifest declares an empty `[workspace]`, the
# cargo-fuzz convention) so that cargo-fuzz's nightly requirement never leaks into an
# ordinary `cargo check`. The cost of that isolation is that every `--workspace` command
# above is blind to it, and a fuzz target calling an API that moved would rot silently until
# someone next ran a campaign — which is scheduled weekly, not per-PR.
#
# This is a plain `cargo build`, deliberately, NOT `cargo fuzz build`: it needs no nightly
# toolchain and no sanitizer, and it answers the only question a per-PR gate should ask —
# do the targets still compile against the code this PR changes? The actual fuzzing is
# `security.yml`'s weekly job, because per-commit fuzzing finds little; long campaigns do.
#
# Worth stating because the sibling RustyNES project's `fuzz/README.md` claims exactly this
# gate and it does not actually exist there: its `fuzz/` is referenced by no workflow at all.
- run: cargo build --manifest-path fuzz/Cargo.toml
# Cheap locally (~4s) so it belongs on every PR, not just tag pushes -- catches broken
# intra-doc links and rustdoc-specific warnings clippy's own lints don't cover.
# `--exclude rustysnes-android`: see the clippy step above -- `cargo doc` still compiles the
# crate, so it hits the same `ndk-sys` host-target failure.
- run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --exclude rustysnes-android --no-deps
# v1.5.0 "Bedrock": the actual gap this release closes. Previously `cargo test --workspace` ran
# ONLY on a tagged release (`full-test` below) — every PR/push-to-main merged on `lint`'s
# fmt+clippy alone, trusting CONTRIBUTING.md's manual pre-push "Quality gate" checklist to have
# been run by hand. This job makes that check programmatic without the cost of `full-test`'s 3-OS
# release-mode matrix: one fast, cached, debug-mode, Linux-only `cargo test` pass, every PR/push.
test-light:
needs: setup
if: needs.setup.outputs.mode != 'skip'
env:
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: ./.github/actions/rust-setup
with:
cache-key-suffix: test-light
linux-frontend-deps: "true"
# `--exclude rustysnes-android`: see `lint`'s own clippy step comment above.
- run: cargo test --workspace --exclude rustysnes-android
# Full gate: release-tag pushes, a push to `main`, the weekly cron, or a manual dispatch — see
# `setup` above. The complete verification battery — the 3-OS matrix, both test invocations, and
# the doc-warnings gate.
full-test:
needs: setup
if: needs.setup.outputs.mode == 'full'
env:
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
# Same rationale as `lint`: neither fmt nor clippy nor `cargo test` cross-compiles here, so
# no extra targets are installed.
- uses: ./.github/actions/rust-setup
with:
components: rustfmt, clippy
cache-key-suffix: full-test-${{ matrix.os }}
linux-frontend-deps: "true"
# Formatting is platform-independent (pure source-text parsing) — one leg is enough.
- run: cargo fmt --all --check
if: runner.os == 'Linux'
# `--exclude rustysnes-android`: see `lint`'s own clippy step comment above.
- run: cargo clippy --workspace --exclude rustysnes-android --all-targets -- -D warnings
- run: cargo test --workspace --exclude rustysnes-android
- run: cargo test --workspace --exclude rustysnes-android --features test-roms
# Exhaustive behavioral coverage (not just clippy) of every Phase 8 flag together, ahead of
# a tagged release (`v0.8.0` T-81-004, extended `v0.9.0` T-82-004). Linux-only, matching
# `lint`'s per-combo clippy: `scripting` vendors and compiles Lua 5.4 via `mlua`'s C source,
# and `retroachievements` vendors and compiles `rcheevos` via `cc` -- both real
# cross-platform build surface `lint` never exercises (Linux only, host target) — verifying
# that specifically on macOS/Windows here too is out of this ticket's scope (it's a genuine
# question of its own, not "is the byte-identical-off gate wired up").
- run: cargo test -p rustysnes-frontend --features full
if: runner.os == 'Linux'
# `emu-thread` was clippy-checked in `lint` (compiled, never run) but its own unit tests
# (`EmuControl`/`PresentBuffer`, post-`v1.3.0`) never actually executed anywhere in CI until
# now (found in review). `netplay` is its one functionally meaningful combo (see `lint`'s
# own comment).
- run: cargo test -p rustysnes-frontend --features emu-thread,netplay
if: runner.os == 'Linux'
- run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --exclude rustysnes-android --no-deps
if: runner.os == 'Linux'
# Full-gate cadence only (see `setup` above) — cross-compiling the whole chip stack is cheap but
# doesn't need to happen on every single PR push.
#
# `v1.14.0 "Foundry"`: expanded from a single `rustysnes-core --no-default-features` build into
# a per-crate matrix. The old single job only proved the no_std posture TRANSITIVELY (every
# chip crate is already `default-features = false` in `rustysnes-core`'s own `Cargo.toml`, and
# `#![no_std]` is unconditional -- not feature-gated -- in each chip crate's `lib.rs`), never as
# its own standalone build target. This matrix builds each chip crate directly, so a future
# accidental `std`-only dependency added to just one of them fails on ITS OWN row instead of
# only surfacing (if at all) through `rustysnes-core`'s aggregate build.
no_std:
needs: setup
if: needs.setup.outputs.mode == 'full'
env:
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
runs-on: ubuntu-latest
strategy:
matrix:
crate:
- rustysnes-cpu
- rustysnes-ppu
- rustysnes-apu
- rustysnes-cart
- rustysnes-core
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: ./.github/actions/rust-setup
with:
targets: thumbv7em-none-eabihf
cache-key-suffix: no_std-${{ matrix.crate }}
- run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabihf --no-default-features
# Full-gate cadence only (`v1.0.0`): a release-mode Criterion build + run is too costly for every
# PR push, and the frame-time gate is an absolute ceiling that only needs to hold at
# release/main/cron cadence, not on every iteration. Ported from RustyNES's own `bench` job
# (`scripts/bench_regression_check.sh` + `docs/performance.md`).
bench:
name: frame-time regression gate
needs: setup
if: needs.setup.outputs.mode == 'full'
env:
CARGO_NET_RETRY: "10"
CARGO_HTTP_MULTIPLEXING: "false"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
# The `rustysnes-core` `headless_frame` bench is chip-stack-only (no wgpu/winit/egui), so
# no Linux frontend system deps are needed here (unlike `lint`/`full-test`).
- uses: ./.github/actions/rust-setup
with:
cache-key-suffix: bench
- run: ./scripts/bench_regression_check.sh
# AccuracySNES reproducibility gate. The cart's `.sfc` is committed so contributors can run the
# battery without an assembler; this job proves the committed image is exactly what the source
# produces, so the binary can never silently drift from `gen/`.
accuracysnes:
needs: setup
if: needs.setup.outputs.mode != 'skip'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: ./.github/actions/rust-setup
with:
cache-key-suffix: accuracysnes
- name: Install cc65 (ca65/ld65)
run: |
dpkg -s cc65 >/dev/null 2>&1 || {
sudo apt-get update
sudo apt-get install -y cc65
}
- name: Rebuild the cartridge from source
run: cargo run -p accuracysnes-gen
- name: Assert the committed artifacts are byte-identical
run: |
if ! git diff --exit-code -- tests/roms/AccuracySNES; then
echo "::error::AccuracySNES build output differs from the committed artifacts."
echo "::error::Run 'cargo run -p accuracysnes-gen' and commit the result."
exit 1
fi
- name: Run the battery
run: |
cargo test -p rustysnes-test-harness --features test-roms \
--test accuracysnes -- --nocapture
# `v1.26.0`: the golden vectors were executed by NO pull-request job.
#
# Only `lint`, `test-light`, and this job run on a PR. `test-light` does not pass
# `--features test-roms`, and the step above scopes itself to `--test accuracysnes` — so the
# 53 rendered-scene goldens and every framebuffer golden were reached only by `full-test`,
# which is `mode == 'full'` (main, tags, the weekly cron). A PR could shift PPU behaviour,
# move a golden, and hear nothing about it until merge-to-main.
#
# That is the same structure that produced the coprocessor-golden staleness this project
# already hit once (goldens left stale by post-bless PPU accuracy fixes): a golden vector
# nothing executes only accumulates drift. Here it was not that nothing executed them ever,
# but that nothing executed them when it could still be cheap to fix.
#
# Cost is bounded and known: the three suites with a committed corpus (~5 min total in a
# debug build — scenes ~84s, undisbeliever ~158s, rainwarrior ~54s). The `*_oncart`
# coprocessor suites need gitignored commercial dumps and firmware, so they self-skip here
# for free; they are listed anyway so the intent is explicit rather than inferred from an
# omission, and so they start gating the moment a runner ever has that corpus.
#
# `--no-fail-fast` is load-bearing, not decoration. Cargo's default stops after the FIRST
# failing test binary, and it stops across binaries, not just within one — verified by
# injecting a failure into `save_state_backward_compat` and watching
# `save_state_determinism` never run at all. With ten suites here that would mean one broken
# golden hides the status of the other nine, so an author fixes one, pushes, and only then
# discovers the next: a serialized debug loop over a ~6-minute job. With the flag, every
# suite reports and the step still fails.
- name: Run the golden vectors
run: |
cargo test -p rustysnes-test-harness --features test-roms --no-fail-fast \
--test accuracysnes_scenes \
--test undisbeliever_golden \
--test rainwarrior_golden \
--test dsp1_oncart \
--test sa1_oncart \
--test superfx_oncart \
--test dsp3_st011_oncart \
--test srtc_st018_oncart \
--test save_state_determinism \
--test save_state_backward_compat
# v1.5.0 "Bedrock": the one stable required-check name branch protection points at
# (`docs/adr/0011`). Individual jobs above are conditionally skipped depending on `setup`'s mode,
# which makes them unsuitable to require directly (a skipped required check blocks merging
# forever). This job succeeds iff every job it depends on either succeeded or was cleanly
# skipped, and fails if any of them failed or were cancelled.
ci-success:
if: always()
needs: [changes, setup, lint, test-light, full-test, no_std, bench, accuracysnes]
runs-on: ubuntu-latest
steps:
- name: Check all required jobs
run: |
echo '${{ toJSON(needs) }}'
for result in $(echo '${{ toJSON(needs.*.result) }}' | jq -r '.[]'); do
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
echo "::error::A required job did not succeed (result: $result)"
exit 1
fi
done