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 path,
103 cargo_output,
105 )
106 .map(|o| String::from_utf8_lossy(&o).contains("ziglang"))
107 .unwrap_or_default()
108 || {
109 match path.file_name().map(OsStr::to_string_lossy) {
110 Some(fname) => fname.contains("zig"),
111 _ => false,
112 }
113 }
114 }
115
116 fn guess_family_from_stdout(
117 stdout: &str,
118 path: &Path,
119 args: &[String],
120 cargo_output: &CargoOutput,
121 ) -> Result<ToolFamily, Error> {
122 cargo_output.print_debug(&stdout);
123
124 let accepts_cl_style_flags = run(
127 Command::new(path).args(args).arg("-?").stdin(Stdio::null()),
128 path,
129 &{
130 let mut cargo_output = cargo_output.clone();
132 cargo_output.warnings = cargo_output.debug;
133 cargo_output.output = OutputKind::Discard;
134 cargo_output
135 },
136 )
137 .is_ok();
138
139 let clang = stdout.contains(r#""clang""#);
140 let gcc = stdout.contains(r#""gcc""#);
141 let emscripten = stdout.contains(r#""emscripten""#);
142 let vxworks = stdout.contains(r#""VxWorks""#);
143
144 match (clang, accepts_cl_style_flags, gcc, emscripten, vxworks) {
145 (clang_cl, true, _, false, false) => Ok(ToolFamily::Msvc { clang_cl }),
146 (true, _, _, _, false) | (_, _, _, true, false) => Ok(ToolFamily::Clang {
147 zig_cc: is_zig_cc(path, cargo_output),
148 }),
149 (false, false, true, _, false) | (_, _, _, _, true) => Ok(ToolFamily::Gnu),
150 (false, false, false, false, false) => {
151 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");
152 Err(Error::new(
153 ErrorKind::ToolFamilyMacroNotFound,
154 "Expects macro `__clang__`, `__GNUC__` or `__EMSCRIPTEN__`, `__VXWORKS__` or accepts cl style flag `-?`, but found none",
155 ))
156 }
157 }
158 }
159
160 fn detect_family_inner(
161 path: &Path,
162 args: &[String],
163 cargo_output: &CargoOutput,
164 out_dir: Option<&Path>,
165 ) -> Result<ToolFamily, Error> {
166 let out_dir = out_dir
167 .map(Cow::Borrowed)
168 .unwrap_or_else(|| Cow::Owned(env::temp_dir()));
169
170 std::fs::create_dir_all(&out_dir).map_err(|err| Error {
173 kind: ErrorKind::IOError,
174 message: format!("failed to create OUT_DIR '{}': {}", out_dir.display(), err)
175 .into(),
176 })?;
177
178 let mut tmp =
179 NamedTempfile::new(&out_dir, "detect_compiler_family.c").map_err(|err| Error {
180 kind: ErrorKind::IOError,
181 message: format!(
182 "failed to create detect_compiler_family.c temp file in '{}': {}",
183 out_dir.display(),
184 err
185 )
186 .into(),
187 })?;
188 let mut tmp_file = tmp.take_file().unwrap();
189 tmp_file.write_all(include_bytes!("detect_compiler_family.c"))?;
190 tmp_file.flush()?;
193 tmp_file.sync_data()?;
194 drop(tmp_file);
195
196 let mut compiler_detect_output = cargo_output.clone();
201 compiler_detect_output.warnings = compiler_detect_output.debug;
202
203 let stdout = run_output(
204 Command::new(path).arg("-E").arg(tmp.path()),
205 path,
206 &compiler_detect_output,
207 )?;
208 let stdout = String::from_utf8_lossy(&stdout);
209
210 if stdout.contains("-Wslash-u-filename") {
211 let stdout = run_output(
212 Command::new(path).arg("-E").arg("--").arg(tmp.path()),
213 path,
214 &compiler_detect_output,
215 )?;
216 let stdout = String::from_utf8_lossy(&stdout);
217 guess_family_from_stdout(&stdout, path, args, cargo_output)
218 } else {
219 guess_family_from_stdout(&stdout, path, args, cargo_output)
220 }
221 }
222 let detect_family = |path: &Path, args: &[String]| -> Result<ToolFamily, Error> {
223 let cache_key = [path.as_os_str()]
224 .iter()
225 .cloned()
226 .chain(args.iter().map(OsStr::new))
227 .map(Into::into)
228 .collect();
229 if let Some(family) = cached_compiler_family.read().unwrap().get(&cache_key) {
230 return Ok(*family);
231 }
232
233 let family = detect_family_inner(path, args, cargo_output, out_dir)?;
234 cached_compiler_family
235 .write()
236 .unwrap()
237 .insert(cache_key, family);
238 Ok(family)
239 };
240
241 let family = detect_family(&path, &args).unwrap_or_else(|e| {
242 cargo_output.print_warning(&format_args!(
243 "Compiler family detection failed due to error: {}",
244 e
245 ));
246 match path.file_name().map(OsStr::to_string_lossy) {
247 Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
248 Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
249 ToolFamily::Msvc { clang_cl: false }
250 }
251 Some(fname) if fname.contains("clang") => {
252 let is_clang_cl = args
253 .iter()
254 .any(|a| a.strip_prefix("--driver-mode=") == Some("cl"));
255 if is_clang_cl {
256 ToolFamily::Msvc { clang_cl: true }
257 } else {
258 ToolFamily::Clang {
259 zig_cc: is_zig_cc(&path, cargo_output),
260 }
261 }
262 }
263 Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
264 _ => ToolFamily::Gnu,
265 }
266 });
267
268 Tool {
269 path,
270 cc_wrapper_path: None,
271 cc_wrapper_args: Vec::new(),
272 args: Vec::new(),
273 env: Vec::new(),
274 family,
275 cuda,
276 removed_args: Vec::new(),
277 has_internal_target_arg: false,
278 }
279 }
280
281 pub(crate) fn remove_arg(&mut self, flag: OsString) {
283 self.removed_args.push(flag);
284 }
285
286 pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
295 if self.cuda {
296 self.args.push("-Xcompiler".into());
297 }
298 self.args.push(flag);
299 }
300
301 pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
305 let flag = flag.to_str().unwrap();
306 let mut chars = flag.chars();
307
308 if self.is_like_msvc() {
310 if chars.next() != Some('/') {
311 return false;
312 }
313 } else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
314 return false;
315 }
316
317 if chars.next() == Some('O') {
319 return self
320 .args()
321 .iter()
322 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
323 }
324
325 false
327 }
328
329 pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
331 if self.is_duplicate_opt_arg(&flag) {
332 eprintln!("Info: Ignoring duplicate arg {:?}", &flag);
333 } else {
334 self.push_cc_arg(flag);
335 }
336 }
337
338 pub fn to_command(&self) -> Command {
344 let mut cmd = match self.cc_wrapper_path {
345 Some(ref cc_wrapper_path) => {
346 let mut cmd = Command::new(cc_wrapper_path);
347 cmd.arg(&self.path);
348 cmd
349 }
350 None => Command::new(&self.path),
351 };
352 cmd.args(&self.cc_wrapper_args);
353
354 let value = self
355 .args
356 .iter()
357 .filter(|a| !self.removed_args.contains(a))
358 .collect::<Vec<_>>();
359 cmd.args(&value);
360
361 for (k, v) in self.env.iter() {
362 cmd.env(k, v);
363 }
364 cmd
365 }
366
367 pub fn path(&self) -> &Path {
372 &self.path
373 }
374
375 pub fn args(&self) -> &[OsString] {
378 &self.args
379 }
380
381 pub fn env(&self) -> &[(OsString, OsString)] {
386 &self.env
387 }
388
389 pub fn cc_env(&self) -> OsString {
394 match self.cc_wrapper_path {
395 Some(ref cc_wrapper_path) => {
396 let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
397 cc_env.push(" ");
398 cc_env.push(self.path.to_path_buf().into_os_string());
399 for arg in self.cc_wrapper_args.iter() {
400 cc_env.push(" ");
401 cc_env.push(arg);
402 }
403 cc_env
404 }
405 None => OsString::from(""),
406 }
407 }
408
409 pub fn cflags_env(&self) -> OsString {
413 let mut flags = OsString::new();
414 for (i, arg) in self.args.iter().enumerate() {
415 if i > 0 {
416 flags.push(" ");
417 }
418 flags.push(arg);
419 }
420 flags
421 }
422
423 pub fn is_like_gnu(&self) -> bool {
425 self.family == ToolFamily::Gnu
426 }
427
428 pub fn is_like_clang(&self) -> bool {
430 matches!(self.family, ToolFamily::Clang { .. })
431 }
432
433 #[cfg(target_vendor = "apple")]
435 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
436 let path = self.path.to_string_lossy();
437 path.contains(".xctoolchain/")
438 }
439 #[cfg(not(target_vendor = "apple"))]
440 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
441 false
442 }
443
444 pub fn is_like_msvc(&self) -> bool {
446 matches!(self.family, ToolFamily::Msvc { .. })
447 }
448
449 pub fn is_like_clang_cl(&self) -> bool {
451 matches!(self.family, ToolFamily::Msvc { clang_cl: true })
452 }
453
454 pub(crate) fn supports_path_delimiter(&self) -> bool {
456 matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda
458 }
459}
460
461#[derive(Copy, Clone, Debug, PartialEq)]
467pub enum ToolFamily {
468 Gnu,
470 Clang { zig_cc: bool },
473 Msvc { clang_cl: bool },
475}
476
477impl ToolFamily {
478 pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
480 match *self {
481 ToolFamily::Msvc { .. } => {
482 cmd.push_cc_arg("-Z7".into());
483 }
484 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
485 cmd.push_cc_arg(
486 dwarf_version
487 .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v))
488 .into(),
489 );
490 }
491 }
492 }
493
494 pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
496 match *self {
497 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
498 cmd.push_cc_arg("-fno-omit-frame-pointer".into());
499 }
500 _ => (),
501 }
502 }
503
504 pub(crate) fn warnings_flags(&self) -> &'static str {
506 match *self {
507 ToolFamily::Msvc { .. } => "-W4",
508 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
509 }
510 }
511
512 pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
514 match *self {
515 ToolFamily::Msvc { .. } => None,
516 ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
517 }
518 }
519
520 pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
522 match *self {
523 ToolFamily::Msvc { .. } => "-WX",
524 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror",
525 }
526 }
527
528 pub(crate) fn verbose_stderr(&self) -> bool {
529 matches!(*self, ToolFamily::Clang { .. })
530 }
531}