From 0b0480a1218de4c43fde0f081a9b49316695edc1 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:15 +0200 Subject: [PATCH] fix: name standalone folders instead of skipped Every install ended on 'Standalone copies skipped', on machines with no standalone emulator as on one whose DuckStation lacked its BIOS. The line now lists the emulator folders that exist here, and nothing is said when there are none. --- install.ps1 | 2 +- install.py | 67 +++++++++++++++++++++++++++++++------------ install.sh | 2 +- tests/test_install.py | 28 ++++++++++++++++++ wiki/installer.md | 2 ++ 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/install.ps1 b/install.ps1 index b2dd493a8..23c8b0820 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "6e7005671e99ab965e815ba96bb6c6c0542667012d9b42bd139d2e3e0f06d40c" + $defaultInstallSha256 = "864b631d417299feb305fdab7db62e4395608cce809c71bbb15b9f942be2b867" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 1f315930b..a22277c8d 100644 --- a/install.py +++ b/install.py @@ -3755,6 +3755,42 @@ def select_data_directories(manifest: dict, files: list[dict], narrowed: bool) - return [e for e in entries if kept & set(e.get("systems") or [])] +def _copy_targets( + entry: dict, os_type: str, extra_dirs: dict[str, Path] | None = None, +) -> list[Path]: + """The emulator folders a standalone copy entry names on this OS. + + WSL falls back to the Linux ones, and extra_dirs adds the folder a + frontend such as LaunchBox keeps the emulator in. + """ + targets = entry.get("targets", {}).get(os_type, []) + if not targets and os_type == "wsl": + targets = entry.get("targets", {}).get("linux", []) + target_dirs = [Path(os.path.expandvars(os.path.expanduser(t))) for t in targets] + extra_root = (extra_dirs or {}).get(entry.get("emulator", "")) + if extra_root is not None: + subdir = PurePosixPath(entry.get("file", "")).parent + extra = extra_root if str(subdir) == "." else extra_root / subdir + if extra not in target_dirs: + target_dirs.append(extra) + return target_dirs + + +def standalone_folders_here( + manifest: dict, os_type: str, extra_dirs: dict[str, Path] | None = None, +) -> list[Path]: + """The emulator folders of the manifest's standalone copies that exist + on this machine: the ones --standalone-copies would write to.""" + found: list[Path] = [] + for entry in manifest.get("standalone_copies", []): + if "note" in entry: + continue + for folder in _copy_targets(entry, os_type, extra_dirs): + if folder not in found and folder.is_dir(): + found.append(folder) + return found + + def do_standalone_copies( manifest: dict, bios_path: Path, os_type: str, extra_dirs: dict[str, Path] | None = None, @@ -3819,22 +3855,7 @@ def do_standalone_copies( if not sources: continue - # Resolve target directories with WSL fallback - targets = entry.get("targets", {}).get(os_type, []) - if not targets and os_type == "wsl": - targets = entry.get("targets", {}).get("linux", []) - - target_dirs = [ - Path(os.path.expandvars(os.path.expanduser(t))) for t in targets - ] - extra_root = (extra_dirs or {}).get(entry.get("emulator", "")) - if extra_root is not None: - subdir = PurePosixPath(entry.get("file", "")).parent - extra = extra_root if str(subdir) == "." else extra_root / subdir - if extra not in target_dirs: - target_dirs.append(extra) - - for target_dir in target_dirs: + for target_dir in _copy_targets(entry, os_type, extra_dirs): if not target_dir.is_dir(): skipped += len(sources) continue @@ -5213,10 +5234,18 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: ) total_errors += copy_failed elif manifest.get("standalone_copies") and not args.check: - print( - "\nStandalone copies skipped " - "(use --standalone-copies to opt in)." + # Said only where it concerns this machine: "skipped" printed on + # every run read as an incident, and named no emulator. + lb_root = launchbox_root(os_type) + folders = standalone_folders_here( + manifest, os_type, launchbox_bios_dirs(lb_root) if lb_root else None ) + if folders: + print("\nStandalone emulator folders found here:") + for folder in folders: + print(f" {folder}") + print(" --standalone-copies also copies the BIOS files and keys " + "they read into them.") sys.stdout = real_stdout # A check scripted as `--check && launch` reads the exit code only. diff --git a/install.sh b/install.sh index 865a5d26f..e3643d4b9 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="6e7005671e99ab965e815ba96bb6c6c0542667012d9b42bd139d2e3e0f06d40c" +DEFAULT_INSTALL_SHA256="864b631d417299feb305fdab7db62e4395608cce809c71bbb15b9f942be2b867" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index edcf28655..79426846c 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -3863,6 +3863,34 @@ class ACompletePartialIsHandedOffOnWindows(unittest.TestCase): self.assertTrue(install._write_refused(PermissionError(13, "denied"))) +class StandaloneFoldersAreNamedWhenThereAreAny(unittest.TestCase, _LoopbackRun): + """'Standalone copies skipped' closed every install, on a machine with + no standalone emulator as on one whose DuckStation lacked its BIOS.""" + + def setUp(self): + self.env = self.serve(self) + + def _run(self) -> str: + proc = _install_run(self.env, "--platform", "retroarch", "--dest", + str(self.home / "system"), "--system", "atari-lynx", + "--no-input") + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + return proc.stdout + + def test_nothing_is_said_without_an_emulator_folder(self): + out = self._run() + self.assertNotIn("skipped", out) + self.assertNotIn("--standalone-copies", out) + + def test_a_found_folder_is_named(self): + duckstation = self.home / ".local" / "share" / "duckstation" / "bios" + duckstation.mkdir(parents=True) + out = self._run() + self.assertIn("Standalone emulator folders found here:", out) + self.assertIn(str(duckstation), out) + self.assertIn("--standalone-copies", out) + + class TheEmulatorNamesHaveTheirOwnListing(unittest.TestCase, _LoopbackRun): """--emulator nope said "--list-cores lists all 441" and --list-cores printed 189: the buildbot names --emulator accepts were listed nowhere.""" diff --git a/wiki/installer.md b/wiki/installer.md index 3241c6b1a..e795a51d9 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -421,6 +421,8 @@ that is not installed. A destination that is already a symbolic link is replaced by the file rather than written through: a write through the link would land outside the folder the user opted into. The summary counts each copy as copied, skipped (folder absent) or failed, and names every failure. +A run without the flag lists the emulator folders of this list that exist on +the machine, and says nothing when there are none. ## RomM firmware layout