diff --git a/install.ps1 b/install.ps1 index 657f4a0a..aaeb64fb 100644 --- a/install.ps1 +++ b/install.ps1 @@ -10,6 +10,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" $defaultInstallSha256 = "61ffed8fddf816c02c1b45268dfd9bfdff9daeb1da2adedf2e13d14472f8654d" $maximumInstallerBytes = 2MB +$installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null @@ -43,28 +44,37 @@ try { $installer = $temporary } + $launcher = @() $python = Get-Command py -ErrorAction SilentlyContinue if ($python) { - & $python.Source -3 -c "import sys; raise SystemExit(sys.version_info < (3, 8))" - if ($LASTEXITCODE -ne 0) { throw "Python 3.8 or newer is required." } - & $python.Source -3 $installer @InstallerArguments - if ($LASTEXITCODE -ne 0) { throw "RetroBIOS installer failed with exit code $LASTEXITCODE." } - return - } - $python = Get-Command python3 -ErrorAction SilentlyContinue - if (-not $python) { - $python = Get-Command python -ErrorAction SilentlyContinue + $launcher = @("-3") + } else { + $python = Get-Command python3 -ErrorAction SilentlyContinue + if (-not $python) { + $python = Get-Command python -ErrorAction SilentlyContinue + } } if (-not $python) { throw "Python 3.8 or newer is required." } - & $python.Source -c "import sys; raise SystemExit(sys.version_info < (3, 8))" + & $python.Source @launcher -c "import sys; raise SystemExit(sys.version_info < (3, 8))" if ($LASTEXITCODE -ne 0) { throw "Python 3.8 or newer is required." } - & $python.Source $installer @InstallerArguments - if ($LASTEXITCODE -ne 0) { throw "RetroBIOS installer failed with exit code $LASTEXITCODE." } + & $python.Source @launcher $installer @InstallerArguments + # The installer has already said what happened: its status is passed on + # as it is (1 for files left to fetch under --check, 2 when nothing was + # installed, 130 on Ctrl+C), not turned into a red exception that reads + # as a crash and flattens every code to one. + $installerExit = $LASTEXITCODE } finally { if ($temporary -and (Test-Path -LiteralPath $temporary)) { Remove-Item -LiteralPath $temporary -Force } } + +if ($installerExit) { + # Run from a file, the script ends with that status. Piped into iex it + # runs in the user's own session, which exit would close. + if ($PSScriptRoot) { exit $installerExit } + $global:LASTEXITCODE = $installerExit +} diff --git a/tests/test_install.py b/tests/test_install.py index 80a03a7e..283c91b6 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1792,5 +1792,24 @@ class RetroArchWhereItReallyLives(unittest.TestCase): found = install.detect_platforms("darwin") self.assertIn(("retroarch", support / "system"), found) + +class ThePowerShellWrapperPassesTheStatusOn(unittest.TestCase): + """Every non-zero exit became a red "installer failed" exception and + code 1: a --check that found work read as a crash, 2 and 130 were lost.""" + + @unittest.skipUnless(shutil.which("pwsh"), "needs PowerShell") + def test_the_installer_code_comes_back_unchanged(self): + folder = Path(_tempdir(self)) + shutil.copy(REPO_ROOT / "install.ps1", folder / "install.ps1") + for code in (0, 1, 2, 130): + with self.subTest(code=code): + (folder / "install.py").write_text(f"import sys\nsys.exit({code})\n") + proc = subprocess.run( + ["pwsh", "-NoProfile", "-File", str(folder / "install.ps1")], + capture_output=True, text=True, timeout=120, + ) + self.assertEqual(proc.returncode, code, proc.stderr) + self.assertNotIn("installer failed", proc.stdout + proc.stderr) + if __name__ == "__main__": unittest.main() diff --git a/wiki/installer.md b/wiki/installer.md index 95e05fb8..73f69022 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -417,9 +417,17 @@ followed. ## Exit status -`0` on success, `1` on failure: an unusable base URL, an unknown platform or -target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, or no -platform to install for in a non-interactive run. +| Code | Meaning | +|------|---------| +| `0` | everything asked for is installed or already up to date | +| `1` | a failure: an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed while others installed, or `--check` found files to fetch | +| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one) | +| `130` | stopped with Ctrl+C; run the same command again to carry on | +| `141` | the output was piped to a reader that closed early (`--list \| head`) | + +`install.ps1` passes the installer's code on unchanged: run as a file it exits +with it, run through `irm \| iex` it sets `$LASTEXITCODE` (an `exit` there +would close the PowerShell window). Failures that are per-file, such as a download exhausting its three attempts, are counted and reported at the end.