fix(install): pin an x64 CPython on Windows on ARM - #3
Merged
Conversation
The Windows installer's dependency stage fails on every tier on ARM64
hardware. `uv python find 3.11` matches a pre-existing arm64 interpreter,
and PyPI publishes no win_arm64 wheels for several exact-pinned deps
(cryptography, pywinpty). uv falls back to the sdist, whose build backend
is maturin, which needs a Rust toolchain for aarch64-pc-windows-msvc and
tries to bootstrap rustup mid-install:
Using CPython 3.11.9 interpreter at: ...\Python311-arm64\python.exe
Python reports platform: win-arm64
Computed rustc target triple: aarch64-pc-windows-msvc
x Failed to build `cryptography==46.0.7`
`-> Call to `maturin.build_wheel` failed (exit code: 1)
error: could not remove 'rustup-bin' file: Access is denied. (os error 5)
All three tiers (all -> all-minus-known-broken -> core-only) hit the same
wall, and the stage then misreports it as "The venv's Python (Python
3.11.9) is supported, so this is NOT a version problem -- look for a
network/proxy block", because it only ever validates the interpreter's
version and never its architecture.
Route every uv interpreter request through a new Get-UvPythonRequest
helper, which on arm64 asks for `cpython-<ver>-windows-x86_64`. The full
request syntax (rather than a bare version) makes uv skip an
already-installed arm64 interpreter instead of matching it, and download
an x64 build when none is present. The x64 build runs under Prism
emulation and resolves prebuilt win_amd64 wheels for the whole tree, so
no compiler is involved. Non-ARM hosts are unaffected -- the helper
returns the bare version unchanged.
The fallback versions are covered too: falling back to 3.12 on arm64
reintroduces the identical missing-wheel failure.
Verified on a Snapdragon-class ARM64 Windows box: with an x64 3.11,
`uv pip install cryptography==46.0.7 pywinpty==2.0.15` resolves wheels in
under a second with no Rust involved.
Tests execute the real PowerShell functions under pwsh (preinstalled on
GitHub's Ubuntu runners) rather than asserting on the shape of the
source, including a stubbed-$UvCmd test that asserts on the argv uv
actually receives. The two EAP regexes in
test_install_ps1_native_stderr_eap.py are loosened to match the
interpreter argument generically, following the rationale already stated
in that file for the git-clone patterns: it is a test about
ErrorActionPreference, not about how the argument is spelled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 903f1a76-87e3-4db7-a707-3fbc8015ee5c
૮ >ﻌ< ა ci reviewran on 2403a69 all good! |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 903f1a76-87e3-4db7-a707-3fbc8015ee5c
Assert-Arm64WheelSupport threw whenever Get-WindowsArch reported arm64, so it fired on every Windows on ARM machine regardless of which Python was actually in use. Its own remedy -- "install the x64 build of Python 3.11 and re-run this installer" -- was therefore unreachable: the x64 interpreter it asks for still trips a CPU-arch check. What decides wheel resolution is the interpreter's platform tag, not the CPU. Windows on ARM runs x64 under emulation, and an x64 CPython reports win-amd64 and resolves the prebuilt amd64 wheels for cryptography and pywinpty. Add Get-PythonPlatformTag and have the guard probe the interpreter the dependency tiers will really install into (the venv's, or uv's resolved one under -NoVenv). A win-arm64 tag still fails fast with the same diagnosis; an unprobeable interpreter fails closed rather than silently falling through to the Rust/OpenSSL source build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 903f1a76-87e3-4db7-a707-3fbc8015ee5c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Installing on a Windows ARM64 machine fails at the
dependenciesstage, on every one of the four fallback tiers. The installer then misreports the cause:It is not a network problem.
uv python find 3.11matches a pre-existing arm64 interpreter, and PyPI publishes nowin_arm64wheel for two exact-pinned deps (cryptography,pywinpty). uv falls back to the sdist, whose build backend is maturin, which wants a full Rust toolchain and an OpenSSL dev environment. Confirmed against the PyPI JSON API: neithercryptography==46.0.7norpywinpty==2.0.15ships awin_arm64wheel.The installer only ever validated an interpreter's version, never its architecture.
Fix
Two parts, because there were two independent blocks.
1. Ask uv for an x64 interpreter on arm64. New
Get-UvPythonRequestreturns uv's fully-qualified requestcpython-<ver>-windows-x86_64on arm64, and the bare version elsewhere. Wired through all 7 uv interpreter call sites (Resolve-AvailablePythonVersion,Test-Python's find/install/verify/fallback,uv venv, both-NoVenvlookups) plus the user-facing recovery string.The full request syntax is load-bearing: it makes uv skip an already-installed arm64 interpreter rather than match it, so
uv python installactually fetches an x64 build. The fallback versions are covered too — falling back to 3.12 on arm64 would reintroduce the identical failure.2. Make
Assert-Arm64WheelSupporttest the interpreter, not the CPU. The guard from b28d9f8 diagnosed this exact root cause correctly, but gated onGet-WindowsArch, so it threw on any ARM64 machine — which made its own prescribed remedy ("install the x64 build of Python 3.11 and re-run this installer") unreachable. With part 1 in place the installer provisions precisely that x64 interpreter, and the guard rejected it.What actually decides wheel resolution is the interpreter's platform tag. Windows on ARM runs x64 under emulation; an x64 CPython reports
win-amd64and resolves every existing amd64 wheel. NewGet-PythonPlatformTagprobessysconfig.get_platform()— the same string maturin echoed asPython reports platform:— on the interpreter the tiers will really install into. Awin-arm64tag still fails fast with the same diagnosis and CVE rationale; an unprobeable interpreter fails closed, since failing open would restore the silent fallthrough into the Rust build for exactly the environments least able to diagnose it.Get-WindowsArchis kept as the cheap pre-filter, so non-arm64 hosts pay nothing and the Prism emulation edge case (an x64 PowerShell host reportingX64on an ARM64 machine) is still handled.No behavior change on x64/x86 hosts: the request passes through untouched and the guard returns immediately.
Verification
CI cannot verify this. Every job in
tests.ymlisubuntu-latest; there is no Windows or ARM64 runner. So this was verified end-to-end on real Snapdragon-class ARM64 hardware.To get a truthful test, the x64 Python 3.11 that had been installed manually was uninstalled first and the baseline bug reproduced (
uv python find 3.11→ arm64 interpreter). Then, with no manual setup:-Stage python-Stage venvUsing CPython 3.11.15-Stage dependenciesResulting venv:
Both previously-unbuildable packages installed from prebuilt wheels. No Rust, no OpenSSL, no manual Python install.
Tests
tests/test_install_ps1_arm64_python_arch.py— 15 tests, all of which execute the real PowerShell functions underpwshrather than pattern-matching the source, per the AGENTS.md ban on reading source in tests. (pwshships on GitHub's Ubuntu images; the module skips cleanly if absent.)Get-PythonPlatformTagis asserted against the live interpreter running the test, so it stays correct on any runner arch.Red-first verified: with
scripts/install.ps1stashed, the interpreter-selection tests and the key regressiontest_x64_interpreter_on_an_arm64_machine_is_allowedall fail.Full local run of all 12
install.ps1test files: 62 passed, 1 skipped. The existingtest_install_ps1_windows_arm64.pyis unmodified and still green — the rewrite deliberately preserves everything it asserts (function name,Get-WindowsArchin the body, theWindows on ARMtext,wheel, andx64|amd64).One unrelated pre-existing change: two regexes in
test_install_ps1_native_stderr_eap.pyhard-coded--python $PythonVersionand so were change-detectors on the literal argument spelling; loosened to--python [^}\n]+, which still pins the contract they exist to protect (the flag is passed inside the EAP-guarded block).Notes
Test-Python's last-resort "system python" fallback is still not arch-checked. Left alone to keep this narrow — the venv is created via the arch-qualified request, so it is not load-bearing on the failing path.