use std::{
borrow::Cow,
collections::HashMap,
env,
ffi::{OsStr, OsString},
io::Write,
path::{Path, PathBuf},
process::{Command, Stdio},
sync::Mutex,
};
use crate::{
command_helpers::{run_output, CargoOutput},
run,
tempfile::NamedTempfile,
Error, ErrorKind,
};
#[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,
out_dir: Option<&Path>,
) -> Self {
Self::with_features(
path,
None,
false,
cached_compiler_family,
cargo_output,
out_dir,
)
}
pub(crate) fn with_clang_driver(
path: PathBuf,
clang_driver: Option<&str>,
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
cargo_output: &CargoOutput,
out_dir: Option<&Path>,
) -> Self {
Self::with_features(
path,
clang_driver,
false,
cached_compiler_family,
cargo_output,
out_dir,
)
}
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,
out_dir: Option<&Path>,
) -> Self {
fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool {
run_output(
Command::new(&path).arg("--version"),
path,
cargo_output,
)
.map(|o| String::from_utf8_lossy(&o).contains("ziglang"))
.unwrap_or_default()
}
fn detect_family_inner(
path: &Path,
cargo_output: &CargoOutput,
out_dir: Option<&Path>,
) -> Result<ToolFamily, Error> {
let tmp = NamedTempfile::new(
&out_dir
.map(Cow::Borrowed)
.unwrap_or_else(|| Cow::Owned(env::temp_dir())),
"detect_compiler_family.c",
)?;
tmp.file()
.write_all(include_bytes!("detect_compiler_family.c"))?;
let stdout = run_output(
Command::new(path).arg("-E").arg(tmp.path()),
path,
&{
let mut cargo_output = cargo_output.clone();
cargo_output.warnings = cargo_output.debug;
cargo_output
},
)?;
let stdout = String::from_utf8_lossy(&stdout);
cargo_output.print_debug(&stdout);
let accepts_cl_style_flags =
run(Command::new(path).arg("-?").stdout(Stdio::null()), path, &{
let mut cargo_output = cargo_output.clone();
cargo_output.warnings = cargo_output.debug;
cargo_output
})
.is_ok();
let clang = stdout.contains(r#""clang""#);
let gcc = stdout.contains(r#""gcc""#);
match (clang, accepts_cl_style_flags, gcc) {
(clang_cl, true, _) => Ok(ToolFamily::Msvc { clang_cl }),
(true, false, _) => Ok(ToolFamily::Clang {
zig_cc: is_zig_cc(path, cargo_output),
}),
(false, false, true) => Ok(ToolFamily::Gnu),
(false, false, false) => {
cargo_output.print_warning(&"Compiler family detection failed since it does not define `__clang__`, `__GNUC__` or `_MSC_VER`, fallback to treating it as GNU");
Err(Error::new(
ErrorKind::ToolFamilyMacroNotFound,
"Expects macro `__clang__`, `__GNUC__` or `_MSC_VER`, but found none",
))
}
}
}
let detect_family = |path: &Path| -> Result<ToolFamily, Error> {
if let Some(family) = cached_compiler_family.lock().unwrap().get(path) {
return Ok(*family);
}
let family = detect_family_inner(path, cargo_output, out_dir)?;
cached_compiler_family
.lock()
.unwrap()
.insert(path.into(), family);
Ok(family)
};
let family = detect_family(&path).unwrap_or_else(|e| {
cargo_output.print_warning(&format_args!(
"Compiler family detection failed due to error: {}",
e
));
match path.file_name().map(OsStr::to_string_lossy) {
Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true },
Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => {
ToolFamily::Msvc { clang_cl: false }
}
Some(fname) if fname.contains("clang") => match clang_driver {
Some("cl") => ToolFamily::Msvc { clang_cl: true },
_ => ToolFamily::Clang {
zig_cc: is_zig_cc(&path, cargo_output),
},
},
Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true },
_ => ToolFamily::Gnu,
}
});
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,
}
}
pub(crate) fn remove_arg(&mut self, flag: OsString) {
self.removed_args.push(flag);
}
pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
if self.cuda {
self.args.push("-Xcompiler".into());
}
self.args.push(flag);
}
pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
let flag = flag.to_str().unwrap();
let mut chars = flag.chars();
if self.is_like_msvc() {
if chars.next() != Some('/') {
return false;
}
} else if (self.is_like_gnu() || self.is_like_clang()) && chars.next() != Some('-') {
return false;
}
if chars.next() == Some('O') {
return self
.args()
.iter()
.any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
}
false
}
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);
}
}
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
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn args(&self) -> &[OsString] {
&self.args
}
pub fn env(&self) -> &[(OsString, OsString)] {
&self.env
}
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(""),
}
}
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
}
pub fn is_like_gnu(&self) -> bool {
self.family == ToolFamily::Gnu
}
pub fn is_like_clang(&self) -> bool {
matches!(self.family, ToolFamily::Clang { .. })
}
#[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
}
pub fn is_like_msvc(&self) -> bool {
match self.family {
ToolFamily::Msvc { .. } => true,
_ => false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ToolFamily {
Gnu,
Clang { zig_cc: bool },
Msvc { clang_cl: bool },
}
impl ToolFamily {
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(),
);
}
}
}
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());
}
_ => (),
}
}
pub(crate) fn warnings_flags(&self) -> &'static str {
match *self {
ToolFamily::Msvc { .. } => "-W4",
ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall",
}
}
pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
match *self {
ToolFamily::Msvc { .. } => None,
ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"),
}
}
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 {
matches!(*self, ToolFamily::Clang { .. })
}
}