Use the windows crate facade consistently (#15737)

The initial motivation for this change was that we were using both the
`windows`, the `window_sys` and the `windows_core` crate in various
places. These crates have slightly unconventional versioning scheme
where there is a large workspace with the same version in general, but
only some crates get breaking releases when a new breaking release
happens, the others stay on the previous breaking version. The `windows`
crate is a shim for all three of them, with a single version. This
simplifies handling the versions.

Using `windows` over `windows_sys` has the advantage of a higher level
error interface, we now get a `Result` for all windows API calls instead
of C-style int-returns and get-last-error calls. This makes the
uv-keyring crate more resilient.

We keep using the `windows_registry` crate, which provides a higher
level interface to windows registry access.
This commit is contained in:
konsti
2025-09-09 17:07:14 +02:00
committed by GitHub
parent 12764df8b2
commit cd49e1d11f
15 changed files with 123 additions and 130 deletions
+1 -2
View File
@@ -70,8 +70,7 @@ once_cell = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
windows-registry = { workspace = true }
windows-result = { workspace = true }
windows-sys = { workspace = true }
windows = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }
+24 -22
View File
@@ -8,9 +8,6 @@ use std::{env, io, iter};
use std::{path::Path, path::PathBuf, str::FromStr};
use thiserror::Error;
use tracing::{debug, instrument, trace};
use uv_preview::Preview;
use which::{which, which_all};
use uv_cache::Cache;
use uv_fs::Simplified;
use uv_fs::which::is_executable;
@@ -18,8 +15,10 @@ use uv_pep440::{
LowerBound, Prerelease, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
release_specifiers_to_ranges,
};
use uv_preview::Preview;
use uv_static::EnvVars;
use uv_warnings::warn_user_once;
use which::{which, which_all};
use crate::downloads::{PlatformRequest, PythonDownloadRequest};
use crate::implementation::ImplementationName;
@@ -251,7 +250,7 @@ pub enum Error {
#[cfg(windows)]
#[error("Failed to query installed Python versions from the Windows registry")]
RegistryError(#[from] windows_result::Error),
RegistryError(#[from] windows::core::Error),
/// An invalid version request was given
#[error("Invalid version request: {0}")]
@@ -1502,13 +1501,15 @@ fn warn_on_unsupported_python(interpreter: &Interpreter) {
pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
use std::os::windows::fs::MetadataExt;
use std::os::windows::prelude::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_OPEN_REPARSE_POINT, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_MODE, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
OPEN_EXISTING,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
use windows::Win32::System::IO::DeviceIoControl;
use windows::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
use windows::core::PCWSTR;
// The path must be absolute.
if !path.is_absolute() {
@@ -1553,7 +1554,7 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
let Ok(md) = fs_err::symlink_metadata(path) else {
return false;
};
if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0 {
if md.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0 == 0 {
return false;
}
@@ -1567,19 +1568,19 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
#[allow(unsafe_code)]
let reparse_handle = unsafe {
CreateFileW(
path_encoded.as_mut_ptr(),
PCWSTR(path_encoded.as_mut_ptr()),
0,
0,
std::ptr::null_mut(),
FILE_SHARE_MODE(0),
None,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(),
None,
)
};
if reparse_handle == INVALID_HANDLE_VALUE {
let Ok(reparse_handle) = reparse_handle else {
return false;
}
};
let mut buf = [0u16; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
let mut bytes_returned = 0;
@@ -1590,19 +1591,20 @@ pub(crate) fn is_windows_store_shim(path: &Path) -> bool {
DeviceIoControl(
reparse_handle,
FSCTL_GET_REPARSE_POINT,
std::ptr::null_mut(),
None,
0,
buf.as_mut_ptr().cast(),
Some(buf.as_mut_ptr().cast()),
buf.len() as u32 * 2,
&raw mut bytes_returned,
std::ptr::null_mut(),
) != 0
Some(&raw mut bytes_returned),
None,
)
.is_ok()
};
// SAFETY: The handle is valid.
#[allow(unsafe_code)]
unsafe {
CloseHandle(reparse_handle);
let _ = CloseHandle(reparse_handle);
}
// If the operation failed, assume it's not a reparse point.
+5 -3
View File
@@ -34,7 +34,7 @@ use crate::{
};
#[cfg(windows)]
use windows_sys::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE};
use windows::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE, WIN32_ERROR};
/// A Python executable and its associated platform markers.
#[derive(Debug, Clone)]
@@ -928,8 +928,10 @@ impl InterpreterInfo {
_ => {}
}
#[cfg(windows)]
if let Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) =
err.raw_os_error().and_then(|code| u32::try_from(code).ok())
if let Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) = err
.raw_os_error()
.and_then(|code| u32::try_from(code).ok())
.map(WIN32_ERROR)
{
// These error codes are returned if the Python interpreter is a corrupt MSIX
// package, which we want to differentiate from a typical spawn failure.
+2 -2
View File
@@ -15,7 +15,7 @@ use thiserror::Error;
use tracing::{debug, warn};
use uv_preview::{Preview, PreviewFeatures};
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
use windows::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
use uv_platform::{Error as PlatformError, Os};
@@ -857,7 +857,7 @@ impl PythonMinorVersionLink {
.is_ok_and(|metadata| {
// Check that this is a reparse point, which indicates this
// is a symlink or junction.
(metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT) != 0
(metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT.0) != 0
})
}
}
+11 -14
View File
@@ -12,13 +12,10 @@ use thiserror::Error;
use tracing::debug;
use uv_platform::Arch;
use uv_warnings::{warn_user, warn_user_once};
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows::Win32::System::Registry::{KEY_WOW64_32KEY, KEY_WOW64_64KEY};
use windows::core::HRESULT;
use windows_registry::{CURRENT_USER, HSTRING, Key, LOCAL_MACHINE, Value};
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);
/// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft
/// Store path.
@@ -32,7 +29,7 @@ pub(crate) struct WindowsPython {
}
/// Find all Pythons registered in the Windows registry following PEP 514.
pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::Error> {
pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows::core::Error> {
let mut registry_pythons = Vec::new();
// 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
@@ -47,7 +44,7 @@ pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::E
let mut open_options = root_key.options();
open_options.read();
if let Some(access_modifier) = access_modifier {
open_options.access(access_modifier);
open_options.access(access_modifier.0);
}
let Ok(key_python) = open_options.open(r"Software\Python") else {
continue;
@@ -131,7 +128,7 @@ pub enum ManagedPep514Error {
#[error("Windows has an unknown pointer width for arch: `{_0}`")]
InvalidPointerSize(Arch),
#[error("Failed to write registry entry: {0}")]
WriteError(#[from] windows_result::Error),
WriteError(#[from] windows::core::Error),
}
/// Register a managed Python installation in the Windows registry following PEP 514.
@@ -216,7 +213,7 @@ pub fn remove_registry_entry<'a>(
if all {
debug!("Removing registry key HKCU:\\{}", astral_key);
if let Err(err) = CURRENT_USER.remove_tree(&astral_key) {
if err.code() == ERROR_NOT_FOUND {
if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
debug!("No registry entries to remove, no registry key {astral_key}");
} else {
warn_user!("Failed to clear registry entries under {astral_key}: {err}");
@@ -230,7 +227,7 @@ pub fn remove_registry_entry<'a>(
let python_entry = format!("{astral_key}\\{python_tag}");
debug!("Removing registry key HKCU:\\{}", python_entry);
if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
if err.code() == ERROR_NOT_FOUND {
if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
debug!(
"No registry entries to remove for {}, no registry key {}",
installation.key(),
@@ -256,7 +253,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
let astral_key = format!("Software\\Python\\{COMPANY_KEY}");
let key = match CURRENT_USER.open(&astral_key) {
Ok(subkeys) => subkeys,
Err(err) if err.code() == ERROR_NOT_FOUND => {
Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => {
return;
}
Err(err) => {
@@ -268,7 +265,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
// Separate assignment since `keys()` creates a borrow.
let subkeys = match key.keys() {
Ok(subkeys) => subkeys,
Err(err) if err.code() == ERROR_NOT_FOUND => {
Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => {
return;
}
Err(err) => {
@@ -284,7 +281,7 @@ pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation
let python_entry = format!("{astral_key}\\{subkey}");
debug!("Removing orphan registry key HKCU:\\{}", python_entry);
if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
if err.code() == ERROR_NOT_FOUND {
if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) {
continue;
}
// TODO(konsti): We don't have an installation key here.