Files
psxrecomp/tools/ci/check_generated.sh
T
Alex Vanderveen f180a32ec1 bios: commit the OpenBIOS and SCPH-1001 backends; releases link both
Decision (Alex, 2026-09-30): the recompiled BIOS carries the same risk as
the recompiled game, which titles already commit, so the retail backend is
compiled in rather than built on the player's machine. The retail image is
still never shipped: the runtime needs its data at run time and accepts only
the exact image the backend was built from, so the player supplies their
own dump as before.

- generated/{OpenBIOS,SCPH1001}_{full,dispatch}.c and their .emitter.sha
  stamps are tracked (.gitignore un-ignores exactly those); regenerated with
  the current emitter.
- bios_emitter_fingerprint.sh no longer hashes the ROM image: the profile's
  SHA-256 pin stands for it and the emitter refuses any other image, and CI
  has no dump, so hashing it made every CI configure read "stale".
- runtime.cmake: the staleness check covers every LINKED stem and
  -DPSXRECOMP_BIOS_STALE_FATAL=ON makes a stale stamp fail configure.
- game-release.yml: no CI-side OpenBIOS emission (generate_openbios.sh is
  gone); configure with BIOS_STALE_FATAL and assert both backends linked.
  check_generated.sh asserts the submodule carries both backends tracked
  and no dump is tracked anywhere.
- The player-side module build stays for a known image a build does not
  link (SCPH-5552).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 20:27:41 -04:00

140 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Gate a bundled release on the committed game C being present and clean.
#
# Releases ship a compiled game: CI builds from the generated/ tree the title
# COMMITS, so a checkout missing it (or carrying the wrong marker name) must
# fail here, at the top of the job, not twenty minutes later at link -- and
# never on a player's machine, which is where the old setup-host model put
# every failure of this kind.
#
# Asserts, from the game repo root:
# 1. generated/<boot>_dispatch.c exists, where <boot> is the boot-EXE name
# game.toml declares (the same name check_boot_exe.sh cross-checks), and
# at least one generated/<boot>_full*.c shard sits beside it.
# 2. Those files are tracked by git. An untracked generated/ builds fine on
# the developer's machine and is absent from every CI checkout, which is
# the exact gap between "works for me" and a red release.
# 3. The framework's BIOS backends are present and tracked in the submodule
# (psxrecomp/generated/{OpenBIOS,SCPH1001}_{full,dispatch}.c with their
# .emitter.sha stamps). Decision 2026-09-30: the recompiled BIOS is
# committed and linked like the recompiled game; the player still
# supplies the retail image at run time. runtime.cmake's fingerprint
# check (PSXRECOMP_BIOS_STALE_FATAL in CI) refuses a stale stamp.
# 4. No BIOS dump is tracked (SCPH*.BIN / *.bin under bios/) in the title or
# the submodule.
#
# Usage: check_generated.sh [--root DIR] (default: cwd)
# Runs on bash 3.2+ (macOS runners).
set -euo pipefail
ROOT="${PWD}"
while [[ $# -gt 0 ]]; do
case "$1" in
--root) ROOT="${2:?}"; shift 2 ;;
-h|--help)
sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "error: unknown arg: $1" >&2
exit 2
;;
esac
done
ROOT="$(cd "${ROOT}" && pwd)"
toml_get() { # <section> <key> -- first quoted value of key inside [section]
awk -v sec="$1" -v key="$2" '
/^[[:space:]]*\[/ { in_sec = ($0 ~ ("^[[:space:]]*\\[" sec "\\]")) }
in_sec && $0 ~ ("^[[:space:]]*" key "[[:space:]]*=") {
if (match($0, /"[^"]*"/)) { print substr($0, RSTART + 1, RLENGTH - 2) }
exit
}' "${ROOT}/game.toml"
}
fail() { echo "error: $*" >&2; exit 1; }
[[ -f "${ROOT}/game.toml" ]] || fail "${ROOT}/game.toml missing -- not a game repo root"
boot="$(toml_get prepare_disc boot_exe || true)"
if [[ -z "${boot}" ]]; then
exe="$(toml_get game exe || true)"
boot="${exe##*/}"
fi
if [[ -z "${boot}" && -f "${ROOT}/CMakeLists.txt" ]]; then
boot="$(sed -n 's/.*GEN_MARKER[[:space:]]*"generated\/\(.*\)_dispatch\.c".*/\1/p' \
"${ROOT}/CMakeLists.txt" | head -n1)"
fi
[[ -n "${boot}" ]] || fail "cannot determine the boot-EXE name (game.toml [prepare_disc] boot_exe / [game] exe, or CMakeLists GEN_MARKER)"
gen="${ROOT}/generated"
dispatch="${gen}/${boot}_dispatch.c"
if [[ ! -f "${dispatch}" ]]; then
echo "error: ${dispatch#"${ROOT}"/} is missing." >&2
echo " Bundled releases build from COMMITTED game C. Generate it once from a" >&2
echo " legal disc and commit generated/:" >&2
echo " python3 psxrecomp/psxrecomp_cli.py generate --config game.toml \\" >&2
echo " --project-root . --disc disc/<game>.cue" >&2
echo " git add generated && git commit -m 'Add generated game C'" >&2
if grep -qE '^/?generated/?[[:space:]]*$' "${ROOT}/.gitignore" 2>/dev/null; then
echo " .gitignore still ignores generated/ (the old setup-host rule) -- remove" >&2
echo " that line; see psxrecomp/docs/ci/templates/game.gitignore." >&2
fi
exit 1
fi
shards=0
for f in "${gen}/${boot}_full"*.c; do
[[ -f "${f}" ]] && shards=$((shards + 1))
done
[[ "${shards}" -gt 0 ]] || fail "no generated/${boot}_full*.c beside ${boot}_dispatch.c -- regenerate"
# 2. Tracked, not merely present.
if git -C "${ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
untracked="$(git -C "${ROOT}" ls-files --others --exclude-standard -- "generated/${boot}_dispatch.c" "generated/${boot}_full"*.c 2>/dev/null || true)"
ignored="$(git -C "${ROOT}" ls-files --others --ignored --exclude-standard -- "generated/${boot}_dispatch.c" "generated/${boot}_full"*.c 2>/dev/null || true)"
if [[ -n "${untracked}${ignored}" ]]; then
echo "error: generated game C is present but NOT tracked by git:" >&2
printf '%s\n' ${untracked} ${ignored} | sed 's/^/ /' >&2
echo " A release builds from the checkout, so these files never reach CI." >&2
echo " Remove any /generated/ rule from .gitignore, then: git add generated" >&2
exit 1
fi
fi
# 3. The framework's committed BIOS backends.
fw="${ROOT}/psxrecomp"
if [[ -d "${fw}" ]]; then
for stem in OpenBIOS SCPH1001; do
for f in "generated/${stem}_full.c" "generated/${stem}_dispatch.c" "generated/${stem}.emitter.sha"; do
if [[ ! -f "${fw}/${f}" ]]; then
echo "error: psxrecomp/${f} is missing -- the framework commits its BIOS backends;" >&2
echo " this submodule pin predates that, or the checkout is not --recurse-submodules." >&2
exit 1
fi
if git -C "${fw}" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
! git -C "${fw}" ls-files --error-unmatch -- "${f}" >/dev/null 2>&1; then
echo "error: psxrecomp/${f} exists but is not tracked in the framework submodule." >&2
exit 1
fi
done
done
fi
# 4. No BIOS dump tracked, in the title or the submodule.
for repo in "${ROOT}" "${fw}"; do
[[ -d "${repo}" ]] || continue
if git -C "${repo}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
dumps="$(git -C "${repo}" ls-files -- 'bios/*' '*.BIN' 'SCPH*' 2>/dev/null \
| grep -Ei '\.(bin|rom)$' | grep -v -i 'openbios' || true)"
if [[ -n "${dumps}" ]]; then
echo "error: BIOS image(s) tracked in ${repo#"${ROOT}"}:" >&2
printf '%s\n' "${dumps}" | sed 's/^/ /' >&2
exit 1
fi
fi
done
echo "generated C ok: generated/${boot}_dispatch.c + ${shards} full shard(s) tracked; framework BIOS backends present; no BIOS dump tracked"