From ceffd7ff80b32248b5ea2ad3f1e6f79b7fb58eb4 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 3 Mar 2025 15:46:00 +0100 Subject: [PATCH] Discover registry PEP 514 Pythons cross 32/64-bit (#11801) Fixes #11217 By default, a 64-bit uv does not see a 32-bit global (HKLM) installation of Python in the registry (https://github.com/astral-sh/uv/issues/11217). To work around this, we manually request both 32-bit and 64-bit access using registry access flags (https://peps.python.org/pep-0514/#sample-code). The flags have no effect on 32-bit (https://stackoverflow.com/a/12796797/3549270). This effect is that there is an asymmetry between discovery modes: For the registry-based discovery using PEP 514, we discover both 32-bit and 64-bit Pythons, while for managed installations, we are stricter and only discover those matching in bit-ness. I tested this manually with an additional 32-bit installation of CPython on a 64-bit machine and windows with 32-bit and 64-bit (x86_64 and i686) builds of uv. --- Cargo.toml | 2 +- crates/uv-python/src/windows_registry.rs | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f25132857..07c6e4a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -181,7 +181,7 @@ walkdir = { version = "2.5.0" } which = { version = "7.0.0", features = ["regex"] } windows-registry = { version = "0.5.0" } windows-result = { version = "0.3.0" } -windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO"] } +windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_Registry"] } winsafe = { version = "0.0.23", features = ["kernel"] } wiremock = { version = "0.6.2" } xz2 = { version = "0.1.7" } diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index 7589c9e47..7b6ea2dd9 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -14,6 +14,7 @@ use uv_warnings::{warn_user, warn_user_once}; use windows_registry::{Key, Value, CURRENT_USER, HSTRING, LOCAL_MACHINE}; use windows_result::HRESULT; use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; +use windows_sys::Win32::System::Registry::{KEY_WOW64_32KEY, KEY_WOW64_64KEY}; /// Code returned when the registry key doesn't exist. const ERROR_NOT_FOUND: HRESULT = HRESULT::from_win32(ERROR_FILE_NOT_FOUND); @@ -32,9 +33,22 @@ pub(crate) struct WindowsPython { /// Find all Pythons registered in the Windows registry following PEP 514. pub(crate) fn registry_pythons() -> Result, windows_result::Error> { let mut registry_pythons = Vec::new(); - // Prefer `HKEY_CURRENT_USER` over `HKEY_LOCAL_MACHINE` - for root_key in [CURRENT_USER, LOCAL_MACHINE] { - let Ok(key_python) = root_key.open(r"Software\Python") else { + // Prefer `HKEY_CURRENT_USER` over `HKEY_LOCAL_MACHINE`. + // By default, a 64-bit program does not see a 32-bit global (HKLM) installation of Python in + // the registry (https://github.com/astral-sh/uv/issues/11217). To work around this, we manually + // request both 32-bit and 64-bit access. The flags have no effect on 32-bit + // (https://stackoverflow.com/a/12796797/3549270). + for (root_key, access_modifier) in [ + (CURRENT_USER, None), + (LOCAL_MACHINE, Some(KEY_WOW64_64KEY)), + (LOCAL_MACHINE, Some(KEY_WOW64_32KEY)), + ] { + let mut open_options = root_key.options(); + open_options.read(); + if let Some(access_modifier) = access_modifier { + open_options.access(access_modifier); + } + let Ok(key_python) = open_options.open(r"Software\Python") else { continue; }; for company in key_python.keys()? {