1use crate::{
2 command_helpers::{run_output, spawn, CargoOutput},
3 run,
4 tempfile::NamedTempfile,
5 Error, ErrorKind, OutputKind,
6};
7use std::io::Read;
8use std::{
9 borrow::Cow,
10 collections::HashMap,
11 env,
12 ffi::{OsStr, OsString},
13 io::Write,
14 path::{Path, PathBuf},
15 process::{Command, Stdio},
16 sync::RwLock,
17};
18
19pub(crate) type CompilerFamilyLookupCache = HashMap<Box<[Box<OsStr>]>, ToolFamily>;
20
21#[derive(Clone, Debug)]
29#[allow(missing_docs)]
30pub struct Tool {
31 pub(crate) path: PathBuf,
32 pub(crate) cc_wrapper_path: Option<PathBuf>,
33 pub(crate) cc_wrapper_args: Vec<OsString>,
34 pub(crate) args: Vec<OsString>,
35 pub(crate) env: Vec<(OsString, OsString)>,
36 pub(crate) family: ToolFamily,
37 pub(crate) cuda: bool,
38 pub(crate) removed_args: Vec<OsString>,
39 pub(crate) has_internal_target_arg: bool,
40}
41
42impl Tool {
43 pub(crate) fn new(
44 path: PathBuf,
45 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
46 cargo_output: &CargoOutput,
47 out_dir: Option<&Path>,
48 ) -> Self {
49 Self::with_features(
50 path,
51 vec![],
52 false,
53 cached_compiler_family,
54 cargo_output,
55 out_dir,
56 )
57 }
58
59 pub(crate) fn with_args(
60 path: PathBuf,
61 args: Vec<String>,
62 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
63 cargo_output: &CargoOutput,
64 out_dir: Option<&Path>,
65 ) -> Self {
66 Self::with_features(
67 path,
68 args,
69 false,
70 cached_compiler_family,
71 cargo_output,
72 out_dir,
73 )
74 }
75
76 pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self {
78 Self {
79 path,
80 cc_wrapper_path: None,
81 cc_wrapper_args: Vec::new(),
82 args: Vec::new(),
83 env: Vec::new(),
84 family,
85 cuda: false,
86 removed_args: Vec::new(),
87 has_internal_target_arg: false,
88 }
89 }
90
91 pub(crate) fn with_features(
92 path: PathBuf,
93 args: Vec<String>,
94 cuda: bool,
95 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
96 cargo_output: &CargoOutput,
97 out_dir: Option<&Path>,
98 ) -> Self {
99 fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool {
100 run_output(
101 Command::new(path).arg("--version"),
102 cargo_output,
104 )
105 .map(|o| String::from_utf8_lossy(&o).contains("ziglang"))
106 .unwrap_or_default()
107 || {
108 match path.file_name().map(OsStr::to_string_lossy) {
109 Some(fname) => fname.contains("zig"),
110 _ => false,
111 }
112 }
113 }
114
115 fn guess_family_from_stdout(
116 stdout: &str,
117 path: &Path,
118 args: &[String],
119 cargo_output: &CargoOutput,
120 ) -> Result<ToolFamily, Error> {
121 cargo_output.print_debug(&stdout);
122
123 let accepts_cl_style_flags = run(
126 Command::new(path).args(args).arg("-?").stdin(Stdio::null()),
127 &{
128 let mut cargo_output = cargo_output.clone();
130 cargo_output.warnings = cargo_output.debug;
131 cargo_output.output = OutputKind::Discard;
132 cargo_output
133 },
134 )
135 .is_ok();
136
137 let clang = stdout.contains(r#""clang""#);
138 let gcc = stdout.contains(r#""gcc""#);
139 let emscripten = stdout.contains(r#""emscripten""#);
140 let vxworks = stdout.contains(r#""VxWorks""#);
141
142 match (clang, accepts_cl_style_flags, gcc, emscripten, vxworks) {
143 (clang_cl, true, _, false, false) => Ok(ToolFamily::Msvc { clang_cl }),
144 (true, _, _, _, false) | (_, _, _, true, false) => Ok(ToolFamily::Clang {
145 zig_cc: is_zig_cc(path, cargo_output),
146 }),
147 (false, false, true, _, false) | (_, _, _, _, true) => Ok(ToolFamily::Gnu),
148 (false, false, false, false, false) => {
149 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");
150 Err(Error::new(
151 ErrorKind::ToolFamilyMacroNotFound,
152 "Expects macro `__clang__`, `__GNUC__` or `__EMSCRIPTEN__`, `__VXWORKS__` or accepts cl style flag `-?`, but found none",
153 ))
154 }
155 }
156 }
157
158 fn detect_family_inner(
159 path: &Path,
160 args: &[String],
161 cargo_output: &CargoOutput,
162 out_dir: Option<&Path>,
163 ) -> Result<ToolFamily, Error> {
164 let out_dir = out_dir
165 .map(Cow::Borrowed)
166 .unwrap_or_else(|| Cow::Owned(env::temp_dir()));
167
168 std::fs::create_dir_all(&out_dir).map_err(|err| Error {
171 kind: ErrorKind::IOError,
172 message: format!("failed to create OUT_DIR '{}': {}", out_dir.display(), err)
173 .into(),
174 })?;
175
176 let mut tmp =
177 NamedTempfile::new(&out_dir, "detect_compiler_family.c").map_err(|err| Error {
178 kind: ErrorKind::IOError,
179 message: format!(
180 "failed to create detect_compiler_family.c temp file in '{}': {}",
181 out_dir.display(),
182 err
183 )
184 .into(),
185 })?;
186 let mut tmp_file = tmp.take_file().unwrap();
187 tmp_file.write_all(include_bytes!("detect_compiler_family.c"))?;
188 tmp_file.flush()?;
191 tmp_file.sync_data()?;
192 drop(tmp_file);
193
194 let mut compiler_detect_output = cargo_output.clone();
199 compiler_detect_output.warnings = compiler_detect_output.debug;
200
201 let mut cmd = Command::new(path);
202 cmd.arg("-E").arg(tmp.path());
203
204 let mut captured_cargo_output = compiler_detect_output.clone();
208 captured_cargo_output.output = OutputKind::Capture;
209 captured_cargo_output.warnings = true;
210 let mut child = spawn(&mut cmd, &captured_cargo_output)?;
211
212 let mut out = vec![];
213 let mut err = vec![];
214 child.stdout.take().unwrap().read_to_end(&mut out)?;
215 child.stderr.take().unwrap().read_to_end(&mut err)?;
216
217 let status = child.wait()?;
218
219 let stdout = if [&out, &err]
220 .iter()
221 .any(|o| String::from_utf8_lossy(o).contains("-Wslash-u-filename"))
222 {
223 run_output(
224 Command::new(path).arg("-E").arg("--").arg(tmp.path()),
225 &compiler_detect_output,
226 )?
227 } else {
228 if !status.success() {
229 return Err(Error::new(
230 ErrorKind::ToolExecError,
231 format!(
232 "command did not execute successfully (status code {status}): {cmd:?}"
233 ),
234 ));
235 }
236
237 out
238 };
239
240 let stdout = String::from_utf8_lossy(&stdout);
241 guess_family_from_stdout(&stdout, path, args, cargo_output)
242 }
243 let detect_family = |path: &Path, args: &[String]| -> Result<ToolFamily, Error> {
244 let cache_key = [path.as_os_str()]
245 .iter()
246 .cloned()
247 .chain(args.iter().map(OsStr::new))
248 .map(Into::into)
249 .collect();
250 if let Some(family) = cached_compiler_family.read().unwrap().get(&cache_key) {
251 return Ok(*family);
252 }
253
254 let family = detect_family_inner(path, args, cargo_output, out_dir)?;
255 cached_compiler_family
256 .write()
257 .unwrap()
258 .insert(cache_key, family);
259 Ok(family)
260 };
261
262 let family = detect_family(&path, &args).unwrap_or_else(|e| {
263 cargo_output.print_warning(&format_args!(
264 "Compiler family detection failed due to error: {e}"
265 ));
266 match path.file_name().map(OsStr::to_string_lossy) {
267 Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
268 Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
269 ToolFamily::Msvc { clang_cl: false }
270 }
271 Some(fname) if fname.contains("clang") => {
272 let is_clang_cl = args
273 .iter()
274 .any(|a| a.strip_prefix("--driver-mode=") == Some("cl"));
275 if is_clang_cl {
276 ToolFamily::Msvc { clang_cl: true }
277 } else {
278 ToolFamily::Clang {
279 zig_cc: is_zig_cc(&path, cargo_output),
280 }
281 }
282 }
283 Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
284 _ => ToolFamily::Gnu,
285 }
286 });
287
288 Tool {
289 path,
290 cc_wrapper_path: None,
291 cc_wrapper_args: Vec::new(),
292 args: Vec::new(),
293 env: Vec::new(),
294 family,
295 cuda,
296 removed_args: Vec::new(),
297 has_internal_target_arg: false,
298 }
299 }
300
301 pub(crate) fn remove_arg(&mut self, flag: OsString) {
303 self.removed_args.push(flag);
304 }
305
306 pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
315 if self.cuda {
316 self.args.push("-Xcompiler".into());
317 }
318 self.args.push(flag);
319 }
320
321 pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
325 let flag = flag.to_str().unwrap();
326 let mut chars = flag.chars();
327
328 if self.is_like_msvc() {
330 if chars.next() != Some('/') {
331 return false;
332 }
333 } else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
334 return false;
335 }
336
337 if chars.next() == Some('O') {
339 return self
340 .args()
341 .iter()
342 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
343 }
344
345 false
347 }
348
349 pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
351 if self.is_duplicate_opt_arg(&flag) {
352 eprintln!("Info: Ignoring duplicate arg {:?}", &flag);
353 } else {
354 self.push_cc_arg(flag);
355 }
356 }
357
358 pub fn to_command(&self) -> Command {
364 let mut cmd = match self.cc_wrapper_path {
365 Some(ref cc_wrapper_path) => {
366 let mut cmd = Command::new(cc_wrapper_path);
367 cmd.arg(&self.path);
368 cmd
369 }
370 None => Command::new(&self.path),
371 };
372 cmd.args(&self.cc_wrapper_args);
373
374 let value = self
375 .args
376 .iter()
377 .filter(|a| !self.removed_args.contains(a))
378 .collect::<Vec<_>>();
379 cmd.args(&value);
380
381 for (k, v) in self.env.iter() {
382 cmd.env(k, v);
383 }
384 cmd
385 }
386
387 pub fn path(&self) -> &Path {
392 &self.path
393 }
394
395 pub fn args(&self) -> &[OsString] {
398 &self.args
399 }
400
401 pub fn env(&self) -> &[(OsString, OsString)] {
406 &self.env
407 }
408
409 pub fn cc_env(&self) -> OsString {
414 match self.cc_wrapper_path {
415 Some(ref cc_wrapper_path) => {
416 let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
417 cc_env.push(" ");
418 cc_env.push(self.path.to_path_buf().into_os_string());
419 for arg in self.cc_wrapper_args.iter() {
420 cc_env.push(" ");
421 cc_env.push(arg);
422 }
423 cc_env
424 }
425 None => OsString::from(""),
426 }
427 }
428
429 pub fn cflags_env(&self) -> OsString {
433 let mut flags = OsString::new();
434 for (i, arg) in self.args.iter().enumerate() {
435 if i > 0 {
436 flags.push(" ");
437 }
438 flags.push(arg);
439 }
440 flags
441 }
442
443 pub fn is_like_gnu(&self) -> bool {
445 self.family == ToolFamily::Gnu
446 }
447
448 pub fn is_like_clang(&self) -> bool {
450 matches!(self.family, ToolFamily::Clang { .. })
451 }
452
453 #[cfg(target_vendor = "apple")]
455 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
456 let path = self.path.to_string_lossy();
457 path.contains(".xctoolchain/")
458 }
459 #[cfg(not(target_vendor = "apple"))]
460 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
461 false
462 }
463
464 pub fn is_like_msvc(&self) -> bool {
466 matches!(self.family, ToolFamily::Msvc { .. })
467 }
468
469 pub fn is_like_clang_cl(&self) -> bool {
471 matches!(self.family, ToolFamily::Msvc { clang_cl: true })
472 }
473
474 pub(crate) fn supports_path_delimiter(&self) -> bool {
476 matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda
478 }
479}
480
481#[derive(Copy, Clone, Debug, PartialEq)]
487pub enum ToolFamily {
488 Gnu,
490 Clang { zig_cc: bool },
493 Msvc { clang_cl: bool },
495}
496
497impl ToolFamily {
498 pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
500 match *self {
501 ToolFamily::Msvc { .. } => {
502 cmd.push_cc_arg("-Z7".into());
503 }
504 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
505 cmd.push_cc_arg(
506 dwarf_version
507 .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{v}"))
508 .into(),
509 );
510 }
511 }
512 }
513
514 pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
516 match *self {
517 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
518 cmd.push_cc_arg("-fno-omit-frame-pointer".into());
519 }
520 _ => (),
521 }
522 }
523
524 pub(crate) fn warnings_flags(&self) -> &'static str {
526 match *self {
527 ToolFamily::Msvc { .. } => "-W4",
528 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
529 }
530 }
531
532 pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
534 match *self {
535 ToolFamily::Msvc { .. } => None,
536 ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
537 }
538 }
539
540 pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
542 match *self {
543 ToolFamily::Msvc { .. } => "-WX",
544 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror",
545 }
546 }
547
548 pub(crate) fn verbose_stderr(&self) -> bool {
549 matches!(*self, ToolFamily::Clang { .. })
550 }
551}