@@ -7,8 +7,11 @@ use futures::StreamExt;
7
7
use itertools:: Itertools ;
8
8
use owo_colors:: OwoColorize ;
9
9
10
+ use same_file:: is_same_file;
11
+ use tracing:: { debug, warn} ;
12
+ use uv_fs:: Simplified ;
10
13
use uv_python:: downloads:: PythonDownloadRequest ;
11
- use uv_python:: managed:: ManagedPythonInstallations ;
14
+ use uv_python:: managed:: { python_executable_dir , ManagedPythonInstallations } ;
12
15
use uv_python:: PythonRequest ;
13
16
14
17
use crate :: commands:: python:: { ChangeEvent , ChangeEventKind } ;
@@ -121,6 +124,40 @@ async fn do_uninstall(
121
124
return Ok ( ExitStatus :: Failure ) ;
122
125
}
123
126
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
+
124
161
let mut tasks = FuturesUnordered :: new ( ) ;
125
162
for installation in & matching_installations {
126
163
tasks. push ( async {
@@ -180,7 +217,13 @@ async fn do_uninstall(
180
217
{
181
218
match event. kind {
182
219
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
+ ) ?;
184
227
}
185
228
_ => unreachable ! ( ) ,
186
229
}
0 commit comments