From 9a1d7390e61918f3a0f364c058ea1ecb83cd049b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:10:08 +0100 Subject: [PATCH] fix: try every Python candidate in install.ps1 --- install.ps1 | 33 +++++++++++++++++++-------------- tests/test_install.py | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/install.ps1 b/install.ps1 index 36236c052..a1e01e61b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -68,18 +68,6 @@ $installer = $temporary } - # The py launcher first, then python3 and python. The Microsoft Store - # places a python.exe that only points at the Store: it fails the - # version check like a missing one. - $findPython = { - $found = Get-Command py -ErrorAction SilentlyContinue - if ($found) { return ,@($found, @("-3")) } - foreach ($name in @("python3", "python")) { - $found = Get-Command $name -ErrorAction SilentlyContinue - if ($found) { return ,@($found, @()) } - } - return $null - } $usable = { param($candidate) if (-not $candidate) { return $false } @@ -87,11 +75,28 @@ & $candidate[0].Source @arguments -c "import sys, ssl, unicodedata, zlib; raise SystemExit(sys.version_info < (3, 8))" 2>$null | Out-Null return ($LASTEXITCODE -eq 0) } + # The py launcher first, then python3 and python, the first that + # passes the check: a py launcher left with no usable runtime hid a + # Python 3.12 on PATH. The Microsoft Store places a python.exe that + # only points at the Store: it fails the check like a missing one. + $findPython = { + $candidates = @() + $found = Get-Command py -ErrorAction SilentlyContinue + if ($found) { $candidates += ,@($found, @("-3")) } + foreach ($name in @("python3", "python")) { + $found = Get-Command $name -ErrorAction SilentlyContinue + if ($found) { $candidates += ,@($found, @()) } + } + foreach ($candidate in $candidates) { + if (& $usable $candidate) { return ,$candidate } + } + return $null + } $needPython = "Python 3.8 or newer is required. Install it with " + "'winget install Python.Python.3.12' or from https://www.python.org/downloads/ " + "(tick 'Add python.exe to PATH'), then open a new PowerShell window and run this command again." $candidate = & $findPython - if (-not (& $usable $candidate)) { + if (-not $candidate) { # winget ships with Windows 10 and 11: at a console the bootstrap # offers to run the install the message names, then carries on. $winget = Get-Command winget -ErrorAction SilentlyContinue @@ -109,7 +114,7 @@ $candidate = & $findPython } } - if (-not (& $usable $candidate)) { throw $needPython } + if (-not $candidate) { throw $needPython } } $python = $candidate[0] $launcher = $candidate[1] diff --git a/tests/test_install.py b/tests/test_install.py index 651cb6023..dea417910 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2694,6 +2694,29 @@ class WindowsWithoutPythonIsOfferedWinget(unittest.TestCase): self.assertFalse((fake_bin / "python3").exists()) +@unittest.skipUnless(shutil.which("pwsh"), "needs PowerShell") +class APyLauncherWithoutARuntimeIsPassed(unittest.TestCase): + """findPython returned the py launcher as soon as it existed: one left + with no usable runtime hid a Python 3.12 on PATH, and the bootstrap + offered a second install or gave up.""" + + def test_python_on_path_runs_the_installer(self): + folder = Path(_tempdir(self)) + shutil.copy(REPO_ROOT / "install.ps1", folder / "install.ps1") + (folder / "install.py").write_text("print('installer ran')\n") + fake_bin = folder / "bin" + fake_bin.mkdir() + (fake_bin / "py").write_text("#!/bin/sh\necho 'No installed Python found!' >&2\nexit 1\n") + (fake_bin / "py").chmod(0o755) + (fake_bin / "python3").symlink_to(sys.executable) + proc = subprocess.run( + [shutil.which("pwsh"), "-NoProfile", "-File", str(folder / "install.ps1")], + env={**os.environ, "PATH": f"{fake_bin}{os.pathsep}/usr/bin/nonexistent"}, + capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + self.assertIn("installer ran", proc.stdout) + + 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."""