1- use std:: collections:: { BTreeMap , BTreeSet , HashSet } ;
1+ use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ;
22use std:: path:: { Path , PathBuf } ;
33use std:: sync:: Arc ;
44use std:: { env, fs} ;
@@ -9,7 +9,7 @@ use tempfile::Builder as TempFileBuilder;
99use crate :: core:: compiler:: Freshness ;
1010use crate :: core:: compiler:: { DefaultExecutor , Executor } ;
1111use crate :: core:: resolver:: ResolveOpts ;
12- use crate :: core:: { Edition , PackageId , PackageIdSpec , Source , SourceId , Workspace } ;
12+ use crate :: core:: { Edition , Package , PackageId , PackageIdSpec , Source , SourceId , Workspace } ;
1313use crate :: ops;
1414use crate :: ops:: common_for_install_and_uninstall:: * ;
1515use crate :: sources:: { GitSource , SourceConfigMap } ;
@@ -414,6 +414,13 @@ fn install_one(
414414 rustc. verbose_version ,
415415 ) ;
416416
417+ if let Err ( e) = remove_orphaned_bins ( & ws, & mut tracker, & duplicates, pkg, & dst) {
418+ // Don't hard error on remove.
419+ config
420+ . shell ( )
421+ . warn ( format ! ( "failed to remove orphan: {:?}" , e) ) ?;
422+ }
423+
417424 match tracker. save ( ) {
418425 Err ( err) => replace_result. chain_err ( || err) ?,
419426 Ok ( _) => replace_result?,
@@ -521,3 +528,58 @@ pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
521528 }
522529 Ok ( ( ) )
523530}
531+
532+ /// Removes executables that are no longer part of a package that was
533+ /// previously installed.
534+ fn remove_orphaned_bins (
535+ ws : & Workspace < ' _ > ,
536+ tracker : & mut InstallTracker ,
537+ duplicates : & BTreeMap < String , Option < PackageId > > ,
538+ pkg : & Package ,
539+ dst : & Path ,
540+ ) -> CargoResult < ( ) > {
541+ let filter = ops:: CompileFilter :: new_all_targets ( ) ;
542+ let all_self_names = exe_names ( pkg, & filter) ;
543+ let mut to_remove: HashMap < PackageId , BTreeSet < String > > = HashMap :: new ( ) ;
544+ // For each package that we stomped on.
545+ for other_pkg in duplicates. values ( ) {
546+ // Only for packages with the same name.
547+ if let Some ( other_pkg) = other_pkg {
548+ if other_pkg. name ( ) == pkg. name ( ) {
549+ // Check what the old package had installed.
550+ if let Some ( installed) = tracker. installed_bins ( * other_pkg) {
551+ // If the old install has any names that no longer exist,
552+ // add them to the list to remove.
553+ for installed_name in installed {
554+ if !all_self_names. contains ( installed_name. as_str ( ) ) {
555+ to_remove
556+ . entry ( * other_pkg)
557+ . or_default ( )
558+ . insert ( installed_name. clone ( ) ) ;
559+ }
560+ }
561+ }
562+ }
563+ }
564+ }
565+
566+ for ( old_pkg, bins) in to_remove {
567+ tracker. remove ( old_pkg, & bins) ;
568+ for bin in bins {
569+ let full_path = dst. join ( bin) ;
570+ if full_path. exists ( ) {
571+ ws. config ( ) . shell ( ) . status (
572+ "Removing" ,
573+ format ! (
574+ "executable `{}` from previous version {}" ,
575+ full_path. display( ) ,
576+ old_pkg
577+ ) ,
578+ ) ?;
579+ paths:: remove_file ( & full_path)
580+ . chain_err ( || format ! ( "failed to remove {:?}" , full_path) ) ?;
581+ }
582+ }
583+ }
584+ Ok ( ( ) )
585+ }
0 commit comments