Skip to content

Commit

Permalink
use tempfile for windows shell execution, because windows std::proces…
Browse files Browse the repository at this point in the history
…s::Command does not work well with huge (>1kb) output piping. see rust-lang/rust#45572 for detail
  • Loading branch information
umegaya committed Dec 28, 2021
1 parent 0cf33aa commit 9f10c66
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"request": "launch",
"program": "${workspaceFolder}/target/debug/cli",
"args": [
"ci", "deploy", "product"
"-v=3", "ci", "deploy", "product"
]
},
{
Expand Down
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ serde_json = "1.0"
simple_logger = "1.6.0"
toml = "0.5.6"
sodalite = { git = "https://github.com/suntomi/sodalite" }
tempfile = "3.2.0"

[features]
default = []
Expand Down
177 changes: 125 additions & 52 deletions core/src/shell/native.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::process::{Command, Stdio, ChildStdout};
use std::collections::HashMap;
use std::error::Error;
use std::io::Read;
use std::path::Path;
use std::io::{Read, Seek};
use std::path::{Path};
use std::ffi::OsStr;
use std::fs::File;
use std::convert::AsRef;

use maplit::hashmap;
use tempfile::tempfile;

use crate::config;
use crate::shell;
Expand All @@ -16,6 +18,20 @@ pub struct Native {
pub cwd: Option<String>,
pub envs: HashMap<String, String>,
}
pub struct CaptureTarget {
pub stdout: File,
pub stderr: File,
}
impl CaptureTarget {
pub fn read_stdout(&mut self, buf: &mut String) -> Result<usize, std::io::Error> {
self.stdout.seek(std::io::SeekFrom::Start(0))?;
return self.stdout.read_to_string(buf);
}
pub fn read_stderr(&mut self, buf: &mut String) -> Result<usize, std::io::Error> {
self.stderr.seek(std::io::SeekFrom::Start(0))?;
return self.stderr.read_to_string(buf);
}
}
impl<'a> shell::Shell for Native {
fn new(config: &config::Container) -> Self {
return Native {
Expand Down Expand Up @@ -43,8 +59,8 @@ impl<'a> shell::Shell for Native {
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>
) -> Result<String, shell::ShellError>
where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, P: AsRef<Path> {
let mut cmd = self.create_command(args, envs, cwd, true);
return Native::get_output(&mut cmd);
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, true);
return Native::get_output(&mut cmd, &mut ct);
}
fn exec<I, K, V, P>(
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>, capture: bool
Expand All @@ -57,18 +73,18 @@ impl<'a> shell::Shell for Native {
return Ok(cmd);
} else if config.should_silent_shell_exec() {
// regardless for the value of `capture`, always capture value
let mut cmd = self.create_command(args, envs, cwd, true);
return Native::run_as_child(&mut cmd);
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, true);
return Native::run_as_child(&mut cmd, &mut ct);
} else {
let mut cmd = self.create_command(args, envs, cwd, capture);
return Native::run_as_child(&mut cmd);
let (mut cmd, mut ct) = self.create_command(args, envs, cwd, capture);
return Native::run_as_child(&mut cmd, &mut ct);
}
}
}
impl Native {
fn create_command<I, K, V, P>(
&self, args: &Vec<&str>, envs: I, cwd: &Option<P>, capture: bool
) -> Command
) -> (Command, Option<CaptureTarget>)
where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, P: AsRef<Path> {
let mut c = Command::new(args[0]);
c.args(&args[1..]);
Expand All @@ -86,34 +102,71 @@ impl Native {
}
}
c.envs(&self.envs);
if capture {
c.stdout(Stdio::piped());
c.stderr(Stdio::piped());
}
return c;
let ct = if capture {
// windows std::process::Command does not work well with huge (>1kb) output piping.
// see https://github.com/rust-lang/rust/issues/45572 for detail
if cfg!(windows) {
let v = CaptureTarget {
stdout: tempfile().unwrap(),
stderr: tempfile().unwrap(),
};
log::debug!("capture to temp file {:?} {:?}", v.stdout, v.stderr);
c.stdout(Stdio::from(v.stdout.try_clone().unwrap()));
c.stderr(Stdio::from(v.stderr.try_clone().unwrap()));
Some(v)
} else {
c.stdout(Stdio::piped());
c.stderr(Stdio::piped());
None
}
} else {
None
};
return (c, ct);
}
fn get_output(cmd: &mut Command) -> Result<String, shell::ShellError> {
fn get_output(cmd: &mut Command, ct: &mut Option<CaptureTarget>) -> Result<String, shell::ShellError> {
// TODO: option to capture stderr as no error case
match cmd.output() {
Ok(output) => {
if output.status.success() {
match String::from_utf8(output.stdout) {
Ok(s) => return Ok(s.trim().to_string()),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("stdout character code error {:?}", err),
cmd: format!("{:?}", cmd)
})
match ct {
Some(v) => {
let mut buf = String::new();
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
cause: format!("cannot read from stdout tempfile error {:?}", e),
cmd: format!("{:?}", cmd)
})?;
log::debug!("stdout: [{}]", buf);
return Ok(buf.trim().to_string());
},
None => match String::from_utf8(output.stdout) {
Ok(s) => return Ok(s.trim().to_string()),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("stdout character code error {:?}", err),
cmd: format!("{:?}", cmd)
})
}
}
} else {
match String::from_utf8(output.stderr) {
Ok(s) => return Err(shell::ShellError::OtherFailure{
cause: format!("command returns error {}", s),
cmd: format!("{:?}", cmd)
}),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("stderr character code error {:?}", err),
cmd: format!("{:?}", cmd)
})
match ct {
Some(v) => {
let mut buf = String::new();
v.read_stderr(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
cause: format!("cannot read from stderr tempfile error {:?}", e),
cmd: format!("{:?}", cmd)
})?;
return Ok(buf.trim().to_string());
},
None => match String::from_utf8(output.stderr) {
Ok(s) => return Err(shell::ShellError::OtherFailure{
cause: format!("command returns error {}", s),
cmd: format!("{:?}", cmd)
}),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("stderr character code error {:?}", err),
cmd: format!("{:?}", cmd)
})
}
}
}
},
Expand All @@ -123,48 +176,68 @@ impl Native {
})
}
}
fn read_stdout_or_empty(stdout: Option<ChildStdout>) -> String {
fn read_stdout_or_empty(cmd: &Command, stdout: Option<ChildStdout>, ct: &mut Option<CaptureTarget>) -> Result<String, shell::ShellError> {
let mut buf = String::new();
match stdout {
Some(mut stream) => {
match stream.read_to_string(&mut buf) {
Ok(_) => {},
Err(err) => {
log::error!("read_stdout_or_empty error: {:?}", err);
}
}
match ct {
Some(v) => {
let mut buf = String::new();
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
cause: format!("cannot read from stderr tempfile error {:?}", e),
cmd: format!("{:?}", cmd)
})?;
},
None => {}
None => match stdout {
Some(mut stream) => {
match stream.read_to_string(&mut buf) {
Ok(_) => {},
Err(err) => {
log::error!("read_stdout_or_empty error: {:?}", err);
}
}
},
None => {}
}
}
return buf;
Ok(buf)
}
fn run_as_child(cmd: &mut Command) -> Result<String,shell::ShellError> {
fn run_as_child(cmd: &mut Command, ct: &mut Option<CaptureTarget>) -> Result<String,shell::ShellError> {
match cmd.spawn() {
Ok(mut process) => {
match process.wait() {
Ok(status) => {
if status.success() {
let mut s = String::new();
match process.stdout {
Some(mut stream) => match stream.read_to_string(&mut s) {
Ok(_) => return Ok(s.trim().to_string()),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("read stream error {:?}", err),
match ct {
Some(v) => {
let mut buf = String::new();
v.read_stdout(&mut buf).map_err(|e| shell::ShellError::OtherFailure{
cause: format!("cannot read from stderr tempfile error {:?}", e),
cmd: format!("{:?}", cmd)
})
})?;
log::debug!("stdout: [{}]", buf);
return Ok(buf.trim().to_string())
},
None => Ok("".to_string())
None => match process.stdout {
Some(mut stream) => match stream.read_to_string(&mut s) {
Ok(_) => return Ok(s.trim().to_string()),
Err(err) => return Err(shell::ShellError::OtherFailure{
cause: format!("read stream error {:?}", err),
cmd: format!("{:?}", cmd)
})
},
None => return Ok("".to_string())
}
}
} else {
let mut s = String::new();
let output = match process.stderr {
Some(mut stream) => {
match stream.read_to_string(&mut s) {
Ok(_) => if s.is_empty() { Self::read_stdout_or_empty(process.stdout) } else { s },
Err(_) => Self::read_stdout_or_empty(process.stdout)
Ok(_) => if s.is_empty() { Self::read_stdout_or_empty(&cmd, process.stdout, ct)? } else { s },
Err(_) => Self::read_stdout_or_empty(&cmd, process.stdout, ct)?
}
},
None => Self::read_stdout_or_empty(process.stdout)
None => Self::read_stdout_or_empty(&cmd, process.stdout, ct)?
};
return match status.code() {
Some(_) => Err(shell::ShellError::ExitStatus{
Expand Down

0 comments on commit 9f10c66

Please sign in to comment.