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