fix: hold installer detection to the registry

This commit is contained in:
Abdessamad Derraz committed 2026-10-18 14:55:39 +02:00
1 parent ce2417ce0f
commit 3799d4a1a4
9 files changed
+139 -59

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 = "8faaf944ff4e0cc23e9049299145077567163837eaabdd65c530554980080ed7"
$defaultInstallSha256 = "236441baeb94f442c15a93ac01a203ce63f2938553b491db13dfb19074bbef46"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+26 -47
View File
@@ -621,55 +621,34 @@ def _parse_ps1_var(path: Path, key: str) -> str | None:
return None
# Embedded systems, first match wins: Knulli also carries the Batocera
# markers. The registry publishes the same rules as each manifest's
# `detect`; a test holds the two equal. MiSTer is a path rule below.
_EMBEDDED_RULES: tuple[tuple[str, str, tuple[str, ...], str], ...] = (
("rocknix", "os_release", ("rocknix",), "/storage/roms/bios"),
("batocera", "file_exists", ("/etc/knulli-release",), "/userdata/bios"),
("lakka", "os_release", ("lakka",), "/storage/system"),
("batocera", "file_exists", ("/etc/batocera-version",), "/userdata/bios"),
("recalbox", "file_exists", ("/recalbox/recalbox.version",), "/recalbox/share/bios"),
("recalbox", "file_exists", ("/usr/bin/recalbox-settings",), "/recalbox/share/bios"),
("retroarch", "file_exists", ("/opt/muos",), "/mnt/mmc/MUOS/bios"),
("retroarch", "file_exists", ("/mnt/mmc/MUOS",), "/mnt/mmc/MUOS/bios"),
("retroarch", "file_exists", ("/home/ark", "/opt/system"), "/roms/bios"),
("retroarch", "file_exists", ("/mnt/vendor/bin/dmenu.bin",), "/mnt/mmc/bios"),
)
def _detect_embedded() -> list[tuple[str, Path]]:
"""Check for embedded Linux retrogaming OSes."""
found: list[tuple[str, Path]] = []
os_release = _parse_os_release()
os_id = os_release.get("ID", "").lower()
if os_id == "rocknix":
found.append(("rocknix", Path("/storage/roms/bios")))
return found
# MiSTer keeps the SD card at /media/fat with the main binary at its root
# (Main_MiSTer file_io.cpp:1150-1156), and cores read from games/
# (file_io.h:168)
if Path("/media/fat/MiSTer").exists():
found.append(("misterfpga", Path("/media/fat/games")))
return found
if Path("/etc/knulli-release").exists():
found.append(("batocera", Path("/userdata/bios")))
return found
if os_id == "lakka":
found.append(("lakka", Path("/storage/system")))
return found
if Path("/etc/batocera-version").exists():
found.append(("batocera", Path("/userdata/bios")))
return found
if (
Path("/recalbox/recalbox.version").exists()
or Path("/usr/bin/recalbox-settings").exists()
):
found.append(("recalbox", Path("/recalbox/share/bios")))
return found
if Path("/opt/muos").exists() or Path("/mnt/mmc/MUOS/").exists():
found.append(("retroarch", Path("/mnt/mmc/MUOS/bios")))
return found
if Path("/home/ark").exists() and Path("/opt/system").exists():
found.append(("retroarch", Path("/roms/bios")))
return found
if Path("/mnt/vendor/bin/dmenu.bin").exists():
found.append(("retroarch", Path("/mnt/mmc/bios")))
return found
return found
os_id = _parse_os_release().get("ID", "").lower()
for platform_name, method, values, bios in _EMBEDDED_RULES:
if method == "os_release":
matched = os_id in values
else:
matched = all(Path(value).exists() for value in values)
if matched:
return [(platform_name, Path(bios))]
return []
def _android_storage_root() -> Path:
+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="8faaf944ff4e0cc23e9049299145077567163837eaabdd65c530554980080ed7"
DEFAULT_INSTALL_SHA256="236441baeb94f442c15a93ac01a203ce63f2938553b491db13dfb19074bbef46"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+6
View File
@@ -8,6 +8,12 @@
"generated": "2026-10-18T11:24:19Z",
"base_destination": "bios",
"detect": [
{
"os": "linux",
"method": "file_exists",
"file": "/etc/knulli-release",
"bios_path": "/userdata/bios"
},
{
"os": "linux",
"method": "file_exists",
+6
View File
@@ -8,6 +8,12 @@
"generated": "2026-10-18T11:24:19Z",
"base_destination": "bios",
"detect": [
{
"os": "linux",
"method": "file_exists",
"file": "/recalbox/recalbox.version",
"bios_path": "/recalbox/share/bios"
},
{
"os": "linux",
"method": "file_exists",
+27
View File
@@ -38,6 +38,33 @@
"method": "config_file",
"config": "%APPDATA%\\RetroArch\\retroarch.cfg",
"parse_key": "system_directory"
},
{
"os": "linux",
"method": "file_exists",
"file": "/opt/muos",
"bios_path": "/mnt/mmc/MUOS/bios"
},
{
"os": "linux",
"method": "file_exists",
"file": "/mnt/mmc/MUOS",
"bios_path": "/mnt/mmc/MUOS/bios"
},
{
"os": "linux",
"method": "file_exists",
"file": [
"/home/ark",
"/opt/system"
],
"bios_path": "/roms/bios"
},
{
"os": "linux",
"method": "file_exists",
"file": "/mnt/vendor/bin/dmenu.bin",
"bios_path": "/mnt/mmc/bios"
}
],
"standalone_copies": [
+2 -3
View File
@@ -10,9 +10,8 @@
"detect": [
{
"os": "linux",
"method": "file_exists",
"file": "/etc/os-release",
"match": "ID=rocknix",
"method": "os_release",
"id": "rocknix",
"bios_path": "/storage/roms/bios"
}
],
+29 -3
View File
@@ -187,6 +187,24 @@ platforms:
method: config_file
config: '%APPDATA%\RetroArch\retroarch.cfg'
parse_key: system_directory
# Handheld firmwares that run RetroArch as their frontend: muOS,
# ArkOS, and the stock Linux of Anbernic devices (dmenu).
- os: linux
method: file_exists
file: /opt/muos
bios_path: /mnt/mmc/MUOS/bios
- os: linux
method: file_exists
file: /mnt/mmc/MUOS
bios_path: /mnt/mmc/MUOS/bios
- os: linux
method: file_exists
file: [/home/ark, /opt/system]
bios_path: /roms/bios
- os: linux
method: file_exists
file: /mnt/vendor/bin/dmenu.bin
bios_path: /mnt/mmc/bios
standalone_copies: *standalone_copies
batocera:
config: batocera.yml
@@ -202,6 +220,11 @@ platforms:
target_source: https://github.com/batocera-linux/batocera.linux
install:
detect:
# Knulli is a Batocera derivative and carries its markers too.
- os: linux
method: file_exists
file: /etc/knulli-release
bios_path: /userdata/bios
- os: linux
method: file_exists
file: /etc/batocera-version
@@ -223,6 +246,10 @@ platforms:
target_source: https://gitlab.com/recalbox/recalbox/-/tree/master/configs
install:
detect:
- os: linux
method: file_exists
file: /recalbox/recalbox.version
bios_path: /recalbox/share/bios
- os: linux
method: file_exists
file: /usr/bin/recalbox-settings
@@ -388,9 +415,8 @@ platforms:
install:
detect:
- os: linux
method: file_exists
file: /etc/os-release
match: 'ID=rocknix'
method: os_release
id: rocknix
bios_path: /storage/roms/bios
retropie:
config: retropie.yml
+41 -4
View File
@@ -264,10 +264,15 @@ class TestEmbeddedDetection(unittest.TestCase):
install._parse_os_release = real_parse
def test_mister_detected_from_main_binary(self):
self.assertEqual(
self._detect_with({"/media/fat/MiSTer"}),
[("misterfpga", Path("/media/fat/games"))],
)
# A path rule of the registry, read by _detect_by_path.
real_exists = install.Path.exists
install.Path.exists = lambda self: str(self) == "/media/fat/MiSTer"
try:
found: list = []
install._detect_by_path("linux", found)
finally:
install.Path.exists = real_exists
self.assertEqual(found, [("misterfpga", Path("/media/fat/games"))])
def test_rocknix_wins_over_path_probes(self):
self.assertEqual(
@@ -940,6 +945,38 @@ class DetectionFollowsTheRegistry(unittest.TestCase):
}
self.assertEqual(set(install._PATH_RULES), rules)
def test_the_embedded_table_is_the_registry(self):
"""Only the path rules were compared: Knulli, muOS, ArkOS and the
Recalbox version file were detected by the installer and absent
from every manifest's `detect`, and ROCKNIX was declared another
way. A changed bios_path in the registry reached no installer."""
import yaml # noqa: PLC0415
registry = yaml.safe_load(
(Path(__file__).resolve().parents[1] / "platforms" / "_registry.yml").read_text()
)
rules = set()
for name, entry in registry["platforms"].items():
for rule in (entry.get("install") or {}).get("detect", []):
if rule["method"] == "os_release":
rules.add((name, "os_release", (rule["id"],), rule["bios_path"]))
elif rule["method"] == "file_exists":
files = rule["file"] if isinstance(rule["file"], list) else [rule["file"]]
rules.add((name, "file_exists", tuple(files), rule["bios_path"]))
self.assertEqual(set(install._EMBEDDED_RULES), rules)
def test_the_first_embedded_match_wins(self):
with unittest.mock.patch.object(install, "_parse_os_release", return_value={}), \
unittest.mock.patch.object(
Path, "exists",
lambda p: str(p) in ("/etc/knulli-release", "/etc/batocera-version"),
):
self.assertEqual(install._detect_embedded(), [("batocera", Path("/userdata/bios"))])
with unittest.mock.patch.object(install, "_parse_os_release", return_value={"ID": "ROCKNIX"}):
self.assertEqual(
install._detect_embedded(), [("rocknix", Path("/storage/roms/bios"))]
)
def _home(self) -> Path:
home = Path(_tempdir(self))
patcher = unittest.mock.patch.dict(os.environ, {"HOME": str(home), "USERPROFILE": str(home)})