Skip to main content

cc/
command_helpers.rs

1//! Miscellaneous helpers for running commands
2
3use std::{
4    borrow::Cow,
5    collections::hash_map,
6    ffi::{OsStr, OsString},
7    fmt::{self, Display},
8    fs,
9    hash::{Hash, Hasher},
10    io::{self, Read, Write},
11    path::Path,
12    process::{Child, ChildStderr, Command, ExitStatus, Output, Stdio},
13    sync::{
14        atomic::{AtomicBool, Ordering},
15        Arc,
16    },
17};
18
19use crate::{
20    build_env::{BuildEnv, EnvSnapshot, EnvVars},
21    logger::Logger,
22    BuildMessageKind, CompileCommand, Error, ErrorKind, Object,
23};
24
25#[derive(Clone, Debug)]
26pub(crate) struct CargoOutput {
27    pub(crate) metadata: bool,
28    pub(crate) warnings: bool,
29    pub(crate) debug: bool,
30    pub(crate) output: OutputKind,
31    pub(crate) logger: Option<Logger>,
32    /// Whether the command only detects something, so cc recovers when it
33    /// fails. Passed to the logger with a failed command.
34    is_detection_cmd: bool,
35    checked_dbg_var: Arc<AtomicBool>,
36}
37
38/// Different strategies for handling compiler output (to stdout)
39#[derive(Clone, Debug)]
40pub(crate) enum OutputKind {
41    /// Forward the output to this process' stdout ([`Stdio::inherit()`])
42    Forward,
43    /// Discard the output ([`Stdio::null()`])
44    Discard,
45    /// Capture the result ([`Stdio::piped()`])
46    Capture,
47}
48
49impl CargoOutput {
50    pub(crate) fn new() -> Self {
51        #[allow(clippy::disallowed_methods)]
52        Self {
53            metadata: true,
54            warnings: true,
55            output: OutputKind::Forward,
56            debug: match std::env::var_os("CC_ENABLE_DEBUG_OUTPUT") {
57                Some(v) => v != "0" && v != "false" && !v.is_empty(),
58                None => false,
59            },
60            logger: None,
61            is_detection_cmd: false,
62            checked_dbg_var: Arc::new(AtomicBool::new(false)),
63        }
64    }
65
66    /// A copy for a detection command, one whose failure cc recovers from.
67    pub(crate) fn for_detection_cmd(&self) -> Self {
68        let mut detection = self.clone();
69        detection.is_detection_cmd = true;
70        detection
71    }
72
73    /// A copy for a command whose stderr is expected to be noise: it is only
74    /// forwarded, as warnings and to the logger, when debugging.
75    pub(crate) fn quiet_unless_debug(&self) -> Self {
76        let mut quiet = self.clone();
77        quiet.warnings = quiet.debug;
78        if !quiet.debug {
79            quiet.logger = None;
80        }
81        quiet
82    }
83
84    pub(crate) fn print_metadata(&self, s: &dyn Display) {
85        if self.metadata {
86            println!("{s}");
87        }
88    }
89
90    pub(crate) fn print_warning(&self, arg: &dyn Display) {
91        if self.warnings {
92            println!("cargo:warning={arg}");
93        }
94        if let Some(logger) = &self.logger {
95            logger.log(BuildMessageKind::GeneralWarning, &arg.to_string(), &());
96        }
97    }
98
99    /// Forward one line of `cmd`'s stderr.
100    fn forward_stderr_line(&self, line: &[u8], cmd: &Command) {
101        if self.warnings {
102            write_warning(line);
103        }
104        if let Some(logger) = &self.logger {
105            // Streamed lines still end in `\r` when the compiler wrote `\r\n`.
106            let line = line.strip_suffix(b"\r").unwrap_or(line);
107            logger.log(
108                BuildMessageKind::StderrForwarding,
109                &String::from_utf8_lossy(line),
110                cmd,
111            );
112        }
113    }
114
115    /// The error for `cmd` exiting with `status`, which also goes to the
116    /// logger.
117    pub(crate) fn command_failed(&self, cmd: &Command, status: ExitStatus) -> Error {
118        let message = format!(
119            "command did not execute successfully (status code {status}): {}",
120            CommandLine(cmd)
121        );
122        if let Some(logger) = &self.logger {
123            logger.log(
124                BuildMessageKind::CommandFailed {
125                    is_detection_cmd: self.is_detection_cmd,
126                    exit_status: status,
127                },
128                &message,
129                cmd,
130            );
131        }
132        Error::new(ErrorKind::ToolExecError, message)
133    }
134
135    /// Pass `cmd`, which compiles `obj` in `directory`, to the logger.
136    pub(crate) fn log_compile_command(&self, cmd: &Command, obj: &Object, directory: &Path) {
137        if let Some(logger) = &self.logger {
138            logger.log(
139                BuildMessageKind::CompileCommand,
140                &CommandLine(cmd).to_string(),
141                &CompileCommand::new(cmd, &obj.src, &obj.dst, directory),
142            );
143        }
144    }
145
146    pub(crate) fn print_debug(&self, arg: &dyn Display) {
147        if self.metadata
148            && self
149                .checked_dbg_var
150                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
151                .is_ok()
152        {
153            println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT");
154        }
155        if self.debug {
156            println!("{arg}");
157        }
158    }
159
160    fn stdio_for_warnings(&self) -> Stdio {
161        if self.warnings || self.logger.is_some() {
162            Stdio::piped()
163        } else {
164            Stdio::null()
165        }
166    }
167
168    fn stdio_for_output(&self) -> Stdio {
169        match self.output {
170            OutputKind::Capture => Stdio::piped(),
171            OutputKind::Forward => Stdio::inherit(),
172            OutputKind::Discard => Stdio::null(),
173        }
174    }
175}
176
177pub(crate) struct StderrForwarder {
178    inner: Option<(ChildStderr, Vec<u8>)>,
179    cargo_output: CargoOutput,
180    #[cfg(feature = "parallel")]
181    is_non_blocking: bool,
182    #[cfg(feature = "parallel")]
183    bytes_available_failed: bool,
184    /// number of bytes buffered in inner
185    bytes_buffered: usize,
186}
187
188const MIN_BUFFER_CAPACITY: usize = 100;
189
190impl StderrForwarder {
191    pub(crate) fn new(child: &mut Child, cargo_output: &CargoOutput) -> Self {
192        Self {
193            inner: child
194                .stderr
195                .take()
196                .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))),
197            cargo_output: cargo_output.clone(),
198            bytes_buffered: 0,
199            #[cfg(feature = "parallel")]
200            is_non_blocking: false,
201            #[cfg(feature = "parallel")]
202            bytes_available_failed: false,
203        }
204    }
205
206    #[cfg(feature = "parallel")]
207    pub(crate) fn stderr(&self) -> Option<&ChildStderr> {
208        self.inner.as_ref().map(|inner| &inner.0)
209    }
210
211    /// Forward the stderr of `cmd` that is available.
212    pub(crate) fn forward_available(&mut self, cmd: &Command) -> bool {
213        let Some((stderr, buffer)) = self.inner.as_mut() else {
214            return true;
215        };
216
217        loop {
218            // For non-blocking we check to see if there is data available, so we should try to
219            // read at least that much. For blocking, always read at least the minimum amount.
220            #[cfg(not(feature = "parallel"))]
221            let to_reserve = MIN_BUFFER_CAPACITY;
222            #[cfg(feature = "parallel")]
223            let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
224                match crate::parallel::stderr::bytes_available(stderr) {
225                    #[cfg(windows)]
226                    Ok(0) => break false,
227                    #[cfg(unix)]
228                    Ok(0) => {
229                        // On Unix, depending on the implementation, we may sometimes get 0 in a
230                        // loop (either there is data available or the pipe is broken), so
231                        // continue with the non-blocking read anyway.
232                        MIN_BUFFER_CAPACITY
233                    }
234                    #[cfg(windows)]
235                    Err(_) => {
236                        // On Windows, if we get an error then the pipe is broken, so flush
237                        // the buffer and bail.
238                        if self.bytes_buffered > 0 {
239                            self.cargo_output
240                                .forward_stderr_line(&buffer[..self.bytes_buffered], cmd);
241                        }
242                        self.inner = None;
243                        break true;
244                    }
245                    #[cfg(unix)]
246                    Err(_) => {
247                        // On Unix, depending on the implementation, we may get spurious
248                        // errors so make a note not to use bytes_available again and try
249                        // the non-blocking read anyway.
250                        self.bytes_available_failed = true;
251                        MIN_BUFFER_CAPACITY
252                    }
253                    #[cfg(target_family = "wasm")]
254                    Err(_) => panic!("bytes_available should always succeed on wasm"),
255                    Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
256                }
257            } else {
258                MIN_BUFFER_CAPACITY
259            };
260            if self.bytes_buffered + to_reserve > buffer.len() {
261                buffer.resize(self.bytes_buffered + to_reserve, 0);
262            }
263
264            match stderr.read(&mut buffer[self.bytes_buffered..]) {
265                Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
266                    // No data currently, yield back.
267                    break false;
268                }
269                Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
270                    // Interrupted, try again.
271                    continue;
272                }
273                Ok(bytes_read) if bytes_read != 0 => {
274                    self.bytes_buffered += bytes_read;
275                    let mut consumed = 0;
276                    for line in buffer[..self.bytes_buffered].split_inclusive(|&b| b == b'\n') {
277                        // Only forward complete lines, leave the rest in the buffer.
278                        if let Some((b'\n', line)) = line.split_last() {
279                            consumed += line.len() + 1;
280                            self.cargo_output.forward_stderr_line(line, cmd);
281                        }
282                    }
283                    if consumed > 0 && consumed < self.bytes_buffered {
284                        // Remove the consumed bytes from buffer
285                        buffer.copy_within(consumed.., 0);
286                    }
287                    self.bytes_buffered -= consumed;
288                }
289                res => {
290                    // End of stream: flush remaining data and bail.
291                    if self.bytes_buffered > 0 {
292                        self.cargo_output
293                            .forward_stderr_line(&buffer[..self.bytes_buffered], cmd);
294                    }
295                    if let Err(err) = res {
296                        self.cargo_output.print_warning(&format_args!(
297                            "Failed to read from child stderr: {err}"
298                        ));
299                    }
300                    self.inner.take();
301                    break true;
302                }
303            }
304        }
305    }
306
307    #[cfg(feature = "parallel")]
308    pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> {
309        assert!(!self.is_non_blocking);
310
311        #[cfg(unix)]
312        if let Some((stderr, _)) = self.inner.as_ref() {
313            crate::parallel::stderr::set_non_blocking(stderr)?;
314        }
315
316        self.is_non_blocking = true;
317        Ok(())
318    }
319
320    #[cfg(feature = "parallel")]
321    pub(crate) fn forward_all(&mut self, cmd: &Command) {
322        while !self.forward_available(cmd) {}
323    }
324
325    #[cfg(not(feature = "parallel"))]
326    fn forward_all(&mut self, cmd: &Command) {
327        let forward_result = self.forward_available(cmd);
328        assert!(forward_result, "Should have consumed all data");
329    }
330}
331
332fn write_warning(line: &[u8]) {
333    let stdout = io::stdout();
334    let mut stdout = stdout.lock();
335    stdout.write_all(b"cargo:warning=").unwrap();
336    stdout.write_all(line).unwrap();
337    stdout.write_all(b"\n").unwrap();
338}
339
340fn wait_on_child(
341    cmd: &Command,
342    child: &mut Child,
343    cargo_output: &CargoOutput,
344) -> Result<(), Error> {
345    StderrForwarder::new(child, cargo_output).forward_all(cmd);
346
347    let status = match child.wait() {
348        Ok(s) => s,
349        Err(e) => {
350            return Err(Error::new(
351                ErrorKind::ToolExecError,
352                format!(
353                    "failed to wait on spawned child process `{}`: {e}",
354                    CommandLine(cmd)
355                ),
356            ));
357        }
358    };
359
360    cargo_output.print_debug(&status);
361
362    if status.success() {
363        Ok(())
364    } else {
365        Err(cargo_output.command_failed(cmd, status))
366    }
367}
368
369/// Find the destination object path for each file in the input source files,
370/// and store them in the output Object. `vars` is the snapshot of the `Build`,
371/// which holds Cargo's `OUT_DIR` and `CARGO_MANIFEST_DIR`.
372pub(crate) fn objects_from_files(
373    files: &[Arc<Path>],
374    dst: &Path,
375    vars: &EnvSnapshot,
376    source_root: Option<&Path>,
377) -> Result<Vec<Object>, Error> {
378    let mut objects = Vec::with_capacity(files.len());
379    for file in files {
380        let basename = file
381            .file_name()
382            .ok_or_else(|| {
383                Error::new(
384                    ErrorKind::InvalidArgument,
385                    "No file_name for object file path!",
386                )
387            })?
388            .to_string_lossy();
389        let dirname = file
390            .parent()
391            .ok_or_else(|| {
392                Error::new(
393                    ErrorKind::InvalidArgument,
394                    "No parent for object file path!",
395                )
396            })?
397            .to_string_lossy();
398
399        // Hash the dirname. This should prevent conflicts if we have multiple
400        // object files with the same filename in different subfolders.
401        let mut hasher = hash_map::DefaultHasher::new();
402
403        // Make the dirname relative (if possible) to avoid full system paths influencing the sha
404        // and making the output system-dependent.
405        // Strips `root` from the start of `dirname`, if it is one.
406        let strip = |root: &str| {
407            dirname
408                .strip_prefix(root)
409                .map(|rel| Cow::Owned(rel.to_owned()))
410        };
411        // Sources generated by a build script live under OUT_DIR, which is not under
412        // CARGO_MANIFEST_DIR when the target dir is elsewhere, so OUT_DIR is tried first.
413        let strip_env_root = |var: &str| {
414            vars.get(OsStr::new(var))
415                .and_then(|root| strip(&root.to_string_lossy()))
416        };
417        let (origin, dirname) = if let Some(rel) = strip_env_root("OUT_DIR") {
418            ("out_dir", rel)
419        } else if let Some(rel) = strip_env_root("CARGO_MANIFEST_DIR") {
420            ("manifest_dir", rel)
421        } else if let Some(rel) = source_root.and_then(|root| {
422            // Component-aware matching, so `external-other` doesn't match `external`.
423            let rel = file.parent()?.strip_prefix(root).ok()?;
424            Some(Cow::Owned(rel.to_string_lossy().into_owned()))
425        }) {
426            ("source_root", rel)
427        } else {
428            ("absolute", dirname)
429        };
430        origin.hash(&mut hasher);
431        dirname.hash(&mut hasher);
432        if let Some(extension) = file.extension() {
433            extension.to_string_lossy().hash(&mut hasher);
434        }
435        let obj = dst
436            .join(format!("{:016x}-{}", hasher.finish(), basename))
437            .with_extension("o");
438
439        match obj.parent() {
440            Some(s) => fs::create_dir_all(s)?,
441            None => {
442                return Err(Error::new(
443                    ErrorKind::InvalidArgument,
444                    "dst is an invalid path with no parent",
445                ));
446            }
447        };
448
449        objects.push(Object::new(file.to_path_buf(), obj));
450    }
451
452    Ok(objects)
453}
454
455/// Which of cc's own probing invocations a [`Command`] is being set up for.
456///
457/// A probe is not a command the caller asked cc to run: it is how cc works out
458/// what the compiler it was handed can do. Which probes run, and how many of
459/// them, depends on the compiler, the target and cc's internal caching, so the
460/// test shim records one only when a test names its class. See
461/// [`set_probe_env`] and `src/bin/cc-shim.rs`.
462#[derive(Clone, Copy, Debug)]
463enum ProbeKind {
464    /// Working out a compiler's [`ToolFamily`](crate::ToolFamily).
465    FamilyDetection,
466    /// Checking whether a compiler accepts a flag.
467    FlagSupportCheck,
468    /// Working out whether an Android NDK ships `llvm-ar` under that name.
469    ArDetection,
470    /// Working out which C++ standard library a compiler uses.
471    CppStdlibDetection,
472}
473
474impl ProbeKind {
475    /// The test-only variable naming where the shim should record this class.
476    const fn out_files_var(self) -> &'static str {
477        match self {
478            Self::FamilyDetection => "CC_SHIM_OUT_FILES_FOR_FAMILY_DETECTION",
479            Self::FlagSupportCheck => "CC_SHIM_OUT_FILES_FOR_FLAG_SUPPORT_CHECK",
480            Self::ArDetection => "CC_SHIM_OUT_FILES_FOR_AR_DETECTION",
481            Self::CppStdlibDetection => "CC_SHIM_OUT_FILES_FOR_CPP_STDLIB_DETECTION",
482        }
483    }
484
485    /// The test-only variable holding what the shim should print when this
486    /// class preprocesses a file.
487    const fn stdout_var(self) -> &'static str {
488        match self {
489            Self::FamilyDetection => "CC_SHIM_STDOUT_FOR_FAMILY_DETECTION",
490            Self::FlagSupportCheck => "CC_SHIM_STDOUT_FOR_FLAG_SUPPORT_CHECK",
491            Self::ArDetection => "CC_SHIM_STDOUT_FOR_AR_DETECTION",
492            Self::CppStdlibDetection => "CC_SHIM_STDOUT_FOR_CPP_STDLIB_DETECTION",
493        }
494    }
495}
496
497/// Apply [`Build::env`](crate::Build::env) to one of cc's own probing
498/// invocations.
499///
500/// A probe is a child process like any other, so it gets the environment the
501/// caller configured - `PATH` above all, without which a probe resolves a bare
502/// compiler name such as `cc` against the ambient environment rather than the
503/// one the compile commands run in, and answers a question about a different
504/// compiler than the one being built with (rust-lang/cc-rs#1859).
505///
506/// The `CC_SHIM_OUT_FILES_FOR_*` variables are the exception, and a third
507/// category beside the two that [`Build::env`](crate::Build::env) already
508/// distinguishes: they are set through `Build::env` but read by cc itself, and
509/// rewritten for exactly one child. cc renames the one matching `kind` to
510/// `CC_SHIM_OUT_FILES` and otherwise clears it, and clears `CC_SHIM_OUT_DIR`
511/// either way, instead of copying `Build::env` over blindly. A probe a test did
512/// not ask about then records nothing at all, rather than taking an `out{i}`
513/// slot and shifting the invocations the test is asserting on. The
514/// `CC_SHIM_STDOUT_FOR_*` variables are renamed to `CC_SHIM_STDOUT` the same
515/// way, so a test can answer one class of probe without changing the others.
516/// They are test-only, like `CC_SHIM_OUT_DIR`, and so are documented in
517/// `src/bin/cc-shim.rs` rather than in the table of public variables in
518/// `src/lib.rs`.
519fn set_probe_env(cmd: &mut Command, env: &EnvVars, kind: ProbeKind) {
520    for (key, value) in env {
521        cmd.env(key, value);
522    }
523
524    cmd.env_remove("CC_SHIM_OUT_DIR");
525    for (var, shim_var) in [
526        (kind.out_files_var(), "CC_SHIM_OUT_FILES"),
527        (kind.stdout_var(), "CC_SHIM_STDOUT"),
528    ] {
529        match env.iter().find(|(key, _)| &**key == OsStr::new(var)) {
530            Some((_, value)) => cmd.env(shim_var, value),
531            None => cmd.env_remove(shim_var),
532        };
533    }
534}
535
536pub(crate) fn run(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<(), Error> {
537    let mut child = spawn(cmd, cargo_output)?;
538    wait_on_child(cmd, &mut child, cargo_output)
539}
540
541/// Like [`run`], but stderr is only forwarded as `cargo:warning=` when the
542/// command succeeds. On failure, stderr is silently discarded.
543///
544/// Useful for probe commands where failure is expected and the error
545/// message is not actionable.
546pub(crate) fn run_silent_on_error(
547    cmd: &mut Command,
548    cargo_output: &CargoOutput,
549) -> Result<(), Error> {
550    let Output {
551        status,
552        stdout: _,
553        stderr,
554    } = spawn_and_wait_for_output(cmd, cargo_output)?;
555
556    cargo_output.print_debug(&status);
557
558    if status.success() {
559        stderr_warnings(&stderr, None).for_each(|line| cargo_output.forward_stderr_line(line, cmd));
560        Ok(())
561    } else {
562        Err(Error::new(
563            ErrorKind::ToolExecError,
564            format!(
565                "command did not execute successfully (status code {status}): {}",
566                CommandLine(cmd)
567            ),
568        ))
569    }
570}
571
572pub(crate) fn spawn_and_wait_for_output(
573    cmd: &mut Command,
574    cargo_output: &CargoOutput,
575) -> Result<Output, Error> {
576    // We specifically need the output to be captured, so override default
577    let mut captured_cargo_output = cargo_output.clone();
578    captured_cargo_output.output = OutputKind::Capture;
579    spawn(cmd, &captured_cargo_output)?
580        .wait_with_output()
581        .map_err(|e| {
582            Error::new(
583                ErrorKind::ToolExecError,
584                format!(
585                    "failed to wait on spawned child process `{}`: {e}",
586                    CommandLine(cmd)
587                ),
588            )
589        })
590}
591
592pub(crate) fn run_output(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Vec<u8>, Error> {
593    run_output_ignoring_line(cmd, cargo_output, None)
594}
595
596/// Like [`run_output`], but a line of stderr equal to `ignored_line` is not
597/// forwarded as a warning.
598pub(crate) fn run_output_ignoring_line(
599    cmd: &mut Command,
600    cargo_output: &CargoOutput,
601    ignored_line: Option<&[u8]>,
602) -> Result<Vec<u8>, Error> {
603    let Output {
604        status,
605        stdout,
606        stderr,
607    } = spawn_and_wait_for_output(cmd, cargo_output)?;
608
609    stderr_warnings(&stderr, ignored_line)
610        .for_each(|line| cargo_output.forward_stderr_line(line, cmd));
611
612    cargo_output.print_debug(&status);
613
614    if status.success() {
615        Ok(stdout)
616    } else {
617        Err(cargo_output.command_failed(cmd, status))
618    }
619}
620
621/// The non-empty lines of `stderr` to forward as warnings, skipping any line
622/// equal to `ignored_line`.
623fn stderr_warnings<'a>(
624    stderr: &'a [u8],
625    ignored_line: Option<&'a [u8]>,
626) -> impl Iterator<Item = &'a [u8]> {
627    stderr
628        .split(|&b| b == b'\n')
629        .map(|line| line.strip_suffix(b"\r").unwrap_or(line))
630        .filter(move |line| !line.is_empty() && Some(*line) != ignored_line)
631}
632
633pub(crate) fn spawn(cmd: &mut Command, cargo_output: &CargoOutput) -> Result<Child, Error> {
634    struct ResetStderr<'cmd>(&'cmd mut Command);
635
636    impl Drop for ResetStderr<'_> {
637        fn drop(&mut self) {
638            // Reset stderr to default to release pipe_writer so that print thread will
639            // not block forever.
640            self.0.stderr(Stdio::inherit());
641        }
642    }
643
644    cargo_output.print_debug(&format_args!("running: {}", CommandLine(cmd)));
645
646    let cmd = ResetStderr(cmd);
647    let child = cmd
648        .0
649        .stderr(cargo_output.stdio_for_warnings())
650        .stdout(cargo_output.stdio_for_output())
651        .spawn();
652    match child {
653        Ok(child) => Ok(child),
654        Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
655            let extra = if cfg!(windows) {
656                " (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help)"
657            } else {
658                ""
659            };
660            Err(Error::new(
661                ErrorKind::ToolNotFound,
662                format!("failed to find tool {:?}: {e}{extra}", cmd.0.get_program()),
663            ))
664        }
665        Err(e) => Err(Error::new(
666            ErrorKind::ToolExecError,
667            format!("command `{}` failed to start: {e}", CommandLine(cmd.0)),
668        )),
669    }
670}
671
672pub(crate) struct CmdAddOutputFileArgs {
673    pub(crate) cuda: bool,
674    pub(crate) is_assembler_msvc: bool,
675    pub(crate) msvc: bool,
676    pub(crate) clang: bool,
677    pub(crate) gnu: bool,
678    pub(crate) is_asm: bool,
679    pub(crate) is_arm: bool,
680}
681
682pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) {
683    if args.is_assembler_msvc
684        || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm))
685    {
686        let mut s = OsString::from("-Fo");
687        s.push(dst);
688        cmd.arg(s);
689    } else {
690        cmd.arg("-o").arg(dst);
691    }
692}
693
694/// Shows a command's program and arguments like `{cmd:?}`, but not its
695/// environment. cc sets the whole environment on the commands it runs, which
696/// the `Debug` output on Unix would list variable by variable.
697pub(crate) struct CommandLine<'a>(pub(crate) &'a Command);
698
699impl fmt::Display for CommandLine<'_> {
700    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701        write!(f, "{:?}", self.0.get_program())?;
702        for arg in self.0.get_args() {
703            write!(f, " {arg:?}")?;
704        }
705        Ok(())
706    }
707}
708
709/// Naming the two probe classes at the call site, so a caller does not have
710/// to reach for [`ProbeKind`] to say which one it means.
711pub(crate) trait CommandExt {
712    /// Apply the `Build`'s environment to a compiler family detection probe.
713    fn set_family_detection_env(&mut self, env: &BuildEnv) -> &mut Self;
714
715    /// Apply `Build::env` to an `is_flag_supported` probe.
716    fn set_flag_supported_env(&mut self, env: &EnvVars) -> &mut Self;
717
718    /// Apply the `Build`'s environment to the Android `llvm-ar` probe.
719    fn set_ar_detection_env(&mut self, env: &BuildEnv) -> &mut Self;
720
721    /// Apply a compiler's environment to its C++ standard library probe.
722    fn set_cpp_stdlib_detection_env(&mut self, env: &EnvVars) -> &mut Self;
723}
724
725impl CommandExt for Command {
726    fn set_family_detection_env(&mut self, env: &BuildEnv) -> &mut Self {
727        env.inherited().apply(self);
728        set_probe_env(self, &env.explicit, ProbeKind::FamilyDetection);
729        self
730    }
731
732    fn set_flag_supported_env(&mut self, env: &EnvVars) -> &mut Self {
733        set_probe_env(self, env, ProbeKind::FlagSupportCheck);
734        self
735    }
736
737    fn set_ar_detection_env(&mut self, env: &BuildEnv) -> &mut Self {
738        env.inherited().apply(self);
739        set_probe_env(self, &env.explicit, ProbeKind::ArDetection);
740        self
741    }
742
743    fn set_cpp_stdlib_detection_env(&mut self, env: &EnvVars) -> &mut Self {
744        set_probe_env(self, env, ProbeKind::CppStdlibDetection);
745        self
746    }
747}
748
749#[cfg(test)]
750mod tests {
751    use super::*;
752
753    #[test]
754    fn stderr_warnings_skips_ignored_line() {
755        // cl.exe echoes the source file name before any real diagnostics (#896).
756        let stderr = b"expando.c\r\nexpando.c(2): warning C4005: 'X': macro redefinition\r\n";
757        let warnings: Vec<_> = stderr_warnings(stderr, Some(b"expando.c")).collect();
758        assert_eq!(
759            warnings,
760            [&b"expando.c(2): warning C4005: 'X': macro redefinition"[..]]
761        );
762    }
763
764    #[test]
765    fn stderr_warnings_keeps_every_line_without_ignored_line() {
766        let stderr = b"expando.c\n\nsecond\n";
767        let warnings: Vec<_> = stderr_warnings(stderr, None).collect();
768        assert_eq!(warnings, [&b"expando.c"[..], &b"second"[..]]);
769    }
770}
771
772#[cfg(test)]
773mod source_root_regression_tests {
774    use super::*;
775
776    fn object_name(
777        file: &Path,
778        vars: &EnvSnapshot,
779        root: Option<&Path>,
780        base: &Path,
781    ) -> std::ffi::OsString {
782        let sources = vec![Arc::<Path>::from(file)];
783        objects_from_files(&sources, &base.join("objects"), vars, root)
784            .unwrap()
785            .remove(0)
786            .dst
787            .file_name()
788            .unwrap()
789            .to_os_string()
790    }
791
792    #[test]
793    fn external_roots_produce_stable_distinct_names_for_duplicate_basenames() {
794        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
795        let first_root = base.join("checkout-a").join("external");
796        let second_root = base.join("checkout-b").join("external");
797
798        let make = |root: &Path| {
799            let sources: Vec<Arc<Path>> = ["first", "second"]
800                .iter()
801                .map(|dir| Arc::<Path>::from(root.join(dir).join("foo.c")))
802                .collect();
803            let vars = EnvSnapshot::from_pairs(&[]);
804            let objects =
805                objects_from_files(&sources, &base.join("objects"), &vars, Some(root)).unwrap();
806            objects
807                .into_iter()
808                .map(|obj| obj.dst.file_name().unwrap().to_os_string())
809                .collect::<Vec<_>>()
810        };
811
812        let a = make(&first_root);
813        let b = make(&second_root);
814        assert_eq!(a, b, "moving an external source tree changed object names");
815        assert_ne!(a[0], a[1], "two sources named foo.c collided");
816    }
817
818    #[test]
819    fn source_root_does_not_match_a_sibling_with_the_same_string_prefix() {
820        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
821        let source_root = base.join("external");
822        let sibling = base.join("external-other").join("foo.c");
823        let sources = vec![Arc::<Path>::from(sibling)];
824        let vars = EnvSnapshot::from_pairs(&[]);
825        let with_root =
826            objects_from_files(&sources, &base.join("objects"), &vars, Some(&source_root)).unwrap();
827        let without_root =
828            objects_from_files(&sources, &base.join("objects"), &vars, None).unwrap();
829        assert_eq!(with_root[0].dst, without_root[0].dst);
830    }
831
832    #[test]
833    fn source_root_names_do_not_collide_with_out_dir_names() {
834        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
835        let out_dir = base.join("out");
836        let root = base.join("external");
837        let vars = EnvSnapshot::from_pairs(&[("OUT_DIR", out_dir.to_str().unwrap())]);
838
839        // same relative dir ("sub") and basename under each root
840        let from_out = object_name(&out_dir.join("sub").join("a.c"), &vars, Some(&root), &base);
841        let from_root = object_name(&root.join("sub").join("a.c"), &vars, Some(&root), &base);
842        assert_ne!(from_out, from_root);
843    }
844
845    #[test]
846    fn manifest_dir_takes_precedence_over_source_root() {
847        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
848        let manifest = base.join("pkg");
849        let vars = EnvSnapshot::from_pairs(&[("CARGO_MANIFEST_DIR", manifest.to_str().unwrap())]);
850        let file = manifest.join("sub").join("a.c");
851
852        // source_root contains the file too, but the manifest match must win,
853        // so setting source_root must not change the name.
854        let with_root = object_name(&file, &vars, Some(&manifest), &base);
855        let without_root = object_name(&file, &vars, None, &base);
856        assert_eq!(with_root, without_root);
857    }
858    #[test]
859    fn out_dir_and_manifest_dir_names_do_not_collide() {
860        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
861        let out_dir = base.join("out");
862        let manifest = base.join("pkg");
863        let vars = EnvSnapshot::from_pairs(&[
864            ("OUT_DIR", out_dir.to_str().unwrap()),
865            ("CARGO_MANIFEST_DIR", manifest.to_str().unwrap()),
866        ]);
867
868        let from_out = object_name(&out_dir.join("sub").join("a.c"), &vars, None, &base);
869        let from_manifest = object_name(&manifest.join("sub").join("a.c"), &vars, None, &base);
870        assert_ne!(from_out, from_manifest);
871    }
872    #[test]
873    fn same_basename_with_different_extensions_do_not_collide() {
874        let base = std::env::temp_dir().join("cc-rs-source-root-regression");
875        let vars = EnvSnapshot::from_pairs(&[]);
876        let c = object_name(&base.join("src").join("foo.c"), &vars, None, &base);
877        let cpp = object_name(&base.join("src").join("foo.cpp"), &vars, None, &base);
878        assert_ne!(c, cpp);
879    }
880}