Enable uv to replace and delete itself on Windows (#8914)
Some checks are pending
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## 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:
Charlie Marsh 2024-11-07 21:57:38 -05:00 committed by GitHub
parent 9cd51c8a57
commit 0db38844d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 36 additions and 0 deletions

View file

@ -52,6 +52,10 @@ tracing = { workspace = true }
walkdir = { workspace = true }
zip = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
same-file = { workspace = true }
self-replace = { workspace = true }
[dev-dependencies]
anyhow = { version = "1.0.89" }
assert_fs = { version = "1.1.2" }

View file

@ -33,10 +33,39 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result<Uninstall, Error> {
let mut file_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.
let mut visited = BTreeSet::new();
for entry in &record {
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) {
Ok(()) => {
trace!("Removed file: {}", path.display());