From d5e51468198be84b62249a4a77ec51758e362a27 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:23:41 +0100 Subject: [PATCH] feat: hold bumps of profiles one platform ships A bulk advance moved fheroes2, supertux and supermariowar past the revision EmuELEC compiles, and nothing stopped it: bump_commit refused built_by only. It now leaves a profile a single registered platform ships until it carries pin_frozen or the new pin_follows, which says that platform builds the branch tip. hode, bermuda, bennugd-monolithic and ccleste are held on the PKG_VERSION of their EmuELEC v4.8 package.mk, equal to their pins. rigelengine was one commit past it (README.md only) and goes back to f05996f9. --- emulators/bennugd-monolithic.yml | 1 + emulators/bermuda.yml | 1 + emulators/ccleste.yml | 1 + emulators/hode.yml | 1 + emulators/rigelengine.yml | 3 ++- schemas/emulator.schema.json | 4 +++ scripts/profile_sync.py | 46 ++++++++++++++++++++++++++++++++ tests/test_profile_sync.py | 29 ++++++++++++++++++++ wiki/profiling.md | 1 + wiki/tools.md | 4 ++- 10 files changed, 89 insertions(+), 2 deletions(-) diff --git a/emulators/bennugd-monolithic.yml b/emulators/bennugd-monolithic.yml index 3af70c233..6f4e2a688 100644 --- a/emulators/bennugd-monolithic.yml +++ b/emulators/bennugd-monolithic.yml @@ -4,6 +4,7 @@ core_classification: game_engine 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." core_version: "1.0.0" version_basis: kind: file diff --git a/emulators/bermuda.yml b/emulators/bermuda.yml index 491a432e8..130dc2538 100644 --- a/emulators/bermuda.yml +++ b/emulators/bermuda.yml @@ -4,6 +4,7 @@ core_classification: game_engine 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." core_version: "0.1.7" version_basis: kind: file diff --git a/emulators/ccleste.yml b/emulators/ccleste.yml index 2422cc459..d2848132d 100644 --- a/emulators/ccleste.yml +++ b/emulators/ccleste.yml @@ -4,6 +4,7 @@ core_classification: game_engine 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." core_version: "v1.4.0" version_basis: kind: tag diff --git a/emulators/hode.yml b/emulators/hode.yml index db6e30537..88c659e9e 100644 --- a/emulators/hode.yml +++ b/emulators/hode.yml @@ -4,6 +4,7 @@ core_classification: game_engine 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." core_version: "0.2.9f" version_basis: kind: file diff --git a/emulators/rigelengine.yml b/emulators/rigelengine.yml index 880db6916..002d4996e 100644 --- a/emulators/rigelengine.yml +++ b/emulators/rigelengine.yml @@ -4,7 +4,8 @@ core_classification: game_engine source: "https://github.com/lethal-guitar/RigelEngine" upstream: "https://github.com/lethal-guitar/RigelEngine" profiled_date: "2026-10-22" -source_commit: "30afeff8f0b105eac77ee0a2fbd54b06e1a49b60" +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." core_version: "0.9.1" version_basis: kind: file diff --git a/schemas/emulator.schema.json b/schemas/emulator.schema.json index a8dc392e0..661ec316b 100644 --- a/schemas/emulator.schema.json +++ b/schemas/emulator.schema.json @@ -991,6 +991,10 @@ "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_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." + }, "data_root": { "type": "object", "minProperties": 1, diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index a61f5b05f..3245ab3b0 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -11,6 +11,7 @@ from __future__ import annotations import argparse import bisect import difflib +import functools import itertools import json import os @@ -35,7 +36,10 @@ import version_basis from common import ( _norm_system_id, github_token, + list_registered_platforms, load_emulator_profiles, + load_platform_config, + resolve_platform_cores, suggest_names, system_index, ) @@ -3497,9 +3501,37 @@ def _needs_review(report: ProfileReport, accept_changed: bool) -> bool: return any((report.counts or {}).get(s) for s in blocking) +@functools.lru_cache(maxsize=4) +def _platform_cores(emulators_dir: str, platforms_dir: str) -> tuple[tuple[str, frozenset[str]], ...]: + profiles = load_emulator_profiles(emulators_dir) + return tuple( + (platform, frozenset(resolve_platform_cores( + load_platform_config(platform, platforms_dir), profiles + ))) + for platform in list_registered_platforms(platforms_dir, include_archived=True) + ) + + +def shipping_platforms( + name: str, emulators_dir: str, platforms_dir: str = "platforms", +) -> list[str]: + """The registered platforms whose cores resolve to this profile. + + Archived platforms count: RetroPie still builds the packages it lists. + """ + return [ + platform + for platform, cores in _platform_cores( + os.path.realpath(emulators_dir), platforms_dir + ) + if name in cores + ] + + def bump_commit( path: Path, report: ProfileReport, accept_changed: bool = False, cache_dir: str = DEFAULT_CACHE, offline: bool = False, + platforms_dir: str = "platforms", ) -> bool: """Advance source_commit to HEAD when nothing needs a read again. @@ -3523,6 +3555,20 @@ def bump_commit( file=sys.stderr, ) return False + if not (document.get("pin_frozen") or document.get("pin_follows")): + shipped = shipping_platforms(path.stem, str(path.parent), platforms_dir) + if len(shipped) == 1: + # One platform ships it, so the pin is the revision that platform + # builds: a bulk advance moved fheroes2, supertux and + # supermariowar past what EmuELEC compiles, and rigelengine is + # already one commit ahead of its package.mk. + print( + f"{path.stem}: only {shipped[0]} ships it. Read the revision its " + "build compiles and set pin_frozen, or pin_follows when it " + "builds the branch tip", + file=sys.stderr, + ) + return False if any(isinstance(document.get(f), dict) for f in ("source_commit", "upstream_commit")): # A pin per build mode names one revision per repository, and the # report heads only the primary one: rewriting str(dict) was a diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 8bb0e78db..a31233d14 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3256,6 +3256,35 @@ class TestBumpCommit(unittest.TestCase): # Nothing was compared upstream, so its pin is not written. self.assertNotIn("uphead", text) + def test_a_profile_one_platform_ships_waits_for_its_build(self): + """EmuELEC alone ships RigelEngine and builds its package.mk + PKG_VERSION. A bulk advance moved fheroes2 and supertux past that + build, and rigelengine is a commit ahead of it already.""" + root = Path(__file__).resolve().parents[1] + path = Path(self.tmp.name) / "rigelengine.yml" + path.write_text( + SAMPLE.replace("\nnotes:", "\ntype: standalone\ncores: [rigelengine, RigelEngine]\n\nnotes:", 1), + encoding="utf-8", + ) + report = ProfileReport( + name="rigelengine", repo="o/n", pin="pin", head="newhead", entries=[], + counts={}, skipped=profile_sync.NO_SOURCE_REF, + ) + platforms = str(root / "platforms") + self.assertEqual( + profile_sync.shipping_platforms("rigelengine", self.tmp.name, platforms), + ["emuelec"], + ) + with contextlib.redirect_stderr(io.StringIO()) as err: + self.assertFalse(bump_commit(path, report, platforms_dir=platforms)) + self.assertIn("only emuelec ships it", err.getvalue()) + path.write_text( + path.read_text(encoding="utf-8") + + 'pin_follows: "EmuELEC builds the branch tip"\n', + encoding="utf-8", + ) + self.assertTrue(bump_commit(path, report, platforms_dir=platforms)) + def test_any_other_skip_still_holds_the_pin(self): report = ProfileReport( name="p", repo="o/n", pin="pin", head="newhead", entries=[], counts={}, diff --git a/wiki/profiling.md b/wiki/profiling.md index 0b69e0423..6ef532a68 100644 --- a/wiki/profiling.md +++ b/wiki/profiling.md @@ -521,6 +521,7 @@ reports the profile as uncheckable, and the CI job that runs | `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_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 | | `prose_written_at` | no | the pin a reader confirmed the prose citations against. When the profile's history shows a text living under several pins, profile_sync takes this one as the revision the text describes, so a bump is no longer refused for a citation already reread. It never overrides a history with a single candidate | diff --git a/wiki/tools.md b/wiki/tools.md index 214920b78..ade23eb86 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -613,7 +613,9 @@ Writes are explicit and mechanical only. `--backfill-commits` fills a missing `source_commit`, `--rebase-refs` recales `SHIFTED` and `RENAMED` line ranges and moves `source_commit` to HEAD with them, all or nothing, `--bump-commit` advances `source_commit` to HEAD only when nothing needs a -re-read. All three refuse to run on a dirty `emulators/` without `--force`, +re-read. It leaves a profile a single registered platform ships until the +profile says what that platform builds: `pin_frozen` for a fixed revision, +`pin_follows` for the branch tip. All three refuse to run on a dirty `emulators/` without `--force`, and every write is verified by reparsing the document. Uses `GITHUB_TOKEN` when set, which `--all` requires. Responses are cached