fix(install): cap requires-python at <3.14 and pin UV_PYTHON to the venv (#38535)
uv selects the project Python from requires-python and from the UV_PYTHON env var, both of which override an already-created venv on the next 'uv sync'. With no upper bound on requires-python, an inherited UV_PYTHON=3.14 (or a fresh distro whose newest interpreter uv auto-picks) silently recreated the installer's 3.11 venv at 3.14, where Rust-backed transitives (pydantic-core) have no cp314 wheel and fall back to a maturin source build that fails. This bit a Windows/WSL user with UV_PYTHON set in their shell and a fresh WSL-arch box where uv auto-picked 3.14. Two layers: - pyproject: requires-python '>=3.11' -> '>=3.11,<3.14' (+ uv lock regen). uv now refuses a 3.14 interpreter with a clear error instead of attempting the maturin build. Backstop independent of the installer. - install.sh / install.ps1: pin UV_PYTHON to the venv interpreter after creating it (in both the venv step and the deps step, since bootstrap runs those stages as separate processes). An inherited UV_PYTHON can no longer hijack the sync/pip tiers, so the install just works regardless of shell env. Verified E2E: hostile UV_PYTHON=3.14 + uv venv --python 3.11 + uv sync now installs into 3.11 with pydantic-core's 3.11 wheel; without the re-pin the capped requires-python produces a legible incompatibility error rather than a cryptic build failure.
This commit is contained in:
@ -1268,7 +1268,19 @@ function Install-Venv {
|
||||
|
||||
# uv creates the venv and pins the Python version in one step
|
||||
& $UvCmd venv venv --python $PythonVersion
|
||||
|
||||
|
||||
# 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
|
||||
# later `uv sync` / `uv pip install` tiers, so without this it would
|
||||
# silently delete this 3.11 venv and recreate it at the inherited version
|
||||
# -- building Rust transitives that have no wheel for that version from
|
||||
# source via maturin, which fails. Pinning UV_PYTHON to the interpreter we
|
||||
# just created forces every subsequent uv command onto it.
|
||||
$venvPythonExe = Join-Path $InstallDir "venv\Scripts\python.exe"
|
||||
if (Test-Path $venvPythonExe) {
|
||||
$env:UV_PYTHON = $venvPythonExe
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
|
||||
Write-Success "Virtual environment ready (Python $PythonVersion)"
|
||||
@ -1284,6 +1296,20 @@ function Install-Dependencies {
|
||||
$env:VIRTUAL_ENV = "$InstallDir\venv"
|
||||
}
|
||||
|
||||
# Re-pin UV_PYTHON to the venv interpreter. Install-Venv already does this,
|
||||
# but the bootstrap runs install stages (venv, python-deps) as separate
|
||||
# processes, so the env var set in Install-Venv does NOT survive into a
|
||||
# separate python-deps invocation. Re-deriving it here covers that path.
|
||||
# Without it, an inherited $env:UV_PYTHON = "3.14" makes the uv sync/pip
|
||||
# tiers below recreate the venv at 3.14 and fail the maturin source build
|
||||
# (no cp314 wheels yet).
|
||||
if (-not $NoVenv) {
|
||||
$venvPythonExe = Join-Path $InstallDir "venv\Scripts\python.exe"
|
||||
if (Test-Path $venvPythonExe) {
|
||||
$env:UV_PYTHON = $venvPythonExe
|
||||
}
|
||||
}
|
||||
|
||||
# Hash-verified install (Tier 0) -- when uv.lock is present, prefer
|
||||
# `uv sync --locked`. The lockfile records SHA256 hashes for every
|
||||
# transitive dependency, so a compromised transitive (different hash
|
||||
|
||||
@ -1178,12 +1178,33 @@ setup_venv() {
|
||||
# uv creates the venv and pins the Python version in one step
|
||||
$UV_CMD venv venv --python "$PYTHON_VERSION"
|
||||
|
||||
# Neutralize any inherited UV_PYTHON (e.g. UV_PYTHON=3.14 left in the
|
||||
# user's shell env). uv honours UV_PYTHON over an existing venv for the
|
||||
# later `uv sync` / `uv pip install` tiers, so without this it would
|
||||
# silently delete this 3.11 venv and recreate it at the inherited
|
||||
# version — building Rust transitives that have no wheel for that
|
||||
# version from source via maturin, which fails. Pinning UV_PYTHON to the
|
||||
# interpreter we just created forces every subsequent uv command onto it.
|
||||
if [ -x "$INSTALL_DIR/venv/bin/python" ]; then
|
||||
export UV_PYTHON="$INSTALL_DIR/venv/bin/python"
|
||||
fi
|
||||
|
||||
log_success "Virtual environment ready (Python $PYTHON_VERSION)"
|
||||
}
|
||||
|
||||
install_deps() {
|
||||
log_info "Installing dependencies..."
|
||||
|
||||
# Re-pin UV_PYTHON to the venv interpreter. setup_venv already does this,
|
||||
# but the bootstrap runs install stages (`venv`, `python-deps`) as separate
|
||||
# processes, so an export from setup_venv does NOT survive into a separate
|
||||
# python-deps invocation. Re-deriving it here covers that path. Without it,
|
||||
# an inherited UV_PYTHON=3.14 makes the uv sync/pip tiers below recreate the
|
||||
# venv at 3.14 and fail the maturin source build (no cp314 wheels yet).
|
||||
if [ "$DISTRO" != "termux" ] && [ -x "$INSTALL_DIR/venv/bin/python" ]; then
|
||||
export UV_PYTHON="$INSTALL_DIR/venv/bin/python"
|
||||
fi
|
||||
|
||||
if [ "$DISTRO" = "termux" ]; then
|
||||
if [ "$USE_VENV" = true ]; then
|
||||
export VIRTUAL_ENV="$INSTALL_DIR/venv"
|
||||
|
||||
Reference in New Issue
Block a user