1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 env,
5 ffi::{OsStr, OsString},
6 io::Write,
7 path::{Path, PathBuf},
8 process::{Command, Stdio},
9 sync::RwLock,
10};
11
12use crate::{
13 command_helpers::{run_output, CargoOutput},
14 run,
15 tempfile::NamedTempfile,
16 Error, ErrorKind, OutputKind,
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 stdout = run_output(
202 Command::new(path).arg("-E").arg(tmp.path()),
203 &compiler_detect_output,
204 )?;
205 let stdout = String::from_utf8_lossy(&stdout);
206
207 if stdout.contains("-Wslash-u-filename") {
208 let stdout = run_output(
209 Command::new(path).arg("-E").arg("--").arg(tmp.path()),
210 &compiler_detect_output,
211 )?;
212 let stdout = String::from_utf8_lossy(&stdout);
213 guess_family_from_stdout(&stdout, path, args, cargo_output)
214 } else {
215 guess_family_from_stdout(&stdout, path, args, cargo_output)
216 }
217 }
218 let detect_family = |path: &Path, args: &[String]| -> Result<ToolFamily, Error> {
219 let cache_key = [path.as_os_str()]
220 .iter()
221 .cloned()
222 .chain(args.iter().map(OsStr::new))
223 .map(Into::into)
224 .collect();
225 if let Some(family) = cached_compiler_family.read().unwrap().get(&cache_key) {
226 return Ok(*family);
227 }
228
229 let family = detect_family_inner(path, args, cargo_output, out_dir)?;
230 cached_compiler_family
231 .write()
232 .unwrap()
233 .insert(cache_key, family);
234 Ok(family)
235 };
236
237 let family = detect_family(&path, &args).unwrap_or_else(|e| {
238 cargo_output.print_warning(&format_args!(
239 "Compiler family detection failed due to error: {}",
240 e
241 ));
242 match path.file_name().map(OsStr::to_string_lossy) {
243 Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
244 Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
245 ToolFamily::Msvc { clang_cl: false }
246 }
247 Some(fname) if fname.contains("clang") => {
248 let is_clang_cl = args
249 .iter()
250 .any(|a| a.strip_prefix("--driver-mode=") == Some("cl"));
251 if is_clang_cl {
252 ToolFamily::Msvc { clang_cl: true }
253 } else {
254 ToolFamily::Clang {
255 zig_cc: is_zig_cc(&path, cargo_output),
256 }
257 }
258 }
259 Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
260 _ => ToolFamily::Gnu,
261 }
262 });
263
264 Tool {
265 path,
266 cc_wrapper_path: None,
267 cc_wrapper_args: Vec::new(),
268 args: Vec::new(),
269 env: Vec::new(),
270 family,
271 cuda,
272 removed_args: Vec::new(),
273 has_internal_target_arg: false,
274 }
275 }
276
277 pub(crate) fn remove_arg(&mut self, flag: OsString) {
279 self.removed_args.push(flag);
280 }
281
282 pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
291 if self.cuda {
292 self.args.push("-Xcompiler".into());
293 }
294 self.args.push(flag);
295 }
296
297 pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
301 let flag = flag.to_str().unwrap();
302 let mut chars = flag.chars();
303
304 if self.is_like_msvc() {
306 if chars.next() != Some('/') {
307 return false;
308 }
309 } else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
310 return false;
311 }
312
313 if chars.next() == Some('O') {
315 return self
316 .args()
317 .iter()
318 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
319 }
320
321 false
323 }
324
325 pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
327 if self.is_duplicate_opt_arg(&flag) {
328 eprintln!("Info: Ignoring duplicate arg {:?}", &flag);
329 } else {
330 self.push_cc_arg(flag);
331 }
332 }
333
334 pub fn to_command(&self) -> Command {
340 let mut cmd = match self.cc_wrapper_path {
341 Some(ref cc_wrapper_path) => {
342 let mut cmd = Command::new(cc_wrapper_path);
343 cmd.arg(&self.path);
344 cmd
345 }
346 None => Command::new(&self.path),
347 };
348 cmd.args(&self.cc_wrapper_args);
349
350 let value = self
351 .args
352 .iter()
353 .filter(|a| !self.removed_args.contains(a))
354 .collect::<Vec<_>>();
355 cmd.args(&value);
356
357 for (k, v) in self.env.iter() {
358 cmd.env(k, v);
359 }
360 cmd
361 }
362
363 pub fn path(&self) -> &Path {
368 &self.path
369 }
370
371 pub fn args(&self) -> &[OsString] {
374 &self.args
375 }
376
377 pub fn env(&self) -> &[(OsString, OsString)] {
382 &self.env
383 }
384
385 pub fn cc_env(&self) -> OsString {
390 match self.cc_wrapper_path {
391 Some(ref cc_wrapper_path) => {
392 let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
393 cc_env.push(" ");
394 cc_env.push(self.path.to_path_buf().into_os_string());
395 for arg in self.cc_wrapper_args.iter() {
396 cc_env.push(" ");
397 cc_env.push(arg);
398 }
399 cc_env
400 }
401 None => OsString::from(""),
402 }
403 }
404
405 pub fn cflags_env(&self) -> OsString {
409 let mut flags = OsString::new();
410 for (i, arg) in self.args.iter().enumerate() {
411 if i > 0 {
412 flags.push(" ");
413 }
414 flags.push(arg);
415 }
416 flags
417 }
418
419 pub fn is_like_gnu(&self) -> bool {
421 self.family == ToolFamily::Gnu
422 }
423
424 pub fn is_like_clang(&self) -> bool {
426 matches!(self.family, ToolFamily::Clang { .. })
427 }
428
429 #[cfg(target_vendor = "apple")]
431 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
432 let path = self.path.to_string_lossy();
433 path.contains(".xctoolchain/")
434 }
435 #[cfg(not(target_vendor = "apple"))]
436 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
437 false
438 }
439
440 pub fn is_like_msvc(&self) -> bool {
442 matches!(self.family, ToolFamily::Msvc { .. })
443 }
444
445 pub fn is_like_clang_cl(&self) -> bool {
447 matches!(self.family, ToolFamily::Msvc { clang_cl: true })
448 }
449
450 pub(crate) fn supports_path_delimiter(&self) -> bool {
452 matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda
454 }
455}
456
457#[derive(Copy, Clone, Debug, PartialEq)]
463pub enum ToolFamily {
464 Gnu,
466 Clang { zig_cc: bool },
469 Msvc { clang_cl: bool },
471}
472
473impl ToolFamily {
474 pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
476 match *self {
477 ToolFamily::Msvc { .. } => {
478 cmd.push_cc_arg("-Z7".into());
479 }
480 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
481 cmd.push_cc_arg(
482 dwarf_version
483 .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v))
484 .into(),
485 );
486 }
487 }
488 }
489
490 pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
492 match *self {
493 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
494 cmd.push_cc_arg("-fno-omit-frame-pointer".into());
495 }
496 _ => (),
497 }
498 }
499
500 pub(crate) fn warnings_flags(&self) -> &'static str {
502 match *self {
503 ToolFamily::Msvc { .. } => "-W4",
504 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
505 }
506 }
507
508 pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
510 match *self {
511 ToolFamily::Msvc { .. } => None,
512 ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
513 }
514 }
515
516 pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
518 match *self {
519 ToolFamily::Msvc { .. } => "-WX",
520 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror",
521 }
522 }
523
524 pub(crate) fn verbose_stderr(&self) -> bool {
525 matches!(*self, ToolFamily::Clang { .. })
526 }
527}