diff --git a/scripts/common.py b/scripts/common.py index 4ad979d3a..5369c71a3 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -2771,19 +2771,6 @@ def system_dir_path(entry: dict, path: str) -> str: return f"{directory}/{path}" if directory else path -def launched_both_ways(config: dict) -> set[str]: - """The names a platform runs as a standalone emulator and as a core. - - ``dual_mode_cores`` names those its frontend offers both ways. A name it - gives a standalone emulator only, whose buildbot core its RetroArch can - download (``installable_cores``), runs both ways too: Batocera's x86_64 - RetroArch installs the amiberry core beside its own standalone Amiberry. - """ - standalone = {str(c) for c in config.get("standalone_cores") or []} - installable = {str(c) for c in config.get("installable_cores") or []} - return {str(c) for c in config.get("dual_mode_cores") or []} | (standalone & installable) - - def own_build_tags(config: dict, standalone: set[str]) -> set[str]: """The standalone names the platform's own builds answer to, tagged. @@ -2810,7 +2797,16 @@ def standalone_set(config: dict) -> set[str]: same set, tagged, so every reader of standalone_cores sees them. """ names = {str(c) for c in config.get("standalone_cores") or []} - names |= {DUAL_MODE_PREFIX + c for c in launched_both_ways(config)} + names |= {DUAL_MODE_PREFIX + str(c) for c in config.get("dual_mode_cores") or []} + # A buildbot core the platform's RetroArch installs under the name of an + # emulator it runs standalone is that emulator's libretro build: run + # both ways, as Batocera runs amiberry standalone and can fetch + # amiberry_libretro. + names |= { + DUAL_MODE_PREFIX + str(c) + for c in set(map(str, config.get("installable_cores") or [])) + & set(map(str, config.get("standalone_cores") or [])) + } names |= own_build_tags(config, names) # The system directory each core is handed travels the same way, so # platform_layouts places a core's files below it for every reader. @@ -3310,24 +3306,26 @@ def resolve_platform_cores( # A name the platform launches both ways designates its libretro # build too. Batocera, Recalbox and RetroBat run pcsx2 standalone and # as LRPS2, two profiles: the key match kept the standalone one and - # LRPS2's files never reached their packs. - dual_names = {str(c) for c in config.get("dual_mode_cores") or []} - both_ways = { - profile: c - for c in sorted(launched_both_ways(config)) - for profile in index.get(c, set()) - if "libretro" in str(profiles[profile].get("type", "")) - and not profiles[profile].get("built_by") - and not replaced_by_own(profile, profiles[profile], libretro_only=True) - } + # LRPS2's files never reached their packs. A core its RetroArch + # installs is a libretro build as well (amiberry's buildbot core + # beside Batocera's standalone amiberry). + def libretro_builds(name: object) -> set[str]: + return { + profile + for profile in index.get(str(name), set()) + if "libretro" in str(profiles[profile].get("type", "")) + and not profiles[profile].get("built_by") + and not replaced_by_own(profile, profiles[profile], libretro_only=True) + } + + dual = {p for c in config.get("dual_mode_cores") or [] for p in libretro_builds(c)} # A core reached only through the RetroArch downloader runs on the # boards whose downloader offers it: Batocera's x86_64 RetroArch # installs the amiberry core, its armv7 and aarch64 boards cannot. - downloaded_only = { - profile: c for profile, c in both_ways.items() - if c not in dual_names and profile not in result - } - result |= set(both_ways) + for c in config.get("installable_cores") or []: + for profile in libretro_builds(c) - dual - result: + downloaded_only.setdefault(profile, str(c)) + result |= dual | set(downloaded_only) # Support "all_libretro" as a list element: combines all libretro # profiles with explicitly listed standalone cores (e.g. RetroDECK # ships RetroArch + standalone emulators) diff --git a/scripts/scraper/batocera_scraper.py b/scripts/scraper/batocera_scraper.py index 49bf18192..49f797077 100644 --- a/scripts/scraper/batocera_scraper.py +++ b/scripts/scraper/batocera_scraper.py @@ -14,7 +14,7 @@ import re import sys import urllib.error import urllib.request -from collections.abc import Callable, Iterable +from collections.abc import Callable from pathlib import Path @@ -558,26 +558,23 @@ def boards_build(selection: BuildSelection, launchers: list[dict]) -> Callable[[ def installable_cores( - selection: BuildSelection, - cores: list[str], - standalone: Iterable[str] = (), - dual: Iterable[str] = (), + selection: BuildSelection, cores: list[str], standalone: list[str], dual: list[str], ) -> list[str]: """Buildbot cores a board's RetroArch core downloader installs, beyond - the cores the image builds. + the libretro cores the image builds. The targets scraper names, per board, the RetroArch targets entry its downloader reads (`downloads`): a Batocera x86_64 board installs any - core of linux-x86_64, an armv7 one any of linux-armhf. A name the image - gives a standalone emulator only (Batocera's amiberry, cemu, xemu) is - not that core: the buildbot one stays installable. + core of linux-x86_64, an armv7 one any of linux-armhf. A name compares + within its build mode: amiberry built standalone is another program + than amiberry_libretro, which the buildbot serves. """ found: set[str] = set() for entry in selection.targets.values(): if entry.get("downloads"): found |= load_target_config("retroarch", entry["downloads"]) or set() - standalone_only = set(standalone) - set(dual) - return sorted(found - (set(cores) - standalone_only)) + built_libretro = (set(cores) - set(standalone)) | set(dual) + return sorted(found - built_libretro) def records_run(launchers: list[dict], built: Callable[[str], bool]) -> list[dict]: diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 10e150c21..a8f937122 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3304,30 +3304,32 @@ class TestBumpCommit(unittest.TestCase): 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.""" + """KNULLI alone ships advanced_drastic and builds its + advanced_drastic.mk version. A bulk advance moved fheroes2 and + supertux past the build EmuELEC then pinned, and a pin ahead of the + one build a platform ships describes nothing anyone runs.""" root = Path(__file__).resolve().parents[1] - path = Path(self.tmp.name) / "rigelengine.yml" + path = Path(self.tmp.name) / "advanced_drastic.yml" path.write_text( - SAMPLE.replace("\nnotes:", "\ntype: standalone\ncores: [rigelengine, RigelEngine]\n\nnotes:", 1), + SAMPLE.replace( + "\nnotes:", "\ntype: standalone\ncores: [advanced_drastic]\n\nnotes:", 1), encoding="utf-8", ) report = ProfileReport( - name="rigelengine", repo="o/n", pin="pin", head="newhead", entries=[], + name="advanced_drastic", 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"], + profile_sync.shipping_platforms("advanced_drastic", self.tmp.name, platforms), + ["knulli"], ) 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()) + self.assertIn("only knulli ships it", err.getvalue()) path.write_text( path.read_text(encoding="utf-8") - + 'pin_follows: "EmuELEC builds the branch tip"\n', + + 'pin_follows: "KNULLI builds the branch tip"\n', encoding="utf-8", ) self.assertTrue(bump_commit(path, report, platforms_dir=platforms)) diff --git a/tests/test_standalone_links.py b/tests/test_standalone_links.py index 98ebce23b..1ead07910 100644 --- a/tests/test_standalone_links.py +++ b/tests/test_standalone_links.py @@ -657,7 +657,6 @@ class AmiberryOnEachPlatform(unittest.TestCase): self.assertNotIn("unplaced", entry) self.assertEqual(entry["standalone_path"], f"{folder}/{layout}") - def test_retrobat_reads_its_folders_below_the_amiberry_emulator_folder(self) -> None: # RetroBat writes rom_path and whdboot_path below the amiberry # folder of its install and copies the kickstarts it finds in bios @@ -681,7 +680,9 @@ class AmiberryOnEachPlatform(unittest.TestCase): class AmiberryBuildEachPlatformShips(unittest.TestCase): """Each platform resolves the name amiberry to the build its own package - compiles. RetroBat keeps the 8.5.0 libretro core beside its standalone.""" + compiles. RetroBat keeps the 8.5.0 libretro core beside its standalone, + and Batocera's x86_64 boards fetch it with RetroArch's core downloader + (installable_cores).""" OWN = { "rocknix": {"amiberry-5.6.5"}, diff --git a/tests/test_target_scrapers.py b/tests/test_target_scrapers.py index 8fb503723..65a90da7d 100644 --- a/tests/test_target_scrapers.py +++ b/tests/test_target_scrapers.py @@ -298,13 +298,41 @@ class CoreDownloaderReach(unittest.TestCase): fetched |= load_target_config("retroarch", entry["downloads"], root) or set() if entry["architecture"] == "aarch64": self.assertNotIn("downloads", entry) - # A name the image gives a standalone emulator only is not the - # buildbot core of that name, which stays installable. - standalone_only = set(config.get("standalone_cores") or []) - set( - config.get("dual_mode_cores") or []) with self.subTest(platform=platform): - self.assertEqual(set(config["installable_cores"]), - fetched - (set(config["cores"]) - standalone_only)) + built = ((set(config["cores"]) - set(config.get("standalone_cores") or [])) + | set(config.get("dual_mode_cores") or [])) + self.assertEqual(set(config["installable_cores"]), fetched - built) + + def test_a_standalone_name_keeps_its_buildbot_core(self): + """Batocera runs amiberry standalone, and its x86_64 boards fetch + amiberry_libretro: removing every name the image builds lost the + libretro core to the name it shares with the standalone package.""" + from common import DUAL_MODE_PREFIX, resolve_platform_cores, standalone_set # noqa: PLC0415 + from scraper.batocera_scraper import installable_cores # noqa: PLC0415 + from scraper.targets.batocera_targets_scraper import BuildSelection # noqa: PLC0415 + + selection = BuildSelection( + targets={"x86_64": {"architecture": "x86_64", "cores": [], + "downloads": "linux-x86_64"}}, + emulators=frozenset(), listed=frozenset(), symbols=frozenset(), packages={}, + ) + found = installable_cores(selection, ["amiberry", "mgba", "dolphin"], + ["amiberry", "dolphin"], ["dolphin"]) + self.assertIn("amiberry", found) + self.assertNotIn("mgba", found) + self.assertNotIn("dolphin", found) + + profiles = { + "amiberry": {"type": "libretro", "cores": ["amiberry"], "systems": ["amiga"]}, + "amiberry-7": {"type": "standalone", "cores": ["amiberry"], "systems": ["amiga"], + "built_by": ["demo"], "source": "https://x/amiberry"}, + } + config = {"_key": "demo", "cores": ["amiberry"], "standalone_cores": ["amiberry"], + "installable_cores": ["amiberry"]} + self.assertEqual(resolve_platform_cores(config, profiles), {"amiberry", "amiberry-7"}) + self.assertEqual(resolve_platform_cores({**config, "installable_cores": []}, profiles), + {"amiberry-7"}) + self.assertIn(DUAL_MODE_PREFIX + "amiberry", standalone_set(config)) class KnulliPerBoard(unittest.TestCase):