Skip to content

Commit b8ce021

Browse files
committed
Retry failed command as .cmd or .bat
1 parent 79faeaf commit b8ce021

File tree

2 files changed

+65
-37
lines changed

2 files changed

+65
-37
lines changed

src/lib.rs

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,23 +1000,56 @@ impl<'a> Cmd<'a> {
10001000
Ok(stream)
10011001
}
10021002

1003+
fn spawn_command_as(
1004+
&self,
1005+
read_stdout: bool,
1006+
read_stderr: bool,
1007+
program: Option<&OsStr>,
1008+
) -> std::io::Result<std::process::Child> {
1009+
let mut command = self.to_command_impl(program);
1010+
1011+
if !self.data.ignore_stdout {
1012+
command.stdout(if read_stdout { Stdio::piped() } else { Stdio::inherit() });
1013+
}
1014+
if !self.data.ignore_stderr {
1015+
command.stderr(if read_stderr { Stdio::piped() } else { Stdio::inherit() });
1016+
}
1017+
1018+
command.stdin(match &self.data.stdin_contents {
1019+
Some(_) => Stdio::piped(),
1020+
None => Stdio::null(),
1021+
});
1022+
1023+
command.spawn()
1024+
}
1025+
10031026
fn output_impl(&self, read_stdout: bool, read_stderr: bool) -> Result<Output> {
10041027
let mut child = {
1005-
let mut command = self.to_command();
1006-
1007-
if !self.data.ignore_stdout {
1008-
command.stdout(if read_stdout { Stdio::piped() } else { Stdio::inherit() });
1009-
}
1010-
if !self.data.ignore_stderr {
1011-
command.stderr(if read_stderr { Stdio::piped() } else { Stdio::inherit() });
1028+
let mut child = self.spawn_command_as(read_stdout, read_stderr, None);
1029+
1030+
// rust stdlib has all necessary code to run `.cmd` and `.bat` files on Windows via 'cmd /c' invocation,
1031+
// but requires the explicit extension. If the command does not have one, add 'cmd' or 'bat' and retry.
1032+
//
1033+
// Ref: https://github.com/rust-lang/rust/blob/051478957371ee0084a7c0913941d2a8c4757bb9/library/std/src/sys/pal/windows/process.rs#L274
1034+
#[cfg(windows)]
1035+
if matches!(&child, Err(err) if err.kind() == io::ErrorKind::NotFound)
1036+
&& self.data.prog.extension().is_none()
1037+
{
1038+
for ext in ["cmd", "bat"] {
1039+
let possible_executable = self.data.prog.with_extension(ext);
1040+
let retry_child = self.spawn_command_as(
1041+
read_stdout,
1042+
read_stderr,
1043+
Some(possible_executable.as_os_str()),
1044+
);
1045+
if retry_child.is_ok() {
1046+
child = retry_child;
1047+
break;
1048+
}
1049+
}
10121050
}
10131051

1014-
command.stdin(match &self.data.stdin_contents {
1015-
Some(_) => Stdio::piped(),
1016-
None => Stdio::null(),
1017-
});
1018-
1019-
command.spawn().map_err(|err| {
1052+
child.map_err(|err| {
10201053
// Try to determine whether the command failed because the current
10211054
// directory does not exist. Return an appropriate error in such a
10221055
// case.
@@ -1049,23 +1082,13 @@ impl<'a> Cmd<'a> {
10491082
}
10501083

10511084
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-
};
1085+
self.to_command_impl(None)
1086+
}
1087+
1088+
fn to_command_impl(&self, program: Option<&OsStr>) -> Command {
1089+
let mut res = Command::new(program.unwrap_or(self.data.prog.as_os_str()));
1090+
res.args(&self.data.args);
1091+
res.current_dir(self.shell.current_dir());
10691092

10701093
for (key, val) in &*self.shell.env.borrow() {
10711094
res.env(key, val);

tests/windows.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
use xshell::{cmd, Shell};
44

55
#[test]
6-
fn echo() {
6+
fn npm() {
77
let sh = Shell::new().unwrap();
88

9-
let res = cmd!(sh, "echo test").read().unwrap();
10-
assert_eq!(res, "test");
9+
if cmd!(sh, "where npm").read().is_ok() {
10+
let script_shell = cmd!(sh, "npm get shell").read().unwrap();
11+
assert!(script_shell.ends_with(".exe"));
12+
13+
let script_shell_explicit = cmd!(sh, "npm.cmd get shell").read().unwrap();
14+
assert_eq!(script_shell, script_shell_explicit);
15+
}
1116
}
1217

1318
#[test]
14-
fn npm() {
19+
fn explicit_extension() {
1520
let sh = Shell::new().unwrap();
1621

17-
if cmd!(sh, "where npm.cmd").read().is_ok() {
18-
let script_shell = cmd!(sh, "npm get shell").read().unwrap();
19-
assert!(script_shell.ends_with(".exe"))
22+
if cmd!(sh, "where npm").read().is_ok() {
23+
// should not try to find npm with explicit extension
24+
assert!(cmd!(sh, "npm.exe get shell").run().is_err());
2025
}
2126
}

0 commit comments

Comments
 (0)