@@ -7,8 +7,11 @@ use futures::StreamExt;
77use itertools:: Itertools ;
88use owo_colors:: OwoColorize ;
99
10+ use same_file:: is_same_file;
11+ use tracing:: { debug, warn} ;
12+ use uv_fs:: Simplified ;
1013use uv_python:: downloads:: PythonDownloadRequest ;
11- use uv_python:: managed:: ManagedPythonInstallations ;
14+ use uv_python:: managed:: { python_executable_dir , ManagedPythonInstallations } ;
1215use uv_python:: PythonRequest ;
1316
1417use 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