Skip to content

Commit 9f10c66

Browse files
committed
use tempfile for windows shell execution, because windows std::process::Command does not work well with huge (>1kb) output piping. see rust-lang/rust#45572 for detail
1 parent 0cf33aa commit 9f10c66

File tree

4 files changed

+160
-53
lines changed

4 files changed

+160
-53
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"request": "launch",
3434
"program": "${workspaceFolder}/target/debug/cli",
3535
"args": [
36-
"ci", "deploy", "product"
36+
"-v=3", "ci", "deploy", "product"
3737
]
3838
},
3939
{

Cargo.lock

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ serde_json = "1.0"
2525
simple_logger = "1.6.0"
2626
toml = "0.5.6"
2727
sodalite = { git = "https://github.com/suntomi/sodalite" }
28+
tempfile = "3.2.0"
2829

2930
[features]
3031
default = []

core/src/shell/native.rs

Lines changed: 125 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use std::process::{Command, Stdio, ChildStdout};
22
use std::collections::HashMap;
33
use std::error::Error;
4-
use std::io::Read;
5-
use std::path::Path;
4+
use std::io::{Read, Seek};
5+
use std::path::{Path};
66
use std::ffi::OsStr;
7+
use std::fs::File;
78
use std::convert::AsRef;
89

910
use maplit::hashmap;
11+
use tempfile::tempfile;
1012

1113
use crate::config;
1214
use crate::shell;
@@ -16,6 +18,20 @@ pub struct Native {
1618
pub cwd: Option<String>,
1719
pub envs: HashMap<String, String>,
1820
}
21+
pub struct CaptureTarget {
22+
pub stdout: File,
23+
pub stderr: File,
24+
}
25+
impl CaptureTarget {
26+
pub fn read_stdout(&mut self, buf: &mut String) -> Result<usize, std::io::Error> {
27+
self.stdout.seek(std::io::SeekFrom::Start(0))?;
28+
return self.stdout.read_to_string(buf);
29+
}
30+
pub fn read_stderr(&mut self, buf: &mut String) -> Result<usize, std::io::Error> {
31+
self.stderr.seek(std::io::SeekFrom::Start(0))?;
32+
return self.stderr.read_to_string(buf);
33+
}
34+
}
1935
impl<'a> shell::Shell for Native {
2036
fn new(config: &config::Container) -> Self {
2137
return Native {
@@ -43,8 +59,8 @@ impl<'a> shell::Shell for Native {
4359
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>
4460
) -> Result<String, shell::ShellError>
4561
where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, P: AsRef<Path> {
46-
let mut cmd = self.create_command(args, envs, cwd, true);
47-
return Native::get_output(&mut cmd);
62+
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, true);
63+
return Native::get_output(&mut cmd, &mut ct);
4864
}
4965
fn exec<I, K, V, P>(
5066
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>, capture: bool
@@ -57,18 +73,18 @@ impl<'a> shell::Shell for Native {
5773
return Ok(cmd);
5874
} else if config.should_silent_shell_exec() {
5975
// regardless for the value of `capture`, always capture value
60-
let mut cmd = self.create_command(args, envs, cwd, true);
61-
return Native::run_as_child(&mut cmd);
76+
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, true);
77+
return Native::run_as_child(&mut cmd, &mut ct);
6278
} else {
63-
let mut cmd = self.create_command(args, envs, cwd, capture);
64-
return Native::run_as_child(&mut cmd);
79+
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, capture);
80+
return Native::run_as_child(&mut cmd, &mut ct);
6581
}
6682
}
6783
}
6884
impl Native {
6985
fn create_command<I, K, V, P>(
7086
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>, capture: bool
71-
) -> Command
87+
) -> (Command, Option<CaptureTarget>)
7288
where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, P: AsRef<Path> {
7389
let mut c = Command::new(args[0]);
7490
c.args(&args[1..]);
@@ -86,34 +102,71 @@ impl Native {
86102
}
87103
}
88104
c.envs(&self.envs);
89-
if capture {
90-
c.stdout(Stdio::piped());
91-
c.stderr(Stdio::piped());
92-
}
93-
return c;
105+
let ct = if capture {
106+
// windows std::process::Command does not work well with huge (>1kb) output piping.
107+
// see https://github.com/rust-lang/rust/issues/45572 for detail
108+
if cfg!(windows) {
109+
let v = CaptureTarget {
110+
stdout: tempfile().unwrap(),
111+
stderr: tempfile().unwrap(),
112+
};
113+
log::debug!("capture to temp file {:?} {:?}", v.stdout, v.stderr);
114+
c.stdout(Stdio::from(v.stdout.try_clone().unwrap()));
115+
c.stderr(Stdio::from(v.stderr.try_clone().unwrap()));
116+
Some(v)
117+
} else {
118+
c.stdout(Stdio::piped());
119+
c.stderr(Stdio::piped());
120+
None
121+
}
122+
} else {
123+
None
124+
};
125+
return (c, ct);
94126
}
95-
fn get_output(cmd: &mut Command) -> Result<String, shell::ShellError> {
127+
fn get_output(cmd: &mut Command, ct: &mut Option<CaptureTarget>) -> Result<String, shell::ShellError> {
96128
// TODO: option to capture stderr as no error case
97129
match cmd.output() {
98130
Ok(output) => {
99131
if output.status.success() {
100-
match String::from_utf8(output.stdout) {
101-
Ok(s) => return Ok(s.trim().to_string()),
102-
Err(err) => return Err(shell::ShellError::OtherFailure{
103-
cause: format!("stdout character code error {:?}", err),
104-
cmd: format!("{:?}", cmd)
105-
})
132+
match ct {
133+
Some(v) => {
134+
let mut buf = String::new();
135+
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
136+
cause: format!("cannot read from stdout tempfile error {:?}", e),
137+
cmd: format!("{:?}", cmd)
138+
})?;
139+
log::debug!("stdout: [{}]", buf);
140+
return Ok(buf.trim().to_string());
141+
},
142+
None => match String::from_utf8(output.stdout) {
143+
Ok(s) => return Ok(s.trim().to_string()),
144+
Err(err) => return Err(shell::ShellError::OtherFailure{
145+
cause: format!("stdout character code error {:?}", err),
146+
cmd: format!("{:?}", cmd)
147+
})
148+
}
106149
}
107150
} else {
108-
match String::from_utf8(output.stderr) {
109-
Ok(s) => return Err(shell::ShellError::OtherFailure{
110-
cause: format!("command returns error {}", s),
111-
cmd: format!("{:?}", cmd)
112-
}),
113-
Err(err) => return Err(shell::ShellError::OtherFailure{
114-
cause: format!("stderr character code error {:?}", err),
115-
cmd: format!("{:?}", cmd)
116-
})
151+
match ct {
152+
Some(v) => {
153+
let mut buf = String::new();
154+
v.read_stderr(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
155+
cause: format!("cannot read from stderr tempfile error {:?}", e),
156+
cmd: format!("{:?}", cmd)
157+
})?;
158+
return Ok(buf.trim().to_string());
159+
},
160+
None => match String::from_utf8(output.stderr) {
161+
Ok(s) => return Err(shell::ShellError::OtherFailure{
162+
cause: format!("command returns error {}", s),
163+
cmd: format!("{:?}", cmd)
164+
}),
165+
Err(err) => return Err(shell::ShellError::OtherFailure{
166+
cause: format!("stderr character code error {:?}", err),
167+
cmd: format!("{:?}", cmd)
168+
})
169+
}
117170
}
118171
}
119172
},
@@ -123,48 +176,68 @@ impl Native {
123176
})
124177
}
125178
}
126-
fn read_stdout_or_empty(stdout: Option<ChildStdout>) -> String {
179+
fn read_stdout_or_empty(cmd: &Command, stdout: Option<ChildStdout>, ct: &mut Option<CaptureTarget>) -> Result<String, shell::ShellError> {
127180
let mut buf = String::new();
128-
match stdout {
129-
Some(mut stream) => {
130-
match stream.read_to_string(&mut buf) {
131-
Ok(_) => {},
132-
Err(err) => {
133-
log::error!("read_stdout_or_empty error: {:?}", err);
134-
}
135-
}
181+
match ct {
182+
Some(v) => {
183+
let mut buf = String::new();
184+
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
185+
cause: format!("cannot read from stderr tempfile error {:?}", e),
186+
cmd: format!("{:?}", cmd)
187+
})?;
136188
},
137-
None => {}
189+
None => match stdout {
190+
Some(mut stream) => {
191+
match stream.read_to_string(&mut buf) {
192+
Ok(_) => {},
193+
Err(err) => {
194+
log::error!("read_stdout_or_empty error: {:?}", err);
195+
}
196+
}
197+
},
198+
None => {}
199+
}
138200
}
139-
return buf;
201+
Ok(buf)
140202
}
141-
fn run_as_child(cmd: &mut Command) -> Result<String,shell::ShellError> {
203+
fn run_as_child(cmd: &mut Command, ct: &mut Option<CaptureTarget>) -> Result<String,shell::ShellError> {
142204
match cmd.spawn() {
143205
Ok(mut process) => {
144206
match process.wait() {
145207
Ok(status) => {
146208
if status.success() {
147209
let mut s = String::new();
148-
match process.stdout {
149-
Some(mut stream) => match stream.read_to_string(&mut s) {
150-
Ok(_) => return Ok(s.trim().to_string()),
151-
Err(err) => return Err(shell::ShellError::OtherFailure{
152-
cause: format!("read stream error {:?}", err),
210+
match ct {
211+
Some(v) => {
212+
let mut buf = String::new();
213+
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
214+
cause: format!("cannot read from stderr tempfile error {:?}", e),
153215
cmd: format!("{:?}", cmd)
154-
})
216+
})?;
217+
log::debug!("stdout: [{}]", buf);
218+
return Ok(buf.trim().to_string())
155219
},
156-
None => Ok("".to_string())
220+
None => match process.stdout {
221+
Some(mut stream) => match stream.read_to_string(&mut s) {
222+
Ok(_) => return Ok(s.trim().to_string()),
223+
Err(err) => return Err(shell::ShellError::OtherFailure{
224+
cause: format!("read stream error {:?}", err),
225+
cmd: format!("{:?}", cmd)
226+
})
227+
},
228+
None => return Ok("".to_string())
229+
}
157230
}
158231
} else {
159232
let mut s = String::new();
160233
let output = match process.stderr {
161234
Some(mut stream) => {
162235
match stream.read_to_string(&mut s) {
163-
Ok(_) => if s.is_empty() { Self::read_stdout_or_empty(process.stdout) } else { s },
164-
Err(_) => Self::read_stdout_or_empty(process.stdout)
236+
Ok(_) => if s.is_empty() { Self::read_stdout_or_empty(&cmd, process.stdout, ct)? } else { s },
237+
Err(_) => Self::read_stdout_or_empty(&cmd, process.stdout, ct)?
165238
}
166239
},
167-
None => Self::read_stdout_or_empty(process.stdout)
240+
None => Self::read_stdout_or_empty(&cmd, process.stdout, ct)?
168241
};
169242
return match status.code() {
170243
Some(_) => Err(shell::ShellError::ExitStatus{

0 commit comments

Comments
 (0)