Skip to content

Commit 7b2c090

Browse files
committed
Remove Python executables on uninstall
1 parent 6bf47d0 commit 7b2c090

File tree

1 file changed

+45
-2
lines changed

1 file changed

+45
-2
lines changed

crates/uv/src/commands/python/uninstall.rs

+45-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ use futures::StreamExt;
77
use itertools::Itertools;
88
use owo_colors::OwoColorize;
99

10+
use same_file::is_same_file;
11+
use tracing::{debug, warn};
12+
use uv_fs::Simplified;
1013
use uv_python::downloads::PythonDownloadRequest;
11-
use uv_python::managed::ManagedPythonInstallations;
14+
use uv_python::managed::{python_executable_dir, ManagedPythonInstallations};
1215
use uv_python::PythonRequest;
1316

1417
use crate::commands::python::{ChangeEvent, ChangeEventKind};
@@ -121,6 +124,40 @@ async fn do_uninstall(
121124
return Ok(ExitStatus::Failure);
122125
}
123126

127+
// Collect files in a directory
128+
let executables = python_executable_dir()?
129+
.read_dir()?
130+
.filter_map(|entry| match entry {
131+
Ok(entry) => Some(entry),
132+
Err(err) => {
133+
warn!("Failed to read executable: {}", err);
134+
None
135+
}
136+
})
137+
.filter(|entry| entry.file_type().is_ok_and(|file_type| !file_type.is_dir()))
138+
.map(|entry| entry.path())
139+
// Only include files that match the expected Python executable names
140+
// TODO(zanieb): This is a minor optimization to avoid opening more files, but we could
141+
// leave broken links behind, i.e., if the user created them.
142+
.filter(|path| {
143+
matching_installations.iter().any(|installation| {
144+
path.file_name().and_then(|name| name.to_str())
145+
== Some(&installation.key().versioned_executable_name())
146+
})
147+
})
148+
// Only include Python executables that match the installations
149+
.filter(|path| {
150+
matching_installations.iter().any(|installation| {
151+
is_same_file(path, installation.executable()).unwrap_or_default()
152+
})
153+
})
154+
.collect::<BTreeSet<_>>();
155+
156+
for executable in &executables {
157+
fs_err::remove_file(executable)?;
158+
debug!("Removed {}", executable.user_display());
159+
}
160+
124161
let mut tasks = FuturesUnordered::new();
125162
for installation in &matching_installations {
126163
tasks.push(async {
@@ -180,7 +217,13 @@ async fn do_uninstall(
180217
{
181218
match event.kind {
182219
ChangeEventKind::Removed => {
183-
writeln!(printer.stderr(), " {} {}", "-".red(), event.key.bold())?;
220+
writeln!(
221+
printer.stderr(),
222+
" {} {} ({})",
223+
"-".red(),
224+
event.key.bold(),
225+
event.key.versioned_executable_name()
226+
)?;
184227
}
185228
_ => unreachable!(),
186229
}

0 commit comments

Comments
 (0)