From 3011ee498e459d95ad410cbc0676dd88d51912fe Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 19:54:48 +0100 Subject: [PATCH] feat: record the build file behind a held pin pin_frozen was prose, and no tool read the build file it cites, so a platform building another revision left the profile holding one nobody ships. pin_build records the platform, its repository, the revision read, the file, the variable and its value. check_freshness reads the file again where the platform's YAML now stands and reports STALE when the value moved. 36 of the 46 held profiles carry it, each value read from the build file at the platform's scraped revision. The other ten name no build file and are counted on one row: six RetroBat binary packages and four holds not tied to a platform build. --- emulators/advanced_drastic.yml | 7 ++ emulators/advmame-1.4.yml | 7 ++ emulators/alephone.yml | 7 ++ emulators/azahar-sdl.yml | 7 ++ emulators/bennugd-monolithic.yml | 7 ++ emulators/bermuda.yml | 7 ++ emulators/ccleste.yml | 7 ++ emulators/chocolate-doom.yml | 7 ++ emulators/daedalusx64.yml | 7 ++ emulators/dsperate.yml | 7 ++ emulators/duckstation-legacy.yml | 7 ++ emulators/eightyone.yml | 7 ++ emulators/exel-hence.yml | 7 ++ emulators/fbalpha2019.yml | 7 ++ emulators/fheroes2.yml | 7 ++ emulators/flycast2021.yml | 7 ++ emulators/flycast2021le.yml | 7 ++ emulators/gsplus-legacy.yml | 7 ++ emulators/hode.yml | 7 ++ emulators/love-0.10.2.yml | 7 ++ emulators/lzdoom.yml | 13 +++ emulators/mame0258.yml | 7 ++ emulators/mame0278.yml | 7 ++ emulators/nanoboyadvance.yml | 7 ++ emulators/piemu.yml | 7 ++ emulators/ppsspp-1.5.4.yml | 7 ++ emulators/rb5000.yml | 7 ++ emulators/reicast.yml | 7 ++ emulators/rigelengine.yml | 7 ++ emulators/supermariowar.yml | 7 ++ emulators/supermodel-legacy.yml | 7 ++ emulators/supersnes9x.yml | 7 ++ emulators/supertux.yml | 7 ++ emulators/supertuxkart.yml | 7 ++ emulators/touchhle.yml | 7 ++ emulators/vice-standalone.yml | 7 ++ schemas/emulator.schema.json | 50 +++++++++++ scripts/check_freshness.py | 112 +++++++++++++++++++++++++ tests/test_doc_pages.py | 2 +- tests/test_pin_build.py | 138 +++++++++++++++++++++++++++++++ wiki/profiling.md | 3 +- wiki/testing-guide.md | 2 +- wiki/tools.md | 10 ++- 43 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 tests/test_pin_build.py diff --git a/emulators/advanced_drastic.yml b/emulators/advanced_drastic.yml index 620b41e0a..019aa9ffa 100644 --- a/emulators/advanced_drastic.yml +++ b/emulators/advanced_drastic.yml @@ -7,6 +7,13 @@ author: "trngaje (hooks), Exophase (DraStic)" profiled_date: "2026-10-22" source_commit: "eaf41ab9bc8adcc15c010bf9517bc94c38e7bffd" pin_frozen: "KNULLI, the only platform that ships it, builds this revision (KNULLI package/emulators/advanced_drastic/advanced_drastic.mk:7 at tag 20260511). Later commits are not part of that build." +pin_build: + - platform: knulli + repo: "https://github.com/knulli-cfw/knulli-linux" + revision: "20260511" + path: "package/emulators/advanced_drastic/advanced_drastic.mk" + variable: ADVANCED_DRASTIC_VERSION + value: "eaf41ab9bc8adcc15c010bf9517bc94c38e7bffd" core_version: "r2.5.2.0" version_basis: kind: file diff --git a/emulators/advmame-1.4.yml b/emulators/advmame-1.4.yml index 490b994d7..db2d0f0d5 100644 --- a/emulators/advmame-1.4.yml +++ b/emulators/advmame-1.4.yml @@ -6,6 +6,13 @@ upstream: "https://www.mamedev.org" profiled_date: "2026-10-20" source_commit: "237ca5c898e51e1b7a70ce52bccf008aa722e924" pin_frozen: "RetroPie builds the advancemame-1.4 release tarball, whose src/ tree is this tag. Later commits are not part of that build." +pin_build: + - platform: retropie + repo: "https://github.com/RetroPie/RetroPie-Setup" + revision: "46128cd252cdecae852a4d0bafa2aaf9d03ab563" + path: "scriptmodules/emulators/advmame-1.4.sh" + variable: rp_module_repo + value: "file $__archive_url/advancemame-1.4.tar.gz" core_version: "advancemame-1.4" version_basis: kind: tag diff --git a/emulators/alephone.yml b/emulators/alephone.yml index d0dfaf1a6..6607f45e9 100644 --- a/emulators/alephone.yml +++ b/emulators/alephone.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/Aleph-One-Marathon/alephone" profiled_date: "2026-10-20" source_commit: "07a9c36aff403d143a0608815f725631a01e382c" pin_frozen: "RetroPie, the only platform that ships it, builds the release-20250829 tag at this commit (scriptmodules/ports/alephone.sh, _get_branch_alephone). Debian 10 and older get release-20250302." +pin_build: + - platform: retropie + repo: "https://github.com/RetroPie/RetroPie-Setup" + revision: "46128cd252cdecae852a4d0bafa2aaf9d03ab563" + path: "scriptmodules/ports/alephone.sh" + variable: version + value: "20250829" core_version: "release-20250829" version_basis: kind: tag diff --git a/emulators/azahar-sdl.yml b/emulators/azahar-sdl.yml index 126f775b3..0673125c2 100644 --- a/emulators/azahar-sdl.yml +++ b/emulators/azahar-sdl.yml @@ -5,6 +5,13 @@ source: "https://github.com/johndoe6345789/azahar_libretro" profiled_date: "2026-10-22" source_commit: "c8d39853c1ebacc5657e51d601713a09404ca8a7" pin_frozen: "KNULLI, the only platform that ships it, builds this revision (KNULLI package/emulators/azahar/azahar.mk:7 at tag 20260511). Later commits are not part of that build." +pin_build: + - platform: knulli + repo: "https://github.com/knulli-cfw/knulli-linux" + revision: "20260511" + path: "package/emulators/azahar/azahar.mk" + variable: AZAHAR_VERSION + value: "c8d39853c1ebacc5657e51d601713a09404ca8a7" core_version: "2122-rc1-178-gc8d3985" version_basis: kind: tag diff --git a/emulators/bennugd-monolithic.yml b/emulators/bennugd-monolithic.yml index 6f4e2a688..a3c6f490b 100644 --- a/emulators/bennugd-monolithic.yml +++ b/emulators/bennugd-monolithic.yml @@ -5,6 +5,13 @@ source: "https://github.com/christianhaitian/bennugd-monolithic" profiled_date: "2026-10-22" source_commit: "60ee3389efcf9b402d66035e87f33d17d70cbd83" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/bennugd-monolithic/package.mk:5 at v4.8). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/bennugd-monolithic/package.mk" + variable: PKG_VERSION + value: "60ee3389efcf9b402d66035e87f33d17d70cbd83" core_version: "1.0.0" version_basis: kind: file diff --git a/emulators/bermuda.yml b/emulators/bermuda.yml index 130dc2538..b2354f468 100644 --- a/emulators/bermuda.yml +++ b/emulators/bermuda.yml @@ -5,6 +5,13 @@ source: "https://github.com/cyxx/bermuda" profiled_date: "2026-10-22" source_commit: "cf6bdb68d53a1618967da565a44c931b15daf791" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/bermuda/package.mk:5 at v4.8). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/bermuda/package.mk" + variable: PKG_VERSION + value: "cf6bdb68d53a1618967da565a44c931b15daf791" core_version: "0.1.7" version_basis: kind: file diff --git a/emulators/ccleste.yml b/emulators/ccleste.yml index d2848132d..351257b8f 100644 --- a/emulators/ccleste.yml +++ b/emulators/ccleste.yml @@ -5,6 +5,13 @@ source: "https://github.com/lemon32767/ccleste" profiled_date: "2026-10-22" source_commit: "261d96f15af430b8111abc7a5250229246654f52" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/ccleste/package.mk:5 at v4.8). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/ccleste/package.mk" + variable: PKG_VERSION + value: "261d96f15af430b8111abc7a5250229246654f52" core_version: "v1.4.0" version_basis: kind: tag diff --git a/emulators/chocolate-doom.yml b/emulators/chocolate-doom.yml index b3f416c2e..5fffc5fad 100644 --- a/emulators/chocolate-doom.yml +++ b/emulators/chocolate-doom.yml @@ -5,6 +5,13 @@ source: "https://github.com/chocolate-doom/chocolate-doom" profiled_date: "2026-10-22" source_commit: "72d9e9318c4ccc61dab29bb9cc30bc8a9a93266d" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/chocolate-doom/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/chocolate-doom/package.mk" + variable: PKG_VERSION + value: "72d9e9318c4ccc61dab29bb9cc30bc8a9a93266d" core_version: "3.0.0" version_basis: kind: file diff --git a/emulators/daedalusx64.yml b/emulators/daedalusx64.yml index 526976ff1..9fa26a8ae 100644 --- a/emulators/daedalusx64.yml +++ b/emulators/daedalusx64.yml @@ -5,6 +5,13 @@ source: "https://github.com/DaedalusX64/daedalus" profiled_date: "2026-10-17" source_commit: "f17e9ed86f3806fadeb69abd29c9526ab2d4bd1b" pin_frozen: "ROCKNIX 20261001 builds this revision. Later upstream commits are not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/standalone/daedalusx64-sa/package.mk" + variable: PKG_VERSION + value: "f17e9ed86f3806fadeb69abd29c9526ab2d4bd1b" core_version: "1.1.8-837-gf17e9ed" version_basis: kind: tag diff --git a/emulators/dsperate.yml b/emulators/dsperate.yml index 9058a2dfa..f83d6f795 100644 --- a/emulators/dsperate.yml +++ b/emulators/dsperate.yml @@ -5,6 +5,13 @@ source: "https://github.com/beebono/DSperate" profiled_date: "2026-10-17" source_commit: "81b73832c2e68e2b10cb5850f0a6d393ffc6fbb7" pin_frozen: "ROCKNIX 20261001 ships the v2.0.1 release binaries, built by CI from the v2.0.1 tag at this commit." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/standalone/dsperate-sa/package.mk" + variable: PKG_VERSION + value: "2.0.1" core_version: "v2.0.1" version_basis: kind: tag diff --git a/emulators/duckstation-legacy.yml b/emulators/duckstation-legacy.yml index 35e5bf244..6c346f7f4 100644 --- a/emulators/duckstation-legacy.yml +++ b/emulators/duckstation-legacy.yml @@ -5,6 +5,13 @@ source: "https://github.com/stenzek/duckstation" profiled_date: "2026-10-22" source_commit: "56ac3d6c324ec4520dac433b071ecab6227fb459" pin_frozen: "Batocera builds this revision of 4 August 2023 as duckstation-legacy, its package marked not to be bumped, and KNULLI ships the same package." +pin_build: + - platform: batocera + repo: "https://github.com/batocera-linux/batocera.linux" + revision: "batocera-43.1" + path: "package/batocera/emulators/duckstation-legacy/duckstation-legacy.mk" + variable: DUCKSTATION_LEGACY_VERSION + value: "56ac3d6c324ec4520dac433b071ecab6227fb459" core_version: "0.1-5612-g56ac3d6" version_basis: kind: tag diff --git a/emulators/eightyone.yml b/emulators/eightyone.yml index bd2c20de4..33b94a5e9 100644 --- a/emulators/eightyone.yml +++ b/emulators/eightyone.yml @@ -11,6 +11,13 @@ version_basis: tag: "v1.2" distance: 0 pin_frozen: "Recalbox, the only platform that ships this core, builds its tag v1.2 (Recalbox package/libretro-eightyone/libretro-eightyone.mk:8 at tag 10.1.1)." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/libretro-eightyone/libretro-eightyone.mk" + variable: LIBRETRO_EIGHTYONE_VERSION + value: "v1.2" display_name: "Sinclair - ZX81 / ZX Spectrum (EightyOne, Recalbox)" cores: ["eightyone"] built_by: [recalbox] diff --git a/emulators/exel-hence.yml b/emulators/exel-hence.yml index 84da03588..0539428f6 100644 --- a/emulators/exel-hence.yml +++ b/emulators/exel-hence.yml @@ -11,6 +11,13 @@ version_basis: tag: "v0.9" distance: 0 pin_frozen: "Recalbox, the only platform that ships this core, builds its tag v0.9 (Recalbox package/libretro-exel-hence/libretro-exel-hence.mk:9 at tag 10.1.1)." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/libretro-exel-hence/libretro-exel-hence.mk" + variable: LIBRETRO_EXEL_HENCE_VERSION + value: "v0.9" display_name: "Exelvision - EXL 100 (Exel-Hence)" cores: - "exel-hence" diff --git a/emulators/fbalpha2019.yml b/emulators/fbalpha2019.yml index dc2100223..51d16bf37 100644 --- a/emulators/fbalpha2019.yml +++ b/emulators/fbalpha2019.yml @@ -7,6 +7,13 @@ profiled_date: "2026-10-17" source_commit: "0581797db6fdffd826086b053ced4b6b29bb6678" upstream_commit: "416948158d88ba17f87813419ddc3c1209ef68c0" pin_frozen: "ROCKNIX 20261001 builds this revision as fbalpha2019. Later commits on libretro/fbalpha are not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/libretro/fbalpha2019-lr/package.mk" + variable: PKG_VERSION + value: "0581797db6fdffd826086b053ced4b6b29bb6678" core_version: "v0.2.97.44" version_basis: kind: file diff --git a/emulators/fheroes2.yml b/emulators/fheroes2.yml index 75f787a71..da3a3ff72 100644 --- a/emulators/fheroes2.yml +++ b/emulators/fheroes2.yml @@ -5,6 +5,13 @@ source: "https://github.com/ihhub/fheroes2" profiled_date: "2026-10-22" source_commit: "35ca4c915be00098547d5050412a325dd55cfd7f" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/fheroes2/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/fheroes2/package.mk" + variable: PKG_VERSION + value: "35ca4c915be00098547d5050412a325dd55cfd7f" core_version: "1.0.11" version_basis: kind: file diff --git a/emulators/flycast2021.yml b/emulators/flycast2021.yml index 7f6257b87..33b357aad 100644 --- a/emulators/flycast2021.yml +++ b/emulators/flycast2021.yml @@ -7,6 +7,13 @@ profiled_date: "2026-10-17" source_commit: "603814c9f73b773c455d9a497f389d2f93a257fd" upstream_commit: "4c293f306bc16a265c2d768af5d0cea138426054" pin_frozen: "ROCKNIX 20261001 builds this revision. No branch of metallic77/flycast holds it any more: master was put back on the libretro merge base 4c293f30, so HEAD is the code before the fork's own commits." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/libretro/flycast2021-lr/package.mk" + variable: PKG_VERSION + value: "603814c9f73b773c455d9a497f389d2f93a257fd" core_version: "0.1" version_basis: kind: file diff --git a/emulators/flycast2021le.yml b/emulators/flycast2021le.yml index e66242c0f..2ced2bd01 100644 --- a/emulators/flycast2021le.yml +++ b/emulators/flycast2021le.yml @@ -7,6 +7,13 @@ profiled_date: "2026-10-22" source_commit: "bf93aece5db7050667075a1107ab380856b44a23" upstream_commit: "4c293f306bc16a265c2d768af5d0cea138426054" pin_frozen: "AmberELEC, the only platform that ships it, builds this revision (AmberELEC packages/games/libretro/flycast2021le/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: amberelec + repo: "https://github.com/AmberELEC/AmberELEC" + revision: "a21975bfb1a3af68f66e07a931b994b523e1e6e2" + path: "packages/games/libretro/flycast2021le/package.mk" + variable: PKG_VERSION + value: "bf93aece5db7050667075a1107ab380856b44a23" core_version: "0.1" version_basis: kind: file diff --git a/emulators/gsplus-legacy.yml b/emulators/gsplus-legacy.yml index f63659cd7..dc3c8a6cd 100644 --- a/emulators/gsplus-legacy.yml +++ b/emulators/gsplus-legacy.yml @@ -7,6 +7,13 @@ source: "https://github.com/digarok/gsplus-legacy-archive" profiled_date: "2026-10-20" source_commit: "480572054518112647c8fae5d7ea7046a6d6ecfb" pin_frozen: "Recalbox builds this commit (Recalbox package/gsplus/gsplus.mk:8-9 at tag 10.1.1). Batocera builds the applemu fork and RetroBat its release build, as the notes say." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/gsplus/gsplus.mk" + variable: GSPLUS_VERSION + value: "480572054518112647c8fae5d7ea7046a6d6ecfb" core_version: "0.14" version_basis: kind: file diff --git a/emulators/hode.yml b/emulators/hode.yml index 88c659e9e..a8d1ec333 100644 --- a/emulators/hode.yml +++ b/emulators/hode.yml @@ -5,6 +5,13 @@ source: "https://github.com/usineur/hode" profiled_date: "2026-10-22" source_commit: "59f3c466923c6b8d49e29176ff697a91165e7efc" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/hodesdl/package.mk:6 at v4.8). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/hodesdl/package.mk" + variable: PKG_VERSION + value: "59f3c466923c6b8d49e29176ff697a91165e7efc" core_version: "0.2.9f" version_basis: kind: file diff --git a/emulators/love-0.10.2.yml b/emulators/love-0.10.2.yml index ca41ff412..9fc298b50 100644 --- a/emulators/love-0.10.2.yml +++ b/emulators/love-0.10.2.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/love2d/love" profiled_date: "2026-10-20" source_commit: "9e139779956ddba89333eece8104bd7b1b4ff2e7" pin_frozen: "RetroPie, the only platform that ships it, builds the 0.10.2 tag at this commit (RetroPie scriptmodules/ports/love-0.10.2.sh:16). Later releases are packaged separately as love." +pin_build: + - platform: retropie + repo: "https://github.com/RetroPie/RetroPie-Setup" + revision: "46128cd252cdecae852a4d0bafa2aaf9d03ab563" + path: "scriptmodules/ports/love-0.10.2.sh" + variable: rp_module_repo + value: "git https://github.com/love2d/love 0.10.2" core_version: "0.10.2" version_basis: kind: tag diff --git a/emulators/lzdoom.yml b/emulators/lzdoom.yml index 8ca3a0e57..0aa911f89 100644 --- a/emulators/lzdoom.yml +++ b/emulators/lzdoom.yml @@ -5,6 +5,19 @@ source: "https://github.com/drfrag666/lzdoom" profiled_date: "2026-10-20" source_commit: "4ce19a12164ebd6802b7fb2f94d700a6e736d0fb" pin_frozen: "The platforms that ship it run the 3.88 line: RetroPie builds tag 3.88b (RetroPie scriptmodules/ports/lzdoom.sh:15) and AmberELEC a fork of the g3.3mgw branch that still reports 3.88b (AmberELEC packages/games/emulators/lzdoom/package.mk:5). The l4.x releases are a GZDoom 4 based line that no platform ships." +pin_build: + - platform: retropie + repo: "https://github.com/RetroPie/RetroPie-Setup" + revision: "46128cd252cdecae852a4d0bafa2aaf9d03ab563" + path: "scriptmodules/ports/lzdoom.sh" + variable: rp_module_repo + value: "git https://github.com/drfrag666/gzdoom 3.88b" + - platform: amberelec + repo: "https://github.com/AmberELEC/AmberELEC" + revision: "a21975bfb1a3af68f66e07a931b994b523e1e6e2" + path: "packages/games/emulators/lzdoom/package.mk" + variable: PKG_VERSION + value: "8e12e62164ea1ba52625c63bc0f1c9e0076bff4c" core_version: "3.88b" version_basis: kind: tag diff --git a/emulators/mame0258.yml b/emulators/mame0258.yml index 76bc45c4f..3df4ed857 100644 --- a/emulators/mame0258.yml +++ b/emulators/mame0258.yml @@ -7,6 +7,13 @@ profiled_date: '2026-10-22' source_commit: dc1a96537089d6d4ecd01d9886b21061471c70c0 upstream_commit: 2e0aa823508663c4a27164be43467d1ef740cdcd pin_frozen: 'Recalbox, the only platform that ships it, builds this revision (Recalbox package/libretro-mame0258/libretro-mame0258.mk:8 at tag 10.1.1). Later commits are not part of that build.' +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/libretro-mame0258/libretro-mame0258.mk" + variable: LIBRETRO_MAME0258_VERSION + value: "dc1a96537089d6d4ecd01d9886b21061471c70c0" core_version: "0.258" version_basis: kind: file diff --git a/emulators/mame0278.yml b/emulators/mame0278.yml index 8ba5f4673..f37e94c1f 100644 --- a/emulators/mame0278.yml +++ b/emulators/mame0278.yml @@ -7,6 +7,13 @@ profiled_date: '2026-10-22' source_commit: fba50741006fa3e27e1897f589d2048a505b8fd6 upstream_commit: eb05584a2b8d98d5af5587244c9cb7dcccf660fd pin_frozen: 'Recalbox, the only platform that ships it, builds this revision (Recalbox package/libretro-mame0278/libretro-mame0278.mk:8 at tag 10.1.1). Later commits are not part of that build.' +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/libretro-mame0278/libretro-mame0278.mk" + variable: LIBRETRO_MAME0278_VERSION + value: "fba50741006fa3e27e1897f589d2048a505b8fd6" core_version: "0.278" version_basis: kind: file diff --git a/emulators/nanoboyadvance.yml b/emulators/nanoboyadvance.yml index bbd61b392..0e113e0f4 100644 --- a/emulators/nanoboyadvance.yml +++ b/emulators/nanoboyadvance.yml @@ -5,6 +5,13 @@ source: "https://github.com/nba-emu/NanoBoyAdvance" profiled_date: "2026-10-17" source_commit: "3bb6f478f977dbfd3106508536e5fbce90d1898b" pin_frozen: "ROCKNIX 20261001 builds this revision of the SDL2 frontend (v1.6 plus four commits). Later upstream work is not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/standalone/nanoboyadvance-sa/package.mk" + variable: PKG_VERSION + value: "3bb6f478f977dbfd3106508536e5fbce90d1898b" core_version: "v1.6-4-g3bb6f47" version_basis: kind: tag diff --git a/emulators/piemu.yml b/emulators/piemu.yml index f0b5bdef3..12e1ec8b4 100644 --- a/emulators/piemu.yml +++ b/emulators/piemu.yml @@ -7,6 +7,13 @@ profiled_date: "2026-10-22" source_commit: "0785214f13746bdfd709b1909f007e213b470ba1" upstream_commit: "23208626e16e53b73fd21a2fbd0c429338625dc4" pin_frozen: "AmberELEC, the only platform that ships it, builds this revision (AmberELEC packages/games/emulators/piemu/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: amberelec + repo: "https://github.com/AmberELEC/AmberELEC" + revision: "a21975bfb1a3af68f66e07a931b994b523e1e6e2" + path: "packages/games/emulators/piemu/package.mk" + variable: PKG_VERSION + value: "0785214f13746bdfd709b1909f007e213b470ba1" core_version: "20030430" version_basis: kind: file diff --git a/emulators/ppsspp-1.5.4.yml b/emulators/ppsspp-1.5.4.yml index 1ba7ad930..d5f8a3d16 100644 --- a/emulators/ppsspp-1.5.4.yml +++ b/emulators/ppsspp-1.5.4.yml @@ -5,6 +5,13 @@ source: "https://github.com/hrydgard/ppsspp" profiled_date: "2026-10-20" source_commit: "a1e74d0d4f89ba3fa2d4fe64bac7a0fa16fc146f" pin_frozen: "RetroPie, the only platform that ships it, builds the v1.5.4 tag at this commit (RetroPie scriptmodules/emulators/ppsspp-1.5.4.sh:16). Later releases are packaged separately as ppsspp." +pin_build: + - platform: retropie + repo: "https://github.com/RetroPie/RetroPie-Setup" + revision: "46128cd252cdecae852a4d0bafa2aaf9d03ab563" + path: "scriptmodules/emulators/ppsspp-1.5.4.sh" + variable: rp_module_repo + value: "git https://github.com/hrydgard/ppsspp.git v1.5.4" core_version: "v1.5.4" version_basis: kind: tag diff --git a/emulators/rb5000.yml b/emulators/rb5000.yml index 01d9064db..637766890 100644 --- a/emulators/rb5000.yml +++ b/emulators/rb5000.yml @@ -10,6 +10,13 @@ source_commit: standalone: "8c75a4b48a5222b0332d0508600f8ddc7ac1191c" libretro: "7464996aa0cb1be77abf495dc901117e1af36e02" pin_frozen: "Recalbox, the only platform that ships the standalone build, builds its tag v1.1 at this standalone revision (Recalbox package/rb5000/rb5000.mk:7). Later commits are not part of that build. The libretro pin is the HEAD of libretro-rb5000." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/rb5000/rb5000.mk" + variable: RB5000_VERSION + value: "v1.1" core_version: standalone: "v1.1" libretro: "v0.9" diff --git a/emulators/reicast.yml b/emulators/reicast.yml index 975bcfa4c..eb7784e7e 100644 --- a/emulators/reicast.yml +++ b/emulators/reicast.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/skmp/reicast-emulator" profiled_date: "2026-10-17" source_commit: "ad61f95dd6b4c6218846a25de6194550311a5d28" pin_frozen: "Recalbox 10.1.1 builds this revision (package/reicast/reicast.mk REICAST_VERSION). Later commits on reicast-emulator are not part of that build." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/reicast/reicast.mk" + variable: REICAST_VERSION + value: "ad61f95dd6b4c6218846a25de6194550311a5d28" core_version: "r8.1-243-gad61f95" version_basis: kind: tag diff --git a/emulators/rigelengine.yml b/emulators/rigelengine.yml index 002d4996e..1e6996c25 100644 --- a/emulators/rigelengine.yml +++ b/emulators/rigelengine.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/lethal-guitar/RigelEngine" profiled_date: "2026-10-22" source_commit: "f05996f9b3ad3b3ea5bb818e49e7977636746343" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/rigelengine/package.mk:5 at v4.8). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/rigelengine/package.mk" + variable: PKG_VERSION + value: "f05996f9b3ad3b3ea5bb818e49e7977636746343" core_version: "0.9.1" version_basis: kind: file diff --git a/emulators/supermariowar.yml b/emulators/supermariowar.yml index d2ce65c99..6f3d87b05 100644 --- a/emulators/supermariowar.yml +++ b/emulators/supermariowar.yml @@ -5,6 +5,13 @@ source: "https://github.com/mmatyas/supermariowar" profiled_date: "2026-10-22" source_commit: "ed60ed45e296ff88a8883cbc51f5bf5aed880636" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/supermariowar/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/supermariowar/package.mk" + variable: PKG_VERSION + value: "ed60ed45e296ff88a8883cbc51f5bf5aed880636" core_version: "2.0.0" version_basis: kind: file diff --git a/emulators/supermodel-legacy.yml b/emulators/supermodel-legacy.yml index a501a3883..ea183429c 100644 --- a/emulators/supermodel-legacy.yml +++ b/emulators/supermodel-legacy.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/trzy/Supermodel" profiled_date: "2026-10-17" source_commit: "0eef09ba2b74e48fd5cfd388ee69a3743fd5f618" pin_frozen: "Batocera's supermodel-legacy package builds this revision, held before the renderer moved to OpenGL 4.1. Recaling would repoint these refs at code this build never had." +pin_build: + - platform: batocera + repo: "https://github.com/batocera-linux/batocera.linux" + revision: "batocera-43.1" + path: "package/batocera/emulators/supermodel-legacy/supermodel-legacy.mk" + variable: SUPERMODEL_LEGACY_VERSION + value: "0eef09ba2b74e48fd5cfd388ee69a3743fd5f618" core_version: "0.3a-WIP" version_basis: kind: file diff --git a/emulators/supersnes9x.yml b/emulators/supersnes9x.yml index bb8966d61..d6f387124 100644 --- a/emulators/supersnes9x.yml +++ b/emulators/supersnes9x.yml @@ -6,6 +6,13 @@ upstream: "https://github.com/snes9xgit/snes9x" profiled_date: "2026-10-17" source_commit: "691e47b40fb38fa522a5471462344a2f0206b7a8" pin_frozen: "ROCKNIX 20261001 builds this revision. Later commits on shanytc/snes9x rework the Super Famicom Box loader and are not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/libretro/supersnes9x-lr/package.mk" + variable: PKG_VERSION + value: "691e47b40fb38fa522a5471462344a2f0206b7a8" core_version: "1.63.29" version_basis: kind: file diff --git a/emulators/supertux.yml b/emulators/supertux.yml index c05f1b9d6..5b086f101 100644 --- a/emulators/supertux.yml +++ b/emulators/supertux.yml @@ -5,6 +5,13 @@ source: "https://github.com/SuperTux/supertux" profiled_date: "2026-10-22" source_commit: "37cddf24a4abfdec7931a84afd9e87fb8ea18767" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision, v0.6.3 (EmuELEC packages/sx05re/emuelec-ports/supertux/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/supertux/package.mk" + variable: PKG_VERSION + value: "37cddf24a4abfdec7931a84afd9e87fb8ea18767" core_version: "v0.6.3-1020-g37cddf2" version_basis: kind: tag diff --git a/emulators/supertuxkart.yml b/emulators/supertuxkart.yml index 9fe306c0f..f2d9f99b5 100644 --- a/emulators/supertuxkart.yml +++ b/emulators/supertuxkart.yml @@ -5,6 +5,13 @@ source: "https://github.com/supertuxkart/stk-code" profiled_date: "2026-10-22" source_commit: "fab79150111944c9e33396db68eb5de84f2efeaa" pin_frozen: "EmuELEC, the only platform that ships it, builds this revision (EmuELEC packages/sx05re/emuelec-ports/supertuxkart/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: emuelec + repo: "https://github.com/EmuELEC/EmuELEC" + revision: "v4.8" + path: "packages/sx05re/emuelec-ports/supertuxkart/package.mk" + variable: PKG_VERSION + value: "fab79150111944c9e33396db68eb5de84f2efeaa" core_version: "preview-582-gfab7915" version_basis: kind: tag diff --git a/emulators/touchhle.yml b/emulators/touchhle.yml index d588e5190..442667f59 100644 --- a/emulators/touchhle.yml +++ b/emulators/touchhle.yml @@ -5,6 +5,13 @@ source: "https://github.com/touchHLE/touchHLE" profiled_date: "2026-10-17" source_commit: "3c5850585ba55615b17a58c58331b6d6f52d4a9d" pin_frozen: "ROCKNIX 20261001 builds this revision. Later upstream commits are not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/standalone/touchhle-sa/package.mk" + variable: PKG_VERSION + value: "3c5850585ba55615b17a58c58331b6d6f52d4a9d" core_version: "0.2.3" version_basis: kind: file diff --git a/emulators/vice-standalone.yml b/emulators/vice-standalone.yml index 612ac8f1f..c4a61a000 100644 --- a/emulators/vice-standalone.yml +++ b/emulators/vice-standalone.yml @@ -7,6 +7,13 @@ logo: "https://raw.githubusercontent.com/VICE-Team/svn-mirror/main/vice/data/com profiled_date: "2026-10-24" source_commit: "81546e60c222fe4aef750b890b0dbe3a5ac280d8" pin_frozen: "ROCKNIX, the only platform that ships it, builds this revision (ROCKNIX projects/ROCKNIX/packages/emulators/standalone/vice-sa/package.mk:5). Later commits are not part of that build." +pin_build: + - platform: rocknix + repo: "https://github.com/ROCKNIX/distribution" + revision: "20261001" + path: "projects/ROCKNIX/packages/emulators/standalone/vice-sa/package.mk" + variable: PKG_VERSION + value: "3.8" core_version: "3.8" version_basis: kind: file diff --git a/schemas/emulator.schema.json b/schemas/emulator.schema.json index 661ec316b..9ca4a8418 100644 --- a/schemas/emulator.schema.json +++ b/schemas/emulator.schema.json @@ -10,6 +10,19 @@ "type" ], "allOf": [ + { + "comment": "A build reference documents why a pin is held: it never stands without the pin_frozen that says so.", + "if": { + "required": [ + "pin_build" + ] + }, + "then": { + "required": [ + "pin_frozen" + ] + } + }, { "comment": "An alias profile points at another profile and carries no files of its own.", "if": { @@ -991,6 +1004,43 @@ "type": "string", "description": "Why source_commit is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag: comparing it to HEAD says nothing and recaling would repoint its refs at code the build it documents never had." }, + "pin_build": { + "type": "array", + "minItems": 1, + "description": "The build file each platform that ships a pin_frozen build reads its revision from, so check_freshness can read it again where the platform's YAML now stands and say when the platform builds another revision.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["platform", "repo", "revision", "path", "variable", "value"], + "properties": { + "platform": { + "type": "string", + "description": "Registry slug of the platform (platforms/.yml)." + }, + "repo": { + "type": "string", + "format": "uri", + "description": "The platform's own repository, the one its YAML is scraped from." + }, + "revision": { + "type": "string", + "description": "Tag or commit of that repository the build file was read at." + }, + "path": { + "type": "string", + "description": "The build file, from the repository root (package.mk, a Buildroot .mk, a RetroPie scriptmodule)." + }, + "variable": { + "type": "string", + "description": "The variable the file sets to the revision it builds (PKG_VERSION, _VERSION, rp_module_repo)." + }, + "value": { + "type": "string", + "description": "What the variable held at revision, quotes and trailing comment removed." + } + } + } + }, "pin_follows": { "type": "string", "description": "Why source_commit follows the branch tip although a single platform ships the profile: that platform builds the tip, cited by its build file. Without it or pin_frozen, a bump refuses a profile one platform ships." diff --git a/scripts/check_freshness.py b/scripts/check_freshness.py index c90ec334c..fac97bc71 100644 --- a/scripts/check_freshness.py +++ b/scripts/check_freshness.py @@ -1461,6 +1461,117 @@ def check_source_paths(profiles: dict) -> Finding: ) +def build_variable(lines: list[str], variable: str) -> str | None: + """The value the first assignment to ``variable`` gives in a build file. + + Read as a Buildroot .mk (`X_VERSION = sha`), a LibreELEC package.mk + (`PKG_VERSION="sha"`) or a RetroPie scriptmodule (`rp_module_repo="git + URL tag"`, `local version="20250829"`): quotes and a trailing comment go, + the rest is compared as written. + """ + assignment = re.compile( + rf"^\s*(?:local\s+|export\s+)?{re.escape(variable)}\s*[:?+]?=\s*(.*)$" + ) + for line in lines: + match = assignment.match(line) + if not match: + continue + value = match.group(1).strip() + if value[:1] in "\"'": + end = value.find(value[0], 1) + return value[1:end] if end > 0 else value[1:] + return value.split(" #", 1)[0].strip() + return None + + +def pin_build_revision(reference: dict, platforms_dir: Path) -> str: + """The revision of the platform repository its YAML was last scraped at, + '' when the platform is not registered or reads another repository.""" + path = platforms_dir / f"{reference.get('platform', '')}.yml" + repo = upstream.parse_repo(str(reference.get("repo") or "")) + if repo is None or not path.is_file(): + return "" + with path.open(encoding="utf-8") as fh: + source = str((yaml_load(fh) or {}).get("source") or "") + if f"/{repo.slug.lower()}/" not in source.lower() + "/": + return "" + return export_native.source_revision(source) + + +def check_pin_builds( + profiles: dict, platforms_dir: Path, cache_dir: str, offline: bool, +) -> list[Finding]: + """Each `pin_build:` read again where the platform now stands. + + A pin_frozen pin is held because one platform builds that revision, and + nothing read the build file again: Recalbox moving mame0278 to another + commit left the profile describing a build no platform ships. The file + is read at the revision the platform's own YAML was scraped at, or at + the repository's branch head for a platform this repository does not + scrape, and its variable compared with the value the profile recorded. + """ + findings: list[Finding] = [] + unreferenced = sorted( + name for name, profile in profiles.items() + if profile.get("pin_frozen") and not profile.get("pin_build") + ) + if unreferenced: + findings.append(Finding( + "profiles", "pin_frozen without pin_build", SKIPPED, + local=f"{len(unreferenced)} profiles", + detail="no platform build file names their revision: " + + ", ".join(unreferenced[:SHOWN_NAMES]) + + (", ..." if len(unreferenced) > SHOWN_NAMES else ""), + items=unreferenced, + )) + for name, profile in sorted(profiles.items()): + for reference in profile.get("pin_build") or []: + subject = f"{name} ({reference['platform']})" + recorded = str(reference["value"]) + revision = pin_build_revision(reference, platforms_dir) + if revision == str(reference["revision"]): + findings.append(Finding( + "profiles", subject, OK, local=recorded[:12], + detail=f"read where platforms/{reference['platform']}.yml is", + )) + continue + repo = upstream.parse_repo(str(reference["repo"])) + try: + at = revision or upstream.resolve_head(repo, cache_dir, offline) + lines = upstream.fetch_file(repo, at, reference["path"], cache_dir, offline) \ + if at else None + except upstream.NotCachedError: + findings.append(Finding("profiles", subject, SKIPPED, local=recorded[:12], + detail="offline, the build file is not cached")) + continue + except upstream.UpstreamError as exc: + findings.append(Finding("profiles", subject, UNKNOWN, local=recorded[:12], + detail=str(exc))) + continue + if not at: + findings.append(Finding("profiles", subject, UNKNOWN, local=recorded[:12], + detail=f"{repo.slug} answered no branch head")) + continue + current = None if lines is None else build_variable(lines, reference["variable"]) + if current == recorded: + findings.append(Finding( + "profiles", subject, OK, local=recorded[:12], remote=at[:12], + detail=f"{reference['path']} still builds it", + )) + continue + findings.append(Finding( + "profiles", subject, STALE, local=recorded[:12], + remote=(current or "gone")[:12], + detail=( + f"{reference['path']} is gone at {at[:12]}" if lines is None + else f"{reference['variable']} is no longer set there" if current is None + else f"{reference['platform']} now builds {current} " + f"({reference['path']} at {at[:12]}): the pin follows the build" + ), + )) + return findings + + def check_profiles(run: bool, offline: bool) -> list[Finding]: if not run: return [ @@ -1708,6 +1819,7 @@ def main() -> int: findings.extend(check_ci(args.cache_dir, args.offline)) if "profiles" in areas: findings.append(check_source_paths(profiles)) + findings.extend(check_pin_builds(profiles, platforms_dir, args.cache_dir, args.offline)) findings.extend(check_profiles(args.profiles, args.offline)) if args.json: diff --git a/tests/test_doc_pages.py b/tests/test_doc_pages.py index 409e80248..8b42cafaa 100644 --- a/tests/test_doc_pages.py +++ b/tests/test_doc_pages.py @@ -538,7 +538,7 @@ class ProfileFieldsRequired(unittest.TestCase): section = _section(_page("profiling.md"), "Profile fields") marked = { row[0].strip("`") for row in _table_rows(section) - if row[1].startswith(("yes", "for `type")) + if row[1].startswith(("yes", "for `type", "with `")) } self.assertEqual(marked, required) diff --git a/tests/test_pin_build.py b/tests/test_pin_build.py new file mode 100644 index 000000000..c4f7a4568 --- /dev/null +++ b/tests/test_pin_build.py @@ -0,0 +1,138 @@ +"""Held pins name the build file that holds them. + +pin_frozen was free prose and no tool read the build file it cites: a +platform moving mame0278 to another commit would have left the profile +holding a revision no platform builds, in silence. `pin_build` records the +file, and check_freshness reads it again where the platform now stands. +""" + +from __future__ import annotations + +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + +import check_freshness as cf # noqa: E402 + +RECALBOX = "https://gitlab.com/recalbox/recalbox" + + +class HeldPinsReadTheirBuildFileAgain(unittest.TestCase): + REF = {"platform": "recalbox", "repo": RECALBOX, + "revision": "10.1.1", "path": "package/libretro-mame0278/libretro-mame0278.mk", + "variable": "LIBRETRO_MAME0278_VERSION", "value": "a" * 40} + + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.addCleanup(self._tmp.cleanup) + self.platforms = Path(self._tmp.name) + + def _platform(self, revision: str) -> None: + (self.platforms / "recalbox.yml").write_text( + f'source: "{RECALBOX}/-/raw/{revision}/board/es_bios.xml"\n' + ) + + def _check(self, lines=None, offline=False): + profiles = {"mame0278": {"pin_frozen": "Recalbox builds it.", "pin_build": [self.REF]}, + "raine": {"pin_frozen": "RetroBat installs this release."}} + with mock.patch.object(cf.upstream, "fetch_file", return_value=lines) as fetched: + findings = cf.check_pin_builds(profiles, self.platforms, "cache", offline) + return {f.subject: f for f in findings}, fetched + + def test_the_variable_forms_the_platforms_write(self): + cases = [ + (["LIBRETRO_MAME0278_VERSION = fba5074"], "LIBRETRO_MAME0278_VERSION", "fba5074"), + (['PKG_VERSION="37cddf2" #v0.6.3'], "PKG_VERSION", "37cddf2"), + (['rp_module_repo="file $__archive_url/advancemame-1.4.tar.gz"'], "rp_module_repo", + "file $__archive_url/advancemame-1.4.tar.gz"), + (["function f() {", ' local version="20250829"', ' version="20250302"'], + "version", "20250829"), + (["OTHER_VERSION = 1"], "PKG_VERSION", None), + ] + for lines, variable, expected in cases: + with self.subTest(variable=variable): + self.assertEqual(cf.build_variable(lines, variable), expected) + + def test_the_platform_read_at_the_same_revision_needs_no_network(self): + self._platform("10.1.1") + found, fetched = self._check() + fetched.assert_not_called() + self.assertEqual(found["mame0278 (recalbox)"].status, cf.OK) + + def test_a_new_revision_building_another_commit_is_stale(self): + self._platform("10.2") + found, fetched = self._check([f"LIBRETRO_MAME0278_VERSION = {'b' * 40}"]) + self.assertEqual(fetched.call_args.args[1:3], + ("10.2", "package/libretro-mame0278/libretro-mame0278.mk")) + row = found["mame0278 (recalbox)"] + self.assertEqual(row.status, cf.STALE) + self.assertIn("now builds " + "b" * 40, row.detail) + + def test_a_new_revision_building_the_same_commit_is_ok(self): + self._platform("10.2") + found, _ = self._check([f"LIBRETRO_MAME0278_VERSION = {'a' * 40}"]) + self.assertEqual(found["mame0278 (recalbox)"].status, cf.OK) + + def test_a_build_file_gone_is_stale(self): + self._platform("10.2") + found, _ = self._check(None) + self.assertEqual(found["mame0278 (recalbox)"].status, cf.STALE) + self.assertIn("is gone", found["mame0278 (recalbox)"].detail) + + def test_offline_and_uncached_is_skipped(self): + self._platform("10.2") + profiles = {"mame0278": {"pin_frozen": "x", "pin_build": [self.REF]}} + cold = cf.upstream.NotCachedError("not cached") + with mock.patch.object(cf.upstream, "fetch_file", side_effect=cold): + (row,) = cf.check_pin_builds(profiles, self.platforms, "cache", True) + self.assertEqual(row.status, cf.SKIPPED) + + def test_a_hold_with_no_build_file_is_counted(self): + self._platform("10.1.1") + found, _ = self._check() + row = found["pin_frozen without pin_build"] + self.assertEqual(row.status, cf.SKIPPED) + self.assertEqual(row.items, ["raine"]) + + +class EveryBuildReferenceNamesThePin(unittest.TestCase): + """Reads the repository: a pin_build value is what the platform builds, + so it is the profile's source_commit or names its core_version, and the + platform's own YAML was scraped from that repository.""" + + def test_the_values_agree(self): + from common import load_emulator_profiles + + disagree = [] + for name, profile in load_emulator_profiles(str(REPO_ROOT / "emulators")).items(): + references = profile.get("pin_build") or [] + if not references: + continue + pins = profile.get("source_commit") + pins = set(pins.values()) if isinstance(pins, dict) else {pins} + versions = profile.get("core_version") + versions = ( + {str(v) for v in versions.values()} if isinstance(versions, dict) + else {str(versions)} if versions else set() + ) + + def names_it(value: str) -> bool: + token = value.split()[-1].rsplit("/", 1)[-1].removesuffix(".tar.gz") + return value in pins or any(token in v or v in value for v in versions) + + if not any(names_it(str(r["value"])) for r in references): + disagree.append(name) + for reference in references: + if not cf.pin_build_revision(reference, REPO_ROOT / "platforms"): + disagree.append(f"{name}: {reference['platform']} is not read from " + f"{reference['repo']}") + self.assertEqual(disagree, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/wiki/profiling.md b/wiki/profiling.md index 7a8ccc108..bd63cb157 100644 --- a/wiki/profiling.md +++ b/wiki/profiling.md @@ -522,7 +522,8 @@ reports the profile as uncheckable, and the CI job that runs | `version_basis` | every profile (`tests/test_version_basis.py`) | where `core_version` is read, by `kind`: `file` (`path`, `pattern`, optional `format`, and `url` with its `commit` for a file outside the profile's repositories, such as the package file of a build a platform ships), `tag` (the nearest `tag` in the pin's history and its `distance`, `core_version` in git describe's form, `match` the glob a tag must fit), `alias` (`of`), `package` (`artifact`, the APK or binary read, `url` where it was obtained), `none` (the code states no version: `core_version` null) or `unverified` (no reading could be made: `core_version` stays as written and is reported unconfirmed, `why` says why: `no_pin`, `host_unsupported`, `host_gone`, `host_unreachable`, `pin_off_branch`, `no_artifact`, `no_source`). `repo` names `upstream` or `source_mirror` when the version is that tree's. `profile_sync --verify-version` reads it again, and a pin advance rewrites `core_version` from it | | `source_commit` | see above | commit SHA of `source` the code was read at. Anchors every `source_ref` line number | | `upstream_commit` | no | same, for `upstream` | -| `pin_frozen` | no | why `source_commit` is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag | +| `pin_frozen` | with `pin_build` | why `source_commit` is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag | +| `pin_build` | no | the build file each platform shipping a `pin_frozen` build reads its revision from: a list of `platform` (registry slug), `repo` (the platform's own repository), `revision` (the tag or commit the file was read at), `path`, `variable` (`PKG_VERSION`, `_VERSION`, `rp_module_repo`) and `value` (what it held there). `check_freshness.py` reads the file again where the platform's YAML now stands and reports the profile `STALE` when the platform builds another revision. A hold that no build file names (a release binary, an abandoned repository) carries `pin_frozen` alone | | `pin_follows` | no | why `source_commit` follows the branch tip when a single platform ships the profile: that platform builds the tip, cited by its build file. A bump refuses a profile one platform ships until it carries `pin_frozen` or `pin_follows` | | `data_root` | no | per operating system (`android`, `ios`, `windows`, `macos`, `linux`), the folder the app reads its user data from, under which `standalone_path` destinations sit: `path` as the user finds it (`Android/data//files`, `%APPDATA%/...`), with the `source_ref` that builds it. Only the systems the app runs on are keyed | | `upstream_frozen` | no | why `upstream_commit` is held: the port vendors that upstream tree, synced to that release, or merged that commit. The upstream view is read at its own pin and a bump leaves it. Without it, a port that is a GitHub fork of its upstream follows the merge base of the two | diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 43640fc57..ceaa396d0 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -65,7 +65,7 @@ Every module in `tests/`, by the area it guards. | Pack probe | the cores a platform runs for a system, staging grown to a fixed point, runs mapped to pack and manifest entries, the line and its schema, the probe lock per run, that no page shows its lines. One class builds a real pack narrowed to PlayStation and lays it out both ways, from the archive and with `install.py --dest`, and skips without the collection | `test_pack_probe` | | Automation runner, disk cleanup, asset sync and bit-flip repair | fake jobs, git repositories with a bare origin and session worktrees, a committed file with one bit flipped, canned CI runs in place of `gh` | `test_autorun` | | Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` | -| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_arcade_boards`, `test_site_availability`, `test_site_bootstrap`, `test_site_built_gate`, `test_site_components`, `test_site_console_folders`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_css`, `test_site_density_cards`, `test_site_emulator_page`, `test_site_emulators`, `test_site_entry`, `test_site_entry_tables`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_integration`, `test_site_nav`, `test_site_picker`, `test_site_platform_density`, `test_site_platform_hashes`, `test_site_reference`, `test_site_render`, `test_site_search`, `test_site_system_links`, `test_site_system_names`, `test_site_system_sections`, `test_site_systems`, `test_site_validation`, `test_site_weight`, `test_site_wiki`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_version_basis`, `test_workflow_paths` | +| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_pin_build`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_arcade_boards`, `test_site_availability`, `test_site_bootstrap`, `test_site_built_gate`, `test_site_components`, `test_site_console_folders`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_css`, `test_site_density_cards`, `test_site_emulator_page`, `test_site_emulators`, `test_site_entry`, `test_site_entry_tables`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_integration`, `test_site_nav`, `test_site_picker`, `test_site_platform_density`, `test_site_platform_hashes`, `test_site_reference`, `test_site_render`, `test_site_search`, `test_site_system_links`, `test_site_system_names`, `test_site_system_sections`, `test_site_systems`, `test_site_validation`, `test_site_weight`, `test_site_wiki`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_version_basis`, `test_workflow_paths` | | Games on the card | small DATs, listxml and setup sources, a fake fetcher, fixture install lists, and the committed data against its schemas | `test_arcade_parents`, `test_game_catalogs`, `test_game_headers`, `test_game_regions`, `test_games_index`, `test_romfolders_card`, `test_romfolders_es` | ## Test architecture diff --git a/wiki/tools.md b/wiki/tools.md index f3af3ee55..5e4359e12 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -695,7 +695,15 @@ NAME` reads the folders again. The `profiles` area always checks `website/src/data/source-paths.json` against the profiles, offline: a recorded tree that no profile pins any more -is `STALE` until `sitesources.py --fetch` runs again. +is `STALE` until `sitesources.py --fetch` runs again. It also reads each +`pin_build` reference of a held pin: where the platform YAML was scraped at +the reference's own revision the row is `OK` without network, otherwise the +build file is fetched at the platform's new revision (or at the branch head +of a platform this repository does not scrape) and its variable compared +with the recorded value. A different value is `STALE`: the platform now +builds another revision, and the pin, the refs and `pin_build` move with it. +Profiles with `pin_frozen` and no `pin_build` are counted on one `SKIPPED` +row, since no build file names their revision. ```bash python scripts/check_freshness.py