diff --git a/install.ps1 b/install.ps1 index ce742565..ce0c9662 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 = "3eac76733e9f27965ef62da4f3ef8e793b02af289d7a3317b1626c99a64ddcff" +$defaultInstallSha256 = "d336ca66e88def775842bca2ed4fae67dabf1d3effcb18e84d057da78470459c" $maximumInstallerBytes = 2MB $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null diff --git a/install.py b/install.py index 470c05bf..74b3f410 100644 --- a/install.py +++ b/install.py @@ -457,7 +457,7 @@ def _retroarch_cfg_dir( def _windows_retroarch_dirs() -> list[Path]: """Folders holding retroarch.exe where the installer and the archive put - it: C:\RetroArch-Win64 by default, C:\RetroArch for 32-bit builds.""" + it: C:/RetroArch-Win64 by default, C:/RetroArch for 32-bit builds.""" candidates = [Path("C:/RetroArch-Win64"), Path("C:/RetroArch")] for var in ("ProgramFiles", "ProgramFiles(x86)"): root = os.environ.get(var, "") @@ -2170,6 +2170,7 @@ def _report_outcome( seconds: float = 0.0, declined: int = 0, assumed: set[Path] | None = None, + omitted_entries: list[dict] | None = None, ) -> None: """Close the run on what happened, and leave with a status that says so. @@ -2218,11 +2219,29 @@ def _report_outcome( f"{_plural(up_to_date, 'file')} already up to date.") print(f"Location: {_locations(paths)}") if omitted: - print(f"{_plural(omitted, 'file')} are not in the collection yet " - "and were skipped.") + for line in _omission_lines(omitted, omitted_entries or []): + print(line) print("To check this later, run the same command with --check.") +def _omission_lines(count: int, entries: list[dict]) -> list[str]: + """What was skipped, the files an emulator cannot start without first. + + "395 file are not in the collection yet" named nothing and gave one + reason for all, while the manifest says which file and why. + """ + one = count == 1 + lines = [f"{_plural(count, 'file')} {'was' if one else 'were'} not installed: " + f"the collection does not hold {'it' if one else 'them'}, or its copy " + f"fails {'its' if one else 'their'} check."] + required = sorted({e["name"] for e in entries if e.get("required")}) + if required: + shown = ", ".join(required[:10]) + more = f" and {len(required) - 10} more" if len(required) > 10 else "" + lines.append(f" Needed to start an emulator: {shown}{more}.") + return lines + + # Asked before, in an interactive run: past this size a download is worth a # second thought, and --system, --core or --region can make it smaller. CONFIRM_FROM_BYTES = 1024 * 1024 * 1024 @@ -2905,6 +2924,7 @@ def _install(args: argparse.Namespace, real_stdout) -> None: total_up_to_date = 0 total_errors = 0 total_omitted = 0 + omitted_seen: list[dict] = [] total_interrupted = False total_bytes = 0 total_seconds = 0.0 @@ -3061,6 +3081,7 @@ def _install(args: argparse.Namespace, real_stdout) -> None: f"entries omitted ({required_omitted} required; {reason_summary})." ) total_omitted += len(omitted_files) + omitted_seen.extend(omitted_files) print("\nChecking existing files...") to_download, up_to_date, mismatched = check_local( @@ -3184,6 +3205,7 @@ def _install(args: argparse.Namespace, real_stdout) -> None: seconds=total_seconds, declined=total_declined, assumed=assumed_dests, + omitted_entries=omitted_seen, ) diff --git a/install.sh b/install.sh index 261683fe..4bd6d255 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="3eac76733e9f27965ef62da4f3ef8e793b02af289d7a3317b1626c99a64ddcff" +DEFAULT_INSTALL_SHA256="d336ca66e88def775842bca2ed4fae67dabf1d3effcb18e84d057da78470459c" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 51019a37..cc75b8dd 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -3304,5 +3304,33 @@ class EveryReadmeNamesItsFolder(unittest.TestCase): self.assertNotIn("retroarch/system", lakka) + +class NoSourceWarnsWhenCompiled(unittest.TestCase): + """A docstring naming C:\\RetroArch-Win64 with one backslash compiled to + an invalid escape: a SyntaxWarning on every run of the installer.""" + + def test_every_script_compiles_clean(self): + import warnings + + sources = [ROOT / "install.py", *sorted((ROOT / "scripts").rglob("*.py"))] + for path in sources: + with self.subTest(path=path.name), warnings.catch_warnings(): + warnings.simplefilter("error", SyntaxWarning) + warnings.simplefilter("error", DeprecationWarning) + compile(path.read_text(encoding="utf-8"), str(path), "exec") + + +class TheInstallerNamesWhatItSkipped(unittest.TestCase): + def test_count_agrees_and_required_files_are_named(self): + one = install._omission_lines(1, [{"name": "diabdat.mpq", "required": True}]) + self.assertTrue(one[0].startswith("1 file was not installed")) + self.assertIn("diabdat.mpq", one[1]) + many = install._omission_lines( + 12, [{"name": f"f{i:02}", "required": True} for i in range(12)] + ) + self.assertTrue(many[0].startswith("12 files were not installed")) + self.assertIn("and 2 more", many[1]) + + if __name__ == "__main__": unittest.main()