diff --git a/Cargo.lock b/Cargo.lock index 04887d74a..a3d7ce814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5674,7 +5674,9 @@ dependencies = [ "tracing", "uv-fs", "uv-static", - "winreg", + "windows-registry 0.5.0", + "windows-result 0.3.1", + "windows-sys 0.59.0", ] [[package]] @@ -6507,16 +6509,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a47b489f8fc5b949477e89dca4d1617f162c6c53fbcbefde553ab17b342ff9" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winsafe" version = "0.0.19" diff --git a/Cargo.toml b/Cargo.toml index 06e9e9f89..3bd1d7e16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -183,7 +183,6 @@ 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"] } -winreg = { version = "0.53.0" } winsafe = { version = "0.0.22", features = ["kernel"] } wiremock = { version = "0.6.2" } xz2 = { version = "0.1.7" } diff --git a/crates/uv-shell/Cargo.toml b/crates/uv-shell/Cargo.toml index f98be0990..7e03328ec 100644 --- a/crates/uv-shell/Cargo.toml +++ b/crates/uv-shell/Cargo.toml @@ -20,4 +20,6 @@ same-file = { workspace = true } tracing = { workspace = true } [target.'cfg(windows)'.dependencies] -winreg = { workspace = true } +windows-registry = { workspace = true } +windows-result = { workspace = true } +windows-sys = { workspace = true } diff --git a/crates/uv-shell/src/windows.rs b/crates/uv-shell/src/windows.rs index 68cf9aa8c..00c46eb79 100644 --- a/crates/uv-shell/src/windows.rs +++ b/crates/uv-shell/src/windows.rs @@ -1,19 +1,18 @@ //! Windows-specific utilities for manipulating the environment. //! -//! Based on rustup's Windows implementation: +//! Based on rustup's Windows implementation: #![cfg(windows)] -use std::ffi::OsString; -use std::io; -use std::os::windows::ffi::OsStrExt; use std::path::Path; -use std::slice; use anyhow::Context; +use tracing::warn; +use windows_registry::{CURRENT_USER, HSTRING}; +use windows_result::HRESULT; +use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_INVALID_DATA}; + use uv_static::EnvVars; -use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; -use winreg::{RegKey, RegValue}; /// Append the given [`Path`] to the `PATH` environment variable in the Windows registry. /// @@ -24,34 +23,27 @@ pub fn prepend_path(path: &Path) -> anyhow::Result { let windows_path = get_windows_path_var()?; // Add the new path to the existing `PATH` variable. - let windows_path = windows_path.and_then(|windows_path| { - prepend_to_path(windows_path, OsString::from(path).encode_wide().collect()) - }); - + let windows_path = + windows_path.and_then(|windows_path| prepend_to_path(&windows_path, HSTRING::from(path))); // If the path didn't change, then we don't need to do anything. let Some(windows_path) = windows_path else { return Ok(false); }; // Set the `PATH` variable in the registry. - apply_windows_path_var(windows_path)?; + apply_windows_path_var(&windows_path)?; Ok(true) } /// Set the windows `PATH` variable in the registry. -fn apply_windows_path_var(path: Vec) -> anyhow::Result<()> { - let root = RegKey::predef(HKEY_CURRENT_USER); - let environment = root.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?; +fn apply_windows_path_var(path: &HSTRING) -> anyhow::Result<()> { + let environment = CURRENT_USER.create("Environment")?; if path.is_empty() { - environment.delete_value(EnvVars::PATH)?; + environment.remove_value(EnvVars::PATH)?; } else { - let reg_value = RegValue { - bytes: to_winreg_bytes(path), - vtype: RegType::REG_EXPAND_SZ, - }; - environment.set_raw_value(EnvVars::PATH, ®_value)?; + environment.set_expand_hstring(EnvVars::PATH, path)?; } Ok(()) @@ -60,23 +52,21 @@ fn apply_windows_path_var(path: Vec) -> anyhow::Result<()> { /// Retrieve the windows `PATH` variable from the registry. /// /// Returns `Ok(None)` if the `PATH` variable is not a string. -fn get_windows_path_var() -> anyhow::Result>> { - let root = RegKey::predef(HKEY_CURRENT_USER); - let environment = root - .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) +fn get_windows_path_var() -> anyhow::Result> { + let environment = CURRENT_USER + .create("Environment") .context("Failed to open `Environment` key")?; - let reg_value = environment.get_raw_value(EnvVars::PATH); + let reg_value = environment.get_hstring(EnvVars::PATH); match reg_value { - Ok(reg_value) => { - if let Some(reg_value) = from_winreg_value(®_value) { - Ok(Some(reg_value)) - } else { - tracing::warn!("`HKEY_CURRENT_USER\\Environment\\PATH` is a non-string"); - Ok(None) - } + Ok(reg_value) => Ok(Some(reg_value)), + Err(err) if err.code() == HRESULT::from_win32(ERROR_INVALID_DATA) => { + warn!("`HKEY_CURRENT_USER\\Environment\\PATH` is a non-string"); + Ok(None) + } + Err(err) if err.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND) => { + Ok(Some(HSTRING::new())) } - Err(ref err) if err.kind() == io::ErrorKind::NotFound => Ok(Some(Vec::new())), Err(err) => Err(err.into()), } } @@ -84,46 +74,15 @@ fn get_windows_path_var() -> anyhow::Result>> { /// Prepend a path to the `PATH` variable in the Windows registry. /// /// Returns `Ok(None)` if the given path is already in `PATH`. -fn prepend_to_path(existing_path: Vec, path: Vec) -> Option> { +fn prepend_to_path(existing_path: &HSTRING, path: HSTRING) -> Option { if existing_path.is_empty() { Some(path) - } else if existing_path.windows(path.len()).any(|p| p == path) { + } else if existing_path.windows(path.len()).any(|p| *p == *path) { None } else { - let mut new_path = path; - new_path.push(u16::from(b';')); - new_path.extend(existing_path); - Some(new_path) - } -} - -/// Convert a vector UCS-2 chars to a null-terminated UCS-2 string in bytes. -fn to_winreg_bytes(mut value: Vec) -> Vec { - value.push(0); - #[allow(unsafe_code)] - unsafe { - slice::from_raw_parts(value.as_ptr().cast::(), value.len() * 2).to_vec() - } -} - -/// Decode the `HKCU\Environment\PATH` value. -/// -/// If the key is not `REG_SZ` or `REG_EXPAND_SZ`, returns `None`. -/// The `winreg` library itself does a lossy unicode conversion. -fn from_winreg_value(val: &RegValue) -> Option> { - match val.vtype { - RegType::REG_SZ | RegType::REG_EXPAND_SZ => { - #[allow(unsafe_code)] - let mut words = unsafe { - #[allow(clippy::cast_ptr_alignment)] - slice::from_raw_parts(val.bytes.as_ptr().cast::(), val.bytes.len() / 2) - .to_owned() - }; - while words.last() == Some(&0) { - words.pop(); - } - Some(words) - } - _ => None, + let mut new_path = path.to_os_string(); + new_path.push(";"); + new_path.push(existing_path.to_os_string()); + Some(HSTRING::from(new_path)) } }