Skip to content

fix(install): pin an x64 CPython on Windows on ARM - #3

Merged
SlowGreek merged 3 commits into
costas-codefrom
fix/win-arm64-python-x64-venv
Jul 29, 2026
Merged

fix(install): pin an x64 CPython on Windows on ARM#3
SlowGreek merged 3 commits into
costas-codefrom
fix/win-arm64-python-x64-venv

Conversation

@fernandoviton

@fernandoviton fernandoviton commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

Installing on a Windows ARM64 machine fails at the dependencies stage, on every one of the four fallback tiers. The installer then misreports the cause:

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)
...
This is NOT a version problem -- look for a network/proxy block

It is not a network problem. uv python find 3.11 matches a pre-existing arm64 interpreter, and PyPI publishes no win_arm64 wheel 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: neither cryptography==46.0.7 nor pywinpty==2.0.15 ships a win_arm64 wheel.

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-UvPythonRequest returns uv's fully-qualified request cpython-<ver>-windows-x86_64 on 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 -NoVenv lookups) 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 install actually 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-Arm64WheelSupport test the interpreter, not the CPU. The guard from b28d9f8 diagnosed this exact root cause correctly, but gated on Get-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-amd64 and resolves every existing amd64 wheel. New Get-PythonPlatformTag probes sysconfig.get_platform() — the same string maturin echoed as Python reports platform: — on the interpreter the tiers will really install into. A win-arm64 tag 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-WindowsArch is kept as the cheap pre-filter, so non-arm64 hosts pay nothing and the Prism emulation edge case (an x64 PowerShell host reporting X64 on 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.yml is ubuntu-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 Result
-Stage python Rejected the arm64 interpreter, installed uv-managed x64 3.11.15
-Stage venv Using CPython 3.11.15
-Stage dependencies Passed in 49s — previously failed all four tiers

Resulting venv:

3.11.15 (main, Jul 18 2026) [MSC v.1944 64 bit (AMD64)]
platform tag: win-amd64
machine: AMD64
cryptography 46.0.7   <- the package that could not build
pywinpty OK           <- the other one

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 under pwsh rather than pattern-matching the source, per the AGENTS.md ban on reading source in tests. (pwsh ships on GitHub's Ubuntu images; the module skips cleanly if absent.) Get-PythonPlatformTag is asserted against the live interpreter running the test, so it stays correct on any runner arch.

Red-first verified: with scripts/install.ps1 stashed, the interpreter-selection tests and the key regression test_x64_interpreter_on_an_arm64_machine_is_allowed all fail.

Full local run of all 12 install.ps1 test files: 62 passed, 1 skipped. The existing test_install_ps1_windows_arm64.py is unmodified and still green — the rewrite deliberately preserves everything it asserts (function name, Get-WindowsArch in the body, the Windows on ARM text, wheel, and x64|amd64).

One unrelated pre-existing change: two regexes in test_install_ps1_native_stderr_eap.py hard-coded --python $PythonVersion and 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.
  • Native ARM64 wheel support remains the known gap; this makes the documented x64 path work automatically instead of requiring a manual install that the guard then rejected.

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

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 2403a69

all good!

fernandoviton and others added 2 commits July 29, 2026 12:03
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
@SlowGreek
SlowGreek merged commit d49eb1c into costas-code Jul 29, 2026
36 checks passed
@SlowGreek
SlowGreek deleted the fix/win-arm64-python-x64-venv branch July 29, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants