summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cc/src/tool.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cc/src/tool.rs')
-rw-r--r--third_party/rust/cc/src/tool.rs399
1 files changed, 399 insertions, 0 deletions
diff --git a/third_party/rust/cc/src/tool.rs b/third_party/rust/cc/src/tool.rs
new file mode 100644
index 0000000000..a193a90ff7
--- /dev/null
+++ b/third_party/rust/cc/src/tool.rs
@@ -0,0 +1,399 @@
+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
+ }
+}