1 | //! Miscellaneous helpers for running commands |
2 | |
3 | use std::{ |
4 | collections::hash_map, |
5 | ffi::OsString, |
6 | fmt::Display, |
7 | fs, |
8 | hash::Hasher, |
9 | io::{self, Read, Write}, |
10 | path::Path, |
11 | process::{Child, ChildStderr, Command, Stdio}, |
12 | sync::{ |
13 | atomic::{AtomicBool, Ordering}, |
14 | Arc, |
15 | }, |
16 | }; |
17 | |
18 | use crate::{Error, ErrorKind, Object}; |
19 | |
20 | #[derive (Clone, Debug)] |
21 | pub(crate) struct CargoOutput { |
22 | pub(crate) metadata: bool, |
23 | pub(crate) warnings: bool, |
24 | pub(crate) debug: bool, |
25 | checked_dbg_var: Arc<AtomicBool>, |
26 | } |
27 | |
28 | impl CargoOutput { |
29 | pub(crate) fn new() -> Self { |
30 | Self { |
31 | metadata: true, |
32 | warnings: true, |
33 | debug: std::env::var_os("CC_ENABLE_DEBUG_OUTPUT" ).is_some(), |
34 | checked_dbg_var: Arc::new(AtomicBool::new(false)), |
35 | } |
36 | } |
37 | |
38 | pub(crate) fn print_metadata(&self, s: &dyn Display) { |
39 | if self.metadata { |
40 | println!(" {}" , s); |
41 | } |
42 | } |
43 | |
44 | pub(crate) fn print_warning(&self, arg: &dyn Display) { |
45 | if self.warnings { |
46 | println!("cargo:warning= {}" , arg); |
47 | } |
48 | } |
49 | |
50 | pub(crate) fn print_debug(&self, arg: &dyn Display) { |
51 | if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) { |
52 | self.checked_dbg_var.store(true, Ordering::Relaxed); |
53 | println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT" ); |
54 | } |
55 | if self.debug { |
56 | println!(" {}" , arg); |
57 | } |
58 | } |
59 | |
60 | fn stdio_for_warnings(&self) -> Stdio { |
61 | if self.warnings { |
62 | Stdio::piped() |
63 | } else { |
64 | Stdio::null() |
65 | } |
66 | } |
67 | } |
68 | |
69 | pub(crate) struct StderrForwarder { |
70 | inner: Option<(ChildStderr, Vec<u8>)>, |
71 | #[cfg (feature = "parallel" )] |
72 | is_non_blocking: bool, |
73 | #[cfg (feature = "parallel" )] |
74 | bytes_available_failed: bool, |
75 | } |
76 | |
77 | const MIN_BUFFER_CAPACITY: usize = 100; |
78 | |
79 | impl StderrForwarder { |
80 | pub(crate) fn new(child: &mut Child) -> Self { |
81 | Self { |
82 | inner: child |
83 | .stderr |
84 | .take() |
85 | .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))), |
86 | #[cfg (feature = "parallel" )] |
87 | is_non_blocking: false, |
88 | #[cfg (feature = "parallel" )] |
89 | bytes_available_failed: false, |
90 | } |
91 | } |
92 | |
93 | #[allow (clippy::uninit_vec)] |
94 | fn forward_available(&mut self) -> bool { |
95 | if let Some((stderr, buffer)) = self.inner.as_mut() { |
96 | loop { |
97 | let old_data_end = buffer.len(); |
98 | |
99 | // For non-blocking we check to see if there is data available, so we should try to |
100 | // read at least that much. For blocking, always read at least the minimum amount. |
101 | #[cfg (not(feature = "parallel" ))] |
102 | let to_reserve = MIN_BUFFER_CAPACITY; |
103 | #[cfg (feature = "parallel" )] |
104 | let to_reserve = if self.is_non_blocking && !self.bytes_available_failed { |
105 | match crate::parallel::stderr::bytes_available(stderr) { |
106 | #[cfg (windows)] |
107 | Ok(0) => return false, |
108 | #[cfg (unix)] |
109 | Ok(0) => { |
110 | // On Unix, depending on the implementation, we may sometimes get 0 in a |
111 | // loop (either there is data available or the pipe is broken), so |
112 | // continue with the non-blocking read anyway. |
113 | MIN_BUFFER_CAPACITY |
114 | } |
115 | #[cfg (windows)] |
116 | Err(_) => { |
117 | // On Windows, if we get an error then the pipe is broken, so flush |
118 | // the buffer and bail. |
119 | if !buffer.is_empty() { |
120 | write_warning(&buffer[..]); |
121 | } |
122 | self.inner = None; |
123 | return true; |
124 | } |
125 | #[cfg (unix)] |
126 | Err(_) => { |
127 | // On Unix, depending on the implementation, we may get spurious |
128 | // errors so make a note not to use bytes_available again and try |
129 | // the non-blocking read anyway. |
130 | self.bytes_available_failed = true; |
131 | MIN_BUFFER_CAPACITY |
132 | } |
133 | Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available), |
134 | } |
135 | } else { |
136 | MIN_BUFFER_CAPACITY |
137 | }; |
138 | buffer.reserve(to_reserve); |
139 | |
140 | // SAFETY: 1) the length is set to the capacity, so we are never using memory beyond |
141 | // the underlying buffer and 2) we always call `truncate` below to set the len back |
142 | // to the initialized data. |
143 | unsafe { |
144 | buffer.set_len(buffer.capacity()); |
145 | } |
146 | match stderr.read(&mut buffer[old_data_end..]) { |
147 | Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { |
148 | // No data currently, yield back. |
149 | buffer.truncate(old_data_end); |
150 | return false; |
151 | } |
152 | Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { |
153 | // Interrupted, try again. |
154 | buffer.truncate(old_data_end); |
155 | } |
156 | Ok(0) | Err(_) => { |
157 | // End of stream: flush remaining data and bail. |
158 | if old_data_end > 0 { |
159 | write_warning(&buffer[..old_data_end]); |
160 | } |
161 | self.inner = None; |
162 | return true; |
163 | } |
164 | Ok(bytes_read) => { |
165 | buffer.truncate(old_data_end + bytes_read); |
166 | let mut consumed = 0; |
167 | for line in buffer.split_inclusive(|&b| b == b' \n' ) { |
168 | // Only forward complete lines, leave the rest in the buffer. |
169 | if let Some((b' \n' , line)) = line.split_last() { |
170 | consumed += line.len() + 1; |
171 | write_warning(line); |
172 | } |
173 | } |
174 | buffer.drain(..consumed); |
175 | } |
176 | } |
177 | } |
178 | } else { |
179 | true |
180 | } |
181 | } |
182 | |
183 | #[cfg (feature = "parallel" )] |
184 | pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> { |
185 | assert!(!self.is_non_blocking); |
186 | |
187 | #[cfg (unix)] |
188 | if let Some((stderr, _)) = self.inner.as_ref() { |
189 | crate::parallel::stderr::set_non_blocking(stderr)?; |
190 | } |
191 | |
192 | self.is_non_blocking = true; |
193 | Ok(()) |
194 | } |
195 | |
196 | #[cfg (feature = "parallel" )] |
197 | fn forward_all(&mut self) { |
198 | while !self.forward_available() {} |
199 | } |
200 | |
201 | #[cfg (not(feature = "parallel" ))] |
202 | fn forward_all(&mut self) { |
203 | let forward_result = self.forward_available(); |
204 | assert!(forward_result, "Should have consumed all data" ); |
205 | } |
206 | } |
207 | |
208 | fn write_warning(line: &[u8]) { |
209 | let stdout: Stdout = io::stdout(); |
210 | let mut stdout: StdoutLock<'_> = stdout.lock(); |
211 | stdout.write_all(buf:b"cargo:warning=" ).unwrap(); |
212 | stdout.write_all(buf:line).unwrap(); |
213 | stdout.write_all(buf:b" \n" ).unwrap(); |
214 | } |
215 | |
216 | fn wait_on_child( |
217 | cmd: &Command, |
218 | program: &str, |
219 | child: &mut Child, |
220 | cargo_output: &CargoOutput, |
221 | ) -> Result<(), Error> { |
222 | StderrForwarder::new(child).forward_all(); |
223 | |
224 | let status = match child.wait() { |
225 | Ok(s) => s, |
226 | Err(e) => { |
227 | return Err(Error::new( |
228 | ErrorKind::ToolExecError, |
229 | format!( |
230 | "Failed to wait on spawned child process, command {:?} with args {:?}: {}." , |
231 | cmd, program, e |
232 | ), |
233 | )); |
234 | } |
235 | }; |
236 | |
237 | cargo_output.print_debug(&status); |
238 | |
239 | if status.success() { |
240 | Ok(()) |
241 | } else { |
242 | Err(Error::new( |
243 | ErrorKind::ToolExecError, |
244 | format!( |
245 | "Command {:?} with args {:?} did not execute successfully (status code {})." , |
246 | cmd, program, status |
247 | ), |
248 | )) |
249 | } |
250 | } |
251 | |
252 | /// Find the destination object path for each file in the input source files, |
253 | /// and store them in the output Object. |
254 | pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> { |
255 | let mut objects = Vec::with_capacity(files.len()); |
256 | for file in files { |
257 | let basename = file |
258 | .file_name() |
259 | .ok_or_else(|| { |
260 | Error::new( |
261 | ErrorKind::InvalidArgument, |
262 | "No file_name for object file path!" , |
263 | ) |
264 | })? |
265 | .to_string_lossy(); |
266 | let dirname = file |
267 | .parent() |
268 | .ok_or_else(|| { |
269 | Error::new( |
270 | ErrorKind::InvalidArgument, |
271 | "No parent for object file path!" , |
272 | ) |
273 | })? |
274 | .to_string_lossy(); |
275 | |
276 | // Hash the dirname. This should prevent conflicts if we have multiple |
277 | // object files with the same filename in different subfolders. |
278 | let mut hasher = hash_map::DefaultHasher::new(); |
279 | hasher.write(dirname.to_string().as_bytes()); |
280 | let obj = dst |
281 | .join(format!(" {:016x}- {}" , hasher.finish(), basename)) |
282 | .with_extension("o" ); |
283 | |
284 | match obj.parent() { |
285 | Some(s) => fs::create_dir_all(s)?, |
286 | None => { |
287 | return Err(Error::new( |
288 | ErrorKind::InvalidArgument, |
289 | "dst is an invalid path with no parent" , |
290 | )); |
291 | } |
292 | }; |
293 | |
294 | objects.push(Object::new(file.to_path_buf(), obj)); |
295 | } |
296 | |
297 | Ok(objects) |
298 | } |
299 | |
300 | pub(crate) fn run( |
301 | cmd: &mut Command, |
302 | program: &str, |
303 | cargo_output: &CargoOutput, |
304 | ) -> Result<(), Error> { |
305 | let mut child: Child = spawn(cmd, program, cargo_output)?; |
306 | wait_on_child(cmd, program, &mut child, cargo_output) |
307 | } |
308 | |
309 | pub(crate) fn run_output( |
310 | cmd: &mut Command, |
311 | program: &str, |
312 | cargo_output: &CargoOutput, |
313 | ) -> Result<Vec<u8>, Error> { |
314 | cmd.stdout(cfg:Stdio::piped()); |
315 | |
316 | let mut child: Child = spawn(cmd, program, cargo_output)?; |
317 | |
318 | let mut stdout: Vec = vec![]; |
319 | childResult |
320 | .stdout |
321 | .take() |
322 | .unwrap() |
323 | .read_to_end(&mut stdout) |
324 | .unwrap(); |
325 | |
326 | wait_on_child(cmd, program, &mut child, cargo_output)?; |
327 | |
328 | Ok(stdout) |
329 | } |
330 | |
331 | pub(crate) fn spawn( |
332 | cmd: &mut Command, |
333 | program: &str, |
334 | cargo_output: &CargoOutput, |
335 | ) -> Result<Child, Error> { |
336 | struct ResetStderr<'cmd>(&'cmd mut Command); |
337 | |
338 | impl Drop for ResetStderr<'_> { |
339 | fn drop(&mut self) { |
340 | // Reset stderr to default to release pipe_writer so that print thread will |
341 | // not block forever. |
342 | self.0.stderr(Stdio::inherit()); |
343 | } |
344 | } |
345 | |
346 | cargo_output.print_debug(&format_args!("running: {:?}" , cmd)); |
347 | |
348 | let cmd = ResetStderr(cmd); |
349 | let child = cmd.0.stderr(cargo_output.stdio_for_warnings()).spawn(); |
350 | match child { |
351 | Ok(child) => Ok(child), |
352 | Err(ref e) if e.kind() == io::ErrorKind::NotFound => { |
353 | let extra = if cfg!(windows) { |
354 | " (see https://github.com/rust-lang/cc-rs#compile-time-requirements \ |
355 | for help)" |
356 | } else { |
357 | "" |
358 | }; |
359 | Err(Error::new( |
360 | ErrorKind::ToolNotFound, |
361 | format!("Failed to find tool. Is ` {}` installed? {}" , program, extra), |
362 | )) |
363 | } |
364 | Err(e) => Err(Error::new( |
365 | ErrorKind::ToolExecError, |
366 | format!( |
367 | "Command {:?} with args {:?} failed to start: {:?}" , |
368 | cmd.0, program, e |
369 | ), |
370 | )), |
371 | } |
372 | } |
373 | |
374 | pub(crate) fn command_add_output_file( |
375 | cmd: &mut Command, |
376 | dst: &Path, |
377 | cuda: bool, |
378 | msvc: bool, |
379 | clang: bool, |
380 | gnu: bool, |
381 | is_asm: bool, |
382 | is_arm: bool, |
383 | ) { |
384 | if msvc && !clang && !gnu && !cuda && !(is_asm && is_arm) { |
385 | let mut s: OsString = OsString::from("-Fo" ); |
386 | s.push(dst); |
387 | cmd.arg(s); |
388 | } else { |
389 | cmd.arg("-o" ).arg(dst); |
390 | } |
391 | } |
392 | |
393 | #[cfg (feature = "parallel" )] |
394 | pub(crate) fn try_wait_on_child( |
395 | cmd: &Command, |
396 | program: &str, |
397 | child: &mut Child, |
398 | stdout: &mut dyn io::Write, |
399 | stderr_forwarder: &mut StderrForwarder, |
400 | ) -> Result<Option<()>, Error> { |
401 | stderr_forwarder.forward_available(); |
402 | |
403 | match child.try_wait() { |
404 | Ok(Some(status)) => { |
405 | stderr_forwarder.forward_all(); |
406 | |
407 | let _ = writeln!(stdout, " {}" , status); |
408 | |
409 | if status.success() { |
410 | Ok(Some(())) |
411 | } else { |
412 | Err(Error::new( |
413 | ErrorKind::ToolExecError, |
414 | format!( |
415 | "Command {:?} with args {:?} did not execute successfully (status code {})." , |
416 | cmd, program, status |
417 | ), |
418 | )) |
419 | } |
420 | } |
421 | Ok(None) => Ok(None), |
422 | Err(e) => { |
423 | stderr_forwarder.forward_all(); |
424 | Err(Error::new( |
425 | ErrorKind::ToolExecError, |
426 | format!( |
427 | "Failed to wait on spawned child process, command {:?} with args {:?}: {}." , |
428 | cmd, program, e |
429 | ), |
430 | )) |
431 | } |
432 | } |
433 | } |
434 | |