mirror of https://github.com/astral-sh/uv
Enable uv to replace and delete itself on Windows (#8914)
## Summary On Windows, we can't delete the currently-running executable -- at least, not trivially. But the [`self_replace`](https://docs.rs/self-replace/latest/self_replace/) crate can help us here. Closes https://github.com/astral-sh/uv/issues/1368. Closes https://github.com/astral-sh/uv/issues/4980. ## Test Plan On my Windows machine: - `maturin build` - `python -m venv .venv` - `.venv/Scripts/activate` - `pip install /path/to/uv.whl` - `uv pip install /path/to/uv.whl` - `uv pip uninstall uv`
This commit is contained in:
parent
9cd51c8a57
commit
0db38844d9
|
|
@ -4796,7 +4796,9 @@ dependencies = [
|
||||||
"reflink-copy",
|
"reflink-copy",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
"same-file",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
"self-replace",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ rustix = { version = "0.38.37", default-features = false, features = ["fs", "std
|
||||||
same-file = { version = "1.0.6" }
|
same-file = { version = "1.0.6" }
|
||||||
schemars = { version = "0.8.21", features = ["url"] }
|
schemars = { version = "0.8.21", features = ["url"] }
|
||||||
seahash = { version = "4.1.0" }
|
seahash = { version = "4.1.0" }
|
||||||
|
self-replace = { version = "1.5.0" }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
serde-untagged = { version = "0.1.6" }
|
serde-untagged = { version = "0.1.6" }
|
||||||
serde_json = { version = "1.0.128" }
|
serde_json = { version = "1.0.128" }
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,10 @@ tracing = { workspace = true }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
zip = { workspace = true }
|
zip = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
same-file = { workspace = true }
|
||||||
|
self-replace = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { version = "1.0.89" }
|
anyhow = { version = "1.0.89" }
|
||||||
assert_fs = { version = "1.1.2" }
|
assert_fs = { version = "1.1.2" }
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,39 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result<Uninstall, Error> {
|
||||||
let mut file_count = 0usize;
|
let mut file_count = 0usize;
|
||||||
let mut dir_count = 0usize;
|
let mut dir_count = 0usize;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let itself = std::env::current_exe().ok();
|
||||||
|
|
||||||
// Uninstall the files, keeping track of any directories that are left empty.
|
// Uninstall the files, keeping track of any directories that are left empty.
|
||||||
let mut visited = BTreeSet::new();
|
let mut visited = BTreeSet::new();
|
||||||
for entry in &record {
|
for entry in &record {
|
||||||
let path = site_packages.join(&entry.path);
|
let path = site_packages.join(&entry.path);
|
||||||
|
|
||||||
|
// On Windows, deleting the current executable is a special case.
|
||||||
|
#[cfg(windows)]
|
||||||
|
if let Some(itself) = itself.as_ref() {
|
||||||
|
if itself
|
||||||
|
.file_name()
|
||||||
|
.is_some_and(|itself| path.file_name().is_some_and(|path| itself == path))
|
||||||
|
{
|
||||||
|
if same_file::is_same_file(itself, &path).unwrap_or(false) {
|
||||||
|
tracing::debug!("Detected self-delete of executable: {}", path.display());
|
||||||
|
match self_replace::self_delete_outside_path(site_packages) {
|
||||||
|
Ok(()) => {
|
||||||
|
trace!("Removed file: {}", path.display());
|
||||||
|
file_count += 1;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
visited.insert(normalize_path(parent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match fs::remove_file(&path) {
|
match fs::remove_file(&path) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
trace!("Removed file: {}", path.display());
|
trace!("Removed file: {}", path.display());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue