| 1 | use 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 |  | 
|---|
| 12 | use crate::{ | 
|---|
| 13 | command_helpers::{run_output, CargoOutput}, | 
|---|
| 14 | run, | 
|---|
| 15 | tempfile::NamedTempfile, | 
|---|
| 16 | Error, ErrorKind, OutputKind, | 
|---|
| 17 | }; | 
|---|
| 18 |  | 
|---|
| 19 | pub(crate) type CompilerFamilyLookupCache = HashMap<Box<[Box<OsStr>]>, ToolFamily>; | 
|---|
| 20 |  | 
|---|
| 21 | /// Configuration used to represent an invocation of a C compiler. | 
|---|
| 22 | /// | 
|---|
| 23 | /// This can be used to figure out what compiler is in use, what the arguments | 
|---|
| 24 | /// to it are, and what the environment variables look like for the compiler. | 
|---|
| 25 | /// This can be used to further configure other build systems (e.g. forward | 
|---|
| 26 | /// along CC and/or CFLAGS) or the `to_command` method can be used to run the | 
|---|
| 27 | /// compiler itself. | 
|---|
| 28 | #[ derive(Clone, Debug)] | 
|---|
| 29 | #[ allow(missing_docs)] | 
|---|
| 30 | pub 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 |  | 
|---|
| 42 | impl 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 | /// Explicitly set the `ToolFamily`, skipping name-based detection. | 
|---|
| 77 | 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 | // tool detection issues should always be shown as warnings | 
|---|
| 103 | 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 | // https://gitlab.kitware.com/cmake/cmake/-/blob/69a2eeb9dff5b60f2f1e5b425002a0fd45b7cadb/Modules/CMakeDetermineCompilerId.cmake#L267-271 | 
|---|
| 124 | // stdin is set to null to ensure that the help output is never paginated. | 
|---|
| 125 | let accepts_cl_style_flags = run( | 
|---|
| 126 | Command::new(path).args(args).arg( "-?").stdin(Stdio::null()), | 
|---|
| 127 | &{ | 
|---|
| 128 | // the errors are not errors! | 
|---|
| 129 | 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 | // Ensure all the parent directories exist otherwise temp file creation | 
|---|
| 169 | // will fail | 
|---|
| 170 | 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 | // Close the file handle *now*, otherwise the compiler may fail to open it on Windows | 
|---|
| 189 | // (#1082). The file stays on disk and its path remains valid until `tmp` is dropped. | 
|---|
| 190 | tmp_file.flush()?; | 
|---|
| 191 | tmp_file.sync_data()?; | 
|---|
| 192 | drop(tmp_file); | 
|---|
| 193 |  | 
|---|
| 194 | // When expanding the file, the compiler prints a lot of information to stderr | 
|---|
| 195 | // that it is not an error, but related to expanding itself. | 
|---|
| 196 | // | 
|---|
| 197 | // cc would have to disable warning here to prevent generation of too many warnings. | 
|---|
| 198 | 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 | /// Add an argument to be stripped from the final command arguments. | 
|---|
| 278 | pub(crate) fn remove_arg(&mut self, flag: OsString) { | 
|---|
| 279 | self.removed_args.push(flag); | 
|---|
| 280 | } | 
|---|
| 281 |  | 
|---|
| 282 | /// Push an "exotic" flag to the end of the compiler's arguments list. | 
|---|
| 283 | /// | 
|---|
| 284 | /// Nvidia compiler accepts only the most common compiler flags like `-D`, | 
|---|
| 285 | /// `-I`, `-c`, etc. Options meant specifically for the underlying | 
|---|
| 286 | /// host C++ compiler have to be prefixed with `-Xcompiler`. | 
|---|
| 287 | /// [Another possible future application for this function is passing | 
|---|
| 288 | /// clang-specific flags to clang-cl, which otherwise accepts only | 
|---|
| 289 | /// MSVC-specific options.] | 
|---|
| 290 | 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 | /// Checks if an argument or flag has already been specified or conflicts. | 
|---|
| 298 | /// | 
|---|
| 299 | /// Currently only checks optimization flags. | 
|---|
| 300 | 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 | // Only duplicate check compiler flags | 
|---|
| 305 | 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 | // Check for existing optimization flags (-O, /O) | 
|---|
| 314 | 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 | // TODO Check for existing -m..., -m...=..., /arch:... flags | 
|---|
| 322 | false | 
|---|
| 323 | } | 
|---|
| 324 |  | 
|---|
| 325 | /// Don't push optimization arg if it conflicts with existing args. | 
|---|
| 326 | 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 | /// Converts this compiler into a `Command` that's ready to be run. | 
|---|
| 335 | /// | 
|---|
| 336 | /// This is useful for when the compiler needs to be executed and the | 
|---|
| 337 | /// command returned will already have the initial arguments and environment | 
|---|
| 338 | /// variables configured. | 
|---|
| 339 | 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 | /// Returns the path for this compiler. | 
|---|
| 364 | /// | 
|---|
| 365 | /// Note that this may not be a path to a file on the filesystem, e.g. "cc", | 
|---|
| 366 | /// but rather something which will be resolved when a process is spawned. | 
|---|
| 367 | pub fn path(&self) -> &Path { | 
|---|
| 368 | &self.path | 
|---|
| 369 | } | 
|---|
| 370 |  | 
|---|
| 371 | /// Returns the default set of arguments to the compiler needed to produce | 
|---|
| 372 | /// executables for the target this compiler generates. | 
|---|
| 373 | pub fn args(&self) -> &[OsString] { | 
|---|
| 374 | &self.args | 
|---|
| 375 | } | 
|---|
| 376 |  | 
|---|
| 377 | /// Returns the set of environment variables needed for this compiler to | 
|---|
| 378 | /// operate. | 
|---|
| 379 | /// | 
|---|
| 380 | /// This is typically only used for MSVC compilers currently. | 
|---|
| 381 | pub fn env(&self) -> &[(OsString, OsString)] { | 
|---|
| 382 | &self.env | 
|---|
| 383 | } | 
|---|
| 384 |  | 
|---|
| 385 | /// Returns the compiler command in format of CC environment variable. | 
|---|
| 386 | /// Or empty string if CC env was not present | 
|---|
| 387 | /// | 
|---|
| 388 | /// This is typically used by configure script | 
|---|
| 389 | 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 | /// Returns the compiler flags in format of CFLAGS environment variable. | 
|---|
| 406 | /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS | 
|---|
| 407 | /// This is typically used by configure script | 
|---|
| 408 | 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 | /// Whether the tool is GNU Compiler Collection-like. | 
|---|
| 420 | pub fn is_like_gnu(&self) -> bool { | 
|---|
| 421 | self.family == ToolFamily::Gnu | 
|---|
| 422 | } | 
|---|
| 423 |  | 
|---|
| 424 | /// Whether the tool is Clang-like. | 
|---|
| 425 | pub fn is_like_clang(&self) -> bool { | 
|---|
| 426 | matches!(self.family, ToolFamily::Clang { .. }) | 
|---|
| 427 | } | 
|---|
| 428 |  | 
|---|
| 429 | /// Whether the tool is AppleClang under .xctoolchain | 
|---|
| 430 | #[ 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 | /// Whether the tool is MSVC-like. | 
|---|
| 441 | pub fn is_like_msvc(&self) -> bool { | 
|---|
| 442 | matches!(self.family, ToolFamily::Msvc { .. }) | 
|---|
| 443 | } | 
|---|
| 444 |  | 
|---|
| 445 | /// Whether the tool is `clang-cl`-based MSVC-like. | 
|---|
| 446 | pub fn is_like_clang_cl(&self) -> bool { | 
|---|
| 447 | matches!(self.family, ToolFamily::Msvc { clang_cl: true }) | 
|---|
| 448 | } | 
|---|
| 449 |  | 
|---|
| 450 | /// Supports using `--` delimiter to separate arguments and path to source files. | 
|---|
| 451 | pub(crate) fn supports_path_delimiter(&self) -> bool { | 
|---|
| 452 | // homebrew clang and zig-cc does not support this while stock version does | 
|---|
| 453 | matches!(self.family, ToolFamily::Msvc { clang_cl: true }) && !self.cuda | 
|---|
| 454 | } | 
|---|
| 455 | } | 
|---|
| 456 |  | 
|---|
| 457 | /// Represents the family of tools this tool belongs to. | 
|---|
| 458 | /// | 
|---|
| 459 | /// Each family of tools differs in how and what arguments they accept. | 
|---|
| 460 | /// | 
|---|
| 461 | /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. | 
|---|
| 462 | #[ derive(Copy, Clone, Debug, PartialEq)] | 
|---|
| 463 | pub enum ToolFamily { | 
|---|
| 464 | /// Tool is GNU Compiler Collection-like. | 
|---|
| 465 | Gnu, | 
|---|
| 466 | /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags | 
|---|
| 467 | /// and its cross-compilation approach is different. | 
|---|
| 468 | Clang { zig_cc: bool }, | 
|---|
| 469 | /// Tool is the MSVC cl.exe. | 
|---|
| 470 | Msvc { clang_cl: bool }, | 
|---|
| 471 | } | 
|---|
| 472 |  | 
|---|
| 473 | impl ToolFamily { | 
|---|
| 474 | /// What the flag to request debug info for this family of tools look like | 
|---|
| 475 | 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 | /// What the flag to force frame pointers. | 
|---|
| 491 | 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 | /// What the flags to enable all warnings | 
|---|
| 501 | 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 | /// What the flags to enable extra warnings | 
|---|
| 509 | 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 | /// What the flag to turn warning into errors | 
|---|
| 517 | 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 | } | 
|---|
| 528 |  | 
|---|