mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-12 21:13:49 -06:00
chore: merge resolver and profiles batch
This commit is contained in:
commit
afd062d111
50 files changed
+8205
-4632
No files matched your search
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections import Counter
|
||||
|
||||
import filecmp
|
||||
import hashlib
|
||||
import functools
|
||||
import json
|
||||
@@ -889,6 +890,38 @@ def _resolve_in_data_dirs(
|
||||
return None, mismatch
|
||||
|
||||
|
||||
def _data_dir_tail(
|
||||
dest_hint: str, data_dir_registry: dict, file_entry: dict, db: dict
|
||||
) -> str | None:
|
||||
"""The cached data-directory file at the destination's own tail.
|
||||
|
||||
The full destination first, then without its leading segment, never
|
||||
down to the bare name. A shortened tail two caches hold with different
|
||||
bytes proves nothing. The size and Adler-32 the code checks still bound
|
||||
the file, as on every name or path step without a hash.
|
||||
"""
|
||||
parts = dest_hint.split("/")
|
||||
for start in range(min(2, len(parts) - 1)):
|
||||
tail = "/".join(parts[start:])
|
||||
hits = [
|
||||
os.path.join(cache_dir, tail)
|
||||
for cache_dir in (
|
||||
dd_entry.get("local_cache", "") for dd_entry in data_dir_registry.values()
|
||||
)
|
||||
if cache_dir and os.path.isfile(os.path.join(cache_dir, tail))
|
||||
]
|
||||
if start and any(
|
||||
not filecmp.cmp(hits[0], other, shallow=False) for other in hits[1:]
|
||||
):
|
||||
continue
|
||||
for candidate in hits:
|
||||
if name_match_size_ok(
|
||||
file_entry, os.path.getsize(candidate)
|
||||
) and adler32_fits(file_entry, candidate, db):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
# Basename index per data-directory cache. Walking the whole tree again for
|
||||
# every unresolved name made resolution O(names x tree) on each pack build.
|
||||
_data_dir_index: dict[str, dict[str, list[tuple[int, str]]]] = {}
|
||||
@@ -1504,6 +1537,27 @@ def resolve_local_file(
|
||||
if status:
|
||||
return path, status
|
||||
|
||||
# 2c. The same tail in a data directory. by_path_suffix indexes bios/
|
||||
# only, so AmberELEC's Machines/MSX - Arabic/config.ini fell through to
|
||||
# the name step and got msx-emu's ColecoVision config.ini, while
|
||||
# data/bluemsx held blueMSX's own at that very path. A platform
|
||||
# declaration is resolved without a hint, and its destination is that
|
||||
# path. The descent and the ambiguity rule are those of the bios/ tails.
|
||||
# A profile's entry is not asked here: it names its owner, the name step
|
||||
# sends it to its data directories when another emulator's copy answers,
|
||||
# and a dump it declares outranks the free one its tree bundles at the
|
||||
# same path (Dolphin's dsp_coef.bin over Sys/GC's).
|
||||
tail_hint = dest_hint or str(file_entry.get("destination") or "")
|
||||
if (
|
||||
tail_hint
|
||||
and data_dir_registry
|
||||
and not has_strong_hash
|
||||
and not _owner_tokens(file_entry)
|
||||
):
|
||||
hit = _data_dir_tail(tail_hint, data_dir_registry, file_entry, db)
|
||||
if hit:
|
||||
return hit, "path_exact"
|
||||
|
||||
# 3. No MD5 = any file with that name or alias (existence check)
|
||||
def _size_ok(match_sha1: str) -> bool:
|
||||
record = files_db.get(match_sha1, {})
|
||||
@@ -2398,6 +2452,22 @@ def standalone_set(config: dict) -> set[str]:
|
||||
return names
|
||||
|
||||
|
||||
def merged_standalone_set(configs: Iterable[dict]) -> set[str]:
|
||||
"""The standalone set of several platforms read as one tree.
|
||||
|
||||
A per-core directory marker says which cores one platform hands a folder
|
||||
and that it hands the others none. Carried into the union, MinUI's 86
|
||||
markers left every core they do not list without a layout: the global
|
||||
gap report lost 238 profiles and 1201 files without a word.
|
||||
"""
|
||||
return {
|
||||
tag
|
||||
for config in configs
|
||||
for tag in standalone_set(config)
|
||||
if not tag.startswith(CORE_DIR_PREFIX)
|
||||
}
|
||||
|
||||
|
||||
def _handed(standalone_cores: Iterable[str]) -> list:
|
||||
"""The directories a standalone set carries, as launchers built them."""
|
||||
from launchers import HandedDirectory # noqa: PLC0415
|
||||
|
||||
@@ -1156,12 +1156,12 @@ def main() -> None:
|
||||
# The tree as every platform runs it: a profile some platform runs
|
||||
# standalone keeps its standalone entries. Laid out as libretro only,
|
||||
# eka2l1, dolphin and xenia-edge lost 133 files no platform declares.
|
||||
from common import list_registered_platforms
|
||||
from common import list_registered_platforms, merged_standalone_set
|
||||
|
||||
standalone_cores = set().union(*(
|
||||
standalone_set(load_platform_config(name, args.platforms_dir))
|
||||
standalone_cores = merged_standalone_set(
|
||||
load_platform_config(name, args.platforms_dir)
|
||||
for name in list_registered_platforms(args.platforms_dir, include_archived=True)
|
||||
))
|
||||
)
|
||||
|
||||
if not profiles:
|
||||
if emulator and args.target and emulator in run_anywhere:
|
||||
|
||||
+32
-8
@@ -106,6 +106,27 @@ def _settled_hashes(
|
||||
)
|
||||
|
||||
|
||||
def _emulator_tree(path: str) -> bool:
|
||||
"""Whether a path lies in one emulator's own tree, bios/Other/<emulator>/."""
|
||||
parts = path.replace("\\", "/").split("/")
|
||||
return len(parts) > 3 and parts[1] == "Other"
|
||||
|
||||
|
||||
def _takes_primary(current: str, candidate: str) -> bool:
|
||||
"""Whether another copy of the same bytes becomes the content's path.
|
||||
|
||||
A .variants/ copy yields to any other one. A copy in an emulator's own
|
||||
tree yields to one filed under its machine: ZEsarUX's sms.rom sorted
|
||||
before bios/Sega/Master System/, the resolver set it aside as another
|
||||
emulator's file, and Genesis Plus GX got a variant instead of v1.3.
|
||||
"""
|
||||
if "/.variants/" in candidate.replace("\\", "/"):
|
||||
return False
|
||||
if "/.variants/" in current:
|
||||
return True
|
||||
return _emulator_tree(current) and not _emulator_tree(candidate)
|
||||
|
||||
|
||||
def scan_bios_dir(bios_dir: Path, cache: dict, force: bool) -> tuple[dict, dict, dict]:
|
||||
"""Scan bios directory and compute hashes, using cache when possible."""
|
||||
files = {}
|
||||
@@ -135,10 +156,8 @@ def scan_bios_dir(bios_dir: Path, cache: dict, force: bool) -> tuple[dict, dict,
|
||||
if fresh and all(name in cached for name in CACHED_HASHES):
|
||||
hashes = {name: cached[name] for name in CACHED_HASHES}
|
||||
sha1 = hashes["sha1"]
|
||||
is_variant = "/.variants/" in rel_path or "\\.variants\\" in rel_path
|
||||
if sha1 in files:
|
||||
existing_is_variant = "/.variants/" in files[sha1]["path"]
|
||||
if existing_is_variant and not is_variant:
|
||||
if _takes_primary(files[sha1]["path"], rel_path):
|
||||
if sha1 not in aliases:
|
||||
aliases[sha1] = []
|
||||
aliases[sha1].append(
|
||||
@@ -169,11 +188,8 @@ def scan_bios_dir(bios_dir: Path, cache: dict, force: bool) -> tuple[dict, dict,
|
||||
|
||||
hashes = _settled_hashes(filepath, cache.get(cache_key), mtime, size)
|
||||
sha1 = hashes["sha1"]
|
||||
is_variant = "/.variants/" in rel_path or "\\.variants\\" in rel_path
|
||||
if sha1 in files:
|
||||
existing_is_variant = "/.variants/" in files[sha1]["path"]
|
||||
if existing_is_variant and not is_variant:
|
||||
# Non-variant file should be primary over .variants/ file
|
||||
if _takes_primary(files[sha1]["path"], rel_path):
|
||||
if sha1 not in aliases:
|
||||
aliases[sha1] = []
|
||||
aliases[sha1].append(
|
||||
@@ -520,8 +536,16 @@ def _collect_all_aliases(files: dict, copies: dict | None = None) -> dict:
|
||||
|
||||
aliases = {}
|
||||
|
||||
# A name only .variants/ copies carry is not a primary's: those copies are
|
||||
# the other revisions of whatever the name designates. Retroarch pins
|
||||
# bios_E.sms to v1.3, and with the name refused, Genesis Plus GX got the
|
||||
# first variant in index order, a 256 KB BIOS, by the name step.
|
||||
named_primaries = {
|
||||
entry["name"] for entry in files.values() if "/.variants/" not in entry["path"]
|
||||
}
|
||||
|
||||
def _add_alias(name: str, matched_sha1: str) -> None:
|
||||
if not name or name in name_to_sha1:
|
||||
if not name or name in named_primaries:
|
||||
return
|
||||
if matched_sha1 not in aliases:
|
||||
aliases[matched_sha1] = []
|
||||
|
||||
@@ -1159,7 +1159,7 @@ def build_emulator_gap_report(
|
||||
for k, v in profiles.items()
|
||||
if v.get("type") not in ("alias", "test")
|
||||
}
|
||||
from common import platform_declarations, standalone_set
|
||||
from common import merged_standalone_set, platform_declarations
|
||||
from validation import case_folded_slots
|
||||
|
||||
# The command line passes these too: without them a RetroBat file named
|
||||
@@ -1171,8 +1171,8 @@ def build_emulator_gap_report(
|
||||
return run_cross_reference(
|
||||
unique_profiles, declared, db,
|
||||
data_names=data_names, all_declared=all_declared,
|
||||
standalone_cores=set().union(
|
||||
*(standalone_set(cov["config"]) for cov in coverages.values())
|
||||
standalone_cores=merged_standalone_set(
|
||||
cov["config"] for cov in coverages.values()
|
||||
),
|
||||
folded_slots=folded,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user