diff --git a/install.ps1 b/install.ps1 index dc2e8869..2a57af8b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ param( $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -$defaultInstallSha256 = "431f7d05350e222a3f4746dd13116bf570ec0f80117eed47d747372c61b57d77" +$defaultInstallSha256 = "dc6b428278a4352923c19ddcf055fd994aac1df77f35cb1d097e8f4c5d887ae1" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 78611275..7dacd8aa 100644 --- a/install.py +++ b/install.py @@ -870,6 +870,47 @@ def detect_frontends(os_type: str, home: Path | None = None) -> list[str]: return frontends +# The registry's path_exists rules (platforms/_registry.yml install.detect), +# one row each: (os, platform, path, BIOS directory when it is not the path). +# The manifests publish them too, but detection runs before any manifest is +# fetched; a test holds this table equal to the registry. Without it a Linux +# home holding RetroPie/BIOS or .config/BizHawk/Firmware detected nothing. +_PATH_RULES: tuple[tuple[str, str, str, str | None], ...] = ( + ("windows", "retrobat", "C:\\RetroBat\\bios", None), + ("windows", "retrobat", "%USERPROFILE%\\RetroBat\\bios", None), + ("linux", "emudeck", "$HOME/Emulation/bios", None), + ("linux", "retrodeck", "$HOME/.var/app/net.retrodeck.retrodeck", "$HOME/retrodeck"), + ("linux", "romm", "/romm/library/bios", None), + ("linux", "retropie", "$HOME/RetroPie/BIOS", None), + ("windows", "bizhawk", "%USERPROFILE%\\BizHawk\\Firmware", None), + ("linux", "bizhawk", "$HOME/.config/BizHawk/Firmware", None), + ("linux", "misterfpga", "/media/fat/MiSTer", "/media/fat/games"), +) + + +def _rule_path(template: str) -> Path: + """A rule's path on this machine: $HOME or %USERPROFILE%, then the rest.""" + bases = ( + ("$HOME", Path.home()), + ("%USERPROFILE%", Path(os.environ.get("USERPROFILE") or Path.home())), + ) + for variable, base in bases: + if template.startswith(variable): + rest = [part for part in re.split(r"[\\/]", template[len(variable):]) if part] + return base.joinpath(*rest) + return Path(template) + + +def _detect_by_path(os_type: str, found: list[tuple[str, Path]]) -> None: + """Add what a registry path rule finds, for a platform not found yet.""" + systems = ("linux", "windows") if os_type == "wsl" else (os_type,) + for rule_os, platform, path, bios in _PATH_RULES: + if rule_os not in systems or any(name == platform for name, _ in found): + continue + if _rule_path(path).exists(): + found.append((platform, _rule_path(bios or path))) + + def detect_platforms(os_type: str) -> list[tuple[str, Path]]: """Detect installed emulator platforms and their BIOS directories.""" found: list[tuple[str, Path]] = [] @@ -981,6 +1022,7 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: if system_dir: found.append(("retroarch", system_dir)) + _detect_by_path(os_type, found) return found diff --git a/install.sh b/install.sh index 1715734c..097e4551 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="431f7d05350e222a3f4746dd13116bf570ec0f80117eed47d747372c61b57d77" +DEFAULT_INSTALL_SHA256="dc6b428278a4352923c19ddcf055fd994aac1df77f35cb1d097e8f4c5d887ae1" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index 1d7f354f..f5caf519 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -920,6 +920,56 @@ class StandaloneCopiesReadWhereThePackPutTheFile(unittest.TestCase): install._validate_manifest(manifest, "emudeck") +class DetectionFollowsTheRegistry(unittest.TestCase): + """The registry declared RetroPie at ~/RetroPie/BIOS and BizHawk at + ~/.config/BizHawk/Firmware, every manifest published it, and the + installer never read it: such a home detected nothing.""" + + def test_the_table_is_the_registry(self): + import yaml # noqa: PLC0415 + + registry = yaml.safe_load( + (Path(__file__).resolve().parents[1] / "platforms" / "_registry.yml").read_text() + ) + rules = { + (rule["os"], name, rule["path"], rule.get("bios_path")) + for name, entry in registry["platforms"].items() + for rule in (entry.get("install") or {}).get("detect", []) + if rule["method"] == "path_exists" + } + self.assertEqual(set(install._PATH_RULES), rules) + + def _home(self) -> Path: + home = Path(_tempdir(self)) + patcher = unittest.mock.patch.dict(os.environ, {"HOME": str(home), "USERPROFILE": str(home)}) + patcher.start() + self.addCleanup(patcher.stop) + return home + + def test_a_home_layout_is_detected(self): + home = self._home() + (home / "RetroPie" / "BIOS").mkdir(parents=True) + (home / ".config" / "BizHawk" / "Firmware").mkdir(parents=True) + found: list = [] + install._detect_by_path("linux", found) + self.assertIn(("retropie", home / "RetroPie" / "BIOS"), found) + self.assertIn(("bizhawk", home / ".config" / "BizHawk" / "Firmware"), found) + + def test_a_windows_profile_path_is_detected(self): + home = self._home() + (home / "BizHawk" / "Firmware").mkdir(parents=True) + found: list = [] + install._detect_by_path("windows", found) + self.assertEqual(found, [("bizhawk", home / "BizHawk" / "Firmware")]) + + def test_a_platform_already_found_is_not_added_again(self): + home = self._home() + (home / "Emulation" / "bios").mkdir(parents=True) + found = [("emudeck", home / "elsewhere" / "bios")] + install._detect_by_path("linux", found) + self.assertEqual(found, [("emudeck", home / "elsewhere" / "bios")]) + + class InstallerStaysOnPython38AndTheStdlib(unittest.TestCase): """install.py runs where the bootstraps let it run: Python 3.8, stdlib only.