diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 8e78ddd3..ee0fdb52 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -520,6 +520,7 @@ def _select_variants( source: str, regions, one_per_slot: bool, + required_only: bool = False, ) -> tuple[set, list, list]: """Which regional and slot alternatives this pack leaves out. @@ -545,6 +546,7 @@ def _select_variants( target_cores=target_cores, include_extras=(source != "platform"), include_all=(source == "truth"), + required_only=required_only, ) if regions: region_index = region_mod.build_region_index(emu_profiles or {}) @@ -704,7 +706,7 @@ def generate_pack( # direction. Runs only when --region is given. region_drops, region_fallbacks, slot_undecidable = _select_variants( config, pack_systems, emulators_dir, db, base_dest, emu_profiles, - target_cores, source, regions, one_per_slot, + target_cores, source, regions, one_per_slot, required_only, ) with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: @@ -3077,6 +3079,7 @@ def generate_manifest( target_cores=target_cores, include_extras=(source != "platform"), include_all=(source == "truth"), + required_only=required_only, ) region_drops = region_mod.resolve_region_drops( region_groups, region_index, regions diff --git a/scripts/packextras.py b/scripts/packextras.py index 9e129bc6..2a7d05b2 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -626,6 +626,7 @@ def platform_region_groups( target_cores: set[str] | None = None, include_extras: bool = True, include_all: bool = False, + required_only: bool = False, ) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str, str], str]]: """Group a platform's pack candidates the way region filtering reads them. @@ -639,6 +640,10 @@ def platform_region_groups( for sys_id, system in systems.items(): members = groups.setdefault(sys_id, []) for file_entry in system.get("files", []): + if required_only and file_entry.get("required") is False: + # Decided over what the pack keeps: an optional winner + # removed afterwards left its slot empty. + continue dest = sanitize_pack_path( file_entry.get("destination", file_entry.get("name", "")) ) @@ -659,6 +664,8 @@ def platform_region_groups( target_cores=target_cores, include_all=include_all, ): + if required_only and extra.get("required") is False: + continue dest = sanitize_pack_path(extra.get("destination", extra.get("name", ""))) if not dest: continue diff --git a/tests/test_region_required_only.py b/tests/test_region_required_only.py new file mode 100644 index 00000000..909dfcea --- /dev/null +++ b/tests/test_region_required_only.py @@ -0,0 +1,39 @@ +"""--region decides among the files --required-only keeps. + +The regional choice ran over optional candidates too. In Batocera's Vic20 +group the optional Japanese kernel won for --region jp, the required PAL +and NTSC kernels were dropped as beaten, and --required-only then removed +the optional winner: the slot shipped empty. +""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + + +class RequiredOnlyBeforeRegion(unittest.TestCase): + def test_optional_files_leave_the_groups(self): + from packextras import platform_region_groups + + systems = {"vic20": {"files": [ + {"name": "kernel-ntsc.bin", "destination": "Vic20/kernel-ntsc.bin", "required": True}, + {"name": "kernel-japanese.bin", "destination": "Vic20/kernel-japanese.bin", + "required": False}, + ]}} + groups, _ = platform_region_groups( + {}, systems, "emulators", None, "", {}, include_extras=False, required_only=True, + ) + self.assertEqual(groups["vic20"], [("Vic20/kernel-ntsc.bin", "kernel-ntsc.bin")]) + + def test_every_caller_passes_it(self): + source = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8") + self.assertEqual(source.count("required_only=required_only,\n )"), 2) + + +if __name__ == "__main__": + unittest.main()