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 let binary = resolve_binary()?;
39 let mut cmd = Command::new(binary);
40 cmd.args(with_globals(args, log_level));
41 for (key, value) in env {
42 cmd.env(key, value);
43 }
44 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
45 if stdin.is_some() {
46 cmd.stdin(Stdio::piped());
47 }
48
49 let mut child = cmd.spawn()?;
50
51 let stdin_thread = stdin.map(|bytes| {
55 let mut handle = child.stdin.take().expect("stdin was piped");
56 let owned = bytes.to_vec();
57 std::thread::spawn(move || {
58 let _ = handle.write_all(&owned);
59 })
60 });
61 let stderr_thread = {
62 let mut handle = child.stderr.take().expect("stderr was piped");
63 std::thread::spawn(move || {
64 let mut buf = Vec::new();
65 let _ = handle.read_to_end(&mut buf);
66 buf
67 })
68 };
69
70 let mut stdout_buf = Vec::new();
71 if let Some(mut out) = child.stdout.take() {
72 out.read_to_end(&mut stdout_buf)?;
73 }
74 let stderr_buf = stderr_thread.join().unwrap_or_default();
75 if let Some(t) = stdin_thread {
76 let _ = t.join();
77 }
78 let status = child.wait()?;
79
80 Ok(RawResult {
81 stdout: String::from_utf8_lossy(&stdout_buf).into_owned(),
82 stderr: String::from_utf8_lossy(&stderr_buf).into_owned(),
83 exit_code: status.code().unwrap_or(-1),
84 })
85}
86
87pub fn run<I, S>(args: I) -> Result<RawResult>
89where
90 I: IntoIterator<Item = S>,
91 S: Into<String>,
92{
93 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
94 run_full(&argv, &[], None, 0)
95}
96
97pub fn run_checked<I, S>(args: I, label: &str) -> Result<RawResult>
100where
101 I: IntoIterator<Item = S>,
102 S: Into<String>,
103{
104 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
105 checked(&argv, label)
106}
107
108pub(crate) fn checked(args: &[String], label: &str) -> Result<RawResult> {
109 let result = run_full(args, &[], None, 0)?;
110 if result.exit_code != 0 {
111 return Err(Error::CommandFailed {
112 exit_code: result.exit_code,
113 stdout: result.stdout,
114 stderr: result.stderr,
115 command: label.to_string(),
116 });
117 }
118 Ok(result)
119}
120
121pub fn spawn<I, S>(args: I) -> Result<i32>
126where
127 I: IntoIterator<Item = S>,
128 S: Into<String>,
129{
130 let binary = resolve_binary()?;
131 let argv: Vec<String> = args.into_iter().map(Into::into).collect();
132 let status = Command::new(binary).args(with_globals(&argv, 0)).status()?;
133 Ok(status.code().unwrap_or(-1))
134}