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:
Teknium
2026-06-03 16:45:47 -07:00
committed by GitHub
parent e8c3ac2f5c
commit 475ecea3d7
4 changed files with 60 additions and 584 deletions

View File

@ -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

View File

@ -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"