fix: keep one case twin where case folds

This commit is contained in:
Abdessamad Derraz committed 2026-10-18 15:04:38 +02:00
1 parent 14f78ab634
commit 8f7b8c9dc9
5 files changed
+58 -4

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ param(
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "b163e90cc3101ce499e1f01899a398fbea27656054fb2fb9b31f07631d15f19a"
$defaultInstallSha256 = "133242b850a1e2dfa820f39b466c8b56a9363aee1cc8df1e9a1f9f28744a0cdc"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+31 -1
View File
@@ -1134,6 +1134,24 @@ def keeps_case(directory: Path) -> bool:
os.unlink(name)
def fold_case_twins(files: list[dict]) -> tuple[list[dict], list[str]]:
"""One file per name where the directory folds case; the others named.
The first in manifest order stays, as the pack's own order places it.
"""
kept: list[dict] = []
seen: set[str] = set()
folded: list[str] = []
for entry in files:
key = str(entry.get("dest", "")).casefold()
if key in seen:
folded.append(str(entry.get("dest", "")))
continue
seen.add(key)
kept.append(entry)
return kept, folded
def installed_cores(dirs: list[Path]) -> list[str]:
"""The cores present in these directories, by the name the buildbot uses.
@@ -3218,10 +3236,22 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
files = manifest.get("files", [])
omitted_files = manifest.get("omitted_files", [])
variants = manifest.get("case_variants", [])
if variants and keeps_case(bios_path):
case_kept = keeps_case(bios_path)
if variants and case_kept:
# The list is folded for Windows and macOS; here bios.col and
# BIOS.col are two files that two cores open.
files = files + variants
if not case_kept:
files, folded = fold_case_twins(files)
if folded:
# Batocera's share opened over SMB, an exFAT card: the second
# spelling overwrote the first, and every --check found one
# of them wrong and fetched it again.
shown = ", ".join(folded[:3]) + (" ..." if len(folded) > 3 else "")
print(
f" {_plural(len(folded), 'file')} left out: {bios_path} does not keep "
f"case and holds another file under the same name ({shown})"
)
if args.list_targets:
targets = fetch_targets(plat_name)
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="b163e90cc3101ce499e1f01899a398fbea27656054fb2fb9b31f07631d15f19a"
DEFAULT_INSTALL_SHA256="133242b850a1e2dfa820f39b466c8b56a9363aee1cc8df1e9a1f9f28744a0cdc"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+18 -1
View File
@@ -2245,7 +2245,7 @@ class CaseVariants(unittest.TestCase):
compact = " ".join(source.split())
self.assertIn(
'variants = manifest.get("case_variants", []) '
"if variants and keeps_case(bios_path):", compact,
"case_kept = keeps_case(bios_path) if variants and case_kept:", compact,
)
def test_the_published_retroarch_list_carries_them(self):
@@ -2318,5 +2318,22 @@ class TheBoardTheSystemNamesIsTheTarget(unittest.TestCase):
self.assertTrue(targets)
class CaseTwinsOnAFoldingVolume(unittest.TestCase):
"""Batocera and Recalbox list np2kai/bios.rom and np2kai/BIOS.ROM, other
bytes each. Installed to a share opened from Windows or an exFAT card,
the second overwrote the first and --check never converged."""
def test_one_file_per_name_is_kept(self):
files = [{"dest": "np2kai/bios.rom"}, {"dest": "np2kai/BIOS.ROM"}, {"dest": "x.bin"}]
kept, folded = install.fold_case_twins(files)
self.assertEqual([f["dest"] for f in kept], ["np2kai/bios.rom", "x.bin"])
self.assertEqual(folded, ["np2kai/BIOS.ROM"])
def test_main_folds_only_where_case_is_not_kept(self):
source = " ".join((REPO_ROOT / "install.py").read_text(encoding="utf-8").split())
self.assertIn("if not case_kept: files, folded = fold_case_twins(files)", source)
if __name__ == "__main__":
unittest.main()
+7
View File
@@ -95,6 +95,13 @@ BIOS directory keeps case and, if it does, also fetches these names. If you
extract the ZIP on Linux by hand, take the Lakka pack instead: it is the
RetroArch pack with both spellings.
The reverse happens on Batocera and Recalbox, whose lists hold both spellings
because their systems keep case: NP2kai reads `np2kai/bios.rom` and
`np2kai/BIOS.ROM` as two different files. Written to a folder that folds case
(the share opened from Windows, an exFAT card), one overwrites the other. The
installer checks the folder first, keeps one file per name there and lists
the ones it left out.
## Hash mismatch / UNTESTED
`verify.py` reports `UNTESTED` for a file.