#!/usr/bin/env bash # The engine gate, as a script instead of as the integrator's memory. # # Why this exists: for a whole day the gate reported 59/59 while the two tests that read # the save corpus were SKIPPING, because SOTS_SAVES_DIR was never set on CT111. The corpus # had grown from 22 to 43 saves and the coverage ratchet had broken exactly as rule 27 says # it should -- and a writer defect had appeared on 12 of them -- and the gate could not see # either. A green verdict on a harness that is comparing nothing (rule 1), delivered by the # person whose job was to notice it. # # Each check is its own command and its own exit code. Nothing here is &&-chained across # checks, because a chain that short-circuits reports a check that never ran (rule 22's # cousin, paid for once already). # # Usage: tools/gate.sh [--fresh] --fresh: rm -rf the remote build dirs first (rule 24) set -u # One gate at a time. Lane DW saw up to FOUR `gate.sh --fresh` runs concurrently, all rsyncing # into the same remote tree and rm -rf'ing its build dirs -- rule 24's shape with no lock, and a # way to report a build that is half someone else's. Every lane runs from this host, so a local # flock is sufficient. Waits up to 30 min, then fails loudly rather than running unlocked. if [ -z "${SOTS_GATE_LOCKED:-}" ]; then exec env SOTS_GATE_LOCKED=1 flock -w 1800 /tmp/sots-gate.lock "$0" "$@" \ || { echo "GATE FAIL could not acquire /tmp/sots-gate.lock within 30 min"; exit 2; } fi ENGINE="${ENGINE:-$HOME/sots-engine}" RE="${RE:-$HOME/sots-re}" CT=111 REMOTE_TREE=/srv/re-lab/build/sots-engine REMOTE_CORPUS=/srv/re-lab/saves-corpus REMOTE_DATA=/srv/re-lab/gob-extract # the extracted asset tree; game_design_census needs it FRESH=0 [ "${1:-}" = "--fresh" ] && FRESH=1 ct() { ssh spicy "pct exec $CT -- bash -lc '$*'"; } status=0 report() { # name rc if [ "$2" -eq 0 ]; then echo "GATE ok $1"; else echo "GATE FAIL $1 (exit $2)"; status=1; fi } echo "== gate: $ENGINE @ $(git -C "$ENGINE" rev-parse --short HEAD), corpus $(ls "$RE"/verify/results/saves/*.sav | wc -l) saves, fresh=$FRESH" # 1. clean-room, host ( cd "$ENGINE" && bash tools/clean_room_check.sh ); report clean-room $? # 2. shim config structure, host ( cd "$ENGINE" && uv run --quiet python3 tools/check_shim_configs.py ); report shim-configs $? # 3. sync the tree -- BOTH halves of rule 24: exclude local build dirs, and clear the remote ones rsync -a --delete --exclude '.git' --exclude 'build*' -e ssh "$ENGINE"/ spicy:/tmp/eng-gate/; report sync-to-host $? if [ $FRESH -eq 1 ]; then ct "rm -rf $REMOTE_TREE/build-host $REMOTE_TREE/build-shim"; report clear-remote-builds $?; fi ssh spicy "tar -C /tmp/eng-gate -cf - . | pct exec $CT -- tar -C $REMOTE_TREE -xf -"; report sync-to-ct $? # 4. sync the corpus -- the tests are only a gate if they run against it rsync -a --delete --include='*.sav' --exclude='*' -e ssh "$RE"/verify/results/saves/ spicy:/tmp/sots-corpus/; report corpus-to-host $? ssh spicy "pct exec $CT -- mkdir -p $REMOTE_CORPUS && tar -C /tmp/sots-corpus -cf - . | pct exec $CT -- tar -C $REMOTE_CORPUS -xf -"; report corpus-to-ct $? NCORPUS=$(ct "ls $REMOTE_CORPUS | grep -c .sav") echo "GATE info corpus on CT111: $NCORPUS saves" # 5. host build + tests, WITH the corpus AND the extracted data tree # # `cmd | tail -1` returns TAIL's status, not the command's -- so for a day this # script printed "GATE ok host-ctest" over a run with two FAILING tests, which is # rule 1's failure wearing this script's own clothes. Every step below captures # the real exit code first and only then trims the output. ct "cd $REMOTE_TREE && cmake -S . -B build-host -DCMAKE_BUILD_TYPE=Release >/tmp/gate-cfg.log 2>&1 && cmake --build build-host -j8 >/tmp/gate-build.log 2>&1; rc=\$?; tail -1 /tmp/gate-build.log; exit \$rc"; report host-build $? ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest --output-on-failure >/tmp/gate-ctest.log 2>&1; rc=\$?; tail -15 /tmp/gate-ctest.log; exit \$rc"; report host-ctest $? # and prove the corpus tests RAN rather than skipped. The check is scoped to the # tests gated on the CORPUS (they name SOTS_SAVES_DIR when they skip); every other # skip is listed below it by name, because a skip nobody reads is how this started. SKIPPED=$(ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest -V 2>&1 | grep -c SOTS_SAVES_DIR") if [ "${SKIPPED:-1}" -ne 0 ]; then echo "GATE FAIL corpus tests skipped ($SKIPPED) -- the gate is hollow"; status=1; else echo "GATE ok corpus tests ran (0 skipped)"; fi ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest -V 2>&1 | grep skipped" | sed 's/^/GATE info skip: /' # 6. shim cross-build -- same exit-code discipline as step 5. The `ls` at the end # is a second, independent check (the artefact exists), not the build's verdict: # without --fresh a DLL from an earlier run would satisfy it on its own. ct "cd $REMOTE_TREE && cmake -S . -B build-shim -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw-i686.cmake -DCMAKE_BUILD_TYPE=Release >/tmp/gate-shim-cfg.log 2>&1; rc=\$?; cmake --build build-shim -j8 >/tmp/gate-shim.log 2>&1 || rc=1; tail -1 /tmp/gate-shim.log; ls -la build-shim/binkw32.dll || rc=1; exit \$rc"; report shim-cross-build $? echo "== gate: $([ $status -eq 0 ] && echo GREEN || echo RED)" exit $status