Merge pull request #48529 from kshitijk4poor/salvage-48372-eap
fix(install): relax EAP=Stop around native git/uv calls + fail-fast on uv venv failure (#48352, salvage of #48372)
This commit is contained in:
commit
2fa16ec2d2
@ -185,6 +185,18 @@ function Write-Err {
|
|||||||
Write-Host "[X] $Message" -ForegroundColor Red
|
Write-Host "[X] $Message" -ForegroundColor Red
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Invoke-NativeWithRelaxedErrorAction {
|
||||||
|
param([scriptblock]$Script)
|
||||||
|
|
||||||
|
$prevEAP = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
try {
|
||||||
|
& $Script
|
||||||
|
} finally {
|
||||||
|
$ErrorActionPreference = $prevEAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Inspect npm output for a TLS-trust failure and, if found, print actionable
|
# Inspect npm output for a TLS-trust failure and, if found, print actionable
|
||||||
# remediation. npm/Node surface corporate MITM proxies and missing root CAs as
|
# remediation. npm/Node surface corporate MITM proxies and missing root CAs as
|
||||||
# "unable to get local issuer certificate" / "self-signed certificate in
|
# "unable to get local issuer certificate" / "self-signed certificate in
|
||||||
@ -1340,7 +1352,7 @@ function Install-Repository {
|
|||||||
Write-Info "Trying SSH clone..."
|
Write-Info "Trying SSH clone..."
|
||||||
$env:GIT_SSH_COMMAND = "ssh -o BatchMode=yes -o ConnectTimeout=5"
|
$env:GIT_SSH_COMMAND = "ssh -o BatchMode=yes -o ConnectTimeout=5"
|
||||||
try {
|
try {
|
||||||
git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlSsh $InstallDir
|
Invoke-NativeWithRelaxedErrorAction { git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlSsh $InstallDir }
|
||||||
if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
|
if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
|
||||||
} catch { }
|
} catch { }
|
||||||
$env:GIT_SSH_COMMAND = $null
|
$env:GIT_SSH_COMMAND = $null
|
||||||
@ -1349,7 +1361,7 @@ function Install-Repository {
|
|||||||
if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue }
|
if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue }
|
||||||
Write-Info "SSH failed, trying HTTPS..."
|
Write-Info "SSH failed, trying HTTPS..."
|
||||||
try {
|
try {
|
||||||
git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlHttps $InstallDir
|
Invoke-NativeWithRelaxedErrorAction { git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlHttps $InstallDir }
|
||||||
if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
|
if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
@ -1477,8 +1489,20 @@ function Install-Venv {
|
|||||||
Remove-Item -Recurse -Force "venv"
|
Remove-Item -Recurse -Force "venv"
|
||||||
}
|
}
|
||||||
|
|
||||||
# uv creates the venv and pins the Python version in one step
|
# uv creates the venv and pins the Python version in one step. uv emits
|
||||||
& $UvCmd venv venv --python $PythonVersion
|
# normal progress such as "Using CPython ..." on stderr; under Windows
|
||||||
|
# PowerShell 5.1 with EAP=Stop that stderr is a NativeCommandError unless
|
||||||
|
# we temporarily relax EAP and trust $LASTEXITCODE for real failures.
|
||||||
|
Invoke-NativeWithRelaxedErrorAction { & $UvCmd venv venv --python $PythonVersion }
|
||||||
|
# Relaxing EAP above means a *genuine* uv-venv failure (exit != 0) no longer
|
||||||
|
# aborts on its own. Capture $LASTEXITCODE immediately and fail fast, so the
|
||||||
|
# `venv` stage can't falsely report success (and Invoke-Stage can't emit
|
||||||
|
# ok=true) when the venv was never created.
|
||||||
|
$venvExitCode = $LASTEXITCODE
|
||||||
|
if ($venvExitCode -ne 0) {
|
||||||
|
Pop-Location
|
||||||
|
throw "Failed to create virtual environment (uv venv exited with $venvExitCode)"
|
||||||
|
}
|
||||||
|
|
||||||
# Neutralize any inherited UV_PYTHON (e.g. $env:UV_PYTHON = "3.14" left in
|
# Neutralize any inherited UV_PYTHON (e.g. $env:UV_PYTHON = "3.14" left in
|
||||||
# the user's shell). uv honours UV_PYTHON over an existing venv for the
|
# the user's shell). uv honours UV_PYTHON over an existing venv for the
|
||||||
@ -1548,7 +1572,7 @@ function Install-Dependencies {
|
|||||||
# in the wrong directory and imports fail with ModuleNotFoundError.
|
# in the wrong directory and imports fail with ModuleNotFoundError.
|
||||||
# (Mirrors the same flag in scripts/install.sh::install_deps.)
|
# (Mirrors the same flag in scripts/install.sh::install_deps.)
|
||||||
$env:UV_PROJECT_ENVIRONMENT = "$InstallDir\venv"
|
$env:UV_PROJECT_ENVIRONMENT = "$InstallDir\venv"
|
||||||
& $UvCmd sync --extra all --locked
|
Invoke-NativeWithRelaxedErrorAction { & $UvCmd sync --extra all --locked }
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
Write-Success "Main package installed (hash-verified via uv.lock)"
|
Write-Success "Main package installed (hash-verified via uv.lock)"
|
||||||
$script:InstalledTier = "hash-verified (uv.lock)"
|
$script:InstalledTier = "hash-verified (uv.lock)"
|
||||||
@ -1623,7 +1647,7 @@ except Exception:
|
|||||||
if (-not $skipPipFallback) {
|
if (-not $skipPipFallback) {
|
||||||
foreach ($tier in $installTiers) {
|
foreach ($tier in $installTiers) {
|
||||||
Write-Info "Trying tier: $($tier.Name) ..."
|
Write-Info "Trying tier: $($tier.Name) ..."
|
||||||
& $UvCmd pip install -e $tier.Spec
|
Invoke-NativeWithRelaxedErrorAction { & $UvCmd pip install -e $tier.Spec }
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
Write-Success "Main package installed ($($tier.Name))"
|
Write-Success "Main package installed ($($tier.Name))"
|
||||||
$script:InstalledTier = $tier.Name
|
$script:InstalledTier = $tier.Name
|
||||||
|
|||||||
94
tests/test_install_ps1_native_stderr_eap.py
Normal file
94
tests/test_install_ps1_native_stderr_eap.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""Regression tests for #48352: Windows PowerShell 5.1 native stderr.
|
||||||
|
|
||||||
|
PowerShell 5.1 turns stderr from native commands into ``NativeCommandError``
|
||||||
|
records when ``$ErrorActionPreference = "Stop"``. ``scripts/install.ps1`` has a
|
||||||
|
few git/uv calls where stderr can be normal progress output, so those calls must
|
||||||
|
run with EAP temporarily relaxed and then inspect ``$LASTEXITCODE``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1"
|
||||||
|
|
||||||
|
|
||||||
|
def _install_ps1() -> str:
|
||||||
|
return INSTALL_PS1.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_relaxed_call(text: str, command_pattern: str) -> None:
|
||||||
|
helper_block_pattern = (
|
||||||
|
r"Invoke-NativeWithRelaxedErrorAction\s*\{[^}]*"
|
||||||
|
+ command_pattern
|
||||||
|
+ r"[^}]*\}"
|
||||||
|
)
|
||||||
|
inline_pattern = (
|
||||||
|
r"\$ErrorActionPreference\s*=\s*\"Continue\"[\s\S]{0,900}?"
|
||||||
|
+ command_pattern
|
||||||
|
)
|
||||||
|
assert re.search(helper_block_pattern, text) or re.search(inline_pattern, text), (
|
||||||
|
f"install.ps1 must relax ErrorActionPreference around {command_pattern}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_repository_stage_relieves_eap_for_ssh_and_https_git_clone() -> None:
|
||||||
|
text = _install_ps1()
|
||||||
|
assert "function Invoke-NativeWithRelaxedErrorAction" in text
|
||||||
|
_assert_relaxed_call(
|
||||||
|
text,
|
||||||
|
r"git -c windows\.appendAtomically=false clone --depth 1 --branch \$Branch \$RepoUrlSsh \$InstallDir",
|
||||||
|
)
|
||||||
|
_assert_relaxed_call(
|
||||||
|
text,
|
||||||
|
r"git -c windows\.appendAtomically=false clone --depth 1 --branch \$Branch \$RepoUrlHttps \$InstallDir",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_uv_venv_and_dependency_installs_relax_eap() -> None:
|
||||||
|
text = _install_ps1()
|
||||||
|
_assert_relaxed_call(text, r"& \$UvCmd venv venv --python \$PythonVersion")
|
||||||
|
_assert_relaxed_call(text, r"& \$UvCmd sync --extra all --locked")
|
||||||
|
_assert_relaxed_call(text, r"& \$UvCmd pip install -e \$tier\.Spec")
|
||||||
|
|
||||||
|
|
||||||
|
def test_uv_venv_failure_is_not_swallowed_after_eap_relax() -> None:
|
||||||
|
"""Relaxing EAP must not let a genuine `uv venv` failure pass as success.
|
||||||
|
|
||||||
|
Once EAP is relaxed, a real non-zero `uv venv` exit no longer aborts on its
|
||||||
|
own, so install.ps1 must capture $LASTEXITCODE right after the call and fail
|
||||||
|
fast — otherwise the `venv` stage falsely reports success (Invoke-Stage emits
|
||||||
|
ok=true) when no venv was created. Regression guard for the gap caught while
|
||||||
|
reviewing #48372 (the explicit check originally proposed in #48463).
|
||||||
|
"""
|
||||||
|
text = _install_ps1()
|
||||||
|
# The uv-venv invocation, then an exit-code capture, then a throw — all
|
||||||
|
# within a small window after the relaxed call.
|
||||||
|
guard = re.search(
|
||||||
|
r"& \$UvCmd venv venv --python \$PythonVersion[\s\S]{0,400}?"
|
||||||
|
r"\$LASTEXITCODE[\s\S]{0,200}?"
|
||||||
|
r"-ne 0[\s\S]{0,200}?throw",
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
assert guard is not None, (
|
||||||
|
"install.ps1 must capture uv venv's exit code and throw on failure after "
|
||||||
|
"relaxing ErrorActionPreference, so a genuine venv-creation failure isn't "
|
||||||
|
"reported as a successful stage"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_native_eap_helper_always_restores_previous_preference() -> None:
|
||||||
|
text = _install_ps1()
|
||||||
|
m = re.search(
|
||||||
|
r"function Invoke-NativeWithRelaxedErrorAction \{(?P<body>[\s\S]*?)^\}",
|
||||||
|
text,
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
assert m is not None, "expected a shared helper for NativeCommandError-safe calls"
|
||||||
|
body = m.group("body")
|
||||||
|
assert "$prevEAP = $ErrorActionPreference" in body
|
||||||
|
assert '$ErrorActionPreference = "Continue"' in body
|
||||||
|
assert "finally" in body
|
||||||
|
assert "$ErrorActionPreference = $prevEAP" in body
|
||||||
Loading…
x
Reference in New Issue
Block a user