Skip to content

Commit 07dda10

Browse files
authored
Update install.ps1
1 parent 34e181e commit 07dda10

1 file changed

Lines changed: 84 additions & 32 deletions

File tree

deepstudio/xtester/install.ps1

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ param(
2424

2525
$ErrorActionPreference = "Stop"
2626

27-
$Script:InstallerVersion = "1.5.0"
27+
$Script:InstallerVersion = "1.8.0"
2828

2929
# Force UTF-8 console output so winget's progress glyphs and other tool output
3030
# are not rendered as mojibake in legacy code-page consoles.
@@ -162,19 +162,6 @@ function New-ManagedVenv($PythonInfo) {
162162
}
163163

164164
$venvPython = Join-Path $VenvPath "Scripts\python.exe"
165-
if ((Test-Path -LiteralPath $venvPython) -and (Test-IsUnsupportedWindowsArm64Python $venvPython @())) {
166-
Invoke-Step "Removing native ARM64 managed venv: $VenvPath" {
167-
Remove-Item -LiteralPath $VenvPath -Recurse -Force
168-
}
169-
Invoke-Step "Creating managed X-Tester venv: $VenvPath" {
170-
$venvArgs = @()
171-
if ($PythonInfo.Arguments) { $venvArgs += $PythonInfo.Arguments }
172-
$venvArgs += @("-m", "venv", $VenvPath)
173-
Invoke-External $PythonInfo.FilePath $venvArgs
174-
}
175-
$venvPython = Join-Path $VenvPath "Scripts\python.exe"
176-
}
177-
178165
if (-not (Test-Path -LiteralPath $venvPython)) {
179166
# Microsoft Store / UWP Python redirects writes under %LOCALAPPDATA% into
180167
# %LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.*\LocalCache\Local\<rest>.
@@ -369,22 +356,34 @@ function Expand-Clients {
369356

370357
function Test-PythonExecutable([string]$FilePath, [string[]]$Arguments) {
371358
if (-not $FilePath) { return $null }
359+
$label = if ($Arguments -and $Arguments.Count -gt 0) { "$FilePath $($Arguments -join ' ')" } else { "$FilePath" }
372360
try {
373361
$output = & $FilePath @Arguments --version 2>&1
374362
}
375363
catch {
364+
Warn "Skipping ${label}: launcher threw $($_.Exception.Message)"
365+
return $null
366+
}
367+
if ($LASTEXITCODE -ne 0) {
368+
Warn "Skipping ${label}: '--version' exited with code $LASTEXITCODE"
376369
return $null
377370
}
378-
if ($LASTEXITCODE -ne 0) { return $null }
379371
$text = ($output | Out-String).Trim()
380372
if ([string]::IsNullOrWhiteSpace($text)) { return $null }
381-
if ($text -notmatch "Python\s+(\d+)\.(\d+)\.(\d+)") { return $null }
373+
if ($text -notmatch "Python\s+(\d+)\.(\d+)\.(\d+)") {
374+
Warn "Skipping ${label}: unrecognised version output '$text'"
375+
return $null
376+
}
382377
$major = [int]$Matches[1]
383378
$minor = [int]$Matches[2]
384-
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 11)) { return $null }
379+
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 11)) {
380+
Warn "Skipping ${label}: $text is older than Python 3.11"
381+
return $null
382+
}
385383

386384
if (Test-IsUnsupportedWindowsArm64Python $FilePath $Arguments) {
387-
return $null
385+
Warn "${label}: native ARM64 Python detected. Modern wheels for cryptography/keyring/artifacts-keyring exist on win_arm64; if pip later falls back to a source build, re-run with x64 Python 3.12."
386+
# Fall through and accept the candidate.
388387
}
389388

390389
# Reject Microsoft Store Python distributions. They virtualize writes under
@@ -395,6 +394,7 @@ function Test-PythonExecutable([string]$FilePath, [string[]]$Arguments) {
395394
if ($LASTEXITCODE -eq 0) {
396395
$sysExeText = ($sysExe | Out-String).Trim()
397396
if ($sysExeText -match "\\WindowsApps\\" -or $sysExeText -match "\\Packages\\PythonSoftwareFoundation\.Python\.") {
397+
Warn "Skipping ${label}: Microsoft Store Python at '$sysExeText' redirects venv writes"
398398
return $null
399399
}
400400
}
@@ -407,6 +407,41 @@ function Test-PythonExecutable([string]$FilePath, [string[]]$Arguments) {
407407
}
408408
}
409409

410+
function Get-RegistryPythonInstalls {
411+
# PEP 514: Python distributions register themselves under
412+
# HKCU/HKLM\Software\Python\<Company>\<Tag>\InstallPath
413+
# The (default) value of InstallPath is the install directory.
414+
$results = @()
415+
$roots = @(
416+
"HKCU:\Software\Python",
417+
"HKLM:\Software\Python",
418+
"HKLM:\Software\WOW6432Node\Python"
419+
)
420+
foreach ($root in $roots) {
421+
if (-not (Test-Path -LiteralPath $root)) { continue }
422+
$companies = Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue
423+
foreach ($company in $companies) {
424+
# Skip ContinuumAnalytics (Anaconda) etc; only PythonCore is the canonical CPython.
425+
if ($company.PSChildName -ne "PythonCore") { continue }
426+
$tags = Get-ChildItem -LiteralPath $company.PSPath -ErrorAction SilentlyContinue
427+
foreach ($tag in $tags) {
428+
$installPathKey = Join-Path $tag.PSPath "InstallPath"
429+
if (-not (Test-Path -LiteralPath $installPathKey)) { continue }
430+
try {
431+
$props = Get-ItemProperty -LiteralPath $installPathKey -ErrorAction Stop
432+
$dir = $props."(default)"
433+
if (-not $dir) { $dir = $props."ExecutablePath" | Split-Path -Parent -ErrorAction SilentlyContinue }
434+
if ($dir -and (Test-Path -LiteralPath $dir)) {
435+
$exe = Join-Path $dir "python.exe"
436+
if (Test-Path -LiteralPath $exe) { $results += $exe }
437+
}
438+
} catch { }
439+
}
440+
}
441+
}
442+
return ($results | Select-Object -Unique)
443+
}
444+
410445
function Find-PythonCandidate {
411446
$candidates = @()
412447
# Skip the Microsoft Store WindowsApps stub which prints a help message and exits 9009.
@@ -434,6 +469,12 @@ function Find-PythonCandidate {
434469
}
435470
}
436471

472+
# PEP 514 registry-based discovery is the most reliable source on Windows;
473+
# it works even when winget installed Python but did not refresh session PATH.
474+
foreach ($exe in (Get-RegistryPythonInstalls)) {
475+
$candidates += ,@($exe, @())
476+
}
477+
437478
# Common install locations for python.org / winget Python.Python.3.x packages.
438479
$installRoots = @(
439480
(Join-Path $env:LOCALAPPDATA "Programs\Python"),
@@ -445,7 +486,7 @@ function Find-PythonCandidate {
445486
if (-not $root) { continue }
446487
if (-not (Test-Path -LiteralPath $root)) { continue }
447488
$found = Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
448-
Where-Object { $_.Name -match "^Python3(1[1-9]|[2-9][0-9])$" } |
489+
Where-Object { $_.Name -match "^Python3(1[1-9]|[2-9][0-9])(x64|-x64)?$" } |
449490
Sort-Object Name -Descending
450491
foreach ($dir in $found) {
451492
$exe = Join-Path $dir.FullName "python.exe"
@@ -476,7 +517,7 @@ function Find-PythonCandidate {
476517

477518
if ($candidates.Count -eq 0) {
478519
Warn "No Python candidates were discovered."
479-
Info "Scanned: PATH (python, python3), py.exe launcher, %LOCALAPPDATA%\Programs\Python, %ProgramFiles%\Python, %ProgramFiles%, %ProgramFiles(x86)%, %LOCALAPPDATA%\Microsoft\WinGet\Packages\Python.Python.3.*, %LOCALAPPDATA%\Microsoft\WinGet\Links."
520+
Info "Scanned: PATH (python, python3), py.exe launcher, HKCU/HKLM\Software\Python\PythonCore registry, %LOCALAPPDATA%\Programs\Python, %ProgramFiles%\Python, %ProgramFiles%, %ProgramFiles(x86)%, %LOCALAPPDATA%\Microsoft\WinGet\Packages\Python.Python.3.*, %LOCALAPPDATA%\Microsoft\WinGet\Links."
480521
}
481522
foreach ($pair in $candidates) {
482523
$result = Test-PythonExecutable $pair[0] $pair[1]
@@ -486,12 +527,16 @@ function Find-PythonCandidate {
486527
}
487528

488529
function Install-PythonViaWinget {
530+
param(
531+
[switch]$ForceX64SideBySide
532+
)
489533
$winget = Get-Command "winget" -ErrorAction SilentlyContinue
490534
if (-not $winget) {
491535
Warn "winget is not available; cannot auto-install Python."
492536
return $false
493537
}
494-
Invoke-Step "Installing Python 3.12 via winget (Python.Python.3.12)" {
538+
$stepLabel = if ($ForceX64SideBySide) { "Installing x64 Python 3.12 alongside existing ARM64 (Python.Python.3.12 --architecture x64 --force)" } else { "Installing Python 3.12 via winget (Python.Python.3.12)" }
539+
Invoke-Step $stepLabel {
495540
# Use --scope user so we do not require an elevation prompt; winget returns
496541
# non-zero for "already installed" too, so we tolerate any non-fatal exit.
497542
# --disable-interactivity suppresses winget's animated progress bar so the
@@ -501,6 +546,10 @@ function Install-PythonViaWinget {
501546
if (Test-IsWindowsArm64Host) {
502547
$wingetArgs += @("--architecture", "x64")
503548
}
549+
if ($ForceX64SideBySide) {
550+
$sideBySideRoot = Join-Path $env:LOCALAPPDATA "Programs\Python\Python312x64"
551+
$wingetArgs += @("--force", "--location", $sideBySideRoot)
552+
}
504553
$wingetArgs += @(
505554
"--accept-source-agreements", "--accept-package-agreements", "--silent",
506555
"--disable-interactivity"
@@ -528,7 +577,7 @@ function Install-PythonViaWinget {
528577
$localProgramsPython = Join-Path $env:LOCALAPPDATA "Programs\Python"
529578
if (Test-Path -LiteralPath $localProgramsPython) {
530579
Get-ChildItem -LiteralPath $localProgramsPython -Directory -ErrorAction SilentlyContinue |
531-
Where-Object { $_.Name -match "^Python3(1[1-9]|[2-9][0-9])$" } |
580+
Where-Object { $_.Name -match "^Python3(1[1-9]|[2-9][0-9])(x64|-x64)?$" } |
532581
ForEach-Object {
533582
$extra += $_.FullName
534583
$extra += (Join-Path $_.FullName "Scripts")
@@ -548,23 +597,26 @@ function Resolve-Python {
548597
if ($installed) {
549598
$found = Find-PythonCandidate
550599
if ($found) { return $found }
600+
# On ARM64 hosts a previously-installed native ARM64 Python.Python.3.12
601+
# makes winget skip the install with NO_APPLICABLE_UPGRADE_FOUND, so try
602+
# forcing a side-by-side x64 install before giving up.
603+
if (Test-IsWindowsArm64Host) {
604+
Warn "Existing Python.Python.3.12 appears to be ARM64; forcing a side-by-side x64 install."
605+
$forced = Install-PythonViaWinget -ForceX64SideBySide
606+
if ($forced) {
607+
$found = Find-PythonCandidate
608+
if ($found) { return $found }
609+
}
610+
}
551611
Warn "Python was installed but is not yet visible to this session."
552612
Info "Open a new PowerShell window and re-run this installer."
553613
}
554614

555615
Fail "No working Python 3.11+ interpreter was found."
556-
Info "Tried: python, python3, py -3.12, py -3.11, py -3.13, py -3.14, py -3 plus %LOCALAPPDATA%\Programs\Python, %ProgramFiles%\Python, %ProgramFiles%, %ProgramFiles(x86)%, %LOCALAPPDATA%\Microsoft\WinGet\Packages\Python.Python.3.*, %LOCALAPPDATA%\Microsoft\WinGet\Links."
616+
Info "Tried: python, python3, py -3.12, py -3.11, py -3.13, py -3.14, py -3, HKCU/HKLM\Software\Python\PythonCore registry, %LOCALAPPDATA%\Programs\Python, %ProgramFiles%\Python, %ProgramFiles%, %ProgramFiles(x86)%, %LOCALAPPDATA%\Microsoft\WinGet\Packages\Python.Python.3.*, %LOCALAPPDATA%\Microsoft\WinGet\Links."
557617
Info "The Microsoft Store stub at WindowsApps\python.exe is intentionally ignored."
558-
if (Test-IsWindowsArm64Host) {
559-
Info "Native ARM64 Python is intentionally ignored because required auth-helper wheels can fall back to cryptography/OpenSSL source builds."
560-
Info "Use x64 Python 3.12 on Windows ARM64 for this installer."
561-
}
562618
Info "Install Python manually with one of:"
563-
if (Test-IsWindowsArm64Host) {
564-
Info " winget install -e --id Python.Python.3.12 --architecture x64"
565-
} else {
566-
Info " winget install -e --id Python.Python.3.12"
567-
}
619+
Info " winget install -e --id Python.Python.3.12"
568620
Info " choco install python --version=3.12"
569621
Info " https://www.python.org/downloads/"
570622
Info "Then open a new terminal and re-run this installer."

0 commit comments

Comments
 (0)