| use std::{ | 
 |     collections::HashMap, | 
 |     ffi::OsString, | 
 |     path::{Path, PathBuf}, | 
 |     process::Command, | 
 |     sync::Mutex, | 
 | }; | 
 |  | 
 | use crate::command_helpers::{run_output, CargoOutput}; | 
 |  | 
 | /// Configuration used to represent an invocation of a C compiler. | 
 | /// | 
 | /// This can be used to figure out what compiler is in use, what the arguments | 
 | /// to it are, and what the environment variables look like for the compiler. | 
 | /// This can be used to further configure other build systems (e.g. forward | 
 | /// along CC and/or CFLAGS) or the `to_command` method can be used to run the | 
 | /// compiler itself. | 
 | #[derive(Clone, Debug)] | 
 | #[allow(missing_docs)] | 
 | pub struct Tool { | 
 |     pub(crate) path: PathBuf, | 
 |     pub(crate) cc_wrapper_path: Option<PathBuf>, | 
 |     pub(crate) cc_wrapper_args: Vec<OsString>, | 
 |     pub(crate) args: Vec<OsString>, | 
 |     pub(crate) env: Vec<(OsString, OsString)>, | 
 |     pub(crate) family: ToolFamily, | 
 |     pub(crate) cuda: bool, | 
 |     pub(crate) removed_args: Vec<OsString>, | 
 |     pub(crate) has_internal_target_arg: bool, | 
 | } | 
 |  | 
 | impl Tool { | 
 |     pub(crate) fn new( | 
 |         path: PathBuf, | 
 |         cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, | 
 |         cargo_output: &CargoOutput, | 
 |     ) -> Self { | 
 |         Self::with_features(path, None, false, cached_compiler_family, cargo_output) | 
 |     } | 
 |  | 
 |     pub(crate) fn with_clang_driver( | 
 |         path: PathBuf, | 
 |         clang_driver: Option<&str>, | 
 |         cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, | 
 |         cargo_output: &CargoOutput, | 
 |     ) -> Self { | 
 |         Self::with_features( | 
 |             path, | 
 |             clang_driver, | 
 |             false, | 
 |             cached_compiler_family, | 
 |             cargo_output, | 
 |         ) | 
 |     } | 
 |  | 
 |     /// Explicitly set the `ToolFamily`, skipping name-based detection. | 
 |     pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self { | 
 |         Self { | 
 |             path, | 
 |             cc_wrapper_path: None, | 
 |             cc_wrapper_args: Vec::new(), | 
 |             args: Vec::new(), | 
 |             env: Vec::new(), | 
 |             family, | 
 |             cuda: false, | 
 |             removed_args: Vec::new(), | 
 |             has_internal_target_arg: false, | 
 |         } | 
 |     } | 
 |  | 
 |     pub(crate) fn with_features( | 
 |         path: PathBuf, | 
 |         clang_driver: Option<&str>, | 
 |         cuda: bool, | 
 |         cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, | 
 |         cargo_output: &CargoOutput, | 
 |     ) -> Self { | 
 |         fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily { | 
 |             let mut cmd = Command::new(path); | 
 |             cmd.arg("--version"); | 
 |  | 
 |             let stdout = match run_output( | 
 |                 &mut cmd, | 
 |                 &path.to_string_lossy(), | 
 |                 // tool detection issues should always be shown as warnings | 
 |                 cargo_output, | 
 |             ) | 
 |             .ok() | 
 |             .and_then(|o| String::from_utf8(o).ok()) | 
 |             { | 
 |                 Some(s) => s, | 
 |                 None => { | 
 |                     // --version failed. fallback to gnu | 
 |                     cargo_output.print_warning(&format_args!("Failed to run: {:?}", cmd)); | 
 |                     return ToolFamily::Gnu; | 
 |                 } | 
 |             }; | 
 |             if stdout.contains("clang") { | 
 |                 ToolFamily::Clang | 
 |             } else if stdout.contains("GCC") { | 
 |                 ToolFamily::Gnu | 
 |             } else { | 
 |                 // --version doesn't include clang for GCC | 
 |                 cargo_output.print_warning(&format_args!( | 
 |                     "Compiler version doesn't include clang or GCC: {:?}", | 
 |                     cmd | 
 |                 )); | 
 |                 ToolFamily::Gnu | 
 |             } | 
 |         } | 
 |         let detect_family = |path: &Path| -> ToolFamily { | 
 |             if let Some(family) = cached_compiler_family.lock().unwrap().get(path) { | 
 |                 return *family; | 
 |             } | 
 |  | 
 |             let family = detect_family_inner(path, cargo_output); | 
 |             cached_compiler_family | 
 |                 .lock() | 
 |                 .unwrap() | 
 |                 .insert(path.into(), family); | 
 |             family | 
 |         }; | 
 |  | 
 |         // Try to detect family of the tool from its name, falling back to Gnu. | 
 |         let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { | 
 |             if fname.contains("clang-cl") { | 
 |                 ToolFamily::Msvc { clang_cl: true } | 
 |             } else if fname.ends_with("cl") || fname == "cl.exe" { | 
 |                 ToolFamily::Msvc { clang_cl: false } | 
 |             } else if fname.contains("clang") { | 
 |                 match clang_driver { | 
 |                     Some("cl") => ToolFamily::Msvc { clang_cl: true }, | 
 |                     _ => ToolFamily::Clang, | 
 |                 } | 
 |             } else { | 
 |                 detect_family(&path) | 
 |             } | 
 |         } else { | 
 |             detect_family(&path) | 
 |         }; | 
 |  | 
 |         Tool { | 
 |             path, | 
 |             cc_wrapper_path: None, | 
 |             cc_wrapper_args: Vec::new(), | 
 |             args: Vec::new(), | 
 |             env: Vec::new(), | 
 |             family, | 
 |             cuda, | 
 |             removed_args: Vec::new(), | 
 |             has_internal_target_arg: false, | 
 |         } | 
 |     } | 
 |  | 
 |     /// Add an argument to be stripped from the final command arguments. | 
 |     pub(crate) fn remove_arg(&mut self, flag: OsString) { | 
 |         self.removed_args.push(flag); | 
 |     } | 
 |  | 
 |     /// Push an "exotic" flag to the end of the compiler's arguments list. | 
 |     /// | 
 |     /// Nvidia compiler accepts only the most common compiler flags like `-D`, | 
 |     /// `-I`, `-c`, etc. Options meant specifically for the underlying | 
 |     /// host C++ compiler have to be prefixed with `-Xcompiler`. | 
 |     /// [Another possible future application for this function is passing | 
 |     /// clang-specific flags to clang-cl, which otherwise accepts only | 
 |     /// MSVC-specific options.] | 
 |     pub(crate) fn push_cc_arg(&mut self, flag: OsString) { | 
 |         if self.cuda { | 
 |             self.args.push("-Xcompiler".into()); | 
 |         } | 
 |         self.args.push(flag); | 
 |     } | 
 |  | 
 |     /// Checks if an argument or flag has already been specified or conflicts. | 
 |     /// | 
 |     /// Currently only checks optimization flags. | 
 |     pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool { | 
 |         let flag = flag.to_str().unwrap(); | 
 |         let mut chars = flag.chars(); | 
 |  | 
 |         // Only duplicate check compiler flags | 
 |         if self.is_like_msvc() { | 
 |             if chars.next() != Some('/') { | 
 |                 return false; | 
 |             } | 
 |         } else if self.is_like_gnu() || self.is_like_clang() { | 
 |             if chars.next() != Some('-') { | 
 |                 return false; | 
 |             } | 
 |         } | 
 |  | 
 |         // Check for existing optimization flags (-O, /O) | 
 |         if chars.next() == Some('O') { | 
 |             return self | 
 |                 .args() | 
 |                 .iter() | 
 |                 .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O')); | 
 |         } | 
 |  | 
 |         // TODO Check for existing -m..., -m...=..., /arch:... flags | 
 |         false | 
 |     } | 
 |  | 
 |     /// Don't push optimization arg if it conflicts with existing args. | 
 |     pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) { | 
 |         if self.is_duplicate_opt_arg(&flag) { | 
 |             println!("Info: Ignoring duplicate arg {:?}", &flag); | 
 |         } else { | 
 |             self.push_cc_arg(flag); | 
 |         } | 
 |     } | 
 |  | 
 |     /// Converts this compiler into a `Command` that's ready to be run. | 
 |     /// | 
 |     /// This is useful for when the compiler needs to be executed and the | 
 |     /// command returned will already have the initial arguments and environment | 
 |     /// variables configured. | 
 |     pub fn to_command(&self) -> Command { | 
 |         let mut cmd = match self.cc_wrapper_path { | 
 |             Some(ref cc_wrapper_path) => { | 
 |                 let mut cmd = Command::new(cc_wrapper_path); | 
 |                 cmd.arg(&self.path); | 
 |                 cmd | 
 |             } | 
 |             None => Command::new(&self.path), | 
 |         }; | 
 |         cmd.args(&self.cc_wrapper_args); | 
 |  | 
 |         let value = self | 
 |             .args | 
 |             .iter() | 
 |             .filter(|a| !self.removed_args.contains(a)) | 
 |             .collect::<Vec<_>>(); | 
 |         cmd.args(&value); | 
 |  | 
 |         for (k, v) in self.env.iter() { | 
 |             cmd.env(k, v); | 
 |         } | 
 |         cmd | 
 |     } | 
 |  | 
 |     /// Returns the path for this compiler. | 
 |     /// | 
 |     /// Note that this may not be a path to a file on the filesystem, e.g. "cc", | 
 |     /// but rather something which will be resolved when a process is spawned. | 
 |     pub fn path(&self) -> &Path { | 
 |         &self.path | 
 |     } | 
 |  | 
 |     /// Returns the default set of arguments to the compiler needed to produce | 
 |     /// executables for the target this compiler generates. | 
 |     pub fn args(&self) -> &[OsString] { | 
 |         &self.args | 
 |     } | 
 |  | 
 |     /// Returns the set of environment variables needed for this compiler to | 
 |     /// operate. | 
 |     /// | 
 |     /// This is typically only used for MSVC compilers currently. | 
 |     pub fn env(&self) -> &[(OsString, OsString)] { | 
 |         &self.env | 
 |     } | 
 |  | 
 |     /// Returns the compiler command in format of CC environment variable. | 
 |     /// Or empty string if CC env was not present | 
 |     /// | 
 |     /// This is typically used by configure script | 
 |     pub fn cc_env(&self) -> OsString { | 
 |         match self.cc_wrapper_path { | 
 |             Some(ref cc_wrapper_path) => { | 
 |                 let mut cc_env = cc_wrapper_path.as_os_str().to_owned(); | 
 |                 cc_env.push(" "); | 
 |                 cc_env.push(self.path.to_path_buf().into_os_string()); | 
 |                 for arg in self.cc_wrapper_args.iter() { | 
 |                     cc_env.push(" "); | 
 |                     cc_env.push(arg); | 
 |                 } | 
 |                 cc_env | 
 |             } | 
 |             None => OsString::from(""), | 
 |         } | 
 |     } | 
 |  | 
 |     /// Returns the compiler flags in format of CFLAGS environment variable. | 
 |     /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS | 
 |     /// This is typically used by configure script | 
 |     pub fn cflags_env(&self) -> OsString { | 
 |         let mut flags = OsString::new(); | 
 |         for (i, arg) in self.args.iter().enumerate() { | 
 |             if i > 0 { | 
 |                 flags.push(" "); | 
 |             } | 
 |             flags.push(arg); | 
 |         } | 
 |         flags | 
 |     } | 
 |  | 
 |     /// Whether the tool is GNU Compiler Collection-like. | 
 |     pub fn is_like_gnu(&self) -> bool { | 
 |         self.family == ToolFamily::Gnu | 
 |     } | 
 |  | 
 |     /// Whether the tool is Clang-like. | 
 |     pub fn is_like_clang(&self) -> bool { | 
 |         self.family == ToolFamily::Clang | 
 |     } | 
 |  | 
 |     /// Whether the tool is AppleClang under .xctoolchain | 
 |     #[cfg(target_vendor = "apple")] | 
 |     pub(crate) fn is_xctoolchain_clang(&self) -> bool { | 
 |         let path = self.path.to_string_lossy(); | 
 |         path.contains(".xctoolchain/") | 
 |     } | 
 |     #[cfg(not(target_vendor = "apple"))] | 
 |     pub(crate) fn is_xctoolchain_clang(&self) -> bool { | 
 |         false | 
 |     } | 
 |  | 
 |     /// Whether the tool is MSVC-like. | 
 |     pub fn is_like_msvc(&self) -> bool { | 
 |         match self.family { | 
 |             ToolFamily::Msvc { .. } => true, | 
 |             _ => false, | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /// Represents the family of tools this tool belongs to. | 
 | /// | 
 | /// Each family of tools differs in how and what arguments they accept. | 
 | /// | 
 | /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. | 
 | #[derive(Copy, Clone, Debug, PartialEq)] | 
 | pub enum ToolFamily { | 
 |     /// Tool is GNU Compiler Collection-like. | 
 |     Gnu, | 
 |     /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags | 
 |     /// and its cross-compilation approach is different. | 
 |     Clang, | 
 |     /// Tool is the MSVC cl.exe. | 
 |     Msvc { clang_cl: bool }, | 
 | } | 
 |  | 
 | impl ToolFamily { | 
 |     /// What the flag to request debug info for this family of tools look like | 
 |     pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) { | 
 |         match *self { | 
 |             ToolFamily::Msvc { .. } => { | 
 |                 cmd.push_cc_arg("-Z7".into()); | 
 |             } | 
 |             ToolFamily::Gnu | ToolFamily::Clang => { | 
 |                 cmd.push_cc_arg( | 
 |                     dwarf_version | 
 |                         .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v)) | 
 |                         .into(), | 
 |                 ); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     /// What the flag to force frame pointers. | 
 |     pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) { | 
 |         match *self { | 
 |             ToolFamily::Gnu | ToolFamily::Clang => { | 
 |                 cmd.push_cc_arg("-fno-omit-frame-pointer".into()); | 
 |             } | 
 |             _ => (), | 
 |         } | 
 |     } | 
 |  | 
 |     /// What the flags to enable all warnings | 
 |     pub(crate) fn warnings_flags(&self) -> &'static str { | 
 |         match *self { | 
 |             ToolFamily::Msvc { .. } => "-W4", | 
 |             ToolFamily::Gnu | ToolFamily::Clang => "-Wall", | 
 |         } | 
 |     } | 
 |  | 
 |     /// What the flags to enable extra warnings | 
 |     pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> { | 
 |         match *self { | 
 |             ToolFamily::Msvc { .. } => None, | 
 |             ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"), | 
 |         } | 
 |     } | 
 |  | 
 |     /// What the flag to turn warning into errors | 
 |     pub(crate) fn warnings_to_errors_flag(&self) -> &'static str { | 
 |         match *self { | 
 |             ToolFamily::Msvc { .. } => "-WX", | 
 |             ToolFamily::Gnu | ToolFamily::Clang => "-Werror", | 
 |         } | 
 |     } | 
 |  | 
 |     pub(crate) fn verbose_stderr(&self) -> bool { | 
 |         *self == ToolFamily::Clang | 
 |     } | 
 | } |