1use crate::{
2 command_helpers::{run_output, spawn_and_wait_for_output, CargoOutput},
3 run,
4 tempfile::NamedTempfile,
5 Error, ErrorKind, OutputKind,
6};
7use std::{
8 borrow::Cow,
9 collections::HashMap,
10 env,
11 ffi::{OsStr, OsString},
12 io::Write,
13 path::{Path, PathBuf},
14 process::{Command, Output, Stdio},
15 sync::RwLock,
16};
17
18pub(crate) type CompilerFamilyLookupCache = HashMap<Box<[Box<OsStr>]>, ToolFamily>;
19
20#[derive(Clone, Debug)]
28#[allow(missing_docs)]
29pub struct Tool {
30 pub(crate) path: PathBuf,
31 pub(crate) cc_wrapper_path: Option<PathBuf>,
32 pub(crate) cc_wrapper_args: Vec<OsString>,
33 pub(crate) args: Vec<OsString>,
34 pub(crate) env: Vec<(OsString, OsString)>,
35 pub(crate) family: ToolFamily,
36 pub(crate) cuda: bool,
37 pub(crate) removed_args: Vec<OsString>,
38 pub(crate) has_internal_target_arg: bool,
39}
40
41impl Tool {
42 pub(crate) fn from_find_msvc_tools(tool: ::find_msvc_tools::Tool) -> Self {
43 let mut cc_tool = Self::with_family(
44 tool.path().into(),
45 ToolFamily::Msvc {
46 clang_cl: tool.is_clang_cl(),
47 },
48 );
49
50 cc_tool.env = tool
51 .env()
52 .into_iter()
53 .map(|(k, v)| (k.clone(), v.clone()))
54 .collect();
55
56 cc_tool
57 }
58
59 pub(crate) fn new(
60 path: PathBuf,
61 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
62 cargo_output: &CargoOutput,
63 out_dir: Option<&Path>,
64 ) -> Self {
65 Self::with_features(
66 path,
67 vec![],
68 false,
69 cached_compiler_family,
70 cargo_output,
71 out_dir,
72 )
73 }
74
75 pub(crate) fn with_args(
76 path: PathBuf,
77 args: Vec<String>,
78 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
79 cargo_output: &CargoOutput,
80 out_dir: Option<&Path>,
81 ) -> Self {
82 Self::with_features(
83 path,
84 args,
85 false,
86 cached_compiler_family,
87 cargo_output,
88 out_dir,
89 )
90 }
91
92 pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self {
94 Self {
95 path,
96 cc_wrapper_path: None,
97 cc_wrapper_args: Vec::new(),
98 args: Vec::new(),
99 env: Vec::new(),
100 family,
101 cuda: false,
102 removed_args: Vec::new(),
103 has_internal_target_arg: false,
104 }
105 }
106
107 pub(crate) fn with_features(
108 path: PathBuf,
109 args: Vec<String>,
110 cuda: bool,
111 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
112 cargo_output: &CargoOutput,
113 out_dir: Option<&Path>,
114 ) -> Self {
115 fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool {
116 run_output(
117 Command::new(path).arg("--version"),
118 cargo_output,
120 )
121 .map(|o| String::from_utf8_lossy(&o).contains("ziglang"))
122 .unwrap_or_default()
123 || {
124 match path.file_name().map(OsStr::to_string_lossy) {
125 Some(fname) => fname.contains("zig"),
126 _ => false,
127 }
128 }
129 }
130
131 fn guess_family_from_stdout(
132 stdout: &str,
133 path: &Path,
134 args: &[String],
135 cargo_output: &CargoOutput,
136 ) -> Result<ToolFamily, Error> {
137 cargo_output.print_debug(&stdout);
138
139 let accepts_cl_style_flags = run(
142 Command::new(path).args(args).arg("-?").stdin(Stdio::null()),
143 &{
144 let mut cargo_output = cargo_output.clone();
146 cargo_output.warnings = cargo_output.debug;
147 cargo_output.output = OutputKind::Discard;
148 cargo_output
149 },
150 )
151 .is_ok();
152
153 let clang = stdout.contains(r#""clang""#);
154 let gcc = stdout.contains(r#""gcc""#);
155 let emscripten = stdout.contains(r#""emscripten""#);
156 let vxworks = stdout.contains(r#""VxWorks""#);
157
158 match (clang, accepts_cl_style_flags, gcc, emscripten, vxworks) {
159 (clang_cl, true, _, false, false) => Ok(ToolFamily::Msvc { clang_cl }),
160 (true, _, _, _, false) | (_, _, _, true, false) => Ok(ToolFamily::Clang {
161 zig_cc: is_zig_cc(path, cargo_output),
162 }),
163 (false, false, true, _, false) | (_, _, _, _, true) => Ok(ToolFamily::Gnu),
164 (false, false, false, false, false) => {
165 cargo_output.print_warning(&"Compiler family detection failed since it does not define `__clang__`, `__GNUC__`, `__EMSCRIPTEN__` or `__VXWORKS__`, also does not accept cl style flag `-?`, fallback to treating it as GNU");
166 Err(Error::new(
167 ErrorKind::ToolFamilyMacroNotFound,
168 "Expects macro `__clang__`, `__GNUC__` or `__EMSCRIPTEN__`, `__VXWORKS__` or accepts cl style flag `-?`, but found none",
169 ))
170 }
171 }
172 }
173
174 fn detect_family_inner(
175 path: &Path,
176 args: &[String],
177 cargo_output: &CargoOutput,
178 out_dir: Option<&Path>,
179 ) -> Result<ToolFamily, Error> {
180 let out_dir = out_dir
181 .map(Cow::Borrowed)
182 .unwrap_or_else(|| Cow::Owned(env::temp_dir()));
183
184 std::fs::create_dir_all(&out_dir).map_err(|err| Error {
187 kind: ErrorKind::IOError,
188 message: format!("failed to create OUT_DIR '{}': {}", out_dir.display(), err)
189 .into(),
190 })?;
191
192 let mut tmp =
193 NamedTempfile::new(&out_dir, "detect_compiler_family.c").map_err(|err| Error {
194 kind: ErrorKind::IOError,
195 message: format!(
196 "failed to create detect_compiler_family.c temp file in '{}': {}",
197 out_dir.display(),
198 err
199 )
200 .into(),
201 })?;
202 let mut tmp_file = tmp.take_file().unwrap();
203 tmp_file.write_all(include_bytes!("detect_compiler_family.c"))?;
204 tmp_file.flush()?;
207 tmp_file.sync_data()?;
208 drop(tmp_file);
209
210 let mut compiler_detect_output = cargo_output.clone();
215 compiler_detect_output.warnings = compiler_detect_output.debug;
216
217 let mut cmd = Command::new(path);
218 cmd.arg("-E").arg(tmp.path());
219
220 let mut captured_cargo_output = compiler_detect_output.clone();
224 captured_cargo_output.warnings = true;
225 let Output {
226 status,
227 stdout,
228 stderr,
229 } = spawn_and_wait_for_output(&mut cmd, &captured_cargo_output)?;
230
231 let stdout = if [&stdout, &stderr]
232 .iter()
233 .any(|o| String::from_utf8_lossy(o).contains("-Wslash-u-filename"))
234 {
235 run_output(
236 Command::new(path).arg("-E").arg("--").arg(tmp.path()),
237 &compiler_detect_output,
238 )?
239 } else {
240 if !status.success() {
241 return Err(Error::new(
242 ErrorKind::ToolExecError,
243 format!(
244 "command did not execute successfully (status code {status}): {cmd:?}"
245 ),
246 ));
247 }
248
249 stdout
250 };
251
252 let stdout = String::from_utf8_lossy(&stdout);
253 guess_family_from_stdout(&stdout, path, args, cargo_output)
254 }
255 let detect_family = |path: &Path, args: &[String]| -> Result<ToolFamily, Error> {
256 let cache_key = [path.as_os_str()]
257 .iter()
258 .cloned()
259 .chain(args.iter().map(OsStr::new))
260 .map(Into::into)
261 .collect();
262 if let Some(family) = cached_compiler_family.read().unwrap().get(&cache_key) {
263 return Ok(*family);
264 }
265
266 let family = detect_family_inner(path, args, cargo_output, out_dir)?;
267 cached_compiler_family
268 .write()
269 .unwrap()
270 .insert(cache_key, family);
271 Ok(family)
272 };
273
274 let family = detect_family(&path, &args).unwrap_or_else(|e| {
275 cargo_output.print_warning(&format_args!(
276 "Compiler family detection failed due to error: {e}"
277 ));
278 match path.file_name().map(OsStr::to_string_lossy) {
279 Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
280 Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
281 ToolFamily::Msvc { clang_cl: false }
282 }
283 Some(fname) if fname.contains("clang") => {
284 let is_clang_cl = args
285 .iter()
286 .any(|a| a.strip_prefix("--driver-mode=") == Some("cl"));
287 if is_clang_cl {
288 ToolFamily::Msvc { clang_cl: true }
289 } else {
290 ToolFamily::Clang {
291 zig_cc: is_zig_cc(&path, cargo_output),
292 }
293 }
294 }
295 Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
296 _ => ToolFamily::Gnu,
297 }
298 });
299
300 Tool {
301 path,
302 cc_wrapper_path: None,
303 cc_wrapper_args: Vec::new(),
304 args: Vec::new(),
305 env: Vec::new(),
306 family,
307 cuda,
308 removed_args: Vec::new(),
309 has_internal_target_arg: false,
310 }
311 }
312
313 pub(crate) fn remove_arg(&mut self, flag: OsString) {
315 self.removed_args.push(flag);
316 }
317
318 pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
327 if self.cuda {
328 self.args.push("-Xcompiler".into());
329 }
330 self.args.push(flag);
331 }
332
333 pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
337 let flag = flag.to_str().unwrap();
338 let mut chars = flag.chars();
339
340 if self.is_like_msvc() {
342 if chars.next() != Some('/') {
343 return false;
344 }
345 } else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
346 return false;
347 }
348
349 if chars.next() == Some('O') {
351 return self
352 .args()
353 .iter()
354 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
355 }
356
357 false
359 }
360
361 pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
363 if self.is_duplicate_opt_arg(&flag) {
364 eprintln!("Info: Ignoring duplicate arg {:?}", &flag);
365 } else {
366 self.push_cc_arg(flag);
367 }
368 }
369
370 pub fn to_command(&self) -> Command {
376 let mut cmd = match self.cc_wrapper_path {
377 Some(ref cc_wrapper_path) => {
378 let mut cmd = Command::new(cc_wrapper_path);
379 cmd.arg(&self.path);
380 cmd
381 }
382 None => Command::new(&self.path),
383 };
384 cmd.args(&self.cc_wrapper_args);
385
386 cmd.args(self.args.iter().filter(|a| !self.removed_args.contains(a)));
387
388 for (k, v) in self.env.iter() {
389 cmd.env(k, v);
390 }
391
392 cmd
393 }
394
395 pub fn path(&self) -> &Path {
400 &self.path
401 }
402
403 pub fn args(&self) -> &[OsString] {
406 &self.args
407 }
408
409 pub fn env(&self) -> &[(OsString, OsString)] {
414 &self.env
415 }
416
417 pub fn cc_env(&self) -> OsString {
422 match self.cc_wrapper_path {
423 Some(ref cc_wrapper_path) => {
424 let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
425 cc_env.push(" ");
426 cc_env.push(self.path.to_path_buf().into_os_string());
427 for arg in self.cc_wrapper_args.iter() {
428 cc_env.push(" ");
429 cc_env.push(arg);
430 }
431 cc_env
432 }
433 None => OsString::from(""),
434 }
435 }
436
437 pub fn cflags_env(&self) -> OsString {
441 let mut flags = OsString::new();
442 for (i, arg) in self.args.iter().enumerate() {
443 if i > 0 {
444 flags.push(" ");
445 }
446 flags.push(arg);
447 }
448 flags
449 }
450
451 pub fn is_like_gnu(&self) -> bool {
453 self.family == ToolFamily::Gnu
454 }
455
456 pub fn is_like_clang(&self) -> bool {
458 matches!(self.family, ToolFamily::Clang { .. })
459 }
460
461 #[cfg(target_vendor = "apple")]
463 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
464 let path = self.path.to_string_lossy();
465 path.contains(".xctoolchain/")
466 }
467 #[cfg(not(target_vendor = "apple"))]
468 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
469 false
470 }
471
472 pub fn is_like_msvc(&self) -> bool {
474 matches!(self.family, ToolFamily::Msvc { .. })
475 }
476
477 pub fn is_like_clang_cl(&self) -> bool {
479 matches!(self.family, ToolFamily::Msvc { clang_cl: true })
480 }
481
482 pub(crate) fn supports_path_delimiter(&self) -> bool {
484 matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda
486 }
487}
488
489#[derive(Copy, Clone, Debug, PartialEq)]
495pub enum ToolFamily {
496 Gnu,
498 Clang { zig_cc: bool },
501 Msvc { clang_cl: bool },
503}
504
505impl ToolFamily {
506 pub(crate) fn add_debug_flags(
508 &self,
509 cmd: &mut Tool,
510 debug_opt: &str,
511 dwarf_version: Option<u32>,
512 ) {
513 match *self {
514 ToolFamily::Msvc { .. } => {
515 cmd.push_cc_arg("-Z7".into());
516 }
517 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
518 match debug_opt {
519 "" | "0" | "false" | "none" => {
521 debug_assert!(
522 false,
523 "earlier check should have avoided calling add_debug_flags"
524 );
525 }
526
527 "line-directives-only" if cmd.is_like_clang() => {
529 cmd.push_cc_arg("-gline-directives-only".into());
530 }
531 "1" | "limited" | "line-tables-only" | "line-directives-only" => {
534 cmd.push_cc_arg("-g1".into());
535 }
536 "2" | "true" | "full" => {
537 cmd.push_cc_arg("-g".into());
538 }
539 _ => {
540 cmd.push_cc_arg("-g".into());
542 }
543 }
544 if let Some(v) = dwarf_version {
545 cmd.push_cc_arg(format!("-gdwarf-{v}").into());
546 }
547 }
548 }
549 }
550
551 pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
553 match *self {
554 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
555 cmd.push_cc_arg("-fno-omit-frame-pointer".into());
556 }
557 _ => (),
558 }
559 }
560
561 pub(crate) fn warnings_flags(&self) -> &'static str {
563 match *self {
564 ToolFamily::Msvc { .. } => "-W4",
565 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
566 }
567 }
568
569 pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
571 match *self {
572 ToolFamily::Msvc { .. } => None,
573 ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
574 }
575 }
576
577 pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
579 match *self {
580 ToolFamily::Msvc { .. } => "-WX",
581 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror",
582 }
583 }
584
585 pub(crate) fn verbose_stderr(&self) -> bool {
586 matches!(*self, ToolFamily::Clang { .. })
587 }
588}