1use crate::error::Message;
2use std::process::{Command, Stdio};
3
4pub enum CmdError {
5 BinaryNotFound(std::io::Error),
8 Io(std::io::Error),
10 ToolErrors {
13 exit_code: i32,
14 messages: Vec<Message>,
16 },
17}
18
19impl From<CmdError> for crate::error::Error {
20 fn from(ce: CmdError) -> Self {
21 use crate::SpirvResult;
22
23 match ce {
24 CmdError::BinaryNotFound(err) => Self {
25 inner: SpirvResult::Unsupported,
26 diagnostic: Some(format!("failed to spawn executable: {err}").into()),
27 },
28 CmdError::Io(err) => Self {
29 inner: SpirvResult::EndOfStream,
30 diagnostic: Some(
31 format!("i/o error occurred communicating with executable: {err}").into(),
32 ),
33 },
34 CmdError::ToolErrors {
35 exit_code,
36 messages,
37 } => {
38 let diagnostic = messages.into_iter().next_back().map_or_else(
41 || {
42 crate::error::Diagnostic::from(format!(
43 "tool exited with code `{exit_code}` and no output"
44 ))
45 },
46 crate::error::Diagnostic::from,
47 );
48
49 Self {
50 inner: SpirvResult::InternalError,
54 diagnostic: Some(diagnostic),
55 }
56 }
57 }
58 }
59}
60
61pub struct CmdOutput {
62 pub binary: Vec<u8>,
64 pub messages: Vec<Message>,
66}
67
68#[derive(PartialEq, Eq, Copy, Clone)]
69pub enum Output {
70 Ignore,
72 Retrieve,
74}
75
76pub fn exec(
77 mut cmd: Command,
78 input: Option<&[u8]>,
79 retrieve_output: Output,
80) -> Result<CmdOutput, CmdError> {
81 cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
82
83 let temp_dir = tempfile::tempdir().map_err(CmdError::Io)?;
85
86 let output_path = temp_dir.path().join("output");
88 if retrieve_output == Output::Retrieve {
89 cmd.arg("-o").arg(&output_path);
90 }
91
92 if let Some(input) = input {
94 let input_path = temp_dir.path().join("input");
95 std::fs::write(&input_path, input).map_err(CmdError::Io)?;
96
97 cmd.arg(&input_path);
98 }
99
100 let child = cmd.spawn().map_err(CmdError::BinaryNotFound)?;
101
102 let output = child.wait_with_output().map_err(CmdError::Io)?;
103
104 let code = if let Some(code) = output.status.code() {
105 code
106 } else {
107 #[cfg(unix)]
108 let message = {
109 use std::os::unix::process::ExitStatusExt;
110 format!(
111 "process terminated by signal: {}",
112 output.status.signal().unwrap_or(666)
113 )
114 };
115 #[cfg(not(unix))]
116 let message = "process ended in an unknown state".to_owned();
117
118 return Err(CmdError::ToolErrors {
119 exit_code: -1,
120 messages: vec![Message::fatal(message)],
121 });
122 };
123
124 if code != 0 {
126 use crate::error::*;
127 let messages: Vec<_> = match String::from_utf8(output.stderr) {
128 Ok(errors) => {
129 let mut messages = Vec::new();
130
131 for line in errors.lines() {
132 if let Some(msg) = Message::parse(line) {
133 messages.push(msg);
134 } else if let Some(msg) = messages.last_mut() {
135 if !msg.notes.is_empty() {
136 msg.notes.push('\n');
137 }
138
139 msg.notes.push_str(line);
140 } else {
141 messages.push(Message {
144 level: MessageLevel::Error,
145 source: None,
146 line: 0,
147 column: 0,
148 index: 0,
149 message: line.to_owned(),
150 notes: String::new(),
151 });
152 }
153 }
154
155 messages
156 }
157 Err(err) => vec![Message::fatal(format!(
158 "unable to read stderr ({err}) but process exited with code {code}",
159 ))],
160 };
161
162 return Err(CmdError::ToolErrors {
163 exit_code: code,
164 messages,
165 });
166 }
167
168 fn split(haystack: &[u8], needle: u8) -> impl Iterator<Item = &[u8]> {
169 struct Split<'a> {
170 haystack: &'a [u8],
171 needle: u8,
172 }
173
174 impl<'a> Iterator for Split<'a> {
175 type Item = &'a [u8];
176
177 fn next(&mut self) -> Option<&'a [u8]> {
178 if self.haystack.is_empty() {
179 return None;
180 }
181 let (ret, remaining) = match memchr::memchr(self.needle, self.haystack) {
182 Some(pos) => (
183 self.haystack.get(..pos).unwrap(),
184 self.haystack.get(pos + 1..).unwrap(),
185 ),
186 None => (self.haystack, &[][..]),
187 };
188 self.haystack = remaining;
189 Some(ret)
190 }
191 }
192
193 Split { haystack, needle }
194 }
195
196 let binary = match retrieve_output {
197 Output::Retrieve => std::fs::read(&output_path).map_err(CmdError::Io)?,
198 Output::Ignore => Vec::new(),
199 };
200
201 let mut messages = Vec::new();
204
205 for line in split(&output.stdout, b'\n') {
206 if let Ok(s) = std::str::from_utf8(line) {
207 if let Some(msg) = crate::error::Message::parse(s) {
208 messages.push(msg);
209 continue;
210 }
211 }
212
213 break;
214 }
215
216 Ok(CmdOutput { binary, messages })
217}