@@ -1048,24 +1048,87 @@ impl<'a> Cmd<'a> {
10481048 Ok ( output)
10491049 }
10501050
1051+ #[ cfg( not( windows) ) ]
1052+ fn resolve_program ( & self ) -> OsString {
1053+ self . data . prog . as_os_str ( ) . into ( )
1054+ }
1055+
1056+ #[ cfg( windows) ]
1057+ fn resolve_program ( & self ) -> OsString {
1058+ if self . data . prog . extension ( ) . is_some ( ) {
1059+ // fast path for explicit extension
1060+ return self . data . prog . as_os_str ( ) . into ( ) ;
1061+ }
1062+
1063+ // mimics `search_paths` behavior:
1064+ // https://github.com/rust-lang/rust/blob/051478957371ee0084a7c0913941d2a8c4757bb9/library/std/src/sys/pal/windows/process.rs#L482
1065+
1066+ const ENV_PATH : & str = "PATH" ;
1067+
1068+ // 1. Child paths
1069+ let paths = self
1070+ . data
1071+ . env_changes
1072+ . iter ( )
1073+ . filter_map ( |change| match change {
1074+ EnvChange :: Set ( name, value) if name. eq_ignore_ascii_case ( ENV_PATH ) => Some ( value) ,
1075+ _ => None ,
1076+ } )
1077+ . last ( ) ;
1078+
1079+ if let Some ( program_path) = self . find_in_paths ( paths) {
1080+ return program_path;
1081+ }
1082+
1083+ // 2. Application path
1084+ let paths = env:: current_exe ( ) . ok ( ) . map ( |mut path| {
1085+ path. pop ( ) ;
1086+ OsString :: from ( path)
1087+ } ) ;
1088+
1089+ if let Some ( program_path) = self . find_in_paths ( paths. as_ref ( ) ) {
1090+ return program_path;
1091+ }
1092+
1093+ // 3 & 4. System paths
1094+ // Sort of compromise: use %SystemRoot% to avoid adding an additional dependency on the `windows` crate.
1095+ // Usually %SystemRoot% expands to 'C:\WINDOWS' and 'C:\WINDOWS\SYSTEM32' exists in `PATH`,
1096+ // so the compromise covers both `GetSystemDirectoryW` and `GetWindowsDirectoryW` cases.
1097+ let paths = self . shell . var_os ( "SystemRoot" ) ;
1098+ if let Some ( program_path) = self . find_in_paths ( paths. as_ref ( ) ) {
1099+ return program_path;
1100+ }
1101+
1102+ // 5. Parent paths
1103+ let paths = self . shell . var_os ( ENV_PATH ) ;
1104+ if let Some ( program_path) = self . find_in_paths ( paths. as_ref ( ) ) {
1105+ return program_path;
1106+ }
1107+
1108+ return self . data . prog . as_os_str ( ) . into ( ) ;
1109+ }
1110+
1111+ fn find_in_paths ( & self , paths : Option < & OsString > ) -> Option < OsString > {
1112+ paths. and_then ( |paths| {
1113+ for folder in env:: split_paths ( & paths) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) {
1114+ for ext in [ "cmd" , "bat" ] {
1115+ let path = folder. join ( self . data . prog . with_extension ( ext) ) ;
1116+ if std:: fs:: metadata ( & path) . is_ok ( ) {
1117+ return Some ( path. into_os_string ( ) ) ;
1118+ }
1119+ }
1120+ }
1121+
1122+ None
1123+ } )
1124+ }
1125+
10511126 fn to_command ( & self ) -> Command {
1052- let mut res = if cfg ! ( windows) {
1053- // On windows have to use "cmd /c" workaround to allow batch (command) files
1054- let mut res = Command :: new ( "cmd" ) ;
1055- res. current_dir ( self . shell . current_dir ( ) ) ;
1056- res. args (
1057- [ OsStr :: new ( "/c" ) , self . data . prog . as_os_str ( ) ]
1058- . iter ( )
1059- . map ( |it| * it)
1060- . chain ( self . data . args . iter ( ) . map ( |it| it. as_os_str ( ) ) ) ,
1061- ) ;
1062- res
1063- } else {
1064- let mut res = Command :: new ( & self . data . prog ) ;
1065- res. current_dir ( self . shell . current_dir ( ) ) ;
1066- res. args ( & self . data . args ) ;
1067- res
1068- } ;
1127+ let program = self . resolve_program ( ) ;
1128+ let mut res = Command :: new ( program) ;
1129+
1130+ res. args ( & self . data . args ) ;
1131+ res. current_dir ( self . shell . current_dir ( ) ) ;
10691132
10701133 for ( key, val) in & * self . shell . env . borrow ( ) {
10711134 res. env ( key, val) ;
0 commit comments