diff --git a/scripts/packextras.py b/scripts/packextras.py index ad31cf17..9861f3aa 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -215,7 +215,7 @@ def hidden_extras( placed: set[str] | None = None, report: list[dict] | None = None, resolver: ExtraResolver | None = None, - placed_by: dict[str, tuple[str, str, str]] | None = None, + placed_kind: dict[str, str] | None = None, ) -> dict[tuple[str, str, str], str]: """Report entries the builder holds and still leaves out, with the reason. @@ -231,8 +231,10 @@ def hidden_extras( file left out. ``shipped`` maps the full destination of each platform file the pack carries to its local copy, or to None for a slot it keeps without one (user_provided, external). ``placed``, when given, - receives the full destination of each held extra the pack carries. - ``resolver`` is the builder's lookup. An extra it cannot resolve is + receives the full destination of each held extra the pack carries, and + ``placed_kind`` its category, carried by the extra itself: the second + pass, the archive-prefix pass and the agnostic scan copy files no gap + report entry names. ``resolver`` is the builder's lookup. An extra it cannot resolve is skipped and takes no path, as the builder skips it. """ from packpaths import _register_path, placement_block @@ -288,11 +290,8 @@ def hidden_extras( lower.add(full.lower()) if placed is not None and local: placed.add(full) - if placed_by is not None and local: - placed_by[full] = ( - fe.get("source_emulator", ""), fe.get("source_name", ""), - fe.get("source_path", ""), - ) + if placed_kind is not None and local: + placed_kind[full] = fe.get("category", "bios") return hidden @@ -469,6 +468,9 @@ def _agnostic_scan_extras( "hle_fallback": False, "source_emulator": profile.get("emulator", emu_name), "source_profile": emu_name, + "source_name": fname, + "source_path": f.get("path") or "", + "category": f.get("category", "bios"), "source_system": f.get("system"), "source_systems": list(profile.get("systems", [])), "region": f.get("region"), @@ -622,6 +624,9 @@ def _companion_extras( "hle_fallback": False, "source_emulator": profile.get("emulator", emu_name), "source_profile": emu_name, + "source_name": seed.get("name", ""), + "source_path": seed.get("path") or "", + "category": seed.get("category", "bios"), "source_system": seed.get("system"), "source_systems": list(profile.get("systems", [])), "region": seed.get("region"), @@ -734,6 +739,10 @@ def _archive_prefix_extras( "hle_fallback": False, "source_emulator": profile.get("emulator", emu_name), "source_profile": emu_name, + # Keyed as the gap report keys an archive: name and path alike. + "source_name": archive_name, + "source_path": archive_name, + "category": archive_entry.get("category", "bios"), "source_system": archive_entry.get("system"), "source_systems": list(profile.get("systems", [])), "region": archive_entry.get("region"), @@ -1032,6 +1041,10 @@ def _collect_emulator_extras( "hle_fallback": f.get("hle_fallback", False), "source_emulator": profile.get("emulator", emu_name), "source_profile": emu_name, + # The key the gap report gives the same entry. + "source_name": fname, + "source_path": raw, + "category": f.get("category", "bios"), "source_system": f.get("system"), "source_systems": list(profile.get("systems", [])), "region": f.get("region"), @@ -1161,6 +1174,7 @@ def _report_extra(u: dict, name: str, dest: str) -> dict: # the wrong regional variant from the report. "source_name": u.get("name", ""), "source_path": u.get("path") or "", + "category": u.get("category", "bios"), "source_system": u.get("system"), "source_systems": u.get("systems", []), "region": u.get("region"), diff --git a/scripts/verify.py b/scripts/verify.py index 5fae7154..d9643fbb 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -885,7 +885,7 @@ def _mark_hidden( region_drops: set[str] | None = None, report: list[dict] | None = None, resolver: object | None = None, - placed_by: dict[str, tuple[str, str, str]] | None = None, + placed_kind: dict[str, str] | None = None, ) -> set[str]: """What the builder holds but cannot place beside the files it packed. @@ -900,7 +900,7 @@ def _mark_hidden( config, emulators_dir, db, emu_profiles if emu_profiles is not None else load_emulator_profiles(emulators_dir), - shipped, target_cores, region_drops, placed, report, resolver, placed_by, + shipped, target_cores, region_drops, placed, report, resolver, placed_kind, ) for u in undeclared: reason = hidden.get( @@ -1333,7 +1333,7 @@ def verify_platform( _mark_unplaced(config, undeclared, profiles) from packextras import ExtraResolver - placed_by: dict[str, tuple[str, str, str]] = {} + placed_kind: dict[str, str] = {} placed = _mark_hidden( config, undeclared, emulators_dir, db, emu_profiles, occupied, target_cores, region_drops, report_for_builder, @@ -1341,20 +1341,15 @@ def verify_platform( db, DEFAULT_BIOS_DIR, zip_contents, data_dir_registry, validation_index, validation_owners, ), - placed_by, + placed_kind, ) # What the builder places from the cores, one per pack path, by category: # several entries claim one path (rom.key from three Amiga cores) and the - # builder ships one file there. - categories = { - (u.get("emulator", ""), u.get("name", ""), u.get("path") or ""): - u.get("category", "bios") - for u in report_for_builder - } + # builder ships one file there. The category travels on the extra: a copy + # the second pass makes has no gap report entry to look it up by. packed_extras = {"bios": 0, "game_data": 0} - for key in placed_by.values(): - kind = "game_data" if categories.get(key) == "game_data" else "bios" - packed_extras[kind] += 1 + for category in placed_kind.values(): + packed_extras["game_data" if category == "game_data" else "bios"] += 1 if not reads_file_contents(mode): # An existence frontend is satisfied by any file at the path, and the # builder fills a free platform destination with a core's file. diff --git a/tests/test_pack_counts.py b/tests/test_pack_counts.py index be858eff..b4141b24 100644 --- a/tests/test_pack_counts.py +++ b/tests/test_pack_counts.py @@ -517,5 +517,41 @@ class TheReleaseNotesReadTheRecord(unittest.TestCase): self.assertIn("scripts/release_notes.py", page) +class CoreCopiesKeepTheirCategory(unittest.TestCase): + """The second pass copies a file the platform declares to the path a core + reads it from. Its extra named no gap report entry, so verify looked its + category up by an empty key and counted RetroArch's six hiscore.dat + copies, game data, under core files.""" + + def test_a_copy_counts_as_its_entry_says(self): + from packextras import _collect_emulator_extras, hidden_extras + + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + held = Path(tmp) / "x.dat" + held.write_bytes(b"data") + sha1 = hashlib.sha1(b"data").hexdigest() + config = {"platform": "P", "_key": "p", "cores": ["demo"], "systems": { + "s": {"files": [{"name": "x.dat", "destination": "x.dat"}]}}} + profiles = {"demo": { + "emulator": "Demo", "type": "libretro", "cores": ["demo"], + "systems": ["s"], + "files": [{"name": "x.dat", "path": "core/x.dat", + "category": "game_data"}], + }} + db = {"files": {sha1: {"path": str(held), "name": "x.dat", "sha1": sha1}}, + "indexes": {"by_name": {"x.dat": [sha1]}, "by_path_suffix": {}}} + (extra,) = _collect_emulator_extras( + config, "emulators", db, {"x.dat"}, "", profiles + ) + self.assertEqual( + (extra["source_name"], extra["source_path"], extra["category"]), + ("x.dat", "core/x.dat", "game_data"), + ) + kinds: dict[str, str] = {} + hidden_extras(config, "emulators", db, profiles, {"x.dat": str(held)}, + report=[], placed_kind=kinds) + self.assertEqual(kinds, {"core/x.dat": "game_data"}) + + if __name__ == "__main__": unittest.main()