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