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:
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user