1use 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 is_detection_cmd: bool,
35 checked_dbg_var: Arc<AtomicBool>,
36}
37
38#[derive(Clone, Debug)]
40pub(crate) enum OutputKind {
41 Forward,
43 Discard,
45 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 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 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 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 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 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 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 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 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 #[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 MIN_BUFFER_CAPACITY
233 }
234 #[cfg(windows)]
235 Err(_) => {
236 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 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 break false;
268 }
269 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
270 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 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 buffer.copy_within(consumed.., 0);
286 }
287 self.bytes_buffered -= consumed;
288 }
289 res => {
290 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
369pub(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 let mut hasher = hash_map::DefaultHasher::new();
402
403 let strip = |root: &str| {
407 dirname
408 .strip_prefix(root)
409 .map(|rel| Cow::Owned(rel.to_owned()))
410 };
411 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 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#[derive(Clone, Copy, Debug)]
463enum ProbeKind {
464 FamilyDetection,
466 FlagSupportCheck,
468 ArDetection,
470 CppStdlibDetection,
472}
473
474impl ProbeKind {
475 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 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
497fn 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
541pub(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 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
596pub(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
621fn 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 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
694pub(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
709pub(crate) trait CommandExt {
712 fn set_family_detection_env(&mut self, env: &BuildEnv) -> &mut Self;
714
715 fn set_flag_supported_env(&mut self, env: &EnvVars) -> &mut Self;
717
718 fn set_ar_detection_env(&mut self, env: &BuildEnv) -> &mut Self;
720
721 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 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 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 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}