From 5a32a42d2575d7d3cf1b815aa975711845fb1d5c Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 05:18:08 +0200 Subject: [PATCH] fix: pass the installer's own lint gate install.py held a local it never read and, since the registry path rules, a loop variable shadowing the platform module, so the py38 ruff gate the installer is held to failed. Both are fixed, and the suite reads the two rules from the syntax tree since CI has no ruff. --- install.ps1 | 2 +- install.py | 7 +++---- install.sh | 2 +- tests/test_install.py | 46 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/install.ps1 b/install.ps1 index 2a57af8b..7a980f89 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 = "dc6b428278a4352923c19ddcf055fd994aac1df77f35cb1d097e8f4c5d887ae1" +$defaultInstallSha256 = "973e116ab59e233cd4fe5ebb77d4973bc06013b22b6388e80d12fa1d61fb0c73" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 7dacd8aa..091af425 100644 --- a/install.py +++ b/install.py @@ -175,7 +175,6 @@ def _os_default_dests(os_type: str) -> dict[str, Path]: home = Path.home() if os_type == "windows": profile = Path(os.environ.get("USERPROFILE", str(home))) - appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming"))) return { # RetroArch's system directory defaults to \system # (platform_win32.c), and its installer to C:\RetroArch-Win64. @@ -904,11 +903,11 @@ def _rule_path(template: str) -> Path: 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): + for rule_os, platform_name, path, bios in _PATH_RULES: + if rule_os not in systems or any(name == platform_name for name, _ in found): continue if _rule_path(path).exists(): - found.append((platform, _rule_path(bios or path))) + found.append((platform_name, _rule_path(bios or path))) def detect_platforms(os_type: str) -> list[tuple[str, Path]]: diff --git a/install.sh b/install.sh index 097e4551..18daa8a3 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="dc6b428278a4352923c19ddcf055fd994aac1df77f35cb1d097e8f4c5d887ae1" +DEFAULT_INSTALL_SHA256="973e116ab59e233cd4fe5ebb77d4973bc06013b22b6388e80d12fa1d61fb0c73" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index f5caf519..0f272cba 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1151,6 +1151,52 @@ class InstallerStaysOnPython38AndTheStdlib(unittest.TestCase): self.assertEqual(used, []) + def test_no_local_is_assigned_and_never_read(self): + """ruff --select F in py38 mode is the installer's lint gate; an + unread `appdata` in _os_default_dests made it exit 1. The suite has + no ruff, so the rule is read from the syntax tree.""" + import ast # noqa: PLC0415 + + tree = ast.parse((Path(__file__).resolve().parents[1] / "install.py").read_text()) + unread = [] + for func in ast.walk(tree): + if not isinstance(func, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + stored, loaded = {}, set() + for node in ast.walk(func): + if isinstance(node, ast.Name): + if isinstance(node.ctx, ast.Store): + stored.setdefault(node.id, node.lineno) + else: + loaded.add(node.id) + nested = { + n.id for inner in ast.walk(func) if inner is not func + and isinstance(inner, (ast.FunctionDef, ast.Lambda)) + for n in ast.walk(inner) if isinstance(n, ast.Name) + } + unread += [ + f"{func.name}:{line} {name}" for name, line in stored.items() + if name not in loaded | nested and not name.startswith("_") + and not any( + isinstance(g, (ast.Global, ast.Nonlocal)) and name in g.names + for g in ast.walk(func) + ) + ] + self.assertEqual(unread, []) + # Nor a module rebound: a loop variable named `platform` shadowed the + # platform module in _detect_by_path (F402). + modules = { + alias.asname or alias.name.split(".")[0] + for node in tree.body if isinstance(node, ast.Import) + for alias in node.names + } + rebound = sorted( + f"{node.id}:{node.lineno}" for node in ast.walk(tree) + if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store) + and node.id in modules + ) + self.assertEqual(rebound, []) + class ARefusedCopyIsAnError(unittest.TestCase): """A PermissionError on a standalone copy was counted as "dir not found" and the run ended on Done with exit code 0."""