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 from_find_msvc_tools(tool: ::find_msvc_tools::Tool) -> Self {
44 let mut cc_tool = Self::with_family(
45 tool.path().into(),
46 ToolFamily::Msvc {
47 clang_cl: tool.is_clang_cl(),
48 },
49 );
50
51 cc_tool.env = tool
52 .env()
53 .into_iter()
54 .map(|(k, v)| (k.clone(), v.clone()))
55 .collect();
56
57 cc_tool
58 }
59
60 pub(crate) fn new(
61 path: PathBuf,
62 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
63 cargo_output: &CargoOutput,
64 out_dir: Option<&Path>,
65 ) -> Self {
66 Self::with_features(
67 path,
68 vec![],
69 false,
70 cached_compiler_family,
71 cargo_output,
72 out_dir,
73 )
74 }
75
76 pub(crate) fn with_args(
77 path: PathBuf,
78 args: Vec<String>,
79 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
80 cargo_output: &CargoOutput,
81 out_dir: Option<&Path>,
82 ) -> Self {
83 Self::with_features(
84 path,
85 args,
86 false,
87 cached_compiler_family,
88 cargo_output,
89 out_dir,
90 )
91 }
92
93 pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self {
95 Self {
96 path,
97 cc_wrapper_path: None,
98 cc_wrapper_args: Vec::new(),
99 args: Vec::new(),
100 env: Vec::new(),
101 family,
102 cuda: false,
103 removed_args: Vec::new(),
104 has_internal_target_arg: false,
105 }
106 }
107
108 pub(crate) fn with_features(
109 path: PathBuf,
110 args: Vec<String>,
111 cuda: bool,
112 cached_compiler_family: &RwLock<CompilerFamilyLookupCache>,
113 cargo_output: &CargoOutput,
114 out_dir: Option<&Path>,
115 ) -> Self {
116 fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool {
117 run_output(
118 Command::new(path).arg("--version"),
119 cargo_output,
121 )
122 .map(|o| String::from_utf8_lossy(&o).contains("ziglang"))
123 .unwrap_or_default()
124 || {
125 match path.file_name().map(OsStr::to_string_lossy) {
126 Some(fname) => fname.contains("zig"),
127 _ => false,
128 }
129 }
130 }
131
132 fn guess_family_from_stdout(
133 stdout: &str,
134 path: &Path,
135 args: &[String],
136 cargo_output: &CargoOutput,
137 ) -> Result<ToolFamily, Error> {
138 cargo_output.print_debug(&stdout);
139
140 let accepts_cl_style_flags = run(
143 Command::new(path).args(args).arg("-?").stdin(Stdio::null()),
144 &{
145 let mut cargo_output = cargo_output.clone();
147 cargo_output.warnings = cargo_output.debug;
148 cargo_output.output = OutputKind::Discard;
149 cargo_output
150 },
151 )
152 .is_ok();
153
154 let clang = stdout.contains(r#""clang""#);
155 let gcc = stdout.contains(r#""gcc""#);
156 let emscripten = stdout.contains(r#""emscripten""#);
157 let vxworks = stdout.contains(r#""VxWorks""#);
158
159 match (clang, accepts_cl_style_flags, gcc, emscripten, vxworks) {
160 (clang_cl, true, _, false, false) => Ok(ToolFamily::Msvc { clang_cl }),
161 (true, _, _, _, false) | (_, _, _, true, false) => Ok(ToolFamily::Clang {
162 zig_cc: is_zig_cc(path, cargo_output),
163 }),
164 (false, false, true, _, false) | (_, _, _, _, true) => Ok(ToolFamily::Gnu),
165 (false, false, false, false, false) => {
166 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");
167 Err(Error::new(
168 ErrorKind::ToolFamilyMacroNotFound,
169 "Expects macro `__clang__`, `__GNUC__` or `__EMSCRIPTEN__`, `__VXWORKS__` or accepts cl style flag `-?`, but found none",
170 ))
171 }
172 }
173 }
174
175 fn detect_family_inner(
176 path: &Path,
177 args: &[String],
178 cargo_output: &CargoOutput,
179 out_dir: Option<&Path>,
180 ) -> Result<ToolFamily, Error> {
181 let out_dir = out_dir
182 .map(Cow::Borrowed)
183 .unwrap_or_else(|| Cow::Owned(env::temp_dir()));
184
185 std::fs::create_dir_all(&out_dir).map_err(|err| Error {
188 kind: ErrorKind::IOError,
189 message: format!("failed to create OUT_DIR '{}': {}", out_dir.display(), err)
190 .into(),
191 })?;
192
193 let mut tmp =
194 NamedTempfile::new(&out_dir, "detect_compiler_family.c").map_err(|err| Error {
195 kind: ErrorKind::IOError,
196 message: format!(
197 "failed to create detect_compiler_family.c temp file in '{}': {}",
198 out_dir.display(),
199 err
200 )
201 .into(),
202 })?;
203 let mut tmp_file = tmp.take_file().unwrap();
204 tmp_file.write_all(include_bytes!("detect_compiler_family.c"))?;
205 tmp_file.flush()?;
208 tmp_file.sync_data()?;
209 drop(tmp_file);
210
211 let mut compiler_detect_output = cargo_output.clone();
216 compiler_detect_output.warnings = compiler_detect_output.debug;
217
218 let mut cmd = Command::new(path);
219 cmd.arg("-E").arg(tmp.path());
220
221 let mut captured_cargo_output = compiler_detect_output.clone();
225 captured_cargo_output.output = OutputKind::Capture;
226 captured_cargo_output.warnings = true;
227 let mut child = spawn(&mut cmd, &captured_cargo_output)?;
228
229 let mut out = vec![];
230 let mut err = vec![];
231 child.stdout.take().unwrap().read_to_end(&mut out)?;
232 child.stderr.take().unwrap().read_to_end(&mut err)?;
233
234 let status = child.wait()?;
235
236 let stdout = if [&out, &err]
237 .iter()
238 .any(|o| String::from_utf8_lossy(o).contains("-Wslash-u-filename"))
239 {
240 run_output(
241 Command::new(path).arg("-E").arg("--").arg(tmp.path()),
242 &compiler_detect_output,
243 )?
244 } else {
245 if !status.success() {
246 return Err(Error::new(
247 ErrorKind::ToolExecError,
248 format!(
249 "command did not execute successfully (status code {status}): {cmd:?}"
250 ),
251 ));
252 }
253
254 out
255 };
256
257 let stdout = String::from_utf8_lossy(&stdout);
258 guess_family_from_stdout(&stdout, path, args, cargo_output)
259 }
260 let detect_family = |path: &Path, args: &[String]| -> Result<ToolFamily, Error> {
261 let cache_key = [path.as_os_str()]
262 .iter()
263 .cloned()
264 .chain(args.iter().map(OsStr::new))
265 .map(Into::into)
266 .collect();
267 if let Some(family) = cached_compiler_family.read().unwrap().get(&cache_key) {
268 return Ok(*family);
269 }
270
271 let family = detect_family_inner(path, args, cargo_output, out_dir)?;
272 cached_compiler_family
273 .write()
274 .unwrap()
275 .insert(cache_key, family);
276 Ok(family)
277 };
278
279 let family = detect_family(&path, &args).unwrap_or_else(|e| {
280 cargo_output.print_warning(&format_args!(
281 "Compiler family detection failed due to error: {e}"
282 ));
283 match path.file_name().map(OsStr::to_string_lossy) {
284 Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
285 Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
286 ToolFamily::Msvc { clang_cl: false }
287 }
288 Some(fname) if fname.contains("clang") => {
289 let is_clang_cl = args
290 .iter()
291 .any(|a| a.strip_prefix("--driver-mode=") == Some("cl"));
292 if is_clang_cl {
293 ToolFamily::Msvc { clang_cl: true }
294 } else {
295 ToolFamily::Clang {
296 zig_cc: is_zig_cc(&path, cargo_output),
297 }
298 }
299 }
300 Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
301 _ => ToolFamily::Gnu,
302 }
303 });
304
305 Tool {
306 path,
307 cc_wrapper_path: None,
308 cc_wrapper_args: Vec::new(),
309 args: Vec::new(),
310 env: Vec::new(),
311 family,
312 cuda,
313 removed_args: Vec::new(),
314 has_internal_target_arg: false,
315 }
316 }
317
318 pub(crate) fn remove_arg(&mut self, flag: OsString) {
320 self.removed_args.push(flag);
321 }
322
323 pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
332 if self.cuda {
333 self.args.push("-Xcompiler".into());
334 }
335 self.args.push(flag);
336 }
337
338 pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
342 let flag = flag.to_str().unwrap();
343 let mut chars = flag.chars();
344
345 if self.is_like_msvc() {
347 if chars.next() != Some('/') {
348 return false;
349 }
350 } else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
351 return false;
352 }
353
354 if chars.next() == Some('O') {
356 return self
357 .args()
358 .iter()
359 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
360 }
361
362 false
364 }
365
366 pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
368 if self.is_duplicate_opt_arg(&flag) {
369 eprintln!("Info: Ignoring duplicate arg {:?}", &flag);
370 } else {
371 self.push_cc_arg(flag);
372 }
373 }
374
375 pub fn to_command(&self) -> Command {
381 let mut cmd = match self.cc_wrapper_path {
382 Some(ref cc_wrapper_path) => {
383 let mut cmd = Command::new(cc_wrapper_path);
384 cmd.arg(&self.path);
385 cmd
386 }
387 None => Command::new(&self.path),
388 };
389 cmd.args(&self.cc_wrapper_args);
390
391 cmd.args(self.args.iter().filter(|a| !self.removed_args.contains(a)));
392
393 for (k, v) in self.env.iter() {
394 cmd.env(k, v);
395 }
396
397 cmd
398 }
399
400 pub fn path(&self) -> &Path {
405 &self.path
406 }
407
408 pub fn args(&self) -> &[OsString] {
411 &self.args
412 }
413
414 pub fn env(&self) -> &[(OsString, OsString)] {
419 &self.env
420 }
421
422 pub fn cc_env(&self) -> OsString {
427 match self.cc_wrapper_path {
428 Some(ref cc_wrapper_path) => {
429 let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
430 cc_env.push(" ");
431 cc_env.push(self.path.to_path_buf().into_os_string());
432 for arg in self.cc_wrapper_args.iter() {
433 cc_env.push(" ");
434 cc_env.push(arg);
435 }
436 cc_env
437 }
438 None => OsString::from(""),
439 }
440 }
441
442 pub fn cflags_env(&self) -> OsString {
446 let mut flags = OsString::new();
447 for (i, arg) in self.args.iter().enumerate() {
448 if i > 0 {
449 flags.push(" ");
450 }
451 flags.push(arg);
452 }
453 flags
454 }
455
456 pub fn is_like_gnu(&self) -> bool {
458 self.family == ToolFamily::Gnu
459 }
460
461 pub fn is_like_clang(&self) -> bool {
463 matches!(self.family, ToolFamily::Clang { .. })
464 }
465
466 #[cfg(target_vendor = "apple")]
468 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
469 let path = self.path.to_string_lossy();
470 path.contains(".xctoolchain/")
471 }
472 #[cfg(not(target_vendor = "apple"))]
473 pub(crate) fn is_xctoolchain_clang(&self) -> bool {
474 false
475 }
476
477 pub fn is_like_msvc(&self) -> bool {
479 matches!(self.family, ToolFamily::Msvc { .. })
480 }
481
482 pub fn is_like_clang_cl(&self) -> bool {
484 matches!(self.family, ToolFamily::Msvc { clang_cl: true })
485 }
486
487 pub(crate) fn supports_path_delimiter(&self) -> bool {
489 matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda
491 }
492}
493
494#[derive(Copy, Clone, Debug, PartialEq)]
500pub enum ToolFamily {
501 Gnu,
503 Clang { zig_cc: bool },
506 Msvc { clang_cl: bool },
508}
509
510impl ToolFamily {
511 pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
513 match *self {
514 ToolFamily::Msvc { .. } => {
515 cmd.push_cc_arg("-Z7".into());
516 }
517 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
518 cmd.push_cc_arg(
519 dwarf_version
520 .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{v}"))
521 .into(),
522 );
523 }
524 }
525 }
526
527 pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
529 match *self {
530 ToolFamily::Gnu | ToolFamily::Clang { .. } => {
531 cmd.push_cc_arg("-fno-omit-frame-pointer".into());
532 }
533 _ => (),
534 }
535 }
536
537 pub(crate) fn warnings_flags(&self) -> &'static str {
539 match *self {
540 ToolFamily::Msvc { .. } => "-W4",
541 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
542 }
543 }
544
545 pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
547 match *self {
548 ToolFamily::Msvc { .. } => None,
549 ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
550 }
551 }
552
553 pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
555 match *self {
556 ToolFamily::Msvc { .. } => "-WX",
557 ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror",
558 }
559 }
560
561 pub(crate) fn verbose_stderr(&self) -> bool {
562 matches!(*self, ToolFamily::Clang { .. })
563 }
564}