mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
fix: compare pack paths, not only counts
Step 5 compared (ok, total) and the core file count. RetroArch, Lakka and RetroPie read 526/526 on both sides while verify credited four paths below SGB1.sfc and SGB2.sfc that the pack never writes: a declared file blocks them as a directory. verify now places the platform files in the builder's order with the same path rules, and a user_provided or external declaration keeps its slot on both sides, so no core file takes it in the report only. verify --destinations writes the paths each pack holds by the report, and step 5 compares them with the archive members (notes and data tree members set aside). Every emulator profile, in each build it has, is built in memory (PackRecord, nothing read or written) and compared with verify --emulator path by path. Cost measured here: about 1 s per platform archive and 11 s for the 930 emulator packs.
This commit is contained in:
1 parent
fb639647e6
commit
38af19091d
8 files changed
+509
-25
No files matched your search
+90
-5
@@ -30,6 +30,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
|
||||
import slots
|
||||
|
||||
@@ -859,7 +860,7 @@ def _mark_hidden(
|
||||
emulators_dir: str,
|
||||
db: dict,
|
||||
emu_profiles: dict | None,
|
||||
shipped: dict[str, str],
|
||||
shipped: dict[str, str | None],
|
||||
target_cores: set[str] | None,
|
||||
region_drops: set[str] | None = None,
|
||||
report: list[dict] | None = None,
|
||||
@@ -891,6 +892,39 @@ def _mark_hidden(
|
||||
return placed
|
||||
|
||||
|
||||
def _builder_placement(
|
||||
packed_order: list[tuple[str, str, str | None]], case_insensitive: bool,
|
||||
) -> tuple[dict[str, str], dict[str, str | None]]:
|
||||
"""The platform files the builder writes, and every path it keeps.
|
||||
|
||||
The builder walks the systems in name order and skips a path that a
|
||||
written file blocks as a directory, or the reverse: RetroArch declares
|
||||
SGB1.sfc and SGB1.sfc/program.rom, and only the first is in the pack.
|
||||
Returns (path -> local copy written there, path -> copy or None for each
|
||||
slot kept, a user_provided or external one included).
|
||||
"""
|
||||
from packpaths import _has_path_conflict, _register_path
|
||||
|
||||
shipped: dict[str, str] = {}
|
||||
occupied: dict[str, str | None] = {}
|
||||
seen: set[str] = set()
|
||||
folded: set[str] = set()
|
||||
parents: set[str] = set()
|
||||
for _sys_id, full, local in sorted(packed_order, key=lambda item: item[0]):
|
||||
if (
|
||||
full in occupied
|
||||
or (case_insensitive and full.lower() in folded)
|
||||
or _has_path_conflict(full, seen, parents)
|
||||
):
|
||||
continue
|
||||
_register_path(full, seen, parents)
|
||||
folded.add(full.lower())
|
||||
occupied[full] = local
|
||||
if local is not None:
|
||||
shipped[full] = local
|
||||
return shipped, occupied
|
||||
|
||||
|
||||
def _dest_key(file_entry: dict, sys_id: str) -> str:
|
||||
"""The path a declaration occupies, as the builder keys its pack."""
|
||||
dest = sanitize_pack_path(file_entry.get("destination", file_entry.get("name", "")))
|
||||
@@ -1111,7 +1145,10 @@ def verify_platform(
|
||||
verify_systems, db, base_dest, zip_contents
|
||||
)
|
||||
|
||||
shipped: dict[str, str] = {}
|
||||
# (system, full destination, local copy) of each declaration the builder
|
||||
# writes, and None for one whose slot it keeps without a file from the
|
||||
# collection (user_provided, external). Placed below in the builder's order.
|
||||
packed_order: list[tuple[str, str, str | None]] = []
|
||||
shipped_keys: set[str] = set()
|
||||
results_at: dict[str, list[dict]] = {}
|
||||
for sys_id, system in verify_systems.items():
|
||||
@@ -1135,6 +1172,12 @@ def verify_platform(
|
||||
if stored is not None:
|
||||
result = stored
|
||||
local_path = None
|
||||
# The builder keeps the slot: instructions for a user_provided
|
||||
# file, the download for an external one, whether it arrives
|
||||
# or not. No core file goes there.
|
||||
packed_order.append((
|
||||
sys_id, f"{base_dest}/{destination}" if base_dest else destination, None,
|
||||
))
|
||||
elif not reads_file_contents(mode):
|
||||
result = verify_entry_existence(
|
||||
file_entry,
|
||||
@@ -1179,10 +1222,11 @@ def verify_platform(
|
||||
file_entry, local_path, resolve_status, mode, config.get("hash_rule", "")
|
||||
)[1] is None
|
||||
):
|
||||
shipped.setdefault(
|
||||
packed_order.append((
|
||||
sys_id,
|
||||
f"{base_dest}/{destination}" if base_dest else destination,
|
||||
local_path,
|
||||
)
|
||||
))
|
||||
shipped_keys.add(_dest_key(file_entry, sys_id))
|
||||
if _twin_unmet(result, held_by):
|
||||
continue
|
||||
@@ -1212,6 +1256,10 @@ def verify_platform(
|
||||
):
|
||||
file_severity[dest] = sev
|
||||
|
||||
shipped, occupied = _builder_placement(
|
||||
packed_order, bool(config.get("case_insensitive_fs", False))
|
||||
)
|
||||
|
||||
# Cross-reference undeclared files, called exactly as the builder calls
|
||||
# it: its declarations, not the enriched ones (a core file the platform
|
||||
# also declares under another name is packed at the core's own path, 21
|
||||
@@ -1243,7 +1291,7 @@ def verify_platform(
|
||||
|
||||
placed_by: dict[str, tuple[str, str, str]] = {}
|
||||
placed = _mark_hidden(
|
||||
config, undeclared, emulators_dir, db, emu_profiles, shipped, target_cores,
|
||||
config, undeclared, emulators_dir, db, emu_profiles, occupied, target_cores,
|
||||
region_drops, report_for_builder,
|
||||
ExtraResolver(
|
||||
db, DEFAULT_BIOS_DIR, zip_contents, data_dir_registry, validation_index,
|
||||
@@ -1323,10 +1371,23 @@ def verify_platform(
|
||||
)
|
||||
total = len(file_status)
|
||||
|
||||
# The paths a pack built from this report holds, as written in the ZIP
|
||||
# (base_destination stripped, as the builder flattens it). The pipeline
|
||||
# compares this set with the archive: equal counts can hide one file
|
||||
# credited here and another written there.
|
||||
from packpaths import _flat
|
||||
|
||||
pack_destinations = sorted(
|
||||
{_flat(dest, base_dest, True) for dest in shipped} | {
|
||||
_flat(dest, base_dest, True) for dest in placed
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"platform": platform,
|
||||
"verification_mode": mode,
|
||||
"total_files": total,
|
||||
"pack_destinations": pack_destinations,
|
||||
"severity_counts": counts,
|
||||
"status_counts": status_counts,
|
||||
"excluded_files": excluded,
|
||||
@@ -2091,6 +2152,11 @@ def verify_emulator(
|
||||
"emulators": [n for n, _ in selected],
|
||||
"verification_mode": label,
|
||||
"total_files": total,
|
||||
# The paths the emulator pack fills with the profile's files, for the
|
||||
# pipeline's comparison with the pack built in memory.
|
||||
"pack_destinations": sorted(
|
||||
dest for dest, status in file_status.items() if status == Status.OK
|
||||
),
|
||||
"severity_counts": counts,
|
||||
"status_counts": status_counts,
|
||||
"details": details,
|
||||
@@ -2271,6 +2337,7 @@ def _refuse_listing_narrowings(
|
||||
("--json", args.json),
|
||||
("--strict", args.strict),
|
||||
("--verbose", args.verbose),
|
||||
("--destinations", args.destinations),
|
||||
):
|
||||
if on:
|
||||
parser.error(f"{flag} is incompatible with {listing}")
|
||||
@@ -2286,6 +2353,10 @@ def _refuse_mode_flags(args: argparse.Namespace, parser: argparse.ArgumentParser
|
||||
"--target requires --platform or --all"),
|
||||
(args.target and (args.emulator or args.system),
|
||||
"--target is incompatible with --emulator and --system"),
|
||||
(args.destinations and not (args.platform or args.all),
|
||||
"--destinations requires --platform or --all"),
|
||||
(args.destinations and getattr(args, "region", None),
|
||||
"--destinations describes the full pack and is incompatible with --region"),
|
||||
):
|
||||
if refused:
|
||||
parser.error(message)
|
||||
@@ -2363,6 +2434,11 @@ def _main(held: contextlib.ExitStack) -> None:
|
||||
help="Show emulator ground truth details",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="JSON output")
|
||||
parser.add_argument(
|
||||
"--destinations", metavar="FILE",
|
||||
help="With --platform or --all, also write the paths each pack holds "
|
||||
"according to this report to FILE (JSON), for comparison with the archive",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strict",
|
||||
action="store_true",
|
||||
@@ -2494,6 +2570,15 @@ def _main(held: contextlib.ExitStack) -> None:
|
||||
if len(group_results) > 1:
|
||||
print(_overall_summary(group_results))
|
||||
|
||||
if args.destinations:
|
||||
held = {}
|
||||
for (result, names), (group, _rep) in zip(group_results, groups):
|
||||
for name, key in zip(names, group):
|
||||
held[name] = {
|
||||
"platform": key, "pack_destinations": result["pack_destinations"],
|
||||
}
|
||||
Path(args.destinations).write_text(json.dumps(held, indent=1, sort_keys=True))
|
||||
|
||||
if args.json:
|
||||
for r in all_results.values():
|
||||
r["details"] = _json_details(r["details"], args.verbose)
|
||||
|
||||
Reference in new issue
Block a user