#![doc(html_root_url = "https://docs.rs/cc/1.0")]
use std::borrow::Cow;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fmt::{self, Display};
use std::fs;
use std::io::{self, Write};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use shlex::Shlex;
#[cfg(feature = "parallel")]
mod parallel;
mod target;
use self::target::*;
pub mod windows_registry {
pub fn find(arch_or_target: &str, tool: &str) -> Option<std::process::Command> {
::find_msvc_tools::find(arch_or_target, tool)
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum VsVers {
#[deprecated = "Visual Studio 12 is no longer supported. cc will never return this value."]
Vs12,
Vs14,
Vs15,
Vs16,
Vs17,
Vs18,
}
pub fn find_vs_version() -> Result<VsVers, String> {
::find_msvc_tools::find_vs_version().map(|vers| match vers {
#[allow(deprecated)]
::find_msvc_tools::VsVers::Vs12 => VsVers::Vs12,
::find_msvc_tools::VsVers::Vs14 => VsVers::Vs14,
::find_msvc_tools::VsVers::Vs15 => VsVers::Vs15,
::find_msvc_tools::VsVers::Vs16 => VsVers::Vs16,
::find_msvc_tools::VsVers::Vs17 => VsVers::Vs17,
::find_msvc_tools::VsVers::Vs18 => VsVers::Vs18,
_ => unreachable!("unknown VS version"),
})
}
pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<crate::Tool> {
::find_msvc_tools::find_tool(arch_or_target, tool)
.map(|tool| crate::Tool::from_find_msvc_tools(tool, crate::EnvSnapshot::capture()))
}
}
mod command_helpers;
use command_helpers::*;
mod logger;
use logger::Logger;
pub use logger::{BuildMessage, BuildMessageKind, BuildMessageLogger, CompileCommand};
#[cfg(feature = "compile-commands")]
pub mod compile_commands;
mod build_env;
use build_env::{BuildEnv, EnvSnapshot, EnvVars};
mod tool;
pub use tool::Tool;
use tool::{CompilerFamilyLookupCache, CppStdlibLookupCache, ToolFamily};
mod tempfile;
mod utilities;
use utilities::*;
mod flags;
#[derive(Clone, Debug, PartialEq)]
struct CompilerFlag {
compiler: Box<Path>,
target: Option<Arc<str>>,
host: Option<Arc<str>>,
cpp: bool,
cuda: bool,
inherited: EnvSnapshot,
explicit: Box<EnvVars>,
}
enum PrefixMapFlag {
Macro,
Debug,
}
#[derive(Debug, Default)]
struct BuildCache {
apple_sdk_root_cache: RwLock<VecMap<&'static str, Arc<OsStr>>>,
apple_versions_cache: RwLock<VecMap<&'static str, Arc<str>>>,
cached_compiler_family: RwLock<CompilerFamilyLookupCache>,
cached_uses_libcxx: RwLock<CppStdlibLookupCache>,
emitted_cpp_link_stdlibs: Mutex<Vec<Box<str>>>,
emitted_rerun_if_env_changed: Mutex<Vec<Box<str>>>,
known_flag_support_status_cache: RwLock<VecMap<CompilerFlag, VecMap<Box<OsStr>, bool>>>,
warned_about_msvc_linker_flags: AtomicBool,
}
fn push_if_new(emitted: &Mutex<Vec<Box<str>>>, value: &str) -> bool {
let mut emitted = emitted.lock().unwrap();
let new = !emitted.iter().any(|emitted| **emitted == *value);
if new {
emitted.push(value.into());
}
new
}
#[derive(Clone, Debug)]
pub struct Build {
include_directories: Vec<Arc<Path>>,
definitions: Vec<(Arc<str>, Option<Arc<str>>)>,
objects: Vec<Arc<Path>>,
flags: Vec<BuildFlag>,
ar_flags: Vec<Arc<OsStr>>,
asm_flags: Vec<Arc<OsStr>>,
no_default_flags: bool,
files: Vec<Arc<Path>>,
source_root: Option<Arc<Path>>,
cpp: bool,
cpp_link_stdlib: Option<Option<Arc<str>>>,
cpp_link_stdlib_static: bool,
cpp_set_stdlib: Option<Arc<str>>,
cuda: bool,
cudart: Option<Arc<str>>,
ccbin: bool,
std: Option<Arc<str>>,
target: Option<Arc<str>>,
host: Option<Arc<str>>,
out_dir: Option<Arc<Path>>,
opt_level: Option<Arc<str>>,
debug: Option<Arc<str>>,
force_frame_pointer: Option<bool>,
env: BuildEnv,
compiler: Option<Arc<Path>>,
archiver: Option<Arc<Path>>,
ranlib: Option<Arc<Path>>,
cargo_output: CargoOutput,
link_lib_modifiers: Vec<Arc<OsStr>>,
pic: Option<bool>,
use_plt: Option<bool>,
static_crt: Option<bool>,
shared_flag: Option<bool>,
static_flag: Option<bool>,
warnings_into_errors: bool,
warnings: Option<bool>,
extra_warnings: Option<bool>,
emit_rerun_if_env_changed: bool,
shell_escaped_flags: Option<bool>,
build_cache: Arc<BuildCache>,
inherit_rustflags: bool,
inherit_trim_paths: bool,
prefer_clang_cl_over_msvc: bool,
}
#[derive(Clone, Debug)]
enum ErrorKind {
IOError,
EnvVarNotFound,
ToolExecError,
ToolNotFound,
InvalidArgument,
ToolFamilyMacroNotFound,
InvalidTarget,
UnknownTarget,
InvalidFlag,
#[cfg(feature = "parallel")]
JobserverHelpThreadError,
Disabled,
}
#[derive(Clone, Debug)]
pub struct Error {
kind: ErrorKind,
message: Cow<'static, str>,
}
impl Error {
fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Error {
Error {
kind,
message: message.into(),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::new(ErrorKind::IOError, format!("{e}"))
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}: {}", self.kind, self.message)
}
}
impl std::error::Error for Error {}
#[derive(Clone, Debug)]
enum BuildFlag {
Always(Arc<OsStr>),
IfSupported(Arc<OsStr>),
}
impl AsRef<OsStr> for BuildFlag {
fn as_ref(&self) -> &OsStr {
match self {
BuildFlag::Always(flag) | BuildFlag::IfSupported(flag) => flag,
}
}
}
#[derive(Clone, Debug)]
struct Object {
src: PathBuf,
dst: PathBuf,
}
impl Object {
fn new(src: PathBuf, dst: PathBuf) -> Object {
Object { src, dst }
}
}
impl Build {
pub fn new() -> Build {
Build {
include_directories: Vec::new(),
definitions: Vec::new(),
objects: Vec::new(),
flags: Vec::new(),
ar_flags: Vec::new(),
asm_flags: Vec::new(),
no_default_flags: false,
files: Vec::new(),
source_root: None,
shared_flag: None,
static_flag: None,
cpp: false,
cpp_link_stdlib: None,
cpp_link_stdlib_static: false,
cpp_set_stdlib: None,
cuda: false,
cudart: None,
ccbin: true,
std: None,
target: None,
host: None,
out_dir: None,
opt_level: None,
debug: None,
force_frame_pointer: None,
env: BuildEnv::default(),
compiler: None,
archiver: None,
ranlib: None,
cargo_output: CargoOutput::new(),
link_lib_modifiers: Vec::new(),
pic: None,
use_plt: None,
static_crt: None,
warnings: None,
extra_warnings: None,
warnings_into_errors: false,
emit_rerun_if_env_changed: true,
shell_escaped_flags: None,
build_cache: Arc::default(),
inherit_rustflags: true,
inherit_trim_paths: true,
prefer_clang_cl_over_msvc: false,
}
}
pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Build {
self.include_directories.push(dir.as_ref().into());
self
}
pub fn includes<P>(&mut self, dirs: P) -> &mut Build
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
for dir in dirs {
self.include(dir);
}
self
}
pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build {
self.definitions
.push((var.into(), val.into().map(Into::into)));
self
}
pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Build {
self.objects.push(obj.as_ref().into());
self
}
pub fn objects<P>(&mut self, objs: P) -> &mut Build
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
for obj in objs {
self.object(obj);
}
self
}
pub fn flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build {
self.flags.push(BuildFlag::Always(flag.as_ref().into()));
self
}
pub fn flags<Iter>(&mut self, flags: Iter) -> &mut Build
where
Iter: IntoIterator,
Iter::Item: AsRef<OsStr>,
{
for flag in flags {
self.flag(flag);
}
self
}
pub fn remove_flag(&mut self, flag: &str) -> &mut Build {
self.flags.retain(
|other_flag| !matches!(other_flag, BuildFlag::Always(other_flag) if &**other_flag == flag),
);
self
}
pub fn ar_flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build {
self.ar_flags.push(flag.as_ref().into());
self
}
pub fn asm_flag(&mut self, flag: impl AsRef<OsStr>) -> &mut Build {
self.asm_flags.push(flag.as_ref().into());
self
}
pub fn flag_if_supported(&mut self, flag: impl AsRef<OsStr>) -> &mut Build {
self.flags
.push(BuildFlag::IfSupported(flag.as_ref().into()));
self
}
pub fn try_flags_from_environment(&mut self, environ_key: &str) -> Result<&mut Build, Error> {
let flags = self.envflags(environ_key)?.ok_or_else(|| {
Error::new(
ErrorKind::EnvVarNotFound,
format!("could not find environment variable {environ_key}"),
)
})?;
self.flags.extend(
flags
.into_iter()
.map(|flag| BuildFlag::Always(Arc::from(OsString::from(flag).as_os_str()))),
);
Ok(self)
}
#[deprecated = "cc only creates static libraries, setting this does nothing"]
pub fn shared_flag(&mut self, shared_flag: bool) -> &mut Build {
self.shared_flag = Some(shared_flag);
self
}
#[deprecated = "cc only creates static libraries, setting this does nothing"]
pub fn static_flag(&mut self, static_flag: bool) -> &mut Build {
self.static_flag = Some(static_flag);
self
}
pub fn no_default_flags(&mut self, no_default_flags: bool) -> &mut Build {
self.no_default_flags = no_default_flags;
self
}
pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build {
self.files.push(p.as_ref().into());
self
}
pub fn files<P>(&mut self, p: P) -> &mut Build
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
for file in p.into_iter() {
self.file(file);
}
self
}
pub fn source_root<P: AsRef<Path>>(&mut self, root: P) -> &mut Build {
self.source_root = Some(root.as_ref().into());
self
}
pub fn get_files(&self) -> impl Iterator<Item = &Path> {
self.files.iter().map(AsRef::as_ref)
}
pub fn cpp(&mut self, cpp: bool) -> &mut Build {
self.cpp = cpp;
self
}
pub fn cuda(&mut self, cuda: bool) -> &mut Build {
self.cuda = cuda;
if cuda {
self.cpp = true;
self.cudart = Some("static".into());
}
self
}
pub fn cudart(&mut self, cudart: &str) -> &mut Build {
if self.cuda {
self.cudart = Some(cudart.into());
}
self
}
pub fn ccbin(&mut self, ccbin: bool) -> &mut Build {
self.ccbin = ccbin;
self
}
pub fn std(&mut self, std: &str) -> &mut Build {
self.std = Some(std.into());
self
}
pub fn warnings_into_errors(&mut self, warnings_into_errors: bool) -> &mut Build {
self.warnings_into_errors = warnings_into_errors;
self
}
pub fn warnings(&mut self, warnings: bool) -> &mut Build {
self.warnings = Some(warnings);
self.extra_warnings = Some(warnings);
self
}
pub fn extra_warnings(&mut self, warnings: bool) -> &mut Build {
self.extra_warnings = Some(warnings);
self
}
pub fn cpp_link_stdlib<'a, V: Into<Option<&'a str>>>(
&mut self,
cpp_link_stdlib: V,
) -> &mut Build {
self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(Arc::from));
self
}
pub fn cpp_link_stdlib_static(&mut self, is_static: bool) -> &mut Build {
self.cpp_link_stdlib_static = is_static;
self
}
pub fn cpp_set_stdlib<'a, V: Into<Option<&'a str>>>(
&mut self,
cpp_set_stdlib: V,
) -> &mut Build {
let cpp_set_stdlib = cpp_set_stdlib.into().map(Arc::from);
self.cpp_set_stdlib.clone_from(&cpp_set_stdlib);
self.cpp_link_stdlib = Some(cpp_set_stdlib);
self
}
pub fn target(&mut self, target: &str) -> &mut Build {
self.target = Some(target.into());
self
}
pub fn host(&mut self, host: &str) -> &mut Build {
self.host = Some(host.into());
self
}
pub fn opt_level(&mut self, opt_level: u32) -> &mut Build {
self.opt_level = Some(opt_level.to_string().into());
self
}
pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Build {
self.opt_level = Some(opt_level.into());
self
}
pub fn debug(&mut self, debug: bool) -> &mut Build {
self.debug = Some(debug.to_string().into());
self
}
pub fn debug_str(&mut self, debug: &str) -> &mut Build {
self.debug = Some(debug.into());
self
}
pub fn force_frame_pointer(&mut self, force: bool) -> &mut Build {
self.force_frame_pointer = Some(force);
self
}
pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Build {
self.out_dir = Some(out_dir.as_ref().into());
self
}
pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Build {
self.compiler = Some(compiler.as_ref().into());
self
}
pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Build {
self.archiver = Some(archiver.as_ref().into());
self
}
pub fn ranlib<P: AsRef<Path>>(&mut self, ranlib: P) -> &mut Build {
self.ranlib = Some(ranlib.as_ref().into());
self
}
pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Build {
self.cargo_output.metadata = cargo_metadata;
self
}
pub fn cargo_warnings(&mut self, cargo_warnings: bool) -> &mut Build {
self.cargo_output.warnings = cargo_warnings;
self
}
#[cfg_attr(
not(feature = "compile-commands"),
allow(rustdoc::broken_intra_doc_links)
)]
pub fn message_logger(&mut self, logger: Option<Arc<dyn BuildMessageLogger>>) -> &mut Build {
self.cargo_output.logger = logger.map(Logger);
self
}
pub fn cargo_debug(&mut self, cargo_debug: bool) -> &mut Build {
self.cargo_output.debug = cargo_debug;
self
}
pub fn cargo_output(&mut self, cargo_output: bool) -> &mut Build {
self.cargo_output.output = if cargo_output {
OutputKind::Forward
} else {
OutputKind::Discard
};
self
}
pub fn link_lib_modifier(&mut self, link_lib_modifier: impl AsRef<OsStr>) -> &mut Build {
self.link_lib_modifiers
.push(link_lib_modifier.as_ref().into());
self
}
pub fn pic(&mut self, pic: bool) -> &mut Build {
self.pic = Some(pic);
self
}
pub fn use_plt(&mut self, use_plt: bool) -> &mut Build {
self.use_plt = Some(use_plt);
self
}
pub fn emit_rerun_if_env_changed(&mut self, emit_rerun_if_env_changed: bool) -> &mut Build {
self.emit_rerun_if_env_changed = emit_rerun_if_env_changed;
self
}
pub fn static_crt(&mut self, static_crt: bool) -> &mut Build {
self.static_crt = Some(static_crt);
self
}
pub fn shell_escaped_flags(&mut self, shell_escaped_flags: bool) -> &mut Build {
self.shell_escaped_flags = Some(shell_escaped_flags);
self
}
pub fn inherit_rustflags(&mut self, inherit_rustflags: bool) -> &mut Build {
self.inherit_rustflags = inherit_rustflags;
self
}
pub fn inherit_trim_paths(&mut self, inherit_trim_paths: bool) -> &mut Build {
self.inherit_trim_paths = inherit_trim_paths;
self
}
pub fn prefer_clang_cl_over_msvc(&mut self, prefer_clang_cl_over_msvc: bool) -> &mut Build {
self.prefer_clang_cl_over_msvc = prefer_clang_cl_over_msvc;
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Build
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.env
.explicit
.push((key.as_ref().into(), val.as_ref().into()));
self
}
#[doc(hidden)]
pub fn set_envs_snapshot<I, K, V>(&mut self, vars: I) -> &mut Build
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.env.set_inherited(EnvSnapshot::from_vars(
&mut vars
.into_iter()
.map(|(key, value)| (key.as_ref().into(), value.as_ref().into())),
));
self
}
#[doc(hidden)]
#[deprecated = "use `env` instead"]
pub fn __set_env<K, V>(&mut self, key: K, val: V) -> &mut Build
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.env(key, val)
}
}
struct FlagSupportProbeFiles<'a> {
dir: Cow<'a, Path>,
src: PathBuf,
obj: PathBuf,
_temp_files: Option<(
crate::tempfile::NamedTempfile,
crate::tempfile::NamedTempfile,
)>,
}
type ArmArch = (&'static str, &'static [&'static str], Option<&'static str>);
impl Build {
pub fn is_flag_supported(&self, flag: impl AsRef<OsStr>) -> Result<bool, Error> {
self.is_flag_supported_inner(
flag.as_ref(),
&self.get_base_compiler()?,
&self.get_target()?,
&mut None,
)
}
fn flag_check_src_name(&self) -> &'static str {
if self.cuda {
assert!(self.cpp);
"flag_check.cu"
} else if self.cpp {
"flag_check.cpp"
} else {
"flag_check.c"
}
}
fn write_flag_check_src(file: &mut fs::File) -> io::Result<()> {
write!(file, "int main(void) {{ return 0; }}")?;
file.flush()?;
file.sync_data()
}
fn ensure_check_file(&self) -> Result<PathBuf, Error> {
let src = self.get_out_dir()?.join(self.flag_check_src_name());
if !src.exists() {
let mut f = fs::File::create(&src)?;
Self::write_flag_check_src(&mut f)?;
}
Ok(src)
}
fn flag_support_probe_files(&self) -> Result<FlagSupportProbeFiles<'_>, Error> {
match self.get_out_dir() {
Ok(dir) => {
let src = self.ensure_check_file()?;
let obj = dir.join("flag_check");
Ok(FlagSupportProbeFiles {
dir: Cow::Borrowed(dir),
src,
obj,
_temp_files: None,
})
}
Err(_) => {
let dir = env::temp_dir();
fs::create_dir_all(&dir)?;
let mut tmp_src =
crate::tempfile::NamedTempfile::new(&dir, self.flag_check_src_name())?;
let mut tmp_file = tmp_src.take_file().unwrap();
Self::write_flag_check_src(&mut tmp_file)?;
drop(tmp_file);
let mut tmp_obj = crate::tempfile::NamedTempfile::new(&dir, "flag_check")?;
drop(tmp_obj.take_file());
let src = tmp_src.path().to_owned();
let obj = tmp_obj.path().to_owned();
Ok(FlagSupportProbeFiles {
dir: Cow::Owned(dir),
src,
obj,
_temp_files: Some((tmp_src, tmp_obj)),
})
}
}
}
fn flag_support_cache_key(&self, tool: &Tool) -> CompilerFlag {
CompilerFlag {
compiler: tool.path().into(),
target: self.target.clone(),
host: self.host.clone(),
cpp: self.cpp,
cuda: self.cuda,
inherited: self.env.inherited().clone(),
explicit: self.env.explicit.clone().into_boxed_slice(),
}
}
fn is_flag_supported_inner(
&self,
flag: &OsStr,
tool: &Tool,
target: &TargetInfo<'_>,
key: &mut Option<CompilerFlag>,
) -> Result<bool, Error> {
let key = &*key.get_or_insert_with(|| self.flag_support_cache_key(tool));
assert_eq!(&*key.compiler, tool.path());
if let Some(is_supported) = self
.build_cache
.known_flag_support_status_cache
.read()
.unwrap()
.get(key)
.and_then(|answers| answers.get(flag))
.copied()
{
return Ok(is_supported);
}
if tool.is_like_msvc() && is_msvc_link_flag(flag) {
return Ok(false);
}
let probe = self.flag_support_probe_files()?;
let mut compiler = {
let mut cfg = Build::new();
cfg.flag(flag)
.compiler(tool.path())
.cargo_metadata(self.cargo_output.metadata)
.cargo_warnings(self.cargo_output.warnings)
.opt_level(0)
.debug(false)
.cpp(self.cpp)
.cuda(self.cuda)
.out_dir(&*probe.dir)
.inherit_rustflags(false)
.inherit_trim_paths(false)
.emit_rerun_if_env_changed(self.emit_rerun_if_env_changed);
cfg.env = BuildEnv::with_inherited(key.inherited.clone(), key.explicit.to_vec());
cfg.build_cache = Arc::clone(&self.build_cache);
cfg.cargo_output
.logger
.clone_from(&self.cargo_output.logger);
if let Some(target) = &self.target {
cfg.target(target);
}
if let Some(host) = &self.host {
cfg.host(host);
}
cfg.try_get_compiler()?
};
if compiler.family.verbose_stderr() {
compiler.remove_arg("-v".into());
}
if compiler.is_like_clang() {
compiler.push_cc_str("-Wno-unused-command-line-argument");
}
let mut cmd = compiler.to_command();
cmd.set_flag_supported_env(&compiler.env);
command_add_output_file(
&mut cmd,
&probe.obj,
CmdAddOutputFileArgs {
cuda: self.cuda,
is_assembler_msvc: false,
msvc: compiler.is_like_msvc(),
clang: compiler.is_like_clang(),
gnu: compiler.is_like_gnu(),
is_asm: false,
is_arm: is_arm(target),
},
);
cmd.arg("-c");
if compiler.supports_path_delimiter() {
cmd.arg("--");
}
cmd.arg(&probe.src);
if compiler.is_like_msvc() {
for (key, value) in &tool.env {
if &**key == "LIB" {
cmd.env("LIB", value);
break;
}
}
}
cmd.current_dir(&*probe.dir);
self.cargo_output
.print_debug(&format_args!("running: {}", CommandLine(&cmd)));
let output = cmd.output()?;
drop(probe);
let is_supported = output.status.success() && output.stderr.is_empty();
let mut cache = self
.build_cache
.known_flag_support_status_cache
.write()
.unwrap();
match cache.get_mut(key) {
Some(answers) => {
answers.insert(flag.into(), is_supported);
}
None => {
let mut answers = VecMap::default();
answers.insert(flag.into(), is_supported);
cache.insert(key.clone(), answers);
}
}
Ok(is_supported)
}
pub fn try_compile(&self, output: &str) -> Result<(), Error> {
lib_and_archive_names(output, "compile")?;
let dst = self.get_out_dir()?;
let objects = objects_from_files(
&self.files,
dst,
self.env.inherited(),
self.source_root.as_deref(),
)?;
self.compile_objects(&objects)?;
let library = self.try_create_archive(output, objects.iter().map(|o| &o.dst))?;
try_emit_link_directives(self, library)
}
fn emit_link_directives_for(&self, lib_name: &str, search_dir: &Path) -> Result<(), Error> {
let target = self.get_target()?;
if target.abi == "pauthtest" {
self.cargo_output.print_warning(
&"cc-rs should not be used with `pauthtest` target: it only builds \
static libraries, while `pauthtest` requires shared objects.",
);
}
if target.env == "msvc" {
let compiler = self.get_base_compiler()?;
let atlmfc_lib = compiler
.env
.iter()
.find(|&(var, _)| &**var == OsStr::new("LIB"))
.and_then(|(_, lib_paths)| {
env::split_paths(lib_paths).find(|path| {
let sub = Path::new("atlmfc/lib");
path.ends_with(sub) || path.parent().map_or(false, |p| p.ends_with(sub))
})
});
if let Some(atlmfc_lib) = atlmfc_lib {
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-link-search=native={}",
atlmfc_lib.display()
));
}
}
if self.link_lib_modifiers.is_empty() {
self.cargo_output
.print_metadata(&format_args!("cargo:rustc-link-lib=static={lib_name}"));
} else {
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-link-lib=static:{}={}",
JoinOsStrs {
slice: &self.link_lib_modifiers,
delimiter: ','
},
lib_name
));
}
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-link-search=native={}",
search_dir.display()
));
if self.cpp {
if let Some(stdlib) = self.get_cpp_link_stdlib()? {
let stdlib = stdlib.to_string_lossy();
let (link_lib, once) = if stdlib.contains('=') {
(stdlib.into_owned(), true)
} else if !self.get_cpp_link_stdlib_static() {
(stdlib.into_owned(), false)
} else if target.arch == "wasm32"
|| target.abi == "pauthtest"
|| target.vendor == "apple"
{
(format!("static={stdlib}"), false)
} else {
(format!("static:-bundle={stdlib}"), true)
};
let emit = !once
|| (self.cargo_output.metadata
&& push_if_new(&self.build_cache.emitted_cpp_link_stdlibs, &link_lib));
if emit {
self.cargo_output
.print_metadata(&format_args!("cargo:rustc-link-lib={link_lib}"));
}
}
if target.arch == "wasm32" {
if target.os == "wasi" {
if let Ok(wasi_sysroot) = self.wasi_sysroot() {
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-flags=-L {}/lib/{} -lstatic=c++ -lstatic=c++abi",
Path::new(&wasi_sysroot).display(),
self.get_raw_target()?
));
}
} else if target.os == "linux" {
let musl_sysroot = self.wasm_musl_sysroot()?;
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-flags=-L {}/lib -lstatic=c++ -lstatic=c++abi",
Path::new(&musl_sysroot).display(),
));
}
}
if target.abi == "pauthtest" {
let pauthtest_sysroot = self.pauthtest_sysroot()?;
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-flags=-L {}/lib -lc++ -lc++abi",
Path::new(&pauthtest_sysroot).display(),
));
}
}
let cudart = match &self.cudart {
Some(opt) => opt, None => "none",
};
if cudart != "none" {
if let Some(nvcc) = self.which(&self.get_compiler().path, None) {
let mut libtst = false;
let mut libdir = nvcc;
libdir.pop(); libdir.push("..");
if cfg!(target_os = "linux") {
libdir.push("targets");
libdir.push(format!("{}-linux", target.arch));
if !libdir.exists() && target.arch == "aarch64" {
libdir.pop();
libdir.push("sbsa-linux");
}
libdir.push("lib");
libtst = true;
} else if cfg!(target_env = "msvc") {
libdir.push("lib");
match target.arch {
"aarch64" => {
libdir.push("arm64");
libtst = true;
}
"x86_64" => {
libdir.push("x64");
libtst = true;
}
"x86" => {
libdir.push("Win32");
libtst = true;
}
_ => libtst = false,
}
}
if libtst && libdir.is_dir() {
self.cargo_output.print_metadata(&format_args!(
"cargo:rustc-link-search=native={}",
libdir.display()
));
}
let lib = match cudart {
"shared" => "cudart",
"static" => "cudart_static",
bad => {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!("unsupported cudart option: {bad}"),
));
}
};
self.cargo_output
.print_metadata(&format_args!("cargo:rustc-link-lib={lib}"));
}
}
Ok(())
}
pub fn compile(&self, output: &str) {
if let Err(e) = self.try_compile(output) {
fail(&e.message);
}
}
pub fn compile_intermediates(&self) -> Vec<PathBuf> {
match self.try_compile_intermediates() {
Ok(v) => v,
Err(e) => fail(&e.message),
}
}
pub fn try_compile_intermediates(&self) -> Result<Vec<PathBuf>, Error> {
let dst = self.get_out_dir()?;
let objects = objects_from_files(
&self.files,
dst,
self.env.inherited(),
self.source_root.as_deref(),
)?;
self.compile_objects(&objects)?;
Ok(objects.into_iter().map(|v| v.dst).collect())
}
pub fn create_archive<P>(&self, output: &str, objects: P) -> PathBuf
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
match self.try_create_archive(output, objects) {
Ok(library) => library,
Err(e) => fail(&e.message),
}
}
pub fn try_create_archive<P>(&self, output: &str, objects: P) -> Result<PathBuf, Error>
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
let (lib_name, gnu_lib_name) = lib_and_archive_names(output, "create_archive")?;
self.check_enabled()?;
let dst = self.get_out_dir()?.join(gnu_lib_name);
let objects: Vec<P::Item> = objects.into_iter().collect();
self.assemble(lib_name, &dst, &mut objects.iter().map(AsRef::as_ref))?;
Ok(dst)
}
fn check_enabled(&self) -> Result<(), Error> {
if self.is_disabled() {
return Err(Error::new(
ErrorKind::Disabled,
"the `cc` crate's functionality has been disabled by the `CC_FORCE_DISABLE` environment variable.",
));
}
Ok(())
}
fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> {
self.check_enabled()?;
if objs.is_empty() {
return Ok(());
}
let compiler = self.try_get_compiler()?;
let mut msvc_asm_tool = None;
let compile_command_dir = self.compile_command_dir();
let mut create_cmd = |obj: &Object| -> Result<Command, Error> {
let cmd = self.create_compile_object_cmd(obj, &compiler, &mut msvc_asm_tool)?;
if let Some(dir) = &compile_command_dir {
self.cargo_output.log_compile_command(&cmd, obj, dir);
}
Ok(cmd)
};
#[cfg(feature = "parallel")]
if objs.len() > 1 {
return parallel::run_commands_in_parallel(
&self.cargo_output,
&mut objs.iter().map(&mut create_cmd),
);
}
for obj in objs {
let mut cmd = create_cmd(obj)?;
run(&mut cmd, &self.cargo_output)?;
}
Ok(())
}
fn compile_command_dir(&self) -> Option<PathBuf> {
self.cargo_output.logger.as_ref()?;
match env::current_dir() {
Ok(dir) => Some(dir),
Err(e) => {
self.cargo_output.print_warning(&format_args!(
"compile commands aren't logged because the current directory can't be read: {e}"
));
None
}
}
}
fn create_compile_object_cmd(
&self,
obj: &Object,
compiler: &Tool,
msvc_asm_tool: &mut Option<Tool>,
) -> Result<Command, Error> {
let asm_ext = AsmFileExt::from_path(&obj.src);
let is_asm = asm_ext.is_some();
let target = self.get_target()?;
let msvc = target.env == "msvc";
let is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm);
let mut cmd = if is_assembler_msvc {
let assembler = match msvc_asm_tool {
Some(assembler) => assembler,
None => msvc_asm_tool.insert(self.msvc_macro_assembler(compiler)?),
};
assembler.to_command()
} else {
compiler.to_command()
};
let is_arm = is_arm(&target);
command_add_output_file(
&mut cmd,
&obj.dst,
CmdAddOutputFileArgs {
cuda: self.cuda,
is_assembler_msvc,
msvc: compiler.is_like_msvc(),
clang: compiler.is_like_clang(),
gnu: compiler.is_like_gnu(),
is_asm,
is_arm,
},
);
if !is_assembler_msvc || !is_arm {
cmd.arg("-c");
}
if self.cuda && self.cuda_file_count() > 1 {
cmd.arg("--device-c");
}
if is_asm {
cmd.args(self.asm_flags.iter().map(core::ops::Deref::deref));
}
self.add_compile_source_arg(&mut cmd, compiler, &obj.src, is_assembler_msvc);
if cfg!(target_os = "macos") {
self.fix_env_for_apple_os(&mut cmd)?;
}
Ok(cmd)
}
fn add_compile_source_arg(
&self,
cmd: &mut Command,
compiler: &Tool,
src: &Path,
is_assembler_msvc: bool,
) {
if self.cpp
&& src.extension() == Some(OsStr::new("cc"))
&& matches!(compiler.family, ToolFamily::Msvc { clang_cl: false })
{
cmd.arg("-Tp");
}
if compiler.supports_path_delimiter() && !is_assembler_msvc {
cmd.arg("--");
}
cmd.arg(src);
}
pub fn try_expand(&self) -> Result<Vec<u8>, Error> {
let compiler = self.try_get_compiler()?;
let mut cmd = compiler.to_command();
cmd.arg("-E");
if self.files.len() > 1 {
return Err(Error::new(
ErrorKind::InvalidArgument,
"Expand may only be called for a single file",
));
}
let is_asm = self
.files
.iter()
.map(core::ops::Deref::deref)
.find_map(AsmFileExt::from_path)
.is_some();
for src in self.files.iter().map(std::ops::Deref::deref) {
self.add_compile_source_arg(&mut cmd, &compiler, src, is_asm);
}
let echoed_file_name = if matches!(compiler.family, ToolFamily::Msvc { clang_cl: false }) {
self.files
.first()
.and_then(|src| src.file_name())
.and_then(OsStr::to_str)
} else {
None
};
run_output_ignoring_line(
&mut cmd,
&self.cargo_output,
echoed_file_name.map(str::as_bytes),
)
}
pub fn expand(&self) -> Vec<u8> {
match self.try_expand() {
Err(e) => fail(&e.message),
Ok(v) => v,
}
}
pub fn get_compiler(&self) -> Tool {
match self.try_get_compiler() {
Ok(tool) => tool,
Err(e) => fail(&e.message),
}
}
pub fn try_get_compiler(&self) -> Result<Tool, Error> {
let opt_level = self.get_opt_level()?;
let target = self.get_target()?;
let mut cmd = self.get_base_compiler()?;
self.rerun_if_compiler_env_changed(&cmd, &target);
let mut flag_support_key = None;
if matches!(cmd.family, ToolFamily::Msvc { clang_cl: false }) {
cmd.env
.push((OsStr::new("VSLANG").into(), OsStr::new("1033").into()));
} else {
cmd.env
.push((OsStr::new("LC_ALL").into(), OsStr::new("C").into()));
}
let no_defaults = self.no_default_flags || self.get_env_boolean("CRATE_CC_NO_DEFAULTS");
if !no_defaults {
self.add_default_flags(&mut cmd, &target, opt_level, &mut flag_support_key)?;
}
if let Some(ref std) = self.std {
let separator = match cmd.family {
ToolFamily::Msvc { .. } => ':',
ToolFamily::Gnu | ToolFamily::Clang { .. } => '=',
};
cmd.push_cc_arg(format!("-std{separator}{std}").into());
}
for directory in self.include_directories.iter() {
cmd.push_arg_str("-I");
cmd.args.push(directory.as_os_str().into());
}
if self.warnings_into_errors {
let warnings_to_errors_flag = cmd.family.warnings_to_errors_flag().into();
cmd.push_cc_arg(warnings_to_errors_flag);
}
let envflags = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" })?;
match self.warnings {
Some(true) => {
let wflags = cmd.family.warnings_flags().into();
cmd.push_cc_arg(wflags);
}
Some(false) => {
let wflags = cmd.family.warnings_suppression_flags().into();
cmd.push_cc_arg(wflags);
}
None => {
if envflags.is_none() {
let wflags = cmd.family.warnings_flags().into();
cmd.push_cc_arg(wflags);
}
}
}
if self.extra_warnings.unwrap_or(envflags.is_none()) {
if let Some(wflags) = cmd.family.extra_warnings_flags() {
cmd.push_cc_arg(wflags.into());
}
}
if self.inherit_rustflags {
self.add_inherited_rustflags(&mut cmd, &target, &mut flag_support_key)?;
}
if self.inherit_trim_paths {
self.add_trim_paths_flags(&mut cmd, &target, &mut flag_support_key)?;
}
let (flags, linker_flags) = split_off_msvc_linker_flags(cmd.family, &self.flags);
let mut ignored_flags: Vec<&OsStr> = linker_flags.iter().map(AsRef::as_ref).collect();
for flag in flags {
match flag {
BuildFlag::Always(flag) => cmd.args.push((**flag).into()),
BuildFlag::IfSupported(flag) => {
if self
.is_flag_supported_inner(flag, &cmd, &target, &mut flag_support_key)
.unwrap_or(false)
{
cmd.push_cc_arg((**flag).into());
}
}
}
}
for (key, value) in self.definitions.iter() {
if let Some(ref value) = *value {
cmd.args.push(format!("-D{key}={value}").into());
} else {
cmd.args.push(format!("-D{key}").into());
}
}
if let Some(flags) = &envflags {
let (flags, linker_flags) = split_off_msvc_linker_flags(cmd.family, flags);
ignored_flags.extend(linker_flags.iter().map(OsStr::new));
for arg in flags {
cmd.push_cc_arg(arg.into());
}
}
if !ignored_flags.is_empty()
&& !self
.build_cache
.warned_about_msvc_linker_flags
.swap(true, Ordering::Relaxed)
{
self.cargo_output.print_warning(&format_args!(
"Ignoring {ignored_flags:?}: cl passes `/link` and the arguments after it to the \
linker, but cc only compiles. Use `cargo:rustc-link-arg` for linker flags, and \
`/Zl` instead of `/link /NODEFAULTLIB`."
));
}
cmd.env.extend_from_slice(&self.env.explicit);
Ok(cmd)
}
#[rustfmt::skip]
const GNU_ARM_ARCHS: &'static [ArmArch] = &[
("thumbv6m", &["-march=armv6s-m"], None),
("thumbv7m", &["-march=armv7-m"], None),
("thumbv7em", &["-march=armv7e-m"], Some("-mfpu=fpv4-sp-d16")),
("thumbv8m.base", &["-march=armv8-m.base"], None),
("thumbv8m.main", &["-march=armv8-m.main"], Some("-mfpu=fpv5-sp-d16")),
("armv6", &["-march=armv6"], Some("-mfpu=vfpv2")),
("thumbv6", &["-march=armv6"], Some("-mfpu=vfpv2")),
("armebv7r", &["-mbig-endian", "-march=armv7-r"], Some("-mfpu=vfpv3-d16")),
("armv7r", &["-march=armv7-r"], Some("-mfpu=vfpv3-d16")),
("thumbv7r", &["-march=armv7-r"], Some("-mfpu=vfpv3-d16")),
("armv7a", &["-march=armv7-a"], Some("-mfpu=vfpv3-d16")),
("thumbv7a", &["-march=armv7-a"], Some("-mfpu=vfpv3-d16")),
("armv8r", &["-march=armv8-r"], Some("-mfpu=fp-armv8")),
("thumbv8r", &["-march=armv8-r"], Some("-mfpu=fp-armv8")),
];
fn add_default_flags(
&self,
cmd: &mut Tool,
target: &TargetInfo<'_>,
opt_level: &str,
flag_support_key: &mut Option<CompilerFlag>,
) -> Result<(), Error> {
let raw_target = self.get_raw_target()?;
match cmd.family {
ToolFamily::Msvc { .. } => {
cmd.push_cc_str("-nologo");
let crt_flag = match self.static_crt {
Some(true) => "-MT",
Some(false) => "-MD",
None if !self.is_cargo_target() => "-MD",
None => {
let features = self
.get_cargo_env("CARGO_CFG_TARGET_FEATURE")
.unwrap_or_default();
if features.to_string_lossy().contains("crt-static") {
"-MT"
} else {
"-MD"
}
}
};
cmd.push_cc_arg(crt_flag.into());
match opt_level {
"z" | "s" | "1" => cmd.push_opt_unless_duplicate("-O1".into()),
"2" | "3" => cmd.push_opt_unless_duplicate("-O2".into()),
_ => {}
}
}
ToolFamily::Gnu | ToolFamily::Clang { .. } => {
if opt_level == "z" && !cmd.is_like_clang() {
cmd.push_opt_unless_duplicate("-Os".into());
} else {
cmd.push_opt_unless_duplicate(format!("-O{opt_level}").into());
}
if cmd.is_like_clang() && target.os == "android" {
cmd.push_opt_unless_duplicate("-DANDROID".into());
}
if target.os != "ios"
&& target.os != "watchos"
&& target.os != "tvos"
&& target.os != "visionos"
{
cmd.push_cc_args(&["-ffunction-sections", "-fdata-sections"]);
}
if self.pic.unwrap_or(
target.os != "windows"
&& target.os != "none"
&& target.os != "uefi"
&& target.os != "vita"
&& target.arch != "wasm32"
&& target.arch != "wasm64",
) {
cmd.push_cc_str("-fPIC");
if (target.os == "linux" || target.os == "android")
&& !self.use_plt.unwrap_or(true)
{
cmd.push_cc_str("-fno-plt");
}
}
if target.os == "wasi" {
if let Ok(wasi_sysroot) = self.wasi_sysroot() {
cmd.push_cc_arg(
format!("--sysroot={}", Path::new(&wasi_sysroot).display()).into(),
);
}
if raw_target.contains("threads") {
cmd.push_cc_str("-pthread");
}
}
if target.os == "nto" || target.os == "qnx" {
let arg = match target.full_arch {
"x86" | "i586" => "-Vgcc_ntox86_cxx",
"aarch64" => "-Vgcc_ntoaarch64le_cxx",
"x86_64" => "-Vgcc_ntox86_64_cxx",
_ => {
return Err(Error::new(
ErrorKind::InvalidTarget,
format!("Unknown architecture for Neutrino QNX: {}", target.arch),
))
}
};
cmd.push_cc_arg(arg.into());
}
}
}
if self.get_debug() {
if self.cuda {
cmd.push_arg_str("-G");
}
let family = cmd.family;
family.add_debug_flags(
cmd,
self.get_debug_str().unwrap_or_default(),
self.get_dwarf_version(),
);
}
if self.get_force_frame_pointer() {
let family = cmd.family;
if let ToolFamily::Gnu | ToolFamily::Clang { .. } = family {
cmd.push_cc_str("-fno-omit-frame-pointer");
let flag = OsString::from("-mno-omit-leaf-frame-pointer");
if self
.is_flag_supported_inner(&flag, cmd, target, flag_support_key)
.unwrap_or(false)
{
cmd.push_cc_arg(flag);
}
}
}
if !cmd.is_like_msvc() {
if target.arch == "x86" {
cmd.push_arg_str("-m32");
} else if target.abi == "x32" {
cmd.push_arg_str("-mx32");
} else if target.os == "aix" {
if cmd.family == ToolFamily::Gnu {
cmd.push_arg_str("-maix64");
} else {
cmd.push_arg_str("-m64");
}
} else if target.arch == "x86_64" || target.arch == "powerpc64" {
cmd.push_arg_str("-m64");
}
}
match cmd.family {
ToolFamily::Clang { .. } => {
if !(cmd.has_internal_target_arg
|| (target.os == "android"
&& android_clang_compiler_uses_target_arg_internally(&cmd.path)))
{
if target.os == "freebsd" {
if self.cpp && self.cpp_set_stdlib.is_none() {
cmd.push_cc_str("-stdlib=libc++");
}
} else if target.arch == "wasm32" && target.os == "linux" {
for x in &[
"atomics",
"bulk-memory",
"mutable-globals",
"sign-ext",
"exception-handling",
] {
cmd.push_cc_arg(format!("-m{x}").into());
}
for x in &["wasm-exceptions", "declspec"] {
cmd.push_cc_arg(format!("-f{x}").into());
}
let musl_sysroot = self.wasm_musl_sysroot()?;
cmd.push_cc_arg(
format!("--sysroot={}", Path::new(&musl_sysroot).display()).into(),
);
cmd.push_cc_str("-pthread");
} else if target.abi == "pauthtest" {
let pauthtest_sysroot = self.pauthtest_sysroot()?;
let pauthtest_resource_dir = self.pauthtest_resource_dir()?;
cmd.push_cc_arg(
format!("--sysroot={}", Path::new(&pauthtest_sysroot).display()).into(),
);
cmd.push_cc_arg(
format!(
"-resource-dir={}",
Path::new(&pauthtest_resource_dir).display()
)
.into(),
);
cmd.push_cc_str("-march=armv8.3-a+pauth");
if self.cpp && self.cpp_set_stdlib.is_none() {
cmd.push_cc_str("-stdlib=libc++");
cmd.push_cc_arg(
format!(
"-I{}/include/c++/v1",
Path::new(&pauthtest_sysroot).display()
)
.into(),
);
cmd.push_cc_arg(
format!("-L{}/lib", Path::new(&pauthtest_sysroot).display()).into(),
);
}
}
let version = (target.os == "visionos" || target.env == "macabi")
.then(|| self.apple_deployment_target(target));
let clang_target =
target.llvm_target(self.get_raw_target()?, version.as_deref());
cmd.push_cc_arg(format!("--target={clang_target}").into());
}
}
ToolFamily::Msvc { clang_cl } => {
cmd.push_cc_str("-Brepro");
if clang_cl {
cmd.push_cc_arg(
format!(
"--target={}",
target.llvm_target(self.get_raw_target()?, None)
)
.into(),
);
if target.arch == "x86" {
cmd.push_cc_str("-arch:SSE2");
}
} else if target.full_arch == "i586" {
cmd.push_cc_str("-arch:IA32");
} else if target.full_arch == "arm64ec" {
cmd.push_cc_str("-arm64EC");
}
if target.arch == "arm" {
cmd.push_arg_str("-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1");
}
}
ToolFamily::Gnu => {
if target.vendor == "kmc" {
cmd.push_arg_str("-finput-charset=utf-8");
}
if self.static_flag.is_none() && self.is_cargo_target() {
let features = self
.get_cargo_env("CARGO_CFG_TARGET_FEATURE")
.unwrap_or_default();
if features.to_string_lossy().contains("crt-static") {
cmd.push_arg_str("-static");
}
}
if (target.full_arch.starts_with("armv7")
|| target.full_arch.starts_with("thumbv7"))
&& (target.os == "linux" || target.vendor == "kmc")
{
cmd.push_arg_str("-march=armv7-a");
if target.abi == "eabihf" {
cmd.push_args(&["-mfpu=vfpv3-d16", "-mfloat-abi=hard"]);
}
}
if target.os == "android" && target.full_arch.contains("v7") {
cmd.push_args(&["-march=armv7-a", "-mthumb"]);
if !target.full_arch.contains("neon") {
cmd.push_arg_str("-mfpu=vfpv3-d16");
}
cmd.push_arg_str("-mfloat-abi=softfp");
}
if target.full_arch.contains("neon") {
cmd.push_arg_str("-mfpu=neon");
}
if target.os == "linux" {
match target.full_arch {
"armv4t" => cmd.push_args(&["-march=armv4t", "-marm", "-mfloat-abi=soft"]),
"armv5te" => {
cmd.push_args(&["-march=armv5te", "-marm", "-mfloat-abi=soft"])
}
"arm" if target.abi == "eabihf" => {
cmd.push_args(&["-march=armv6", "-marm", "-mfpu=vfp"])
}
"arm" => cmd.push_args(&["-march=armv6", "-marm", "-mfloat-abi=soft"]),
"i586" => cmd.push_arg_str("-march=pentium"),
"i686" => cmd.push_arg_str("-march=i686"),
_ => {}
}
}
if target.arch == "x86" && target.env == "musl" {
cmd.push_arg_str("-Wl,-melf_i386");
}
if target.arch == "arm" && target.os == "none" && target.abi == "eabihf" {
cmd.push_arg_str("-mfloat-abi=hard");
}
if target.full_arch.starts_with("thumb") {
cmd.push_arg_str("-mthumb");
}
for (prefix, flags, eabihf_fpu) in Self::GNU_ARM_ARCHS {
if target.full_arch.starts_with(prefix) {
cmd.push_args(flags);
if let (Some(fpu), "eabihf") = (eabihf_fpu, target.abi) {
cmd.push_arg_str(fpu);
}
break;
}
}
if target.arch == "riscv32" || target.arch == "riscv64" {
let arch = &target.full_arch[5..];
if arch.starts_with("64") {
if matches!(
target.os,
"linux" | "freebsd" | "netbsd" | "managarm" | "redox"
) {
cmd.push_args(&["-march=rv64gc", "-mabi=lp64d"]);
} else {
cmd.args.push(format!("-march=rv{arch}").into());
cmd.push_arg_str("-mabi=lp64");
}
} else if arch.starts_with("32") {
if target.os == "linux" {
cmd.push_args(&["-march=rv32gc", "-mabi=ilp32d"]);
} else {
cmd.args.push(format!("-march=rv{arch}").into());
cmd.push_arg_str("-mabi=ilp32");
}
} else {
cmd.push_arg_str("-mcmodel=medany");
}
}
}
}
if raw_target == "wasm32v1-none" {
cmd.push_cc_args(&["-mcpu=mvp", "-mmutable-globals"]);
}
if target.os == "solaris" || target.os == "illumos" {
cmd.push_arg_str("-D_REENTRANT");
}
if target.vendor == "apple" {
self.apple_flags(cmd)?;
}
if self.static_flag.unwrap_or(false) {
cmd.push_arg_str("-static");
}
if self.shared_flag.unwrap_or(false) {
cmd.push_arg_str("-shared");
}
if self.cpp {
match (self.cpp_set_stdlib.as_ref(), cmd.family) {
(None, _) => {}
(Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Clang { .. }) => {
cmd.push_cc_arg(format!("-stdlib=lib{stdlib}").into());
}
_ => {
self.cargo_output.print_warning(&format_args!("cpp_set_stdlib is specified, but the {:?} compiler does not support this option, ignored", cmd.family));
}
}
}
self.add_sanitizer_flags(cmd, target);
Ok(())
}
fn add_sanitizer_flags(&self, cmd: &mut Tool, target: &TargetInfo<'_>) {
let tested = match cmd.family {
ToolFamily::Gnu | ToolFamily::Clang { .. } => ("x86_64", "linux", "gnu", ""),
ToolFamily::Msvc { clang_cl: false } => ("x86_64", "windows", "msvc", ""),
ToolFamily::Msvc { clang_cl: true } => return,
};
if (target.arch, target.os, target.env, target.abi) != tested
|| cmd.cuda
|| !self.is_cargo_target()
{
return;
}
let Some(sanitizers) = self
.get_cargo_env("CARGO_CFG_SANITIZE")
.and_then(OsStr::to_str)
else {
return;
};
if sanitizers.split(',').any(|name| name == "address") {
cmd.push_cc_str("-fsanitize=address");
}
}
fn add_inherited_rustflags(
&self,
cmd: &mut Tool,
target: &TargetInfo<'_>,
flag_support_key: &mut Option<CompilerFlag>,
) -> Result<(), Error> {
let Some(codegen_flags) = self.env.inherited().rustc_codegen_flags()? else {
return Ok(());
};
codegen_flags.cc_flags(self, cmd, target, flag_support_key);
Ok(())
}
fn add_trim_paths_flags(
&self,
cmd: &mut Tool,
target: &TargetInfo<'_>,
flag_support_key: &mut Option<CompilerFlag>,
) -> Result<(), Error> {
if cmd.is_like_msvc() && !cmd.is_like_clang_cl() {
return Ok(());
}
let Some(scope) = self.get_cargo_env("CARGO_TRIM_PATHS_SCOPE") else {
return Ok(());
};
let Some(remap) = self.get_cargo_env("CARGO_TRIM_PATHS_REMAP") else {
return Ok(());
};
let mut macro_scope = false;
let mut object_scope = false;
for scope in scope.to_string_lossy().split(',') {
match scope {
"all" => {
macro_scope = true;
object_scope = true;
break;
}
"macro" => macro_scope = true,
"object" => {
macro_scope = true;
object_scope = true;
break;
}
_ => {}
}
}
let macro_scope = macro_scope
&& self.probe_prefix_map_flag(PrefixMapFlag::Macro, cmd, target, flag_support_key);
let object_scope = object_scope
&& self.probe_prefix_map_flag(PrefixMapFlag::Debug, cmd, target, flag_support_key);
if !macro_scope && !object_scope {
return Ok(());
}
let clang_driver = if cmd.is_like_clang_cl() {
"/clang:"
} else {
""
};
for pair in env::split_paths(&remap) {
let pair = pair.as_os_str();
if pair.is_empty() {
continue;
}
if macro_scope {
let mut flag = OsString::from(clang_driver);
flag.push("-fmacro-prefix-map=");
flag.push(pair);
cmd.push_cc_arg(flag);
}
if object_scope {
let mut flag = OsString::from(clang_driver);
flag.push("-fdebug-prefix-map=");
flag.push(pair);
cmd.push_cc_arg(flag);
}
}
Ok(())
}
fn probe_prefix_map_flag(
&self,
flag: PrefixMapFlag,
cmd: &Tool,
target: &TargetInfo<'_>,
flag_support_key: &mut Option<CompilerFlag>,
) -> bool {
let (flag, unsupported_warning) = match flag {
PrefixMapFlag::Macro => (
"-fmacro-prefix-map",
"paths embedded by macros will not be remapped",
),
PrefixMapFlag::Debug => (
"-fdebug-prefix-map",
"paths embedded in debug info will not be remapped",
),
};
let flag = if cmd.is_like_clang_cl() {
format!("/clang:{flag}")
} else {
flag.to_owned()
};
let probe = format!("{flag}=/probe=/probe");
let supported = self
.is_flag_supported_inner(OsStr::new(&probe), cmd, target, flag_support_key)
.unwrap_or(false);
if !supported {
self.cargo_output.print_warning(&format_args!(
"{flag} is not supported by {:?}, {unsupported_warning}",
cmd.path()
));
}
supported
}
fn msvc_macro_assembler(&self, compiler: &Tool) -> Result<Tool, Error> {
let target = self.get_target()?;
let tool = match target.arch {
"x86_64" => "ml64.exe",
"arm" => "armasm.exe",
"aarch64" | "arm64ec" => "armasm64.exe",
_ => "ml.exe",
};
let (mut cmd, args) = match self.env_tool("CC_MASM_ASM") {
Some((program, _wrapper, args)) => (self.msvc_macro_assembler_tool(program), args),
None => (
self.default_msvc_macro_assembler(&target, tool, compiler),
Vec::new(),
),
};
let is_llvm_ml = cmd
.path
.file_stem()
.and_then(OsStr::to_str)
.map_or(false, |stem| stem.starts_with_ignore_ascii_case("llvm-ml"));
if !is_arm(&target) && !is_llvm_ml {
self.rerun_if_env_changed("ML");
}
if is_llvm_ml {
match target.arch {
"x86_64" => cmd.push_arg_str("-m64"),
"x86" => cmd.push_arg_str("-m32"),
_ => {}
}
}
cmd.args.extend(args.into_iter().map(OsString::from));
cmd.push_arg_str("-nologo"); for directory in self.include_directories.iter() {
cmd.push_arg_str("-I");
cmd.args.push(directory.as_os_str().into());
}
if is_arm(&target) {
if self.get_debug() {
cmd.push_arg_str("-g");
}
if target.arch == "arm64ec" {
cmd.push_args(&["-machine", "ARM64EC"]);
}
for (key, value) in self.definitions.iter() {
cmd.push_arg_str("-PreDefine");
if let Some(ref value) = *value {
if let Ok(i) = value.parse::<i32>() {
cmd.args.push(format!("{key} SETA {i}").into());
} else if value.starts_with('"') && value.ends_with('"') {
cmd.args.push(format!("{key} SETS {value}").into());
} else {
cmd.args.push(format!("{key} SETS \"{value}\"").into());
}
} else {
cmd.args.push(format!("{} SETL {}", key, "{TRUE}").into());
}
}
} else {
if self.get_debug() && !is_llvm_ml {
cmd.push_arg_str("-Zi");
}
for (key, value) in self.definitions.iter() {
if let Some(ref value) = *value {
cmd.args.push(format!("-D{key}={value}").into());
} else {
cmd.args.push(format!("-D{key}").into());
}
}
}
if target.arch == "x86" {
cmd.push_arg_str("-safeseh");
}
Ok(cmd)
}
fn default_msvc_macro_assembler(
&self,
target: &TargetInfo<'_>,
tool: &str,
compiler: &Tool,
) -> Tool {
if let Some(assembler) = self.find_msvc_tools_find_tool(target, tool) {
return assembler;
}
let path = self.get_env_overridable("PATH").map(|path| &**path);
if matches!(target.arch, "x86" | "x86_64") && self.which(Path::new(tool), path).is_none() {
let llvm_ml = self
.find_llvm_tool_next_to_clang_cl(compiler, "llvm-ml")
.or_else(|| self.which(Path::new("llvm-ml"), path));
if let Some(llvm_ml) = llvm_ml {
return self.msvc_macro_assembler_tool(llvm_ml);
}
}
self.msvc_macro_assembler_tool(tool.into())
}
fn msvc_macro_assembler_tool(&self, program: PathBuf) -> Tool {
let mut tool = Tool::with_family(
program,
ToolFamily::Msvc { clang_cl: false },
self.env.inherited().clone(),
);
tool.env.extend_from_slice(&self.env.explicit);
tool
}
fn assemble<'a>(
&'a self,
lib_name: &str,
dst: &Path,
objs: &mut dyn Iterator<Item = &'a Path>,
) -> Result<(), Error> {
let _ = fs::remove_file(dst);
let mut deterministic_ar: Option<bool> = None;
let mut objs = objs
.chain(self.objects.iter().map(core::ops::Deref::deref))
.peekable();
let libtool = is_libtool(&self.try_get_archiver_and_flags()?.1);
let mut libtool_objs = Vec::new();
if libtool {
libtool_objs.extend(objs);
self.assemble_libtool(dst, &libtool_objs)?;
} else {
let mut batch = Vec::new();
while objs.peek().is_some() {
let mut remaining_len = 4000;
while let Some(path) =
objs.next_if(|peek| batch.is_empty() || peek.as_os_str().len() <= remaining_len)
{
batch.push(path);
remaining_len = remaining_len.saturating_sub(path.as_os_str().len());
}
self.assemble_progressive(dst, &batch, &mut deterministic_ar)?;
batch.clear();
}
}
if self.cuda && self.cuda_file_count() > 0 {
let out_dir = self.get_out_dir()?;
let dlink = out_dir.join(lib_name.to_owned() + "_dlink.o");
let mut nvcc = self.get_compiler().to_command();
nvcc.arg("--device-link").arg("-o").arg(&dlink).arg(dst);
run(&mut nvcc, &self.cargo_output)?;
if libtool {
let mut objs: Vec<&Path> = libtool_objs.clone();
objs.push(&dlink);
self.assemble_libtool(dst, &objs)?;
} else {
self.assemble_progressive(dst, &[dlink.as_path()], &mut deterministic_ar)?;
}
}
let target = self.get_target()?;
if target.env == "msvc" {
let lib_dst = dst.with_file_name(format!("{lib_name}.lib"));
let _ = fs::remove_file(&lib_dst);
match fs::hard_link(dst, &lib_dst).or_else(|_| {
fs::copy(dst, &lib_dst).map(|_| ())
}) {
Ok(_) => (),
Err(_) => {
return Err(Error::new(
ErrorKind::IOError,
"Could not copy or create a hard-link to the generated lib file.",
));
}
};
} else if libtool {
} else {
let mut ar = self.try_get_archiver()?;
match deterministic_ar {
Some(false) => {
ar.env("ZERO_AR_DATE", "1");
run(ar.arg("s").arg(dst), &self.cargo_output)?;
}
Some(true) => {
run(ar.arg("sD").arg(dst), &self.cargo_output)?;
}
None => {
if run_silent_on_error(ar.arg("sD").arg(dst), &self.cargo_output).is_err() {
let mut ar = self.try_get_archiver()?;
ar.env("ZERO_AR_DATE", "1");
run(ar.arg("s").arg(dst), &self.cargo_output)?;
}
}
}
}
Ok(())
}
fn assemble_progressive(
&self,
dst: &Path,
objs: &[&Path],
deterministic_ar: &mut Option<bool>,
) -> Result<(), Error> {
let target = self.get_target()?;
let (mut cmd, program, any_flags, any_args) = self.try_get_archiver_and_flags()?;
let is_llvm_ar = program.file_name().map_or(false, |name| {
name.to_string_lossy().contains_ignore_ascii_case("llvm-ar")
});
if target.env == "msvc" && !is_llvm_ar {
let mut out = OsString::from("-out:");
out.push(dst);
cmd.arg(out);
if !any_flags {
cmd.arg("-nologo");
}
if dst.exists() {
cmd.arg(dst);
}
let dir = dst.parent().filter(|dir| {
dir.is_absolute()
&& !any_args
&& (program.is_absolute() || program.components().count() == 1)
});
if let Some(dir) = dir {
let current_dir = env::current_dir()?;
cmd.current_dir(dir);
for obj in objs {
match obj.strip_prefix(dir) {
Ok(relative) => cmd.arg(relative),
Err(_) if obj.is_relative() => cmd.arg(current_dir.join(obj)),
Err(_) => cmd.arg(obj),
};
}
} else {
cmd.args(objs);
}
run(&mut cmd, &self.cargo_output)?;
} else {
cmd.env("ZERO_AR_DATE", "1");
match *deterministic_ar {
Some(false) => {
run(cmd.arg("cq").arg(dst).args(objs), &self.cargo_output)?;
}
Some(true) => {
run(cmd.arg("cqD").arg(dst).args(objs), &self.cargo_output)?;
}
None => {
if run_silent_on_error(cmd.arg("cqD").arg(dst).args(objs), &self.cargo_output)
.is_ok()
{
*deterministic_ar = Some(true);
} else {
*deterministic_ar = Some(false);
let (mut cmd, ..) = self.try_get_archiver_and_flags()?;
cmd.env("ZERO_AR_DATE", "1");
run(cmd.arg("cq").arg(dst).args(objs), &self.cargo_output)?;
}
}
}
}
Ok(())
}
fn assemble_libtool(&self, dst: &Path, objs: &[&Path]) -> Result<(), Error> {
let mut list = Vec::new();
for obj in objs {
#[cfg(unix)]
list.extend_from_slice(std::os::unix::ffi::OsStrExt::as_bytes(obj.as_os_str()));
#[cfg(not(unix))]
list.extend_from_slice(
obj.to_str()
.ok_or_else(|| {
Error::new(
ErrorKind::InvalidArgument,
format!("libtool can't read the object path {}", obj.display()),
)
})?
.as_bytes(),
);
list.push(b'\n');
}
let mut list_path = dst.as_os_str().to_owned();
list_path.push(".filelist");
let list_path = PathBuf::from(list_path);
fs::write(&list_path, list)?;
let mut filelist_arg = list_path.as_os_str().to_owned();
filelist_arg.push(",");
let mut cmd = self.try_get_archiver()?;
cmd.arg("-static")
.arg("-D")
.arg("-o")
.arg(dst)
.arg("-filelist")
.arg(filelist_arg);
run(&mut cmd, &self.cargo_output)
}
fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> {
let target = self.get_target()?;
if cmd.is_like_gnu() {
let arch = map_darwin_target_from_rust_to_compiler_architecture(&target);
cmd.push_arg_str("-arch");
cmd.args.push(arch.into());
}
if cmd.is_like_gnu() || !(target.os == "visionos" || target.env == "macabi") {
let min_version = self.apple_deployment_target(&target);
cmd.args
.push(target.apple_version_flag(&min_version).into());
}
if cmd.is_xctoolchain_clang() || target.os != "macos" {
self.cargo_output.print_metadata(&format_args!(
"Detecting {:?} SDK path for {}",
target.os,
target.apple_sdk_name(),
));
let sdk_path = self.apple_sdk_root(&target)?;
cmd.push_arg_str("-isysroot");
cmd.args.push(OsStr::new(&sdk_path).to_owned());
cmd.env
.push((OsStr::new("SDKROOT").into(), Arc::clone(&sdk_path)));
if target.env == "macabi" {
let ios_support = Path::new(&sdk_path).join("System/iOSSupport");
cmd.args.extend([
OsString::from("-isystem"),
ios_support.join("usr/include").into(),
OsString::from("-iframework"),
ios_support.join("System/Library/Frameworks").into(),
{
let mut s = OsString::from("-L");
s.push(ios_support.join("usr/lib"));
s
},
{
let mut s = OsString::from("-F");
s.push(ios_support.join("System/Library/Frameworks"));
s
},
]);
}
}
Ok(())
}
fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command {
let mut cmd = Command::new(prog);
self.env.apply(&mut cmd);
cmd
}
fn prefer_clang(&self) -> bool {
matches!(
self.env.inherited().rustc_codegen_flags(),
Ok(Some(flags)) if flags.linker_plugin_lto()
)
}
fn get_base_compiler(&self) -> Result<Tool, Error> {
let out_dir = self.get_out_dir().ok();
if let Some(c) = &self.compiler {
return Ok(Tool::new(
(**c).to_owned(),
&self.env,
&self.build_cache.cached_compiler_family,
&self.cargo_output,
out_dir,
));
}
let target = self.get_target()?;
let raw_target = self.get_raw_target()?;
let prefer_clang_cl = target.env == "msvc"
&& self
.get_env("CC_PREFER_CLANG_CL_OVER_MSVC")
.filter(|v| !v.is_empty())
.map_or(self.prefer_clang_cl_over_msvc, |v| env_value_is_true(v));
let msvc = if prefer_clang_cl {
"clang-cl.exe"
} else {
"cl.exe"
};
let (env, gnu, traditional, clang) = if self.cpp {
("CXX", "g++", "c++", "clang++")
} else {
("CC", "gcc", "cc", "clang")
};
let fallback = Cow::Borrowed(Path::new(traditional));
let default = if cfg!(target_os = "solaris") || cfg!(target_os = "illumos") {
Cow::Borrowed(Path::new(gnu))
} else if self.prefer_clang() || target.abi == "pauthtest" {
self.which(Path::new(clang), None)
.map(Cow::Owned)
.unwrap_or(fallback)
} else {
fallback
};
let cl_exe = self.find_msvc_tools_find_tool(&target, msvc);
let tool_opt: Option<Tool> = self
.env_tool(env)
.map(|(tool, wrapper, args)| {
let mut t = Tool::with_args(
tool,
args.clone(),
&self.env,
&self.build_cache.cached_compiler_family,
&self.cargo_output,
out_dir,
);
if let Some(cc_wrapper) = wrapper {
t.cc_wrapper_path = Some(Path::new(&cc_wrapper).to_owned());
}
for arg in args {
t.cc_wrapper_args.push(arg.into());
}
t
})
.or_else(|| {
if target.os == "emscripten" {
let tool = if self.cpp { "em++" } else { "emcc" };
if cfg!(windows) {
let mut t = Tool::with_family(
PathBuf::from("cmd"),
ToolFamily::Clang { zig_cc: false },
self.env.inherited().clone(),
);
t.push_arg_str("/c");
t.args.push(format!("{tool}.bat").into());
Some(t)
} else {
Some(Tool::new(
PathBuf::from(tool),
&self.env,
&self.build_cache.cached_compiler_family,
&self.cargo_output,
out_dir,
))
}
} else {
None
}
})
.or_else(|| {
let mut t = cl_exe.clone()?;
self.apply_rustc_wrapper_fallback(&mut t);
Some(t)
});
let tool = match tool_opt {
Some(t) => t,
None => {
let compiler: PathBuf = if cfg!(windows) && target.os == "windows" {
if target.env == "msvc" {
msvc.into()
} else {
let cc = if target.abi == "llvm" { clang } else { gnu };
format!("{cc}.exe").into()
}
} else if target.os == "ios"
|| target.os == "watchos"
|| target.os == "tvos"
|| target.os == "visionos"
{
clang.into()
} else if target.os == "android" {
autodetect_android_compiler(raw_target, gnu, clang, self.env.inherited())
} else if target.os == "cloudabi" {
format!(
"{}-{}-{}-{}",
target.full_arch, target.vendor, target.os, traditional
)
.into()
} else if target.os == "wasi" {
self.autodetect_wasi_compiler(raw_target, clang)
} else if target.arch == "wasm32" || target.arch == "wasm64" {
clang.into()
} else if target.os == "vxworks" {
if self.cpp { "wr-c++" } else { "wr-cc" }.into()
} else if target.arch == "arm" && target.vendor == "kmc" {
format!("arm-kmc-eabi-{gnu}").into()
} else if target.arch == "aarch64" && target.vendor == "kmc" {
format!("aarch64-kmc-elf-{gnu}").into()
} else if target.os == "nto" || target.os == "qnx" {
if self.cpp { "q++" } else { "qcc" }.into()
} else if self.get_is_cross_compile()? {
let prefix = self.prefix_for_target(raw_target);
match prefix {
Some(prefix) => {
let cc = if target.abi == "llvm" { clang } else { gnu };
format!("{prefix}-{cc}").into()
}
None => default.into(),
}
} else {
default.into()
};
let mut t = Tool::new(
compiler,
&self.env,
&self.build_cache.cached_compiler_family,
&self.cargo_output,
out_dir,
);
self.apply_rustc_wrapper_fallback(&mut t);
t
}
};
let mut tool = if self.cuda {
assert!(
tool.args.is_empty(),
"CUDA compilation currently assumes empty pre-existing args"
);
let nvcc = match self.getenv_with_target_prefixes("NVCC") {
Err(_) => PathBuf::from("nvcc"),
Ok(nvcc) => PathBuf::from(nvcc),
};
let mut nvcc_tool = Tool::with_features(
nvcc,
vec![],
self.cuda,
&self.env,
&self.build_cache.cached_compiler_family,
&self.cargo_output,
out_dir,
);
if self.ccbin {
nvcc_tool
.args
.push(format!("-ccbin={}", tool.path.display()).into());
}
self.apply_rustc_wrapper_fallback(&mut nvcc_tool);
nvcc_tool.family = tool.family;
nvcc_tool
} else {
tool
};
if cfg!(windows) && android_clang_compiler_uses_target_arg_internally(&tool.path) {
if let Some(path) = tool.path.file_name() {
let file_name = path.to_string_lossy().to_ascii_lowercase();
let (target, clang) = file_name.split_at(file_name.rfind('-').unwrap());
tool.has_internal_target_arg = true;
tool.path.set_file_name(clang.trim_start_matches('-'));
tool.path.set_extension("exe");
tool.args.push(format!("--target={target}").into());
if target.contains("i686-linux-android") {
let (_, version) = target.split_at(target.rfind('d').unwrap() + 1);
if let Ok(version) = version.parse::<u32>() {
if version > 15 && version < 25 {
tool.push_arg_str("-mstackrealign");
}
}
}
};
}
if !cfg!(windows) && target.os == "windows" && is_llvm_mingw_wrapper(&tool.path) {
tool.has_internal_target_arg = true;
}
if let Some(cl_exe) = cl_exe {
if tool.family == (ToolFamily::Msvc { clang_cl: true })
&& tool.env.is_empty()
&& target.env == "msvc"
{
tool.env = cl_exe.env;
}
}
if target.env == "msvc" && tool.family == ToolFamily::Gnu {
self.cargo_output
.print_warning(&"GNU compiler is not supported for this target");
}
if target.abi == "pauthtest" {
match tool.family {
ToolFamily::Clang { .. } => {}
_ => {
return Err(Error::new(
ErrorKind::ToolNotFound,
format!(
"target '{}' requires a Clang-based toolchain, but found {:?} ({})",
raw_target,
tool.family,
tool.path.display()
),
));
}
}
}
Ok(tool)
}
fn apply_rustc_wrapper_fallback(&self, tool: &mut Tool) {
if let Some(cc_wrapper) = self.rustc_wrapper_fallback() {
tool.cc_wrapper_path = Some(Path::new(cc_wrapper).to_owned());
}
}
fn rustc_wrapper_fallback(&self) -> Option<&OsStr> {
const VALID_WRAPPERS: &[&str] = &["sccache", "cachepot", "buildcache", "kache"];
let rustc_wrapper = self.get_cargo_env("RUSTC_WRAPPER")?;
let wrapper_path = Path::new(rustc_wrapper);
let wrapper_stem = wrapper_path.file_stem()?;
VALID_WRAPPERS
.iter()
.any(|wrapper| wrapper_stem.eq_ignore_ascii_case(wrapper))
.then_some(rustc_wrapper)
}
fn env_tool(&self, name: &str) -> Option<(PathBuf, Option<Cow<'_, OsStr>>, Vec<String>)> {
let tool = self.getenv_with_target_prefixes(name).ok()?;
let tool = tool.to_string_lossy();
let tool = tool.trim();
if tool.is_empty() {
return None;
}
if let Some(exe) = check_exe(Path::new(tool).into()) {
return Some((
exe,
self.rustc_wrapper_fallback().map(Cow::Borrowed),
Vec::new(),
));
}
let mut known_wrappers = vec![
"ccache",
"distcc",
"sccache",
"icecc",
"cachepot",
"buildcache",
"kache",
];
if let Some(custom_wrapper) = self
.get_env("CC_KNOWN_WRAPPER_CUSTOM")
.and_then(|v| v.to_str())
{
known_wrappers.push(custom_wrapper);
}
let mut parts = tool.split_whitespace();
let maybe_wrapper = parts.next()?;
let file_stem = Path::new(maybe_wrapper).file_stem()?.to_str()?;
if known_wrappers
.iter()
.any(|wrapper| wrapper.eq_ignore_ascii_case(file_stem))
{
if let Some(compiler) = parts.next() {
return Some((
compiler.into(),
Some(Cow::Owned(maybe_wrapper.into())),
parts.map(|s| s.to_string()).collect(),
));
}
}
Some((
maybe_wrapper.into(),
self.rustc_wrapper_fallback().map(Cow::Borrowed),
parts.map(|s| s.to_string()).collect(),
))
}
fn get_cpp_link_stdlib(&self) -> Result<Option<&Path>, Error> {
match &self.cpp_link_stdlib {
Some(s) => Ok(s.as_deref().map(Path::new)),
None => {
if let Ok(stdlib) = self.getenv_with_target_prefixes("CXXSTDLIB") {
Ok((!stdlib.is_empty()).then(|| Path::new(&**stdlib)))
} else {
let target = self.get_target()?;
if target.env == "msvc" {
Ok(None)
} else if target.vendor == "apple"
|| target.os == "freebsd"
|| target.os == "openbsd"
|| target.os == "aix"
|| (target.os == "linux" && target.env == "ohos")
|| target.os == "wasi"
|| target.abi == "pauthtest"
{
Ok(Some(Path::new("c++")))
} else if target.os == "android" {
Ok(Some(Path::new("c++_shared")))
} else if self.compiler_uses_libcxx(&target) {
Ok(Some(Path::new("c++")))
} else {
Ok(Some(Path::new("stdc++")))
}
}
}
}
}
fn compiler_uses_libcxx(&self, target: &TargetInfo<'_>) -> bool {
if !self.cargo_output.metadata || self.cuda || target.arch == "wasm32" {
return false;
}
let mut quiet = self.clone();
quiet.cargo_output.metadata = false;
quiet.cargo_output.warnings = false;
if quiet.is_disabled() {
return false;
}
let compiler = match quiet.try_get_compiler() {
Ok(compiler) if compiler.is_like_clang() => compiler,
_ => return false,
};
if compiler
.args()
.iter()
.any(|arg| arg == "-nostdinc++" || arg == "-nostdinc")
{
return false;
}
compiler
.uses_libcxx(
&self.build_cache.cached_uses_libcxx,
&quiet.cargo_output,
quiet.get_out_dir().ok(),
)
.unwrap_or(false)
}
fn get_cpp_link_stdlib_static(&self) -> bool {
self.cpp_link_stdlib_static
|| self
.getenv_with_target_prefixes("CXXSTDLIB_STATIC")
.map_or(false, |s| env_value_is_true(s))
}
pub fn get_archiver(&self) -> Command {
match self.try_get_archiver() {
Ok(tool) => tool,
Err(e) => fail(&e.message),
}
}
pub fn try_get_archiver(&self) -> Result<Command, Error> {
Ok(self.try_get_archiver_and_flags()?.0)
}
fn try_get_archiver_and_flags(&self) -> Result<(Command, PathBuf, bool, bool), Error> {
let (mut cmd, name, any_ar_args) = self.get_base_archiver()?;
let mut any_flags = false;
if let Some(flags) = self.envflags("ARFLAGS")? {
any_flags = true;
cmd.args(flags);
}
for flag in &self.ar_flags {
any_flags = true;
cmd.arg(&**flag);
}
Ok((cmd, name, any_flags, any_flags || any_ar_args))
}
fn get_base_archiver(&self) -> Result<(Command, PathBuf, bool), Error> {
if let Some(ref a) = self.archiver {
let archiver = &**a;
return Ok((self.cmd(archiver), archiver.into(), false));
}
self.get_base_archiver_variant("AR", "ar")
}
pub fn get_ranlib(&self) -> Command {
match self.try_get_ranlib() {
Ok(tool) => tool,
Err(e) => fail(&e.message),
}
}
pub fn try_get_ranlib(&self) -> Result<Command, Error> {
let mut cmd = self.get_base_ranlib()?;
if let Some(flags) = self.envflags("RANLIBFLAGS")? {
cmd.args(flags);
}
Ok(cmd)
}
fn get_base_ranlib(&self) -> Result<Command, Error> {
if let Some(ref r) = self.ranlib {
return Ok(self.cmd(&**r));
}
Ok(self.get_base_archiver_variant("RANLIB", "ranlib")?.0)
}
fn get_base_archiver_variant(
&self,
env: &str,
tool: &str,
) -> Result<(Command, PathBuf, bool), Error> {
let target = self.get_target()?;
let mut name = PathBuf::new();
let mut any_args = false;
let tool_opt: Option<Command> = self
.env_tool(env)
.map(|(tool, _wrapper, args)| {
name.clone_from(&tool);
any_args = !args.is_empty();
let mut cmd = self.cmd(tool);
cmd.args(args);
cmd
})
.or_else(|| {
if target.os == "emscripten" {
if cfg!(windows) {
let mut cmd = self.cmd("cmd");
name = format!("em{tool}.bat").into();
cmd.arg("/c").arg(&name);
Some(cmd)
} else {
name = format!("em{tool}").into();
Some(self.cmd(&name))
}
} else if target.arch == "wasm32" || target.arch == "wasm64" {
let compiler = self.get_base_compiler().ok()?;
if compiler.is_like_clang() {
name = format!("llvm-{tool}").into();
self.search_programs(&compiler.path, &name, &self.cargo_output)
.map(|name| self.cmd(name))
} else {
None
}
} else {
None
}
});
let tool = match tool_opt {
Some(t) => t,
None => {
if target.os == "android" {
name = format!("llvm-{tool}").into();
let mut probe = Command::new(&name);
probe.arg("--version").set_ar_detection_env(&self.env);
match probe.status() {
Ok(status) if status.success() => (),
_ => {
let raw_target = self.get_raw_target()?;
name = format!("{}-{}", raw_target.replace("armv7", "arm"), tool).into()
}
}
self.cmd(&name)
} else if target.env == "msvc" {
let compiler = self.get_base_compiler()?;
if let Some(lib) = self.find_llvm_tool_next_to_clang_cl(&compiler, "llvm-lib") {
name = lib;
self.cmd(&name)
} else {
name = PathBuf::from("lib.exe");
let mut cmd = match self.find_msvc_tools_find(&target, "lib.exe") {
Some(t) => t,
None => self.cmd("lib.exe"),
};
if target.full_arch == "arm64ec" {
cmd.arg("/machine:arm64ec");
}
cmd
}
} else if target.os == "illumos" {
name = format!("g{tool}").into();
self.cmd(&name)
} else if target.os == "vxworks" {
name = format!("wr-{tool}").into();
self.cmd(&name)
} else if target.os == "nto" || target.os == "qnx" {
name = match target.full_arch {
"i686" | "i586" => format!("ntox86-{tool}").into(),
"x86" | "aarch64" | "x86_64" => {
format!("nto{}-{}", target.arch, tool).into()
}
_ => {
return Err(Error::new(
ErrorKind::InvalidTarget,
format!("Unknown architecture for Neutrino QNX: {}", target.arch),
))
}
};
self.cmd(&name)
} else if self.get_is_cross_compile()? {
match self.prefix_for_target(self.get_raw_target()?) {
Some(prefix) => {
let chosen = ["", "-gcc"]
.iter()
.filter_map(|infix| {
let target_p = format!("{prefix}{infix}-{tool}");
let mut cmd = Command::new(&target_p);
self.env.inherited().apply(&mut cmd);
let status = cmd
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok()?;
status.success().then_some(target_p)
})
.next()
.unwrap_or_else(|| tool.to_string());
name = chosen.into();
self.cmd(&name)
}
None => {
name = tool.into();
self.cmd(&name)
}
}
} else {
name = tool.into();
self.cmd(&name)
}
}
};
Ok((tool, name, any_args))
}
const GNU_PREFIXES: &'static [(&'static str, &'static str)] = &[
("aarch64-pc-windows-gnullvm", "aarch64-w64-mingw32"),
("aarch64-unknown-helenos", "aarch64-helenos"),
("aarch64-unknown-linux-gnu", "aarch64-linux-gnu"),
("aarch64-unknown-linux-musl", "aarch64-linux-musl"),
("aarch64-unknown-linux-relibc", "aarch64-linux-relibc"),
("aarch64-unknown-netbsd", "aarch64--netbsd"),
("aarch64-uwp-windows-gnu", "aarch64-w64-mingw32"),
("aarch64_be-unknown-linux-gnu", "aarch64_be-linux-gnu"),
("arm-unknown-linux-gnueabi", "arm-linux-gnueabi"),
("arm-unknown-linux-gnueabihf", "arm-linux-gnueabihf"),
("arm-unknown-linux-musleabi", "arm-linux-musleabi"),
("arm-unknown-linux-musleabihf", "arm-linux-musleabihf"),
("arm-unknown-netbsd-eabi", "arm--netbsdelf-eabi"),
("armeb-unknown-linux-gnueabi", "armeb-linux-gnueabi"),
("armebv7r-none-eabi", "arm-none-eabi"),
("armebv7r-none-eabihf", "arm-none-eabi"),
("armv4t-none-eabi", "arm-none-eabi"),
("armv4t-unknown-linux-gnueabi", "arm-linux-gnueabi"),
("armv5te-none-eabi", "arm-none-eabi"),
("armv5te-unknown-helenos-eabi", "arm-helenos"),
("armv5te-unknown-linux-gnueabi", "arm-linux-gnueabi"),
("armv5te-unknown-linux-musleabi", "arm-linux-gnueabi"),
("armv6-none-eabi", "arm-none-eabi"),
("armv6-none-eabihf", "arm-none-eabi"),
("armv6-unknown-netbsd-eabihf", "armv6--netbsdelf-eabihf"),
("armv7-unknown-linux-gnueabi", "arm-linux-gnueabi"),
("armv7-unknown-linux-gnueabihf", "arm-linux-gnueabihf"),
("armv7-unknown-linux-musleabihf", "arm-linux-musleabihf"),
("armv7-unknown-netbsd-eabihf", "armv7--netbsdelf-eabihf"),
("armv7a-none-eabi", "arm-none-eabi"),
("armv7a-none-eabihf", "arm-none-eabi"),
("armv7neon-unknown-linux-gnueabihf", "arm-linux-gnueabihf"),
("armv7neon-unknown-linux-musleabihf", "arm-linux-musleabihf"),
("armv7r-none-eabi", "arm-none-eabi"),
("armv7r-none-eabihf", "arm-none-eabi"),
("armv8r-none-eabihf", "arm-none-eabi"),
("hexagon-unknown-linux-musl", "hexagon-linux-musl"),
("i586-unknown-linux-musl", "musl"),
("i686-pc-windows-gnu", "i686-w64-mingw32"),
("i686-pc-windows-gnullvm", "i686-w64-mingw32"),
("i686-unknown-helenos", "i686-helenos"),
("i686-unknown-linux-musl", "musl"),
("i686-unknown-netbsd", "i486--netbsdelf"),
("i686-uwp-windows-gnu", "i686-w64-mingw32"),
("loongarch64-unknown-linux-gnu", "loongarch64-linux-gnu"),
("m68k-unknown-linux-gnu", "m68k-linux-gnu"),
("mips-unknown-linux-gnu", "mips-linux-gnu"),
("mips-unknown-linux-musl", "mips-linux-musl"),
("mips64-unknown-linux-gnuabi64", "mips64-linux-gnuabi64"),
("mips64el-unknown-linux-gnuabi64", "mips64el-linux-gnuabi64"),
("mipsel-unknown-linux-gnu", "mipsel-linux-gnu"),
("mipsel-unknown-linux-musl", "mipsel-linux-musl"),
("mipsisa32r6-unknown-linux-gnu", "mipsisa32r6-linux-gnu"),
("mipsisa32r6el-unknown-linux-gnu", "mipsisa32r6el-linux-gnu"),
(
"mipsisa64r6-unknown-linux-gnuabi64",
"mipsisa64r6-linux-gnuabi64",
),
(
"mipsisa64r6el-unknown-linux-gnuabi64",
"mipsisa64r6el-linux-gnuabi64",
),
("powerpc-unknown-helenos", "ppc-helenos"),
("powerpc-unknown-linux-gnu", "powerpc-linux-gnu"),
("powerpc-unknown-linux-gnuspe", "powerpc-linux-gnuspe"),
("powerpc-unknown-netbsd", "powerpc--netbsd"),
("powerpc64-unknown-linux-gnu", "powerpc64-linux-gnu"),
("powerpc64le-unknown-linux-gnu", "powerpc64le-linux-gnu"),
("riscv32gc-unknown-linux-gnu", "riscv32-linux-gnu"),
("riscv32gc-unknown-linux-musl", "riscv32-linux-musl"),
("riscv32imac-esp-espidf", "riscv32-esp-elf"),
("riscv32imc-esp-espidf", "riscv32-esp-elf"),
("riscv64a23-unknown-linux-gnu", "riscv64-linux-gnu"),
("riscv64gc-unknown-linux-gnu", "riscv64-linux-gnu"),
("riscv64gc-unknown-linux-musl", "riscv64-linux-musl"),
("riscv64gc-unknown-netbsd", "riscv64--netbsd"),
("s390x-unknown-linux-gnu", "s390x-linux-gnu"),
("sparc-unknown-linux-gnu", "sparc-linux-gnu"),
("sparc64-unknown-helenos", "sparc64-helenos"),
("sparc64-unknown-linux-gnu", "sparc64-linux-gnu"),
("sparc64-unknown-netbsd", "sparc64--netbsd"),
("sparcv9-sun-solaris", "sparcv9-sun-solaris"),
("thumbv4t-none-eabi", "arm-none-eabi"),
("thumbv5te-none-eabi", "arm-none-eabi"),
("thumbv6-none-eabi", "arm-none-eabi"),
("thumbv6m-none-eabi", "arm-none-eabi"),
("thumbv7-unknown-linux-gnueabihf", "arm-linux-gnueabihf"),
("thumbv7-unknown-linux-musleabihf", "arm-linux-musleabihf"),
("thumbv7a-none-eabi", "arm-none-eabi"),
("thumbv7a-none-eabihf", "arm-none-eabi"),
("thumbv7em-none-eabi", "arm-none-eabi"),
("thumbv7em-none-eabihf", "arm-none-eabi"),
("thumbv7m-none-eabi", "arm-none-eabi"),
("thumbv7neon-unknown-linux-gnueabihf", "arm-linux-gnueabihf"),
(
"thumbv7neon-unknown-linux-musleabihf",
"arm-linux-musleabihf",
),
("thumbv7r-none-eabi", "arm-none-eabi"),
("thumbv7r-none-eabihf", "arm-none-eabi"),
("thumbv8m.base-none-eabi", "arm-none-eabi"),
("thumbv8m.main-none-eabi", "arm-none-eabi"),
("thumbv8m.main-none-eabihf", "arm-none-eabi"),
("thumbv8r-none-eabihf", "arm-none-eabi"),
("x86_64-pc-windows-gnu", "x86_64-w64-mingw32"),
("x86_64-pc-windows-gnullvm", "x86_64-w64-mingw32"),
("x86_64-rumprun-netbsd", "x86_64-rumprun-netbsd"),
("x86_64-unknown-helenos", "amd64-helenos"),
("x86_64-unknown-netbsd", "x86_64--netbsd"),
("x86_64-uwp-windows-gnu", "x86_64-w64-mingw32"),
("xtensa-esp32-espidf", "xtensa-esp-elf"),
("xtensa-esp32-none-elf", "xtensa-esp-elf"),
("xtensa-esp32s2-espidf", "xtensa-esp-elf"),
("xtensa-esp32s2-none-elf", "xtensa-esp-elf"),
("xtensa-esp32s3-espidf", "xtensa-esp-elf"),
("xtensa-esp32s3-none-elf", "xtensa-esp-elf"),
];
const RISCV32_ELF: &'static [&'static str] = &[
"riscv32-unknown-elf",
"riscv64-unknown-elf",
"riscv-none-embed",
];
const RISCV64_ELF: &'static [&'static str] = &[
"riscv64-unknown-elf",
"riscv32-unknown-elf",
"riscv-none-embed",
];
const GNU_PREFIX_CANDIDATES: &'static [(&'static str, &'static [&'static str])] = &[
(
"i686-unknown-linux-gnu",
&["i686-linux-gnu", "x86_64-linux-gnu"],
),
("riscv32i-unknown-none-elf", Self::RISCV32_ELF),
("riscv32im-unknown-none-elf", Self::RISCV32_ELF),
("riscv32imac-unknown-none-elf", Self::RISCV32_ELF),
("riscv32imafc-unknown-none-elf", Self::RISCV32_ELF),
("riscv32imac-unknown-xous-elf", Self::RISCV32_ELF),
("riscv32imc-unknown-none-elf", Self::RISCV32_ELF),
("riscv64gc-unknown-none-elf", Self::RISCV64_ELF),
("riscv64imac-unknown-none-elf", Self::RISCV64_ELF),
("x86_64-unknown-linux-gnu", &["x86_64-linux-gnu"]),
("x86_64-unknown-linux-musl", &["x86_64-linux-musl", "musl"]),
(
"x86_64-unknown-linux-relibc",
&["x86_64-linux-relibc", "relibc"],
),
];
fn prefix_for_target(&self, target: &str) -> Option<Cow<'static, str>> {
self.get_env("CROSS_COMPILE")
.map(|s| s.to_string_lossy().trim_end_matches('-').to_owned())
.map(Cow::Owned)
.or_else(|| {
self.get_cargo_env("RUSTC_LINKER").and_then(|var| {
var.to_string_lossy()
.strip_suffix("-gcc")
.map(str::to_string)
.map(Cow::Owned)
})
})
.or_else(|| {
if let Some(prefix) = lookup_sorted(Self::GNU_PREFIXES, target) {
return Some(Cow::Borrowed(prefix));
}
for (candidate, prefixes) in Self::GNU_PREFIX_CANDIDATES {
if *candidate == target {
return self.find_working_gnu_prefix(prefixes).map(Cow::Borrowed);
}
}
None
})
}
fn find_working_gnu_prefix(&self, prefixes: &[&'static str]) -> Option<&'static str> {
let suffix = if self.cpp { "-g++" } else { "-gcc" };
let extension = std::env::consts::EXE_SUFFIX;
self.get_env("PATH")
.and_then(|path_entries| {
env::split_paths(path_entries).find_map(|path_entry| {
for prefix in prefixes {
let target_compiler = format!("{prefix}{suffix}{extension}");
if path_entry.join(&target_compiler).exists() {
return Some(prefix);
}
}
None
})
})
.copied()
.or_else(|| prefixes.first().copied())
}
fn get_target(&self) -> Result<TargetInfo<'_>, Error> {
match &self.target {
Some(t) if Some(OsStr::new(&**t)) != self.get_cargo_env("TARGET") => {
TargetInfo::from_rustc_target(t)
}
_ => self.env.inherited().cargo_target(),
}
}
fn is_cargo_target(&self) -> bool {
match &self.target {
Some(t) => Some(OsStr::new(&**t)) == self.get_cargo_env("TARGET"),
None => true,
}
}
fn get_raw_target(&self) -> Result<&str, Error> {
match self.target.as_deref() {
Some(t) => Ok(t),
None => self.get_cargo_env_str("TARGET"),
}
}
fn get_host(&self) -> Result<&str, Error> {
match &self.host {
Some(h) => Ok(h),
None => self.get_cargo_env_str("HOST"),
}
}
fn get_is_cross_compile(&self) -> Result<bool, Error> {
let target = self.get_raw_target()?;
Ok(self.get_host()? != target)
}
fn get_opt_level(&self) -> Result<&str, Error> {
match self.opt_level.as_deref() {
Some(ol) => Ok(ol),
None => self.get_cargo_env_str("OPT_LEVEL"),
}
}
fn get_debug(&self) -> bool {
match self.get_debug_str() {
Err(_) => false,
Ok(d) => match d {
"" | "0" | "false" | "none" => false,
_ => true,
},
}
}
fn get_debug_str(&self) -> Result<&str, Error> {
match self.debug.as_deref() {
Some(d) => Ok(d),
None => self.get_cargo_env_str("DEBUG"),
}
}
fn get_shell_escaped_flags(&self) -> bool {
self.shell_escaped_flags
.unwrap_or_else(|| self.get_env_boolean("CC_SHELL_ESCAPED_FLAGS"))
}
fn get_dwarf_version(&self) -> Option<u32> {
let target = self.get_target().ok()?;
if matches!(
target.os,
"android" | "dragonfly" | "freebsd" | "netbsd" | "openbsd"
) || target.vendor == "apple"
|| (target.os == "windows" && target.env == "gnu")
{
Some(2)
} else if target.os == "linux" {
Some(4)
} else {
None
}
}
fn get_force_frame_pointer(&self) -> bool {
self.force_frame_pointer.unwrap_or_else(|| self.get_debug())
}
fn get_out_dir(&self) -> Result<&Path, Error> {
match self.out_dir.as_deref() {
Some(p) => Ok(p),
None => self.get_cargo_env("OUT_DIR").map(Path::new).ok_or_else(|| {
Error::new(
ErrorKind::EnvVarNotFound,
"Environment variable OUT_DIR not defined.",
)
}),
}
}
fn get_env(&self, v: &str) -> Option<&Arc<OsStr>> {
self.rerun_if_env_changed(v);
let r = self.env.inherited().get(OsStr::new(v));
self.cargo_output.print_metadata(&format_args!(
"{} = {}",
v,
OptionOsStrDisplay(r.map(|r| &**r))
));
r
}
fn get_cargo_env(&self, key: &str) -> Option<&OsStr> {
self.env.inherited().get(OsStr::new(key)).map(|v| &**v)
}
fn get_cargo_env_str(&self, key: &str) -> Result<&str, Error> {
self.env.inherited().get_cargo_var(key)
}
fn get_env_overridable(&self, key: &str) -> Option<&Arc<OsStr>> {
if let Some((_key, val)) = self.env.explicit.iter().find(|(k, _)| k.as_ref() == key) {
return Some(val);
}
self.get_env(key)
}
fn get_env_boolean(&self, key: &str) -> bool {
match self.get_env(key) {
Some(s) => env_value_is_true(s),
None => false,
}
}
fn rerun_if_env_changed(&self, v: &str) {
if !self.emit_rerun_if_env_changed || !self.cargo_output.metadata || v == "PATH" {
return;
}
if !push_if_new(&self.build_cache.emitted_rerun_if_env_changed, v) {
return;
}
self.cargo_output
.print_metadata(&format_args!("cargo:rerun-if-env-changed={v}"));
}
fn rerun_if_compiler_env_changed(&self, compiler: &Tool, target: &TargetInfo<'_>) {
let gcc = compiler.family == ToolFamily::Gnu;
let clang_cl = compiler.family == ToolFamily::Msvc { clang_cl: true };
let cl_exe = compiler.family == ToolFamily::Msvc { clang_cl: false };
let clang = clang_cl || matches!(compiler.family, ToolFamily::Clang { .. });
let zig_cc = compiler.family == ToolFamily::Clang { zig_cc: true };
let msvc_target = target.env == "msvc";
let apple_target = target.vendor == "apple";
let macos_host = matches!(self.get_host(), Ok(host) if host.ends_with("-apple-darwin"));
let groups: &[(bool, &[&str])] = &[
(
gcc || clang,
&[
"CPATH",
"C_INCLUDE_PATH",
"CPLUS_INCLUDE_PATH",
"OBJC_INCLUDE_PATH",
"OBJCPLUS_INCLUDE_PATH",
"COMPILER_PATH",
"SOURCE_DATE_EPOCH",
],
),
(gcc, &["GCC_EXEC_PREFIX"]),
(clang, &["CCC_OVERRIDE_OPTIONS", "CLANG_NO_DEFAULT_CONFIG"]),
(zig_cc, &["ZIG_LIB_DIR", "ZIG_LIBC"]),
(
cl_exe || (clang && msvc_target),
&["INCLUDE", "EXTERNAL_INCLUDE"],
),
(clang && msvc_target, &["VCToolsInstallDir", "VCINSTALLDIR"]),
(cl_exe || clang_cl, &["CL", "_CL_"]),
(cl_exe, &["LIBPATH"]),
(
self.cuda,
&["NVCC_PREPEND_FLAGS", "NVCC_APPEND_FLAGS", "NVCC_CCBIN"],
),
(self.cuda && msvc_target, &["VSCMD_VER"]),
(
target.os == "emscripten",
&[
"EMCC_CFLAGS",
"EMCC_LOCAL_PORTS",
"EM_CONFIG",
"EM_CACHE",
"EM_PORTS",
"EM_LLVM_ROOT",
"EM_CLANG_ADD_VERSION",
"EM_COMPILER_WRAPPER",
],
),
(
apple_target || macos_host,
&["DEVELOPER_DIR", "TOOLCHAINS", "SDKROOT"],
),
(apple_target, &["RC_DEBUG_OPTIONS", "RC_DEBUG_PREFIX_MAP"]),
];
for &(reads, vars) in groups {
if reads {
for var in vars {
self.rerun_if_env_changed(var);
}
}
}
if apple_target {
if let Some(var) = target.apple_deployment_target_var() {
self.rerun_if_env_changed(var);
}
}
}
fn target_envs(&self, env: &str) -> Result<[String; 4], Error> {
let target = self.get_raw_target()?;
let kind = if self.get_is_cross_compile()? {
"TARGET"
} else {
"HOST"
};
let target_u = target.replace("-", "_").replace(".", "_");
Ok([
format!("{env}_{target}"),
format!("{env}_{target_u}"),
format!("{kind}_{env}"),
env.to_string(),
])
}
fn getenv_with_target_prefixes(&self, env: &str) -> Result<&Arc<OsStr>, Error> {
let res = self
.target_envs(env)?
.iter()
.filter_map(|env| self.get_env(env))
.next();
match res {
Some(res) => Ok(res),
None => Err(Error::new(
ErrorKind::EnvVarNotFound,
format!("could not find environment variable {env}"),
)),
}
}
fn envflags(&self, env: &str) -> Result<Option<Vec<String>>, Error> {
let mut any_set = false;
let mut res = vec![];
for env in self.target_envs(env)?.iter().rev() {
if let Some(var) = self.get_env(env) {
any_set = true;
let var = var.to_string_lossy();
if self.get_shell_escaped_flags() {
res.extend(Shlex::new(&var));
} else {
res.extend(var.split_ascii_whitespace().map(ToString::to_string));
}
}
}
Ok(if any_set { Some(res) } else { None })
}
fn is_disabled(&self) -> bool {
self.get_env_boolean("CC_FORCE_DISABLE")
}
fn fix_env_for_apple_os(&self, cmd: &mut Command) -> Result<(), Error> {
let target = self.get_target()?;
if cfg!(target_os = "macos") && target.os == "macos" {
cmd.env_remove("IPHONEOS_DEPLOYMENT_TARGET");
}
Ok(())
}
fn apple_sdk_root_inner(&self, sdk: &str) -> Result<Arc<OsStr>, Error> {
if let Some(sdkroot) = self.get_env_overridable("SDKROOT") {
let p = Path::new(sdkroot);
let does_sdkroot_contain = |strings: &[&str]| {
let sdkroot_str = p.to_string_lossy();
strings.iter().any(|s| sdkroot_str.contains(s))
};
match sdk {
"appletvos"
if does_sdkroot_contain(&["TVSimulator.platform", "MacOSX.platform"]) => {}
"appletvsimulator"
if does_sdkroot_contain(&["TVOS.platform", "MacOSX.platform"]) => {}
"iphoneos"
if does_sdkroot_contain(&["iPhoneSimulator.platform", "MacOSX.platform"]) => {}
"iphonesimulator"
if does_sdkroot_contain(&["iPhoneOS.platform", "MacOSX.platform"]) => {}
"macosx10.15"
if does_sdkroot_contain(&["iPhoneOS.platform", "iPhoneSimulator.platform"]) => {
}
"watchos"
if does_sdkroot_contain(&["WatchSimulator.platform", "MacOSX.platform"]) => {}
"watchsimulator"
if does_sdkroot_contain(&["WatchOS.platform", "MacOSX.platform"]) => {}
"xros" if does_sdkroot_contain(&["XRSimulator.platform", "MacOSX.platform"]) => {}
"xrsimulator" if does_sdkroot_contain(&["XROS.platform", "MacOSX.platform"]) => {}
_ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {}
_ => return Ok(Arc::clone(sdkroot)),
}
}
let sdk_path = run_output(
self.cmd("xcrun")
.arg("--show-sdk-path")
.arg("--sdk")
.arg(sdk),
&self.cargo_output,
)?;
let Ok(sdk_path) = String::from_utf8(sdk_path) else {
return Err(Error::new(
ErrorKind::IOError,
"Unable to determine Apple SDK path.",
));
};
Ok(OsStr::new(sdk_path.trim()).into())
}
fn apple_sdk_root(&self, target: &TargetInfo<'_>) -> Result<Arc<OsStr>, Error> {
let sdk = target.apple_sdk_name();
if let Some(ret) = self
.build_cache
.apple_sdk_root_cache
.read()
.expect("apple_sdk_root_cache lock failed")
.get(sdk)
.cloned()
{
return Ok(ret);
}
let sdk_path = self.apple_sdk_root_inner(sdk)?;
self.build_cache
.apple_sdk_root_cache
.write()
.expect("apple_sdk_root_cache lock failed")
.insert(sdk, sdk_path.clone());
Ok(sdk_path)
}
fn apple_deployment_target(&self, target: &TargetInfo<'_>) -> Arc<str> {
let sdk = target.apple_sdk_name();
if let Some(ret) = self
.build_cache
.apple_versions_cache
.read()
.expect("apple_versions_cache lock failed")
.get(sdk)
.cloned()
{
return ret;
}
let default_deployment_from_sdk = || -> Option<Arc<str>> {
let version = run_output(
self.cmd("xcrun")
.arg("--show-sdk-version")
.arg("--sdk")
.arg(sdk),
&self.cargo_output.for_detection_cmd(),
)
.ok()?;
Some(Arc::from(core::str::from_utf8(&version).ok()?.trim()))
};
let deployment_from_env = || -> Option<Arc<str>> {
let name = target.apple_deployment_target_var()?;
self.get_env_overridable(name)?.to_str().map(Arc::from)
};
let maybe_cpp_version_baseline = |deployment_target_ver: Arc<str>| -> Option<Arc<str>> {
if !self.cpp || !matches!(target.os, "macos" | "ios") {
return Some(deployment_target_ver);
}
let Ok(parts) = deployment_target_ver
.split('.')
.map(str::parse::<u32>)
.collect::<Result<Vec<_>, _>>()
else {
self.cargo_output.print_warning(&format_args!(
"ignoring invalid deployment target {deployment_target_ver:?}"
));
return None;
};
let mut deployment_target = parts.into_iter();
match target.os {
"macos" => {
let major = deployment_target.next().unwrap_or(0);
let minor = deployment_target.next().unwrap_or(0);
if major == 10 && minor < 9 {
self.cargo_output.print_warning(&format_args!(
"macOS deployment target ({deployment_target_ver}) too low, it will be increased"
));
return None;
}
}
"ios" => {
let major = deployment_target.next().unwrap_or(0);
if major < 7 {
self.cargo_output.print_warning(&format_args!(
"iOS deployment target ({deployment_target_ver}) too low, it will be increased"
));
return None;
}
}
_ => {}
}
Some(deployment_target_ver)
};
let fallback = match target.os {
"macos" if target.arch == "aarch64" => "11.0",
"macos" => "10.7",
"ios" => "7.0",
"watchos" => "5.0",
"tvos" => "9.0",
"visionos" => "1.0",
os => unreachable!("unknown Apple OS: {}", os),
};
let version = deployment_from_env()
.and_then(maybe_cpp_version_baseline)
.or_else(default_deployment_from_sdk)
.unwrap_or_else(|| {
let fallback: Arc<str> = fallback.into();
maybe_cpp_version_baseline(fallback.clone()).unwrap_or(fallback)
});
self.build_cache
.apple_versions_cache
.write()
.expect("apple_versions_cache lock failed")
.insert(sdk, version.clone());
version
}
fn wasm_musl_sysroot(&self) -> Result<&Arc<OsStr>, Error> {
if let Some(musl_sysroot_path) = self.get_env("WASM_MUSL_SYSROOT") {
Ok(musl_sysroot_path)
} else {
Err(Error::new(
ErrorKind::EnvVarNotFound,
"Environment variable WASM_MUSL_SYSROOT not defined for wasm32. Download sysroot from GitHub & setup environment variable MUSL_SYSROOT targeting the folder.",
))
}
}
fn wasi_sysroot(&self) -> Result<&Arc<OsStr>, Error> {
if let Some(wasi_sysroot_path) = self.get_env("WASI_SYSROOT") {
Ok(wasi_sysroot_path)
} else {
Err(Error::new(
ErrorKind::EnvVarNotFound,
"Environment variable WASI_SYSROOT not defined. Download sysroot from GitHub & setup environment variable WASI_SYSROOT targeting the folder.",
))
}
}
fn cuda_file_count(&self) -> usize {
self.files
.iter()
.filter(|file| file.extension() == Some(OsStr::new("cu")))
.count()
}
fn which(&self, tool: &Path, path_entries: Option<&OsStr>) -> Option<PathBuf> {
let find_exe_in_path = |path_entries: &OsStr| -> Option<PathBuf> {
env::split_paths(path_entries).find_map(|path_entry| check_exe(path_entry.join(tool)))
};
if tool.components().count() > 1 {
check_exe(PathBuf::from(tool))
} else {
path_entries
.and_then(find_exe_in_path)
.or_else(|| find_exe_in_path(self.get_env("PATH")?))
}
}
fn search_programs(
&self,
cc: &Path,
prog: &Path,
cargo_output: &CargoOutput,
) -> Option<PathBuf> {
let search_dirs = run_output(
self.cmd(cc).arg("--print-search-dirs"),
&cargo_output.for_detection_cmd(),
)
.ok()?;
let search_dirs = core::str::from_utf8(&search_dirs).ok()?;
for dirs in search_dirs.split(['\r', '\n']) {
if let Some(path) = dirs.strip_prefix("programs: =") {
return self.which(prog, Some(OsStr::new(path)));
}
}
None
}
fn find_llvm_tool_next_to_clang_cl(&self, compiler: &Tool, tool: &str) -> Option<PathBuf> {
if !compiler.is_like_clang_cl() {
return None;
}
self.search_programs(&compiler.path, Path::new(tool), &self.cargo_output)
.or_else(|| {
let mut path = self.which(&compiler.path, None)?;
path.pop();
path.push(tool);
self.which(&path, None)
})
}
fn find_msvc_tools_find(&self, target: &TargetInfo<'_>, tool: &str) -> Option<Command> {
self.find_msvc_tools_find_tool(target, tool)
.map(|c| c.to_command())
}
fn find_msvc_tools_find_tool(&self, target: &TargetInfo<'_>, tool: &str) -> Option<Tool> {
struct BuildEnvGetter<'s>(&'s Build);
impl ::find_msvc_tools::EnvGetter for BuildEnvGetter<'_> {
fn get_env(&self, name: &str) -> Option<::find_msvc_tools::Env> {
self.0
.get_env(name)
.cloned()
.map(::find_msvc_tools::Env::Arced)
}
}
if target.env != "msvc" {
return None;
}
::find_msvc_tools::find_tool_with_env(target.full_arch, tool, &BuildEnvGetter(self))
.map(|tool| Tool::from_find_msvc_tools(tool, self.env.inherited().clone()))
}
fn autodetect_wasi_compiler(&self, raw_target: &str, clang: &str) -> PathBuf {
if let Some(path) = self.get_env_overridable("WASI_SDK_PATH") {
let target_clang = Path::new(&path)
.join("bin")
.join(format!("{raw_target}-clang"));
if let Some(path) = self.which(&target_clang, None) {
return path;
}
}
clang.into()
}
fn pauthtest_sysroot(&self) -> Result<&Arc<OsStr>, Error> {
if let Some(pauthtest_sysroot) = self.get_env("PAUTHTEST_SYSROOT") {
Ok(pauthtest_sysroot)
} else {
let target = self.get_raw_target()?;
Err(Error::new(
ErrorKind::EnvVarNotFound,
format!(
"Environment variable PAUTHTEST_SYSROOT not defined for the {} target. Please consult target's platform support document for instructions on how to obtain the sysroot and then setup the environment variable PAUTHTEST_SYSROOT.",
target
),
))
}
}
fn pauthtest_resource_dir(&self) -> Result<&Arc<OsStr>, Error> {
if let Some(pauthtest_resource_dir) = self.get_env("PAUTHTEST_RESOURCE_DIR") {
Ok(pauthtest_resource_dir)
} else {
let target = self.get_raw_target()?;
Err(Error::new(
ErrorKind::EnvVarNotFound,
format!(
"Environment variable PAUTHTEST_RESOURCE_DIR not defined for the {} target. Please consult target's platform support document for instructions on how to obtain the sysroot and then setup the environment variable PAUTHTEST_RESOURCE_DIR.",
target
),
))
}
}
}
impl Default for Build {
fn default() -> Build {
Build::new()
}
}
pub fn emit_link_directives<P: AsRef<Path>>(build: &Build, library: P) {
if let Err(e) = try_emit_link_directives(build, library) {
fail(&e.message);
}
}
pub fn try_emit_link_directives<P: AsRef<Path>>(build: &Build, library: P) -> Result<(), Error> {
let library = library.as_ref();
let file_name = library.file_name().and_then(OsStr::to_str).unwrap_or("");
let mut lib_name = file_name
.strip_prefix("lib")
.and_then(|name| name.strip_suffix(".a"));
if lib_name.is_none() && build.get_target()?.env == "msvc" {
lib_name = file_name.strip_suffix(".lib");
}
let search_dir = library.parent().filter(|dir| !dir.as_os_str().is_empty());
match (lib_name, search_dir) {
(Some(lib_name), Some(search_dir)) if !lib_name.is_empty() => {
build.emit_link_directives_for(lib_name, search_dir)
}
_ => Err(Error::new(
ErrorKind::InvalidArgument,
format!(
"`emit_link_directives` expects the path of a static library named \
`lib<name>.a`, or `<name>.lib` on MSVC targets, including its folder, \
got `{}`",
library.display()
),
)),
}
}
fn lib_and_archive_names<'a>(output: &'a str, method: &str) -> Result<(&'a str, String), Error> {
let mut output_components = Path::new(output).components();
match (output_components.next(), output_components.next()) {
(Some(Component::Normal(_)), None) => {}
_ => {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!("argument of `{method}` must be a single normal path component"),
));
}
}
Ok(if output.starts_with("lib") && output.ends_with(".a") {
(&output[3..output.len() - 2], output.to_owned())
} else {
let mut gnu = String::with_capacity(5 + output.len());
gnu.push_str("lib");
gnu.push_str(output);
gnu.push_str(".a");
(output, gnu)
})
}
fn split_off_msvc_linker_flags<T: AsRef<OsStr>>(family: ToolFamily, flags: &[T]) -> (&[T], &[T]) {
let start = match family {
ToolFamily::Msvc { .. } => flags
.iter()
.position(|flag| is_msvc_link_flag(flag.as_ref())),
ToolFamily::Gnu | ToolFamily::Clang { .. } => None,
};
flags.split_at(start.unwrap_or(flags.len()))
}
fn is_msvc_link_flag(flag: &OsStr) -> bool {
matches!(flag.to_str(), Some("/link" | "-link"))
}
fn env_value_is_true(value: &OsStr) -> bool {
value != "0" && value != "false" && value != "no" && !value.is_empty()
}
fn fail(s: &str) -> ! {
eprintln!("\n\nerror occurred in cc-rs: {s}\n\n");
std::process::exit(1);
}
static NEW_STANDALONE_ANDROID_COMPILERS: [&str; 4] = [
"aarch64-linux-android21-clang",
"armv7a-linux-androideabi16-clang",
"i686-linux-android16-clang",
"x86_64-linux-android21-clang",
];
fn android_clang_compiler_uses_target_arg_internally(clang_path: &Path) -> bool {
if let Some(filename) = clang_path.file_name() {
if let Some(filename_str) = filename.to_str() {
if let Some(idx) = filename_str.rfind('-') {
return filename_str[..idx].contains_ignore_ascii_case("android");
}
}
}
false
}
fn is_llvm_mingw_wrapper(clang_path: &Path) -> bool {
if let Some(filename) = clang_path
.file_name()
.and_then(|file_name| file_name.to_str())
{
filename.ends_with_ignore_ascii_case("-w64-mingw32-clang")
|| filename.ends_with_ignore_ascii_case("-w64-mingw32-clang++")
} else {
false
}
}
fn is_libtool(archiver: &Path) -> bool {
archiver.file_name().map_or(false, |name| {
name.to_string_lossy().contains_ignore_ascii_case("libtool")
})
}
fn autodetect_android_compiler(
raw_target: &str,
gnu: &str,
clang: &str,
env: &EnvSnapshot,
) -> PathBuf {
let exists = |program: &str| {
let mut cmd = Command::new(program);
env.apply(&mut cmd);
cmd.output().is_ok()
};
let new_clang_key = match raw_target {
"aarch64-linux-android" => Some("aarch64"),
"armv7-linux-androideabi" => Some("armv7a"),
"i686-linux-android" => Some("i686"),
"x86_64-linux-android" => Some("x86_64"),
_ => None,
};
let new_clang = new_clang_key
.map(|key| {
NEW_STANDALONE_ANDROID_COMPILERS
.iter()
.find(|x| x.starts_with(key))
})
.unwrap_or(None);
if let Some(new_clang) = new_clang {
if exists(new_clang) {
return (*new_clang).into();
}
}
let target = raw_target
.replace("armv7neon", "arm")
.replace("armv7", "arm")
.replace("thumbv7neon", "arm")
.replace("thumbv7", "arm");
let gnu_compiler = format!("{target}-{gnu}");
let clang_compiler = format!("{target}-{clang}");
let clang_compiler_cmd = format!("{target}-{clang}.cmd");
if exists(&gnu_compiler) {
gnu_compiler
} else if cfg!(windows) && exists(&clang_compiler_cmd) {
clang_compiler_cmd
} else {
clang_compiler
}
.into()
}
fn map_darwin_target_from_rust_to_compiler_architecture<'a>(target: &TargetInfo<'a>) -> &'a str {
match target.full_arch {
"aarch64" => "arm64",
"arm64_32" => "arm64_32",
"arm64e" => "arm64e",
"armv7k" => "armv7k",
"armv7s" => "armv7s",
"i386" => "i386",
"i686" => "i386",
"powerpc" => "ppc",
"powerpc64" => "ppc64",
"x86_64" => "x86_64",
"x86_64h" => "x86_64h",
arch => arch,
}
}
fn is_arm(target: &TargetInfo<'_>) -> bool {
matches!(target.arch, "aarch64" | "arm64ec" | "arm")
}
#[derive(Clone, Copy, PartialEq)]
enum AsmFileExt {
DotAsm,
DotS,
}
impl AsmFileExt {
fn from_path(file: &Path) -> Option<Self> {
if let Some(ext) = file.extension() {
if let Some(ext) = ext.to_str() {
let ext = ext.to_lowercase();
match &*ext {
"asm" => return Some(AsmFileExt::DotAsm),
"s" => return Some(AsmFileExt::DotS),
_ => return None,
}
}
}
None
}
}
fn check_exe(exe: PathBuf) -> Option<PathBuf> {
if exe.exists() {
return Some(exe);
}
let exe_ext = std::env::consts::EXE_EXTENSION;
if exe_ext.is_empty() {
return None;
}
let _ = exe.file_name()?;
let with_ext = {
let mut buf = exe.into_os_string();
buf.push(".");
buf.push(exe_ext);
PathBuf::from(buf)
};
with_ext.exists().then_some(with_ext)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gnu_prefixes_are_sorted() {
assert!(Build::GNU_PREFIXES.windows(2).all(|w| w[0].0 < w[1].0));
}
#[test]
fn test_android_clang_compiler_uses_target_arg_internally() {
for version in 16..21 {
assert!(android_clang_compiler_uses_target_arg_internally(
&PathBuf::from(format!("armv7a-linux-androideabi{}-clang", version))
));
assert!(android_clang_compiler_uses_target_arg_internally(
&PathBuf::from(format!("armv7a-linux-androideabi{}-clang++", version))
));
}
assert!(!android_clang_compiler_uses_target_arg_internally(
&PathBuf::from("clang-i686-linux-android")
));
assert!(!android_clang_compiler_uses_target_arg_internally(
&PathBuf::from("clang")
));
assert!(!android_clang_compiler_uses_target_arg_internally(
&PathBuf::from("clang++")
));
}
}