ci: provision Python via nuget (idempotent, no registry side effects)

The per-user MSI installer is stateful across runs on a persistent VM: once a
version is registered, a later /quiet install no-ops and never lands at the new
TargetDir, so python.exe went missing. Switch to a standalone CPython from
nuget cached under LOCALAPPDATA — no registry/PATH changes, idempotent, reused
across runs. A pre-existing Python 3.11 on PATH is still honoured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 20:44:47 -07:00
parent fc01f8e995
commit 0d0786e222
+20 -20
View File
@@ -45,16 +45,17 @@ jobs:
run: | run: |
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$python = $null
# 1) Existing "py -3.11" launcher # Persistent, side-effect-free Python for this builder. We use a
try { # standalone CPython from nuget (no registry/PATH changes, idempotent)
$exe = & py -3.11 -c "import sys; print(sys.executable)" 2>$null # cached under LOCALAPPDATA so subsequent runs reuse it. A pre-existing
if ($LASTEXITCODE -eq 0 -and $exe) { $python = $exe.Trim() } # Python 3.11 on PATH is honoured if present.
} catch {} $pyVersion = "3.11.9"
$root = Join-Path $env:LOCALAPPDATA "MacroPadBuild"
$python = Join-Path $root "python.$pyVersion\tools\python.exe"
# 2) A python on PATH that is 3.11.x if (-not (Test-Path $python)) {
if (-not $python) { # Honour an existing 3.11 on PATH (e.g. if the VM is pre-provisioned).
try { try {
$v = & python --version 2>&1 $v = & python --version 2>&1
if ($LASTEXITCODE -eq 0 -and "$v" -match '3\.11') { if ($LASTEXITCODE -eq 0 -and "$v" -match '3\.11') {
@@ -63,21 +64,20 @@ jobs:
} catch {} } catch {}
} }
# 3) Install per-user, silent (no elevation, cannot prompt) if (-not (Test-Path $python)) {
if (-not $python) { Write-Host "Provisioning standalone Python $pyVersion via nuget..."
Write-Host "Python 3.11 not found - installing per-user..." New-Item -ItemType Directory -Force -Path $root | Out-Null
$tmp = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { $env:TEMP } $nuget = Join-Path $root "nuget.exe"
$installer = Join-Path $tmp "python-3.11.9-amd64.exe" if (-not (Test-Path $nuget)) {
Invoke-WebRequest -UseBasicParsing -Uri "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe" -OutFile $installer Invoke-WebRequest -UseBasicParsing -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -OutFile $nuget
$dir = Join-Path $tmp "Python311" }
$proc = Start-Process -FilePath $installer -Wait -PassThru -NoNewWindow -ArgumentList @( & $nuget install python -Version $pyVersion -OutputDirectory $root -Source "https://api.nuget.org/v3/index.json" -NonInteractive
"/quiet","InstallAllUsers=0","PrependPath=0","Include_launcher=0","Include_test=0","TargetDir=$dir" if ($LASTEXITCODE -ne 0) { throw "nuget install python exited with $LASTEXITCODE" }
) $python = Join-Path $root "python.$pyVersion\tools\python.exe"
if ($proc.ExitCode -ne 0) { throw "Python installer exited with $($proc.ExitCode)" }
$python = Join-Path $dir "python.exe"
} }
if (-not (Test-Path $python)) { throw "Python not found at '$python'" } if (-not (Test-Path $python)) { throw "Python not found at '$python'" }
& $python -m ensurepip --upgrade 2>$null | Out-Null
& $python --version & $python --version
# Write to GITHUB_ENV without a BOM (ascii) so the value parses cleanly. # Write to GITHUB_ENV without a BOM (ascii) so the value parses cleanly.
Add-Content -Path $env:GITHUB_ENV -Value "PYTHON=$python" -Encoding ascii Add-Content -Path $env:GITHUB_ENV -Value "PYTHON=$python" -Encoding ascii