1use std::io::{Read, Write};
7use std::process::{Command, Stdio};
8
9use crate::binary::resolve_binary;
10use crate::error::{Error, Result};
11
12#[derive(Debug, Clone)]
14pub struct RawResult {
15 pub stdout: String,
16 pub stderr: String,
17 pub exit_code: i32,
18}
19
20fn with_globals(args: &[String], log_level: u32) -> Vec<String> {
21 let mut out = vec!["--log-level".to_string(), log_level.to_string()];
22 out.extend(args.iter().cloned());
23 out
24}
25
26pub(crate) fn run_full(
33 args: &[String],
34 env: &[(String, String)],
35 stdin: Option<&[u8]>,
36 log_level: u32,
37) -> Result<RawResult> {
38 run_full_stream(args, env, stdin, log_level, None, None)
39}
40
41pub(crate) fn run_full_stream(
42 args: &[String],
43 env: &[(String, String)],
44 stdin: Option<&[u8]>,
45 log_level: u32,
46 mut on_stdout: Option<Box<dyn Write + Send>>,
47 mut on_stderr: Option<Box<dyn Write + Send>>,
48) -> Result<RawResult> {
49 let binary = resolve_binary()?;
50 let mut cmd = Command::new(binary);
51 cmd.args(with_globals(args, log_level));
52 for (key, value) in env {
53 cmd.env(key, value);
54 }
55 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
56 if stdin.is_some() {
57 cmd.stdin(Stdio::piped());
58 }
59
60 let mut child = cmd.spawn()?;
61
62 let stdin_thread = stdin.map(|bytes| {
66 let mut handle = child.stdin.take().expect("stdin was piped");
67 let owned = bytes.to_vec();
68 std::thread::spawn(move || {
69 let _ = handle.write_all(&owned);
70 })
71 });
72 let stderr_thread = {
73 let mut handle = child.stderr.take().expect("stderr was piped");
74 std::thread::spawn(move || {
75 let mut buf = Vec::new();
76 let mut chunk = [0_u8; 8192];
77 loop {
78 match handle.read(&mut chunk) {
79 Ok(0) | Err(_) => break,
80 Ok(n) => {
81 buf.extend_from_slice(&chunk[..n]);
82 if let Some(w) = on_stderr.as_mut() {
83 let _ = w.write_all(&chunk[..n]);
84 }
85 }
86 }
87 }
88 buf
89 })
90 };
91
92 let mut stdout_buf = Vec::new();
93 if let Some(mut out) = child.stdout.take() {
94 let mut chunk = [0_u8; 8192];
95 loop {
96 let n = out.read(&mut chunk)?;
97 if n == 0 {
98 break;
99 }
100 stdout_buf.extend_from_slice(&chunk[..n]);
101 if let Some(w) = on_stdout.as_mut() {
102 w.write_all(&chunk[..n])?;
103 }
104 }
105 }
106 let stderr_buf = stderr_thread.join().unwrap_or_default();
107 if let Some(t) = stdin_thread {
108 let _ = t.join();
109 }
110 let status = child.wait()?;
111
112 Ok(RawResult {
113 stdout: String::from_utf8_lossy(&stdout_buf).into_owned(),
114 stderr: String::from_utf8_lossy(&stderr_buf).into_owned(),
115 exit_code: status.code().unwrap_or(-1),
116 })
117}
118
119pub fn run<I, S>(args: I) -> Result<RawResult>
121where
122 I: IntoIterator<Item = S>,
123 S: Into<String>,
124{
125 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
126 run_full(&argv, &[], None, 0)
127}
128
129pub fn run_checked<I, S>(args: I, label: &str) -> Result<RawResult>
132where
133 I: IntoIterator<Item = S>,
134 S: Into<String>,
135{
136 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
137 checked(&argv, label)
138}
139
140pub(crate) fn checked(args: &[String], label: &str) -> Result<RawResult> {
141 let result = run_full(args, &[], None, 0)?;
142 if result.exit_code != 0 {
143 return Err(Error::CommandFailed {
144 exit_code: result.exit_code,
145 stdout: result.stdout,
146 stderr: result.stderr,
147 command: label.to_string(),
148 });
149 }
150 Ok(result)
151}
152
153pub fn spawn<I, S>(args: I) -> Result<i32>
158where
159 I: IntoIterator<Item = S>,
160 S: Into<String>,
161{
162 let binary = resolve_binary()?;
163 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
164 let status = Command::new(binary).args(with_globals(&argv, 0)).status()?;
165 Ok(status.code().unwrap_or(-1))
166}