fix: add a retropie file to its own system's list

Only the first BIOS clause of a help string was read, so naomi2.zip and
segasp.zip went into flycast's Dreamcast sentence. A file now joins the
clause whose names the platform files under its system, or is not added.
This commit is contained in:
Abdessamad Derraz committed 2026-11-05 11:43:54 +01:00
1 parent 4e5b48ddbe
commit a434c1a8a0
2 files changed
+96 -20

No files matched your search

+62 -20
View File
@@ -156,6 +156,30 @@ class Exporter(BaseExporter):
@classmethod
def _enumeration(cls, help_text: str) -> tuple[int, int, list[str]] | None:
"""The first BIOS clause's list of names to extend, or None."""
clauses = cls._clauses(help_text)
return clauses[0][2] if clauses else None
@classmethod
def _clauses(
cls, help_text: str
) -> list[tuple[int, list[str], tuple[int, int, list[str]] | None]]:
"""Each BIOS clause that names files: its start, its names, and the
list to extend or None when it cannot be extended.
flycast's help has a Dreamcast clause and a Naomi/Atomiswave one.
Only the first was read, and naomi2.zip and segasp.zip were added to
the Dreamcast list.
"""
found = []
for clause in _BIOS_CLAUSE.finditer(help_text):
names = [m.group(0) for m in cls._names_in(clause.group(0))]
if names:
found.append((clause.start(), names, cls._extendable(clause)))
return found
@classmethod
def _extendable(cls, clause: re.Match[str]) -> tuple[int, int, list[str]] | None:
"""The list of names to extend: its span and its names, or None.
A list of alternatives ("a or b", "a/b") is not an enumeration: an
@@ -164,9 +188,6 @@ class Exporter(BaseExporter):
are a list of examples ("eg. a, b") and one with words between its
names, which a rewrite would lose.
"""
clause = _BIOS_CLAUSE.search(help_text)
if clause is None:
return None
text = clause.group(0)
names = cls._names_in(text)
if not names:
@@ -308,13 +329,12 @@ class Exporter(BaseExporter):
by_core = self._files_by_core(systems)
# Aliases count as names we know: RetroPie writes dc_flash.bin where
# flycast's profile files it as an alias of another primary.
known: set[str] = set()
systems_of: dict[str, set[str]] = {}
for system in systems.values():
for fe in system.files:
known.add(fe.name.lower())
known.update(
str(a).lower() for a in (fe.native("aliases", []) or [])
)
for name in [fe.name, *(fe.native("aliases", []) or [])]:
systems_of.setdefault(str(name).lower(), set()).add(fe.native_system)
known = set(systems_of)
self._skipped: dict[str, list[str]] = {}
self._named: set[int] = set()
produced: dict[str, str] = {}
@@ -362,30 +382,52 @@ class Exporter(BaseExporter):
and (slot := self._slot_of(fe)) is not None
}
proposed = self._one_per_slot(candidates, filled)
missing = self._in_search_order(proposed)
if not missing:
if not proposed:
continue
enumeration = self._enumeration(help_text)
if enumeration is None:
# No enumeration to extend, and a sentence we would have to
# write ourselves is a documentation change, not a correction.
skip("whose help text lists no files to add to", module_id)
# A file joins the list of its own system: the clause whose
# names the platform files under that system, and only one.
clauses = self._clauses(help_text)
homes: dict[int, list[NativeFile]] = {}
homeless = False
for fe in proposed:
owners = [
i for i, (_, names, _) in enumerate(clauses)
if fe.native_system in set().union(
*(systems_of.get(n.lower(), set()) for n in names))
]
if len(owners) == 1 and clauses[owners[0]][2] is not None:
homes.setdefault(owners[0], []).append(fe)
else:
homeless = True
if homeless:
# No enumeration of that system to extend, and a sentence we
# would have to write ourselves is a documentation change,
# not a correction.
skip("whose help text lists no files of that system to add to", module_id)
if not homes:
continue
if len(listed) + len(missing) > self._longest:
added = sum(len(self._in_search_order(fes)) for fes in homes.values())
if len(listed) + added > self._longest:
skip("whose help text would grow longer than any it has today", module_id)
continue
new_help = self._extended(help_text, enumeration, missing)
if len(listed) + len(missing) > 1:
new_help = _BIOS_NOUN.sub(r"\1 files", new_help, count=1)
new_help = help_text
for i in sorted(homes, reverse=True):
start, _, enumeration = clauses[i]
missing = self._in_search_order(homes[i])
new_help = self._extended(new_help, enumeration, missing)
if len(enumeration[2]) + len(missing) > 1:
new_help = new_help[:start] + _BIOS_NOUN.sub(
r"\1 files", new_help[start:enumeration[0]], count=1
) + new_help[enumeration[0]:]
self._named.update(id(fe) for fe in homes[i])
produced[relative] = originals[relative].replace(
f'rp_module_help="{help_text}"',
f'rp_module_help="{new_help}"',
1,
)
self._named.update(id(fe) for fe in proposed)
return produced
+34
View File
@@ -737,6 +737,40 @@ class SearchOrder(unittest.TestCase):
self.assertNotIn("us_scd2_9306", produced)
self.assertNotIn("jp_mcd1_9112", produced)
def test_a_file_joins_the_list_of_its_own_system(self):
"""flycast's help names the Dreamcast BIOS, then the Naomi/Atomiswave
ones. naomi2.zip, an arcade archive, went into the Dreamcast list:
only the first BIOS clause was read."""
from exporter.baseline import NativeSystem # noqa: PLC0415
def entry(name, system):
return NativeFile(name=name, destination=f"dc/{name}", native_system=system,
truth={"name": name, "required": True, "md5": "0" * 32,
"_cores": ["flycast"], "_required_by": ["flycast"]})
dc, arcade = "Sega - Dreamcast", "Sega - Dreamcast-based Arcade"
systems = {
dc: NativeSystem(dc, files=[entry("dc_boot.bin", dc)]),
arcade: NativeSystem(arcade, files=[entry("naomi.zip", arcade),
entry("awbios.zip", arcade),
entry("naomi2.zip", arcade)]),
"other": NativeSystem("other", files=[entry("other.zip", "other")]),
}
script = ('rp_module_id="lr-flycast"\nrp_module_help="Copy the required Dreamcast '
'BIOS file dc_boot.bin to $biosdir/dc\\n\\nCopy the required '
'Naomi/Atomiswave BIOS files naomi.zip and awbios.zip to $biosdir/dc"\n')
longest = ('rp_module_id="xm7"\nrp_module_help="Copy bios files '
+ ", ".join(f"n{i}.rom" for i in range(12)) + ' and n12.rom to $biosdir"\n')
exporter = discover_exporters()["retropie"]()
produced = exporter.render(
systems, None,
{"scriptmodules/libretrocores/lr-flycast.sh": script,
"scriptmodules/emulators/xm7.sh": longest},
)["scriptmodules/libretrocores/lr-flycast.sh"]
self.assertIn("Dreamcast BIOS file dc_boot.bin to", produced)
self.assertIn("BIOS files naomi.zip, awbios.zip and naomi2.zip to", produced)
self.assertNotIn("other.zip", produced)
def test_cores_that_disagree_keep_the_best_rank(self):
"""Nothing is dropped on the rank here, so the best one wins.