mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-16 16:33:27 -05:00
fix: settle gaps by slot and size as verify does
This commit is contained in:
1 parent
b173a2a851
commit
be7a357999
6 files changed
+142
-58
No files matched your search
@@ -1829,6 +1829,17 @@ def declared_root(config: dict) -> str:
|
||||
return root if count / len(roots) > 0.9 else ""
|
||||
|
||||
|
||||
def merged_platform_declarations(
|
||||
configs: list[dict], db: dict
|
||||
) -> dict[str, list[tuple[int | None, str]]]:
|
||||
"""`platform_declarations` of several platforms, one index."""
|
||||
merged: dict[str, list[tuple[int | None, str]]] = {}
|
||||
for config in configs:
|
||||
for name, places in platform_declarations(config, db).items():
|
||||
merged.setdefault(name, []).extend(places)
|
||||
return merged
|
||||
|
||||
|
||||
def expand_platform_declared_names(config: dict, db: dict) -> set[str]:
|
||||
"""The names a platform declares, enriched as `platform_declarations`."""
|
||||
return set(platform_declarations(config, db))
|
||||
|
||||
+20
-19
@@ -32,7 +32,7 @@ from common import (
|
||||
resolve_local_file,
|
||||
runs_standalone,
|
||||
)
|
||||
from validation import outside_gap_scope
|
||||
from validation import declaration_settles, outside_gap_scope
|
||||
|
||||
yaml = require_yaml()
|
||||
|
||||
@@ -70,23 +70,27 @@ def load_platform_files(
|
||||
return declared, platform_data_dirs
|
||||
|
||||
|
||||
def declared_names(platforms_dir: str, platforms: list[str] | None, db: dict) -> set[str]:
|
||||
"""Every name the platforms declare, under every spelling the database knows.
|
||||
def declared_names(
|
||||
platforms_dir: str, platforms: list[str] | None, db: dict
|
||||
) -> dict[str, list[tuple[int | None, str]]]:
|
||||
"""Every name the platforms declare, with each declaration's size and slot.
|
||||
|
||||
The report judged "undeclared" per normalized system id, which does not
|
||||
bring bk and elektronika-bk, or nintendo-sgb and nintendo-super-game-boy,
|
||||
together: 646 entries whose exact name a platform declares were counted
|
||||
undeclared, while verify and the site read one flat set.
|
||||
undeclared, while verify and the site read one flat index.
|
||||
"""
|
||||
from common import expand_platform_declared_names
|
||||
from common import merged_platform_declarations
|
||||
|
||||
names: set[str] = set()
|
||||
for platform_name in platforms or list_registered_platforms(
|
||||
platforms_dir, include_archived=True
|
||||
):
|
||||
config = load_platform_config(platform_name, platforms_dir)
|
||||
names.update(expand_platform_declared_names(config, db))
|
||||
return names
|
||||
return merged_platform_declarations(
|
||||
[
|
||||
load_platform_config(platform_name, platforms_dir)
|
||||
for platform_name in platforms or list_registered_platforms(
|
||||
platforms_dir, include_archived=True
|
||||
)
|
||||
],
|
||||
db,
|
||||
)
|
||||
|
||||
|
||||
def _build_supplemental_index(
|
||||
@@ -405,10 +409,7 @@ def _cross_reference_profile(
|
||||
# Check platform declaration: by name, by an alias the core answers
|
||||
# to (RetroArch lists amiberry's cd32.rom as kick40060.CD32), or by
|
||||
# archive.
|
||||
answers = {fname, *f.get("aliases", [])}
|
||||
in_platform = bool(answers & platform_names)
|
||||
if not in_platform and archive:
|
||||
in_platform = archive in platform_names
|
||||
in_platform = declaration_settles(f, fname, platform_names, effective_path)
|
||||
|
||||
if in_platform:
|
||||
seen_files.add(seen_key)
|
||||
@@ -506,7 +507,7 @@ def cross_reference(
|
||||
db: dict,
|
||||
platform_data_dirs: dict[str, set[str]] | None = None,
|
||||
data_names: set[str] | None = None,
|
||||
all_declared: set[str] | None = None,
|
||||
all_declared: dict[str, list[tuple[int | None, str]]] | None = None,
|
||||
standalone_cores: set[str] | None = None,
|
||||
) -> dict:
|
||||
"""Compare emulator profiles against platform declarations.
|
||||
@@ -519,8 +520,8 @@ def cross_reference(
|
||||
|
||||
The boolean ``in_repo`` is derived: ``source != "missing"``.
|
||||
|
||||
When *all_declared* is provided (flat set of every filename declared by
|
||||
any platform for any system), it is used for the ``in_platform`` check
|
||||
When *all_declared* is provided (every filename declared by any platform
|
||||
for any system, with its sizes and slots), it is used for the ``in_platform`` check
|
||||
instead of the per-system lookup. This is appropriate for the global
|
||||
gap analysis page where "undeclared" means "no platform declares it at all".
|
||||
"""
|
||||
|
||||
@@ -530,15 +530,17 @@ def build_emulator_gap_report(
|
||||
The gap analysis page and the published gaps export must not compute this
|
||||
twice and drift; both read this one report.
|
||||
"""
|
||||
from common import _norm_system_id, expand_platform_declared_names
|
||||
from common import _norm_system_id, merged_platform_declarations
|
||||
from cross_reference import cross_reference as run_cross_reference
|
||||
|
||||
all_declared: set[str] = set()
|
||||
# Enriched with alias resolution, with each declaration's size and slot:
|
||||
# a name alone settled DOSBox's 32 KiB SC-55 ROM1.BIN on Galaksija's.
|
||||
all_declared = merged_platform_declarations(
|
||||
[cov["config"] for cov in coverages.values()], db
|
||||
)
|
||||
declared: dict[str, set[str]] = {}
|
||||
for _name, cov in coverages.items():
|
||||
config = cov["config"]
|
||||
# Enrich with alias resolution (MD5 -> SHA1 -> canonical name + aliases)
|
||||
all_declared.update(expand_platform_declared_names(config, db))
|
||||
for sys_id, system in config.get("systems", {}).items():
|
||||
for fe in system.get("files", []):
|
||||
fname = fe.get("name", "")
|
||||
|
||||
+50
-1
@@ -8,8 +8,9 @@ truth data for reporting.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
|
||||
from common import compute_hashes, size_fits
|
||||
from common import compute_hashes, name_match_size_ok, sanitize_pack_path, size_fits
|
||||
from hashing import parse_md5_list
|
||||
from nativemode import digest_algorithm, hash_mismatch_excludes_file
|
||||
from ziptools import check_inside_zip
|
||||
@@ -487,6 +488,54 @@ def outside_gap_scope(file_entry: dict, is_standalone: bool) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def declaration_settles(
|
||||
file_entry: dict,
|
||||
fname: str,
|
||||
declared_names: Mapping[str, list[tuple[int | None, str]]] | set[str],
|
||||
dest: str = "",
|
||||
) -> bool:
|
||||
"""Whether a platform declaration meets a core's requirement.
|
||||
|
||||
A platform declaring the name the core reads has met the requirement
|
||||
where the declaration fills the entry's own slot, whose content the slot
|
||||
arbitration decides, or elsewhere at a size the core accepts: the builder
|
||||
copies that file to the core's path. Galaksija's 4 KiB ROM1.BIN does not
|
||||
stand for DOSBox's 32 KiB SC-55 one. The builder copies nothing for an
|
||||
alias, so an alias answers only where the core reads it: quasi88 reads
|
||||
n88sub.rom or disk.rom in quasi88/, and System.dat names quasi88/disk.rom.
|
||||
RetroDECK's root MSX.ROM is not CLK's MSX/MSX.ROM, and RetroBat's 16 KiB
|
||||
MSX DISK.ROM at the root is not the 256-byte Disk II ROM gsplus also
|
||||
accepts as c600.rom. The archive answers by its name. A bare set of names
|
||||
carries no slot or size and answers by name.
|
||||
|
||||
Read by verify and by the gap report, which settled on the name alone
|
||||
and left DOSBox's SC-55 ROMs off the gaps page.
|
||||
"""
|
||||
if file_entry.get("archive") in declared_names:
|
||||
return True
|
||||
slot = sanitize_pack_path(dest or fname).lower()
|
||||
directory = slot if dest.endswith("/") else slot.rpartition("/")[0]
|
||||
for name in (fname, *file_entry.get("aliases", [])):
|
||||
if name not in declared_names:
|
||||
continue
|
||||
if not isinstance(declared_names, Mapping):
|
||||
return True
|
||||
if name == fname:
|
||||
if any(
|
||||
where == slot or name_match_size_ok(file_entry, size)
|
||||
for size, where in declared_names[name]
|
||||
):
|
||||
return True
|
||||
continue
|
||||
read_at = f"{directory}/{name}".lower() if directory else name.lower()
|
||||
if any(
|
||||
where == read_at and name_match_size_ok(file_entry, size)
|
||||
for size, where in declared_names[name]
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def filter_files_by_mode(files: list[dict], standalone: bool) -> list[dict]:
|
||||
"""Filter file entries by libretro/standalone mode."""
|
||||
result = []
|
||||
|
||||
+5
-34
@@ -73,6 +73,7 @@ from nativemode import (
|
||||
reads_file_contents,
|
||||
)
|
||||
from validation import (
|
||||
declaration_settles,
|
||||
existence_discrepancy,
|
||||
destination_owners,
|
||||
validated_choice,
|
||||
@@ -414,40 +415,10 @@ def _candidate_verdict(
|
||||
return "settled"
|
||||
if outside_gap_scope(file_entry, is_standalone):
|
||||
return "skip"
|
||||
if not include_all:
|
||||
# A platform declaring the name the core reads has met the
|
||||
# requirement where the declaration fills the entry's own slot,
|
||||
# whose content the slot arbitration decides, or elsewhere at a size
|
||||
# the core accepts: the builder copies that file to the core's path.
|
||||
# Galaksija's 4 KiB ROM1.BIN does not stand for DOSBox's 32 KiB SC-55
|
||||
# one. The builder copies nothing for an alias, so an alias answers
|
||||
# only where the core reads it: quasi88 reads n88sub.rom or disk.rom
|
||||
# in quasi88/, and System.dat names quasi88/disk.rom. RetroDECK's
|
||||
# root MSX.ROM is not CLK's MSX/MSX.ROM, and RetroBat's 16 KiB MSX
|
||||
# DISK.ROM at the root is not the 256-byte Disk II ROM gsplus also
|
||||
# accepts as c600.rom. The archive answers by its name.
|
||||
if file_entry.get("archive") in declared_names:
|
||||
return "settled"
|
||||
slot = sanitize_pack_path(dest or fname).lower()
|
||||
directory = slot if dest.endswith("/") else slot.rpartition("/")[0]
|
||||
for name in (fname, *file_entry.get("aliases", [])):
|
||||
if name not in declared_names:
|
||||
continue
|
||||
if not isinstance(declared_names, Mapping):
|
||||
return "settled"
|
||||
if name == fname:
|
||||
if any(
|
||||
where == slot or name_match_size_ok(file_entry, size)
|
||||
for size, where in declared_names[name]
|
||||
):
|
||||
return "settled"
|
||||
continue
|
||||
read_at = f"{directory}/{name}".lower() if directory else name.lower()
|
||||
if any(
|
||||
where == read_at and name_match_size_ok(file_entry, size)
|
||||
for size, where in declared_names[name]
|
||||
):
|
||||
return "settled"
|
||||
if not include_all and declaration_settles(
|
||||
file_entry, fname, declared_names, dest
|
||||
):
|
||||
return "settled"
|
||||
return "keep"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""The gap report settles a core file as verify does: slot or size.
|
||||
|
||||
cross_reference counted a core file declared as soon as a platform named
|
||||
it. RetroArch names Galaksija's 4 KiB galaksija/rom1.bin, so DOSBox's
|
||||
32 KiB SC-55/ROM1.BIN left the gaps page while verify listed it undeclared.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
from cross_reference import cross_reference # noqa: E402
|
||||
from validation import declaration_settles # noqa: E402
|
||||
|
||||
SC55 = {"name": "ROM1.BIN", "path": "SC-55/ROM1.BIN", "size": 32768,
|
||||
"validation": ["size"], "required": True}
|
||||
|
||||
|
||||
class OneRule(unittest.TestCase):
|
||||
def test_a_same_named_file_of_another_size_does_not_settle(self):
|
||||
declared = {"ROM1.BIN": [(4096, "galaksija/rom1.bin")]}
|
||||
self.assertFalse(declaration_settles(SC55, "ROM1.BIN", declared, SC55["path"]))
|
||||
declared["ROM1.BIN"].append((32768, "rom1.bin"))
|
||||
self.assertTrue(declaration_settles(SC55, "ROM1.BIN", declared, SC55["path"]))
|
||||
|
||||
def test_the_gap_report_reads_it(self):
|
||||
profiles = {"dosbox": {"emulator": "DOSBox", "type": "libretro",
|
||||
"systems": ["dos"], "files": [SC55]}}
|
||||
report = cross_reference(
|
||||
profiles, {}, {"files": {}, "indexes": {}},
|
||||
all_declared={"ROM1.BIN": [(4096, "galaksija/rom1.bin")]},
|
||||
)
|
||||
self.assertEqual(
|
||||
[g["name"] for g in report["dosbox"]["gap_details"]], ["ROM1.BIN"]
|
||||
)
|
||||
|
||||
def test_both_sides_call_it(self):
|
||||
for module in ("verify.py", "cross_reference.py"):
|
||||
source = (REPO_ROOT / "scripts" / module).read_text(encoding="utf-8")
|
||||
self.assertIn("declaration_settles(", source, module)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user