Revision control
Copy as Markdown
Other Tools
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
}
}