diff --git a/Cargo.lock b/Cargo.lock index f8daa380b..7da47902c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5558,6 +5558,7 @@ version = "0.0.1" dependencies = [ "fs-err 3.0.0", "pathdiff", + "self-replace", "serde", "thiserror 2.0.9", "toml", @@ -5624,6 +5625,7 @@ dependencies = [ "fs-err 3.0.0", "itertools 0.13.0", "pathdiff", + "self-replace", "thiserror 2.0.9", "tracing", "uv-fs", diff --git a/crates/uv-tool/Cargo.toml b/crates/uv-tool/Cargo.toml index b82791800..081162c01 100644 --- a/crates/uv-tool/Cargo.toml +++ b/crates/uv-tool/Cargo.toml @@ -29,6 +29,7 @@ uv-settings = { workspace = true } uv-state = { workspace = true } uv-static = { workspace = true } uv-virtualenv = { workspace = true } + fs-err = { workspace = true } pathdiff = { workspace = true } serde = { workspace = true } @@ -36,3 +37,6 @@ thiserror = { workspace = true } toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true } + +[target.'cfg(target_os = "windows")'.dependencies] +self-replace = { workspace = true } diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index 081be9a72..773506b3d 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -184,8 +184,16 @@ impl InstalledTools { environment_path.user_display() ); - // TODO(charlie): On Windows, if the current executable is in the directory, - // we need to use `safe_delete`. + // On Windows, if the current executable is in the directory, guard against self-deletion. + #[cfg(windows)] + if let Ok(itself) = std::env::current_exe() { + let target = std::path::absolute(&environment_path)?; + if itself.starts_with(&target) { + debug!("Detected self-delete of executable: {}", itself.display()); + self_replace::self_delete_outside_path(&environment_path)?; + } + } + fs_err::remove_dir_all(environment_path)?; Ok(()) diff --git a/crates/uv-virtualenv/Cargo.toml b/crates/uv-virtualenv/Cargo.toml index cf51a1c59..4ab958f2a 100644 --- a/crates/uv-virtualenv/Cargo.toml +++ b/crates/uv-virtualenv/Cargo.toml @@ -32,3 +32,6 @@ itertools = { workspace = true } pathdiff = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } + +[target.'cfg(target_os = "windows")'.dependencies] +self-replace = { workspace = true } diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 89552f477..74b7fb0c5 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -105,9 +105,19 @@ pub(crate) fn create( if allow_existing { debug!("Allowing existing directory"); } else if location.join("pyvenv.cfg").is_file() { - // TODO(charlie): On Windows, if the current executable is in the directory, - // we need to use `safe_delete`. debug!("Removing existing directory"); + + // On Windows, if the current executable is in the directory, guard against + // self-deletion. + #[cfg(windows)] + if let Ok(itself) = std::env::current_exe() { + let target = std::path::absolute(location)?; + if itself.starts_with(&target) { + debug!("Detected self-delete of executable: {}", itself.display()); + self_replace::self_delete_outside_path(location)?; + } + } + fs::remove_dir_all(location)?; fs::create_dir_all(location)?; } else if location