Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split Tool and ToolFamily to a new tool module #929

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 4 additions & 366 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ pub mod windows_registry;
mod command_helpers;
use command_helpers::*;

mod tool;
pub use tool::Tool;
use tool::ToolFamily;

/// A builder for compilation of a native library.
///
/// A `Build` is the main type of the `cc` crate and is used to control all the
Expand Down Expand Up @@ -187,98 +191,6 @@ impl Display for Error {

impl std::error::Error for Error {}

/// 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)]
pub struct Tool {
path: PathBuf,
cc_wrapper_path: Option<PathBuf>,
cc_wrapper_args: Vec<OsString>,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
family: ToolFamily,
cuda: bool,
removed_args: Vec<OsString>,
has_internal_target_arg: bool,
}

/// 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)]
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
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.
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
fn warnings_flags(&self) -> &'static str {
match *self {
ToolFamily::Msvc { .. } => "-W4",
ToolFamily::Gnu | ToolFamily::Clang => "-Wall",
}
}

/// What the flags to enable extra warnings
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
fn warnings_to_errors_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc { .. } => "-WX",
ToolFamily::Gnu | ToolFamily::Clang => "-Werror",
}
}

fn verbose_stderr(&self) -> bool {
*self == ToolFamily::Clang
}
}

/// Represents an object.
///
/// This is a source file -> object file pair.
Expand Down Expand Up @@ -3555,280 +3467,6 @@ impl Default for Build {
}
}

impl Tool {
fn new(path: PathBuf, cargo_output: &CargoOutput) -> Self {
Tool::with_features(path, None, false, cargo_output)
}

fn with_clang_driver(
path: PathBuf,
clang_driver: Option<&str>,
cargo_output: &CargoOutput,
) -> Self {
Self::with_features(path, clang_driver, false, cargo_output)
}

#[cfg(windows)]
/// Explicitly set the `ToolFamily`, skipping name-based detection.
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,
}
}

fn with_features(
path: PathBuf,
clang_driver: Option<&str>,
cuda: bool,
cargo_output: &CargoOutput,
) -> Self {
fn detect_family(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
}
}

// 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, cargo_output)
}
} else {
detect_family(&path, cargo_output)
};

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.
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.]
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.
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.
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")]
fn is_xctoolchain_clang(&self) -> bool {
let path = self.path.to_string_lossy();
path.contains(".xctoolchain/")
}
#[cfg(not(target_vendor = "apple"))]
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,
}
}
}

fn fail(s: &str) -> ! {
eprintln!("\n\nerror occurred: {}\n\n", s);
std::process::exit(1);
Expand Down
Loading