diff options
Diffstat (limited to 'third_party/rust/cc/src/lib.rs')
-rw-r--r-- | third_party/rust/cc/src/lib.rs | 2668 |
1 files changed, 1511 insertions, 1157 deletions
diff --git a/third_party/rust/cc/src/lib.rs b/third_party/rust/cc/src/lib.rs index 1ebd2cc7a5..1b193dbd0a 100644 --- a/third_party/rust/cc/src/lib.rs +++ b/third_party/rust/cc/src/lib.rs @@ -1,88 +1,251 @@ -//! A library for build scripts to compile custom C code +//! A library for [Cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) +//! to compile a set of C/C++/assembly/CUDA files into a static archive for Cargo +//! to link into the crate being built. This crate does not compile code itself; +//! it calls out to the default compiler for the platform. This crate will +//! automatically detect situations such as cross compilation and +//! [various environment variables](#external-configuration-via-environment-variables) and will build code appropriately. //! -//! This library is intended to be used as a `build-dependencies` entry in -//! `Cargo.toml`: +//! # Example +//! +//! First, you'll want to both add a build script for your crate (`build.rs`) and +//! also add this crate to your `Cargo.toml` via: //! //! ```toml //! [build-dependencies] //! cc = "1.0" //! ``` //! -//! The purpose of this crate is to provide the utility functions necessary to -//! compile C code into a static archive which is then linked into a Rust crate. -//! Configuration is available through the `Build` struct. +//! Next up, you'll want to write a build script like so: +//! +//! ```rust,no_run +//! // build.rs +//! +//! fn main() { +//! cc::Build::new() +//! .file("foo.c") +//! .file("bar.c") +//! .compile("foo"); +//! } +//! ``` +//! +//! And that's it! Running `cargo build` should take care of the rest and your Rust +//! application will now have the C files `foo.c` and `bar.c` compiled into a file +//! named `libfoo.a`. If the C files contain +//! +//! ```c +//! void foo_function(void) { ... } +//! ``` +//! +//! and +//! +//! ```c +//! int32_t bar_function(int32_t x) { ... } +//! ``` +//! +//! you can call them from Rust by declaring them in +//! your Rust code like so: +//! +//! ```rust,no_run +//! extern "C" { +//! fn foo_function(); +//! fn bar_function(x: i32) -> i32; +//! } //! -//! This crate will automatically detect situations such as cross compilation or -//! other environment variables set by Cargo and will build code appropriately. +//! pub fn call() { +//! unsafe { +//! foo_function(); +//! bar_function(42); +//! } +//! } +//! +//! fn main() { +//! call(); +//! } +//! ``` //! -//! The crate is not limited to C code, it can accept any source code that can -//! be passed to a C or C++ compiler. As such, assembly files with extensions -//! `.s` (gcc/clang) and `.asm` (MSVC) can also be compiled. +//! See [the Rustonomicon](https://doc.rust-lang.org/nomicon/ffi.html) for more details. //! -//! [`Build`]: struct.Build.html +//! # External configuration via environment variables //! -//! # Parallelism +//! To control the programs and flags used for building, the builder can set a +//! number of different environment variables. //! -//! To parallelize computation, enable the `parallel` feature for the crate. +//! * `CFLAGS` - a series of space separated flags passed to compilers. Note that +//! individual flags cannot currently contain spaces, so doing +//! something like: `-L=foo\ bar` is not possible. +//! * `CC` - the actual C compiler used. Note that this is used as an exact +//! executable name, so (for example) no extra flags can be passed inside +//! this variable, and the builder must ensure that there aren't any +//! trailing spaces. This compiler must understand the `-c` flag. For +//! certain `TARGET`s, it also is assumed to know about other flags (most +//! common is `-fPIC`). +//! * `AR` - the `ar` (archiver) executable to use to build the static library. +//! * `CRATE_CC_NO_DEFAULTS` - the default compiler flags may cause conflicts in +//! some cross compiling scenarios. Setting this variable +//! will disable the generation of default compiler +//! flags. +//! * `CC_ENABLE_DEBUG_OUTPUT` - if set, compiler command invocations and exit codes will +//! be logged to stdout. This is useful for debugging build script issues, but can be +//! overly verbose for normal use. +//! * `CXX...` - see [C++ Support](#c-support). +//! +//! Furthermore, projects using this crate may specify custom environment variables +//! to be inspected, for example via the `Build::try_flags_from_environment` +//! function. Consult the project’s own documentation or its use of the `cc` crate +//! for any additional variables it may use. +//! +//! Each of these variables can also be supplied with certain prefixes and suffixes, +//! in the following prioritized order: +//! +//! 1. `<var>_<target>` - for example, `CC_x86_64-unknown-linux-gnu` +//! 2. `<var>_<target_with_underscores>` - for example, `CC_x86_64_unknown_linux_gnu` +//! 3. `<build-kind>_<var>` - for example, `HOST_CC` or `TARGET_CFLAGS` +//! 4. `<var>` - a plain `CC`, `AR` as above. +//! +//! If none of these variables exist, cc-rs uses built-in defaults. +//! +//! In addition to the above optional environment variables, `cc-rs` has some +//! functions with hard requirements on some variables supplied by [cargo's +//! build-script driver][cargo] that it has the `TARGET`, `OUT_DIR`, `OPT_LEVEL`, +//! and `HOST` variables. +//! +//! [cargo]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script +//! +//! # Optional features +//! +//! ## Parallel +//! +//! Currently cc-rs supports parallel compilation (think `make -jN`) but this +//! feature is turned off by default. To enable cc-rs to compile C/C++ in parallel, +//! you can change your dependency to: //! //! ```toml //! [build-dependencies] //! cc = { version = "1.0", features = ["parallel"] } //! ``` -//! To specify the max number of concurrent compilation jobs, set the `NUM_JOBS` -//! environment variable to the desired amount. //! -//! Cargo will also set this environment variable when executed with the `-jN` flag. +//! By default cc-rs will limit parallelism to `$NUM_JOBS`, or if not present it +//! will limit it to the number of cpus on the machine. If you are using cargo, +//! use `-jN` option of `build`, `test` and `run` commands as `$NUM_JOBS` +//! is supplied by cargo. +//! +//! # Compile-time Requirements +//! +//! To work properly this crate needs access to a C compiler when the build script +//! is being run. This crate does not ship a C compiler with it. The compiler +//! required varies per platform, but there are three broad categories: //! -//! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can -//! also specify the build parallelism. +//! * Unix platforms require `cc` to be the C compiler. This can be found by +//! installing cc/clang on Linux distributions and Xcode on macOS, for example. +//! * Windows platforms targeting MSVC (e.g. your target triple ends in `-msvc`) +//! require Visual Studio to be installed. `cc-rs` attempts to locate it, and +//! if it fails, `cl.exe` is expected to be available in `PATH`. This can be +//! set up by running the appropriate developer tools shell. +//! * Windows platforms targeting MinGW (e.g. your target triple ends in `-gnu`) +//! require `cc` to be available in `PATH`. We recommend the +//! [MinGW-w64](https://www.mingw-w64.org/) distribution, which is using the +//! [Win-builds](http://win-builds.org/) installation system. +//! You may also acquire it via +//! [MSYS2](https://www.msys2.org/), as explained [here][msys2-help]. Make sure +//! to install the appropriate architecture corresponding to your installation of +//! rustc. GCC from older [MinGW](http://www.mingw.org/) project is compatible +//! only with 32-bit rust compiler. //! -//! # Examples +//! [msys2-help]: https://github.com/rust-lang/rust#building-on-windows //! -//! Use the `Build` struct to compile `src/foo.c`: +//! # C++ support //! -//! ```no_run +//! `cc-rs` supports C++ libraries compilation by using the `cpp` method on +//! `Build`: +//! +//! ```rust,no_run +//! fn main() { +//! cc::Build::new() +//! .cpp(true) // Switch to C++ library compilation. +//! .file("foo.cpp") +//! .compile("foo"); +//! } +//! ``` +//! +//! For C++ libraries, the `CXX` and `CXXFLAGS` environment variables are used instead of `CC` and `CFLAGS`. +//! +//! The C++ standard library may be linked to the crate target. By default it's `libc++` for macOS, FreeBSD, and OpenBSD, `libc++_shared` for Android, nothing for MSVC, and `libstdc++` for anything else. It can be changed in one of two ways: +//! +//! 1. by using the `cpp_link_stdlib` method on `Build`: +//! ```rust,no_run //! fn main() { //! cc::Build::new() -//! .file("src/foo.c") -//! .define("FOO", Some("bar")) -//! .include("src") +//! .cpp(true) +//! .file("foo.cpp") +//! .cpp_link_stdlib("stdc++") // use libstdc++ //! .compile("foo"); //! } //! ``` +//! 2. by setting the `CXXSTDLIB` environment variable. +//! +//! In particular, for Android you may want to [use `c++_static` if you have at most one shared library](https://developer.android.com/ndk/guides/cpp-support). +//! +//! Remember that C++ does name mangling so `extern "C"` might be required to enable Rust linker to find your functions. +//! +//! # CUDA C++ support +//! +//! `cc-rs` also supports compiling CUDA C++ libraries by using the `cuda` method +//! on `Build`: +//! +//! ```rust,no_run +//! fn main() { +//! cc::Build::new() +//! // Switch to CUDA C++ library compilation using NVCC. +//! .cuda(true) +//! .cudart("static") +//! // Generate code for Maxwell (GTX 970, 980, 980 Ti, Titan X). +//! .flag("-gencode").flag("arch=compute_52,code=sm_52") +//! // Generate code for Maxwell (Jetson TX1). +//! .flag("-gencode").flag("arch=compute_53,code=sm_53") +//! // Generate code for Pascal (GTX 1070, 1080, 1080 Ti, Titan Xp). +//! .flag("-gencode").flag("arch=compute_61,code=sm_61") +//! // Generate code for Pascal (Tesla P100). +//! .flag("-gencode").flag("arch=compute_60,code=sm_60") +//! // Generate code for Pascal (Jetson TX2). +//! .flag("-gencode").flag("arch=compute_62,code=sm_62") +//! // Generate code in parallel +//! .flag("-t0") +//! .file("bar.cu") +//! .compile("bar"); +//! } +//! ``` #![doc(html_root_url = "https://docs.rs/cc/1.0")] #![cfg_attr(test, deny(warnings))] #![allow(deprecated)] #![deny(missing_docs)] -use std::collections::{hash_map, HashMap}; +use std::borrow::Cow; +use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt::{self, Display, Formatter}; use std::fs; -use std::hash::Hasher; -use std::io::{self, BufRead, BufReader, Read, Write}; +use std::io::{self, Write}; use std::path::{Component, Path, PathBuf}; -use std::process::{Child, Command, Stdio}; +#[cfg(feature = "parallel")] +use std::process::Child; +use std::process::Command; use std::sync::{Arc, Mutex}; -use std::thread::{self, JoinHandle}; - -// These modules are all glue to support reading the MSVC version from -// the registry and from COM interfaces -#[cfg(windows)] -mod registry; -#[cfg(windows)] -#[macro_use] -mod winapi; -#[cfg(windows)] -mod com; -#[cfg(windows)] -mod setup_config; -#[cfg(windows)] -mod vs_instances; - -pub mod windows_registry; + +#[cfg(feature = "parallel")] +mod parallel; +mod windows; +// Regardless of whether this should be in this crate's public API, +// it has been since 2015, so don't break it. +pub use windows::find_tools as windows_registry; + +mod command_helpers; +use command_helpers::*; + +mod tool; +pub use tool::Tool; +use tool::ToolFamily; /// A builder for compilation of a native library. /// @@ -91,32 +254,34 @@ pub mod windows_registry; /// documentation on each method itself. #[derive(Clone, Debug)] pub struct Build { - include_directories: Vec<PathBuf>, - definitions: Vec<(String, Option<String>)>, - objects: Vec<PathBuf>, - flags: Vec<String>, - flags_supported: Vec<String>, + include_directories: Vec<Arc<Path>>, + definitions: Vec<(Arc<str>, Option<Arc<str>>)>, + objects: Vec<Arc<Path>>, + flags: Vec<Arc<str>>, + flags_supported: Vec<Arc<str>>, known_flag_support_status: Arc<Mutex<HashMap<String, bool>>>, - ar_flags: Vec<String>, - asm_flags: Vec<String>, + ar_flags: Vec<Arc<str>>, + asm_flags: Vec<Arc<str>>, no_default_flags: bool, - files: Vec<PathBuf>, + files: Vec<Arc<Path>>, cpp: bool, - cpp_link_stdlib: Option<Option<String>>, - cpp_set_stdlib: Option<String>, + cpp_link_stdlib: Option<Option<Arc<str>>>, + cpp_set_stdlib: Option<Arc<str>>, cuda: bool, - cudart: Option<String>, - target: Option<String>, - host: Option<String>, - out_dir: Option<PathBuf>, - opt_level: Option<String>, + cudart: Option<Arc<str>>, + std: Option<Arc<str>>, + target: Option<Arc<str>>, + host: Option<Arc<str>>, + out_dir: Option<Arc<Path>>, + opt_level: Option<Arc<str>>, debug: Option<bool>, force_frame_pointer: Option<bool>, - env: Vec<(OsString, OsString)>, - compiler: Option<PathBuf>, - archiver: Option<PathBuf>, - cargo_metadata: bool, - link_lib_modifiers: Vec<String>, + env: Vec<(Arc<OsStr>, Arc<OsStr>)>, + compiler: Option<Arc<Path>>, + archiver: Option<Arc<Path>>, + ranlib: Option<Arc<Path>>, + cargo_output: CargoOutput, + link_lib_modifiers: Vec<Arc<str>>, pic: Option<bool>, use_plt: Option<bool>, static_crt: Option<bool>, @@ -125,9 +290,11 @@ pub struct Build { warnings_into_errors: bool, warnings: Option<bool>, extra_warnings: Option<bool>, - env_cache: Arc<Mutex<HashMap<String, Option<String>>>>, + env_cache: Arc<Mutex<HashMap<String, Option<Arc<str>>>>>, apple_sdk_root_cache: Arc<Mutex<HashMap<String, OsString>>>, + apple_versions_cache: Arc<Mutex<HashMap<String, String>>>, emit_rerun_if_env_changed: bool, + cached_compiler_family: Arc<Mutex<HashMap<Box<Path>, ToolFamily>>>, } /// Represents the types of errors that may occur while using cc-rs. @@ -145,6 +312,9 @@ enum ErrorKind { ToolNotFound, /// One of the function arguments failed validation. InvalidArgument, + #[cfg(feature = "parallel")] + /// jobserver helpthread failure + JobserverHelpThreadError, } /// Represents an internal error that occurred, with an explanation. @@ -153,21 +323,21 @@ pub struct Error { /// Describes the kind of error that occurred. kind: ErrorKind, /// More explanation of error that occurred. - message: String, + message: Cow<'static, str>, } impl Error { - fn new(kind: ErrorKind, message: &str) -> Error { + fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Error { Error { - kind: kind, - message: message.to_owned(), + kind, + message: message.into(), } } } impl From<io::Error> for Error { fn from(e: io::Error) -> Error { - Error::new(ErrorKind::IOError, &format!("{}", e)) + Error::new(ErrorKind::IOError, format!("{}", e)) } } @@ -179,97 +349,6 @@ impl Display for Error { impl std::error::Error for Error {} -/// Configuration used to represent an invocation of a C compiler. -/// -/// This can be used to figure out what compiler is in use, what the arguments -/// to it are, and what the environment variables look like for the compiler. -/// This can be used to further configure other build systems (e.g. forward -/// along CC and/or CFLAGS) or the `to_command` method can be used to run the -/// compiler itself. -#[derive(Clone, Debug)] -pub struct Tool { - path: PathBuf, - cc_wrapper_path: Option<PathBuf>, - cc_wrapper_args: Vec<OsString>, - args: Vec<OsString>, - env: Vec<(OsString, OsString)>, - family: ToolFamily, - cuda: bool, - removed_args: Vec<OsString>, -} - -/// Represents the family of tools this tool belongs to. -/// -/// Each family of tools differs in how and what arguments they accept. -/// -/// Detection of a family is done on best-effort basis and may not accurately reflect the tool. -#[derive(Copy, Clone, Debug, PartialEq)] -enum ToolFamily { - /// Tool is GNU Compiler Collection-like. - Gnu, - /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags - /// and its cross-compilation approach is different. - Clang, - /// Tool is the MSVC cl.exe. - Msvc { clang_cl: bool }, -} - -impl ToolFamily { - /// What the flag to request debug info for this family of tools look like - fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) { - match *self { - ToolFamily::Msvc { .. } => { - cmd.push_cc_arg("-Z7".into()); - } - ToolFamily::Gnu | ToolFamily::Clang => { - cmd.push_cc_arg( - dwarf_version - .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v)) - .into(), - ); - } - } - } - - /// What the flag to force frame pointers. - fn add_force_frame_pointer(&self, cmd: &mut Tool) { - match *self { - ToolFamily::Gnu | ToolFamily::Clang => { - cmd.push_cc_arg("-fno-omit-frame-pointer".into()); - } - _ => (), - } - } - - /// What the flags to enable all warnings - fn warnings_flags(&self) -> &'static str { - match *self { - ToolFamily::Msvc { .. } => "-W4", - ToolFamily::Gnu | ToolFamily::Clang => "-Wall", - } - } - - /// What the flags to enable extra warnings - fn extra_warnings_flags(&self) -> Option<&'static str> { - match *self { - ToolFamily::Msvc { .. } => None, - ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"), - } - } - - /// What the flag to turn warning into errors - fn warnings_to_errors_flag(&self) -> &'static str { - match *self { - ToolFamily::Msvc { .. } => "-WX", - ToolFamily::Gnu | ToolFamily::Clang => "-Werror", - } - } - - fn verbose_stderr(&self) -> bool { - *self == ToolFamily::Clang - } -} - /// Represents an object. /// /// This is a source file -> object file pair. @@ -282,7 +361,7 @@ struct Object { impl Object { /// Create a new source file -> object file pair. fn new(src: PathBuf, dst: PathBuf) -> Object { - Object { src: src, dst: dst } + Object { src, dst } } } @@ -311,6 +390,7 @@ impl Build { cpp_set_stdlib: None, cuda: false, cudart: None, + std: None, target: None, host: None, out_dir: None, @@ -320,7 +400,8 @@ impl Build { env: Vec::new(), compiler: None, archiver: None, - cargo_metadata: true, + ranlib: None, + cargo_output: CargoOutput::new(), link_lib_modifiers: Vec::new(), pic: None, use_plt: None, @@ -330,7 +411,9 @@ impl Build { warnings_into_errors: false, env_cache: Arc::new(Mutex::new(HashMap::new())), apple_sdk_root_cache: Arc::new(Mutex::new(HashMap::new())), + apple_versions_cache: Arc::new(Mutex::new(HashMap::new())), emit_rerun_if_env_changed: true, + cached_compiler_family: Arc::default(), } } @@ -350,7 +433,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Build { - self.include_directories.push(dir.as_ref().to_path_buf()); + self.include_directories.push(dir.as_ref().into()); self } @@ -396,13 +479,13 @@ impl Build { /// ``` pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build { self.definitions - .push((var.to_string(), val.into().map(|s| s.to_string()))); + .push((var.into(), val.into().map(Into::into))); self } /// Add an arbitrary object file to link in pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Build { - self.objects.push(obj.as_ref().to_path_buf()); + self.objects.push(obj.as_ref().into()); self } @@ -417,7 +500,25 @@ impl Build { /// .compile("foo"); /// ``` pub fn flag(&mut self, flag: &str) -> &mut Build { - self.flags.push(flag.to_string()); + self.flags.push(flag.into()); + self + } + + /// Removes a compiler flag that was added by [`Build::flag`]. + /// + /// Will not remove flags added by other means (default flags, + /// flags from env, and so on). + /// + /// # Example + /// ``` + /// cc::Build::new() + /// .file("src/foo.c") + /// .flag("unwanted_flag") + /// .remove_flag("unwanted_flag"); + /// ``` + + pub fn remove_flag(&mut self, flag: &str) -> &mut Build { + self.flags.retain(|other_flag| &**other_flag != flag); self } @@ -433,7 +534,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn ar_flag(&mut self, flag: &str) -> &mut Build { - self.ar_flags.push(flag.to_string()); + self.ar_flags.push(flag.into()); self } @@ -452,7 +553,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn asm_flag(&mut self, flag: &str) -> &mut Build { - self.asm_flags.push(flag.to_string()); + self.asm_flags.push(flag.into()); self } @@ -499,6 +600,7 @@ impl Build { let host = self.get_host()?; let mut cfg = Build::new(); cfg.flag(flag) + .cargo_metadata(self.cargo_output.metadata) .target(&target) .opt_level(0) .host(&host) @@ -515,30 +617,34 @@ impl Build { if compiler.family.verbose_stderr() { compiler.remove_arg("-v".into()); } + if compiler.family == ToolFamily::Clang { + // Avoid reporting that the arg is unsupported just because the + // compiler complains that it wasn't used. + compiler.push_cc_arg("-Wno-unused-command-line-argument".into()); + } let mut cmd = compiler.to_command(); let is_arm = target.contains("aarch64") || target.contains("arm"); let clang = compiler.family == ToolFamily::Clang; + let gnu = compiler.family == ToolFamily::Gnu; command_add_output_file( &mut cmd, &obj, self.cuda, target.contains("msvc"), clang, + gnu, false, is_arm, ); - // We need to explicitly tell msvc not to link and create an exe - // in the root directory of the crate - if target.contains("msvc") && !self.cuda { - cmd.arg("-c"); - } + // Checking for compiler flags does not require linking + cmd.arg("-c"); cmd.arg(&src); let output = cmd.output()?; - let is_supported = output.stderr.is_empty(); + let is_supported = output.status.success() && output.stderr.is_empty(); known_status.insert(flag.to_owned(), is_supported); Ok(is_supported) @@ -556,10 +662,39 @@ impl Build { /// .compile("foo"); /// ``` pub fn flag_if_supported(&mut self, flag: &str) -> &mut Build { - self.flags_supported.push(flag.to_string()); + self.flags_supported.push(flag.into()); self } + /// Add flags from the specified environment variable. + /// + /// Normally the `cc` crate will consult with the standard set of environment + /// variables (such as `CFLAGS` and `CXXFLAGS`) to construct the compiler invocation. Use of + /// this method provides additional levers for the end user to use when configuring the build + /// process. + /// + /// Just like the standard variables, this method will search for an environment variable with + /// appropriate target prefixes, when appropriate. + /// + /// # Examples + /// + /// This method is particularly beneficial in introducing the ability to specify crate-specific + /// flags. + /// + /// ```no_run + /// cc::Build::new() + /// .file("src/foo.c") + /// .try_flags_from_environment(concat!(env!("CARGO_PKG_NAME"), "_CFLAGS")) + /// .expect("the environment variable must be specified and UTF-8") + /// .compile("foo"); + /// ``` + /// + pub fn try_flags_from_environment(&mut self, environ_key: &str) -> Result<&mut Build, Error> { + let flags = self.envflags(environ_key)?; + self.flags.extend(flags.into_iter().map(Into::into)); + Ok(self) + } + /// Set the `-shared` flag. /// /// When enabled, the compiler will produce a shared object which can @@ -610,7 +745,7 @@ impl Build { /// Add a file which will be compiled pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build { - self.files.push(p.as_ref().to_path_buf()); + self.files.push(p.as_ref().into()); self } @@ -626,10 +761,21 @@ impl Build { self } + /// Get the files which will be compiled + pub fn get_files(&self) -> impl Iterator<Item = &Path> { + self.files.iter().map(AsRef::as_ref) + } + /// Set C++ support. /// /// The other `cpp_*` options will only become active if this is set to /// `true`. + /// + /// The name of the C++ standard library to link is decided by: + /// 1. If [`cpp_link_stdlib`](Build::cpp_link_stdlib) is set, use its value. + /// 2. Else if the `CXXSTDLIB` environment variable is set, use its value. + /// 3. Else the default is `libc++` for OS X and BSDs, `libc++_shared` for Android, + /// `None` for MSVC and `libstdc++` for anything else. pub fn cpp(&mut self, cpp: bool) -> &mut Build { self.cpp = cpp; self @@ -637,17 +783,19 @@ impl Build { /// Set CUDA C++ support. /// - /// Enabling CUDA will pass the detected C/C++ toolchain as an argument to - /// the CUDA compiler, NVCC. NVCC itself accepts some limited GNU-like args; - /// any other arguments for the C/C++ toolchain will be redirected using - /// "-Xcompiler" flags. + /// Enabling CUDA will invoke the CUDA compiler, NVCC. While NVCC accepts + /// the most common compiler flags, e.g. `-std=c++17`, some project-specific + /// flags might have to be prefixed with "-Xcompiler" flag, for example as + /// `.flag("-Xcompiler").flag("-fpermissive")`. See the documentation for + /// `nvcc`, the CUDA compiler driver, at <https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/> + /// for more information. /// /// If enabled, this also implicitly enables C++ support. pub fn cuda(&mut self, cuda: bool) -> &mut Build { self.cuda = cuda; if cuda { self.cpp = true; - self.cudart = Some("static".to_string()); + self.cudart = Some("static".into()); } self } @@ -660,11 +808,42 @@ impl Build { /// at all, if the default is right for the project. pub fn cudart(&mut self, cudart: &str) -> &mut Build { if self.cuda { - self.cudart = Some(cudart.to_string()); + self.cudart = Some(cudart.into()); } self } + /// Specify the C or C++ language standard version. + /// + /// These values are common to modern versions of GCC, Clang and MSVC: + /// - `c11` for ISO/IEC 9899:2011 + /// - `c17` for ISO/IEC 9899:2018 + /// - `c++14` for ISO/IEC 14882:2014 + /// - `c++17` for ISO/IEC 14882:2017 + /// - `c++20` for ISO/IEC 14882:2020 + /// + /// Other values have less broad support, e.g. MSVC does not support `c++11` + /// (`c++14` is the minimum), `c89` (omit the flag instead) or `c99`. + /// + /// For compiling C++ code, you should also set `.cpp(true)`. + /// + /// The default is that no standard flag is passed to the compiler, so the + /// language version will be the compiler's default. + /// + /// # Example + /// + /// ```no_run + /// cc::Build::new() + /// .file("src/modern.cpp") + /// .cpp(true) + /// .std("c++17") + /// .compile("modern"); + /// ``` + pub fn std(&mut self, std: &str) -> &mut Build { + self.std = Some(std.into()); + self + } + /// Set warnings into errors flag. /// /// Disabled by default. @@ -736,8 +915,6 @@ impl Build { /// Set the standard library to link against when compiling with C++ /// support. /// - /// See [`get_cpp_link_stdlib`](cc::Build::get_cpp_link_stdlib) documentation - /// for the default value. /// If the `CXXSTDLIB` environment variable is set, its value will /// override the default value, but not the value explicitly set by calling /// this function. @@ -826,7 +1003,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn target(&mut self, target: &str) -> &mut Build { - self.target = Some(target.to_string()); + self.target = Some(target.into()); self } @@ -844,7 +1021,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn host(&mut self, host: &str) -> &mut Build { - self.host = Some(host.to_string()); + self.host = Some(host.into()); self } @@ -853,7 +1030,7 @@ impl Build { /// This option is automatically scraped from the `OPT_LEVEL` environment /// variable by build scripts, so it's not required to call this function. pub fn opt_level(&mut self, opt_level: u32) -> &mut Build { - self.opt_level = Some(opt_level.to_string()); + self.opt_level = Some(opt_level.to_string().into()); self } @@ -862,7 +1039,7 @@ impl Build { /// This option is automatically scraped from the `OPT_LEVEL` environment /// variable by build scripts, so it's not required to call this function. pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Build { - self.opt_level = Some(opt_level.to_string()); + self.opt_level = Some(opt_level.into()); self } @@ -893,7 +1070,7 @@ impl Build { /// This option is automatically scraped from the `OUT_DIR` environment /// variable by build scripts, so it's not required to call this function. pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Build { - self.out_dir = Some(out_dir.as_ref().to_owned()); + self.out_dir = Some(out_dir.as_ref().into()); self } @@ -903,7 +1080,7 @@ impl Build { /// number of environment variables, so it's not required to call this /// function. pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Build { - self.compiler = Some(compiler.as_ref().to_owned()); + self.compiler = Some(compiler.as_ref().into()); self } @@ -913,9 +1090,20 @@ impl Build { /// number of environment variables, so it's not required to call this /// function. pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Build { - self.archiver = Some(archiver.as_ref().to_owned()); + self.archiver = Some(archiver.as_ref().into()); self } + + /// Configures the tool used to index archives. + /// + /// This option is automatically determined from the target platform or a + /// number of environment variables, so it's not required to call this + /// function. + pub fn ranlib<P: AsRef<Path>>(&mut self, ranlib: P) -> &mut Build { + self.ranlib = Some(ranlib.as_ref().into()); + self + } + /// Define whether metadata should be emitted for cargo allowing it to /// automatically link the binary. Defaults to `true`. /// @@ -928,17 +1116,37 @@ impl Build { /// - If `emit_rerun_if_env_changed` is not `false`, `rerun-if-env-changed=`*env* /// pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Build { - self.cargo_metadata = cargo_metadata; + self.cargo_output.metadata = cargo_metadata; + self + } + + /// Define whether compile warnings should be emitted for cargo. Defaults to + /// `true`. + /// + /// If disabled, compiler messages will not be printed. + /// Issues unrelated to the compilation will always produce cargo warnings regardless of this setting. + pub fn cargo_warnings(&mut self, cargo_warnings: bool) -> &mut Build { + self.cargo_output.warnings = cargo_warnings; + self + } + + /// Define whether debug information should be emitted for cargo. Defaults to whether + /// or not the environment variable `CC_ENABLE_DEBUG_OUTPUT` is set. + /// + /// If enabled, the compiler will emit debug information when generating object files, + /// such as the command invoked and the exit status. + pub fn cargo_debug(&mut self, cargo_debug: bool) -> &mut Build { + self.cargo_output.debug = cargo_debug; self } /// Adds a native library modifier that will be added to the /// `rustc-link-lib=static:MODIFIERS=LIBRARY_NAME` metadata line /// emitted for cargo if `cargo_metadata` is enabled. - /// See https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library + /// See <https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library> /// for the list of modifiers accepted by rustc. pub fn link_lib_modifier(&mut self, link_lib_modifier: &str) -> &mut Build { - self.link_lib_modifiers.push(link_lib_modifier.to_string()); + self.link_lib_modifiers.push(link_lib_modifier.into()); self } @@ -991,14 +1199,13 @@ impl Build { A: AsRef<OsStr>, B: AsRef<OsStr>, { - self.env - .push((a.as_ref().to_owned(), b.as_ref().to_owned())); + self.env.push((a.as_ref().into(), b.as_ref().into())); self } /// Run the compiler, generating the file `output` /// - /// This will return a result instead of panicing; see compile() for the complete description. + /// This will return a result instead of panicking; see compile() for the complete description. pub fn try_compile(&self, output: &str) -> Result<(), Error> { let mut output_components = Path::new(output).components(); match (output_components.next(), output_components.next()) { @@ -1016,52 +1223,14 @@ impl Build { } else { let mut gnu = String::with_capacity(5 + output.len()); gnu.push_str("lib"); - gnu.push_str(&output); + gnu.push_str(output); gnu.push_str(".a"); (output, gnu) }; let dst = self.get_out_dir()?; - let mut objects = Vec::new(); - for file in self.files.iter() { - let obj = if file.has_root() { - // If `file` is an absolute path, prefix the `basename` - // with the `dirname`'s hash to ensure name uniqueness. - let basename = file - .file_name() - .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "file_name() failure"))? - .to_string_lossy(); - let dirname = file - .parent() - .ok_or_else(|| Error::new(ErrorKind::InvalidArgument, "parent() failure"))? - .to_string_lossy(); - let mut hasher = hash_map::DefaultHasher::new(); - hasher.write(dirname.to_string().as_bytes()); - dst.join(format!("{:016x}-{}", hasher.finish(), basename)) - .with_extension("o") - } else { - dst.join(file).with_extension("o") - }; - let obj = if !obj.starts_with(&dst) { - dst.join(obj.file_name().ok_or_else(|| { - Error::new(ErrorKind::IOError, "Getting object file details failed.") - })?) - } else { - obj - }; + let objects = objects_from_files(&self.files, &dst)?; - match obj.parent() { - Some(s) => fs::create_dir_all(s)?, - None => { - return Err(Error::new( - ErrorKind::IOError, - "Getting object file details failed.", - )); - } - }; - - objects.push(Object::new(file.to_path_buf(), obj)); - } self.compile_objects(&objects)?; self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; @@ -1070,8 +1239,8 @@ impl Build { let atlmfc_lib = compiler .env() .iter() - .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB")) - .and_then(|&(_, ref lib_paths)| { + .find(|&(var, _)| var.as_os_str() == 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)) @@ -1079,7 +1248,7 @@ impl Build { }); if let Some(atlmfc_lib) = atlmfc_lib { - self.print(&format!( + self.cargo_output.print_metadata(&format_args!( "cargo:rustc-link-search=native={}", atlmfc_lib.display() )); @@ -1087,26 +1256,34 @@ impl Build { } if self.link_lib_modifiers.is_empty() { - self.print(&format!("cargo:rustc-link-lib=static={}", lib_name)); + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib=static={}", lib_name)); } else { let m = self.link_lib_modifiers.join(","); - self.print(&format!("cargo:rustc-link-lib=static:{}={}", m, lib_name)); + self.cargo_output.print_metadata(&format_args!( + "cargo:rustc-link-lib=static:{}={}", + m, lib_name + )); } - self.print(&format!("cargo:rustc-link-search=native={}", dst.display())); + self.cargo_output.print_metadata(&format_args!( + "cargo:rustc-link-search=native={}", + dst.display() + )); // Add specific C++ libraries, if enabled. if self.cpp { if let Some(stdlib) = self.get_cpp_link_stdlib()? { - self.print(&format!("cargo:rustc-link-lib={}", stdlib)); + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib)); } } let cudart = match &self.cudart { - Some(opt) => opt.as_str(), // {none|shared|static} + Some(opt) => &*opt, // {none|shared|static} None => "none", }; if cudart != "none" { - if let Some(nvcc) = which(&self.get_compiler().path) { + if let Some(nvcc) = which(&self.get_compiler().path, None) { // Try to figure out the -L search path. If it fails, // it's on user to specify one by passing it through // RUSTFLAGS environment variable. @@ -1135,10 +1312,10 @@ impl Build { } } if libtst && libdir.is_dir() { - println!( + self.cargo_output.print_metadata(&format_args!( "cargo:rustc-link-search=native={}", libdir.to_str().unwrap() - ); + )); } // And now the -l flag. @@ -1147,7 +1324,8 @@ impl Build { "static" => "cudart_static", bad => panic!("unsupported cudart option: {}", bad), }; - println!("cargo:rustc-link-lib={}", lib); + self.cargo_output + .print_metadata(&format_args!("cargo:rustc-link-lib={}", lib)); } } @@ -1197,18 +1375,48 @@ impl Build { } } + /// Run the compiler, generating intermediate files, but without linking + /// them into an archive file. + /// + /// This will return a list of compiled object files, in the same order + /// as they were passed in as `file`/`files` methods. + pub fn compile_intermediates(&self) -> Vec<PathBuf> { + match self.try_compile_intermediates() { + Ok(v) => v, + Err(e) => fail(&e.message), + } + } + + /// Run the compiler, generating intermediate files, but without linking + /// them into an archive file. + /// + /// This will return a result instead of panicking; see `compile_intermediates()` for the complete description. + 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.compile_objects(&objects)?; + + Ok(objects.into_iter().map(|v| v.dst).collect()) + } + #[cfg(feature = "parallel")] - fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> { - use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; - use std::sync::Once; - - // Limit our parallelism globally with a jobserver. Start off by - // releasing our own token for this process so we can have a bit of an - // easier to write loop below. If this fails, though, then we're likely - // on Windows with the main implicit token, so we just have a bit extra - // parallelism for a bit and don't reacquire later. - let server = jobserver(); - let reacquire = server.release_raw().is_ok(); + fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { + use std::cell::Cell; + + use parallel::async_executor::{block_on, YieldOnce}; + + if objs.len() <= 1 { + for obj in objs { + let (mut cmd, name) = self.create_compile_object_cmd(obj)?; + run(&mut cmd, &name, &self.cargo_output)?; + } + + return Ok(()); + } + + // Limit our parallelism globally with a jobserver. + let tokens = parallel::job_token::ActiveJobTokenServer::new()?; // When compiling objects in parallel we do a few dirty tricks to speed // things up: @@ -1222,153 +1430,154 @@ impl Build { // Note that this jobserver is cached globally so we only used one per // process and only worry about creating it once. // - // * Next we use a raw `thread::spawn` per thread to actually compile - // objects in parallel. We only actually spawn a thread after we've - // acquired a token to perform some work - // - // * Finally though we want to keep the dependencies of this crate - // pretty light, so we avoid using a safe abstraction like `rayon` and - // instead rely on some bits of `unsafe` code. We know that this stack - // frame persists while everything is compiling so we use all the - // stack-allocated objects without cloning/reallocating. We use a - // transmute to `State` with a `'static` lifetime to persist - // everything we need across the boundary, and the join-on-drop - // semantics of `JoinOnDrop` should ensure that our stack frame is - // alive while threads are alive. + // * Next we use spawn the process to actually compile objects in + // parallel after we've acquired a token to perform some work // // With all that in mind we compile all objects in a loop here, after we // acquire the appropriate tokens, Once all objects have been compiled - // we join on all the threads and propagate the results of compilation. - // - // Note that as a slight optimization we try to break out as soon as - // possible as soon as any compilation fails to ensure that errors get - // out to the user as fast as possible. - let error = AtomicBool::new(false); - let mut threads = Vec::new(); - for obj in objs { - if error.load(SeqCst) { - break; - } - let token = server.acquire()?; - let state = State { - build: self, - obj, - error: &error, - }; - let state = unsafe { std::mem::transmute::<State, State<'static>>(state) }; - let thread = thread::spawn(|| { - let state: State<'me> = state; // erase the `'static` lifetime - let result = state.build.compile_object(state.obj); - if result.is_err() { - state.error.store(true, SeqCst); + // we wait on all the processes and propagate the results of compilation. + + let pendings = Cell::new(Vec::<( + Command, + String, + KillOnDrop, + parallel::job_token::JobToken, + )>::new()); + let is_disconnected = Cell::new(false); + let has_made_progress = Cell::new(false); + + let wait_future = async { + let mut error = None; + // Buffer the stdout + let mut stdout = io::BufWriter::with_capacity(128, io::stdout()); + + loop { + // If the other end of the pipe is already disconnected, then we're not gonna get any new jobs, + // so it doesn't make sense to reuse the tokens; in fact, + // releasing them as soon as possible (once we know that the other end is disconnected) is beneficial. + // Imagine that the last file built takes an hour to finish; in this scenario, + // by not releasing the tokens before that last file is done we would effectively block other processes from + // starting sooner - even though we only need one token for that last file, not N others that were acquired. + + let mut pendings_is_empty = false; + + cell_update(&pendings, |mut pendings| { + // Try waiting on them. + parallel::retain_unordered_mut( + &mut pendings, + |(cmd, program, child, _token)| { + match try_wait_on_child( + cmd, + program, + &mut child.0, + &mut stdout, + &mut child.1, + ) { + Ok(Some(())) => { + // Task done, remove the entry + has_made_progress.set(true); + false + } + Ok(None) => true, // Task still not finished, keep the entry + Err(err) => { + // Task fail, remove the entry. + // Since we can only return one error, log the error to make + // sure users always see all the compilation failures. + has_made_progress.set(true); + + if self.cargo_output.warnings { + let _ = writeln!(stdout, "cargo:warning={}", err); + } + error = Some(err); + + false + } + } + }, + ); + pendings_is_empty = pendings.is_empty(); + pendings + }); + + if pendings_is_empty && is_disconnected.get() { + break if let Some(err) = error { + Err(err) + } else { + Ok(()) + }; } - drop(token); // make sure our jobserver token is released after the compile - return result; - }); - threads.push(JoinOnDrop(Some(thread))); - } - for mut thread in threads { - if let Some(thread) = thread.0.take() { - thread.join().expect("thread should not panic")?; + YieldOnce::default().await; } - } - - // Reacquire our process's token before we proceed, which we released - // before entering the loop above. - if reacquire { - server.acquire_raw()?; - } - - return Ok(()); - - /// Shared state from the parent thread to the child thread. This - /// package of pointers is temporarily transmuted to a `'static` - /// lifetime to cross the thread boundary and then once the thread is - /// running we erase the `'static` to go back to an anonymous lifetime. - struct State<'a> { - build: &'a Build, - obj: &'a Object, - error: &'a AtomicBool, - } - - /// Returns a suitable `jobserver::Client` used to coordinate - /// parallelism between build scripts. - fn jobserver() -> &'static jobserver::Client { - static INIT: Once = Once::new(); - static mut JOBSERVER: Option<jobserver::Client> = None; - - fn _assert_sync<T: Sync>() {} - _assert_sync::<jobserver::Client>(); - - unsafe { - INIT.call_once(|| { - let server = default_jobserver(); - JOBSERVER = Some(server); + }; + let spawn_future = async { + for obj in objs { + let (mut cmd, program) = self.create_compile_object_cmd(obj)?; + let token = tokens.acquire().await?; + let mut child = spawn(&mut cmd, &program, &self.cargo_output)?; + let mut stderr_forwarder = StderrForwarder::new(&mut child); + stderr_forwarder.set_non_blocking()?; + + cell_update(&pendings, |mut pendings| { + pendings.push((cmd, program, KillOnDrop(child, stderr_forwarder), token)); + pendings }); - JOBSERVER.as_ref().unwrap() - } - } - unsafe fn default_jobserver() -> jobserver::Client { - // Try to use the environmental jobserver which Cargo typically - // initializes for us... - if let Some(client) = jobserver::Client::from_env() { - return client; + has_made_progress.set(true); } + is_disconnected.set(true); - // ... but if that fails for whatever reason select something - // reasonable and crate a new jobserver. Use `NUM_JOBS` if set (it's - // configured by Cargo) and otherwise just fall back to a - // semi-reasonable number. Note that we could use `num_cpus` here - // but it's an extra dependency that will almost never be used, so - // it's generally not too worth it. - let mut parallelism = 4; - if let Ok(amt) = env::var("NUM_JOBS") { - if let Ok(amt) = amt.parse() { - parallelism = amt; - } - } + Ok::<_, Error>(()) + }; - // If we create our own jobserver then be sure to reserve one token - // for ourselves. - let client = jobserver::Client::new(parallelism).expect("failed to create jobserver"); - client.acquire_raw().expect("failed to acquire initial"); - return client; - } + return block_on(wait_future, spawn_future, &has_made_progress); - struct JoinOnDrop(Option<thread::JoinHandle<Result<(), Error>>>); + struct KillOnDrop(Child, StderrForwarder); - impl Drop for JoinOnDrop { + impl Drop for KillOnDrop { fn drop(&mut self) { - if let Some(thread) = self.0.take() { - drop(thread.join()); - } + let child = &mut self.0; + + child.kill().ok(); } } + + fn cell_update<T, F>(cell: &Cell<T>, f: F) + where + T: Default, + F: FnOnce(T) -> T, + { + let old = cell.take(); + let new = f(old); + cell.set(new); + } } #[cfg(not(feature = "parallel"))] fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { for obj in objs { - self.compile_object(obj)?; + let (mut cmd, name) = self.create_compile_object_cmd(obj)?; + run(&mut cmd, &name, &self.cargo_output)?; } + Ok(()) } - fn compile_object(&self, obj: &Object) -> Result<(), Error> { + fn create_compile_object_cmd(&self, obj: &Object) -> Result<(Command, String), Error> { let asm_ext = AsmFileExt::from_path(&obj.src); let is_asm = asm_ext.is_some(); let target = self.get_target()?; let msvc = target.contains("msvc"); let compiler = self.try_get_compiler()?; let clang = compiler.family == ToolFamily::Clang; + let gnu = compiler.family == ToolFamily::Gnu; - let (mut cmd, name) = if msvc && asm_ext == Some(AsmFileExt::DotAsm) { + let is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm); + let (mut cmd, name) = if is_assembler_msvc { self.msvc_macro_assembler()? } else { let mut cmd = compiler.to_command(); - for &(ref a, ref b) in self.env.iter() { + for (a, b) in self.env.iter() { cmd.env(a, b); } ( @@ -1382,18 +1591,20 @@ impl Build { ) }; let is_arm = target.contains("aarch64") || target.contains("arm"); - command_add_output_file(&mut cmd, &obj.dst, self.cuda, msvc, clang, is_asm, is_arm); + command_add_output_file( + &mut cmd, &obj.dst, self.cuda, msvc, clang, gnu, is_asm, is_arm, + ); // armasm and armasm64 don't requrie -c option - if !msvc || !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); + cmd.args(self.asm_flags.iter().map(std::ops::Deref::deref)); } - if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_asm { + if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_assembler_msvc { // #513: For `clang-cl`, separate flags/options from the input file. // When cross-compiling macOS -> Windows, this avoids interpreting // common `/Users/...` paths as the `/U` flag and triggering @@ -1405,15 +1616,14 @@ impl Build { self.fix_env_for_apple_os(&mut cmd)?; } - run(&mut cmd, &name)?; - Ok(()) + Ok((cmd, name)) } - /// This will return a result instead of panicing; see expand() for the complete description. + /// This will return a result instead of panicking; see expand() for the complete description. pub fn try_expand(&self) -> Result<Vec<u8>, Error> { let compiler = self.try_get_compiler()?; let mut cmd = compiler.to_command(); - for &(ref a, ref b) in self.env.iter() { + for (a, b) in self.env.iter() { cmd.env(a, b); } cmd.arg("-E"); @@ -1423,10 +1633,23 @@ impl Build { "Expand may only be called for a single file" ); - for file in self.files.iter() { - cmd.arg(file); + let is_asm = self + .files + .iter() + .map(std::ops::Deref::deref) + .find_map(AsmFileExt::from_path) + .is_some(); + + if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_asm { + // #513: For `clang-cl`, separate flags/options from the input file. + // When cross-compiling macOS -> Windows, this avoids interpreting + // common `/Users/...` paths as the `/U` flag and triggering + // `-Wslash-u-filename` warning. + cmd.arg("--"); } + cmd.args(self.files.iter().map(std::ops::Deref::deref)); + let name = compiler .path .file_name() @@ -1434,7 +1657,7 @@ impl Build { .to_string_lossy() .into_owned(); - Ok(run_output(&mut cmd, &name)?) + Ok(run_output(&mut cmd, &name, &self.cargo_output)?) } /// Run the compiler, returning the macro-expanded version of the input files. @@ -1483,13 +1706,13 @@ impl Build { /// Get the compiler that's in use for this configuration. /// - /// This will return a result instead of panicing; see get_compiler() for the complete description. + /// This will return a result instead of panicking; see + /// [`get_compiler()`](Self::get_compiler) for the complete description. 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()?; - let envflags = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }); // Disable default flag generation via `no_default_flags` or environment variable let no_defaults = self.no_default_flags || self.getenv("CRATE_CC_NO_DEFAULTS").is_some(); @@ -1500,13 +1723,23 @@ impl Build { println!("Info: default compiler flags are disabled"); } - for arg in envflags { - cmd.push_cc_arg(arg.into()); + 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()); + } + + if let Ok(flags) = self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }) { + for arg in flags { + cmd.push_cc_arg(arg.into()); + } } for directory in self.include_directories.iter() { cmd.args.push("-I".into()); - cmd.args.push(directory.into()); + cmd.args.push(directory.as_os_str().into()); } // If warnings and/or extra_warnings haven't been explicitly set, @@ -1514,34 +1747,28 @@ impl Build { // CFLAGS/CXXFLAGS, since those variables presumably already contain // the desired set of warnings flags. - if self - .warnings - .unwrap_or(if self.has_flags() { false } else { true }) - { + if self.warnings.unwrap_or(!self.has_flags()) { let wflags = cmd.family.warnings_flags().into(); cmd.push_cc_arg(wflags); } - if self - .extra_warnings - .unwrap_or(if self.has_flags() { false } else { true }) - { + if self.extra_warnings.unwrap_or(!self.has_flags()) { if let Some(wflags) = cmd.family.extra_warnings_flags() { cmd.push_cc_arg(wflags.into()); } } for flag in self.flags.iter() { - cmd.args.push(flag.into()); + cmd.args.push((**flag).into()); } for flag in self.flags_supported.iter() { if self.is_flag_supported(flag).unwrap_or(false) { - cmd.push_cc_arg(flag.into()); + cmd.push_cc_arg((**flag).into()); } } - for &(ref key, ref value) in self.definitions.iter() { + for (key, value) in self.definitions.iter() { if let Some(ref value) = *value { cmd.args.push(format!("-D{}={}", key, value).into()); } else { @@ -1573,9 +1800,8 @@ impl Build { Some(true) => "-MT", Some(false) => "-MD", None => { - let features = self - .getenv("CARGO_CFG_TARGET_FEATURE") - .unwrap_or(String::new()); + let features = self.getenv("CARGO_CFG_TARGET_FEATURE"); + let features = features.as_deref().unwrap_or_default(); if features.contains("crt-static") { "-MT" } else { @@ -1602,6 +1828,13 @@ impl Build { cmd.push_opt_unless_duplicate(format!("-O{}", opt_level).into()); } + if cmd.family == ToolFamily::Clang && target.contains("windows") { + // Disambiguate mingw and msvc on Windows. Problem is that + // depending on the origin clang can default to a mismatchig + // run-time. + cmd.push_cc_arg(format!("--target={}", target).into()); + } + if cmd.family == ToolFamily::Clang && target.contains("android") { // For compatibility with code that doesn't use pre-defined `__ANDROID__` macro. // If compiler used via ndk-build or cmake (officially supported build methods) @@ -1611,7 +1844,10 @@ impl Build { cmd.push_opt_unless_duplicate("-DANDROID".into()); } - if !target.contains("apple-ios") && !target.contains("apple-watchos") { + if !target.contains("apple-ios") + && !target.contains("apple-watchos") + && !target.contains("apple-tvos") + { cmd.push_cc_arg("-ffunction-sections".into()); cmd.push_cc_arg("-fdata-sections".into()); } @@ -1645,55 +1881,50 @@ impl Build { family.add_force_frame_pointer(cmd); } + if !cmd.is_like_msvc() { + if target.contains("i686") || target.contains("i586") { + cmd.args.push("-m32".into()); + } else if target == "x86_64-unknown-linux-gnux32" { + cmd.args.push("-mx32".into()); + } else if target.contains("x86_64") || target.contains("powerpc64") { + cmd.args.push("-m64".into()); + } + } + // Target flags + if target.contains("-apple-") { + self.apple_flags(cmd, target)?; + } else { + self.target_flags(cmd, target); + } + + if self.static_flag.unwrap_or(false) { + cmd.args.push("-static".into()); + } + if self.shared_flag.unwrap_or(false) { + cmd.args.push("-shared".into()); + } + + 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)); + } + } + } + + Ok(()) + } + + fn target_flags(&self, cmd: &mut Tool, target: &str) { match cmd.family { ToolFamily::Clang => { - if !(target.contains("android") - && android_clang_compiler_uses_target_arg_internally(&cmd.path)) - { - if target.contains("darwin") { - if let Some(arch) = - map_darwin_target_from_rust_to_compiler_architecture(target) - { - cmd.args - .push(format!("--target={}-apple-darwin", arch).into()); - } - } else if target.contains("macabi") { - if let Some(arch) = - map_darwin_target_from_rust_to_compiler_architecture(target) - { - cmd.args - .push(format!("--target={}-apple-ios-macabi", arch).into()); - } - } else if target.contains("ios-sim") { - if let Some(arch) = - map_darwin_target_from_rust_to_compiler_architecture(target) - { - let deployment_target = env::var("IPHONEOS_DEPLOYMENT_TARGET") - .unwrap_or_else(|_| "7.0".into()); - cmd.args.push( - format!( - "--target={}-apple-ios{}-simulator", - arch, deployment_target - ) - .into(), - ); - } - } else if target.contains("watchos-sim") { - if let Some(arch) = - map_darwin_target_from_rust_to_compiler_architecture(target) - { - let deployment_target = env::var("WATCHOS_DEPLOYMENT_TARGET") - .unwrap_or_else(|_| "5.0".into()); - cmd.args.push( - format!( - "--target={}-apple-watchos{}-simulator", - arch, deployment_target - ) - .into(), - ); - } - } else if target.starts_with("riscv64gc-") { + if !(target.contains("android") && cmd.has_internal_target_arg) { + if target.starts_with("riscv64gc-") { cmd.args.push( format!("--target={}", target.replace("riscv64gc", "riscv64")).into(), ); @@ -1709,6 +1940,30 @@ impl Build { } else if target.contains("aarch64") { cmd.args.push("--target=aarch64-unknown-windows-gnu".into()) } + } else if target.ends_with("-freebsd") { + // FreeBSD only supports C++11 and above when compiling against libc++ + // (available from FreeBSD 10 onwards). Under FreeBSD, clang uses libc++ by + // default on FreeBSD 10 and newer unless `--target` is manually passed to + // the compiler, in which case its default behavior differs: + // * If --target=xxx-unknown-freebsdX(.Y) is specified and X is greater than + // or equal to 10, clang++ uses libc++ + // * If --target=xxx-unknown-freebsd is specified (without a version), + // clang++ cannot assume libc++ is available and reverts to a default of + // libstdc++ (this behavior was changed in llvm 14). + // + // This breaks C++11 (or greater) builds if targeting FreeBSD with the + // generic xxx-unknown-freebsd triple on clang 13 or below *without* + // explicitly specifying that libc++ should be used. + // When cross-compiling, we can't infer from the rust/cargo target triple + // which major version of FreeBSD we are targeting, so we need to make sure + // that libc++ is used (unless the user has explicitly specified otherwise). + // There's no compelling reason to use a different approach when compiling + // natively. + if self.cpp && self.cpp_set_stdlib.is_none() { + cmd.push_cc_arg("-stdlib=libc++".into()); + } + + cmd.push_cc_arg(format!("--target={}", target).into()); } else { cmd.push_cc_arg(format!("--target={}", target).into()); } @@ -1732,6 +1987,8 @@ impl Build { } else { if target.contains("i586") { cmd.push_cc_arg("-arch:IA32".into()); + } else if target.contains("arm64ec") { + cmd.push_cc_arg("-arm64EC".into()); } } @@ -1750,30 +2007,13 @@ impl Build { } } ToolFamily::Gnu => { - if target.contains("i686") || target.contains("i586") { - cmd.args.push("-m32".into()); - } else if target == "x86_64-unknown-linux-gnux32" { - cmd.args.push("-mx32".into()); - } else if target.contains("x86_64") || target.contains("powerpc64") { - cmd.args.push("-m64".into()); - } - - if target.contains("darwin") { - if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) - { - cmd.args.push("-arch".into()); - cmd.args.push(arch.into()); - } - } - if target.contains("-kmc-solid_") { cmd.args.push("-finput-charset=utf-8".into()); } if self.static_flag.is_none() { - let features = self - .getenv("CARGO_CFG_TARGET_FEATURE") - .unwrap_or(String::new()); + let features = self.getenv("CARGO_CFG_TARGET_FEATURE"); + let features = features.as_deref().unwrap_or_default(); if features.contains("crt-static") { cmd.args.push("-static".into()); } @@ -1927,64 +2167,38 @@ impl Build { let mut parts = target.split('-'); if let Some(arch) = parts.next() { let arch = &arch[5..]; - if target.contains("linux") && arch.starts_with("64") { - cmd.args.push(("-march=rv64gc").into()); - cmd.args.push("-mabi=lp64d".into()); - } else if target.contains("freebsd") && arch.starts_with("64") { - cmd.args.push(("-march=rv64gc").into()); - cmd.args.push("-mabi=lp64d".into()); - } else if target.contains("openbsd") && arch.starts_with("64") { - cmd.args.push(("-march=rv64gc").into()); - cmd.args.push("-mabi=lp64d".into()); - } else if target.contains("linux") && arch.starts_with("32") { - cmd.args.push(("-march=rv32gc").into()); - cmd.args.push("-mabi=ilp32d".into()); - } else if arch.starts_with("64") { - cmd.args.push(("-march=rv".to_owned() + arch).into()); - cmd.args.push("-mabi=lp64".into()); + if arch.starts_with("64") { + if target.contains("linux") + | target.contains("freebsd") + | target.contains("netbsd") + | target.contains("linux") + { + cmd.args.push(("-march=rv64gc").into()); + cmd.args.push("-mabi=lp64d".into()); + } else { + cmd.args.push(("-march=rv".to_owned() + arch).into()); + cmd.args.push("-mabi=lp64".into()); + } + } else if arch.starts_with("32") { + if target.contains("linux") { + cmd.args.push(("-march=rv32gc").into()); + cmd.args.push("-mabi=ilp32d".into()); + } else { + cmd.args.push(("-march=rv".to_owned() + arch).into()); + cmd.args.push("-mabi=ilp32".into()); + } } else { - cmd.args.push(("-march=rv".to_owned() + arch).into()); - cmd.args.push("-mabi=ilp32".into()); + cmd.args.push("-mcmodel=medany".into()); } - cmd.args.push("-mcmodel=medany".into()); } } } } - - if target.contains("apple-ios") || target.contains("apple-watchos") { - self.ios_watchos_flags(cmd)?; - } - - if self.static_flag.unwrap_or(false) { - cmd.args.push("-static".into()); - } - if self.shared_flag.unwrap_or(false) { - cmd.args.push("-shared".into()); - } - - 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()); - } - _ => { - println!( - "cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \ - does not support this option, ignored", - cmd.family - ); - } - } - } - - Ok(()) } fn has_flags(&self) -> bool { let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" }; - let flags_env_var_value = self.get_var(flags_env_var_name); + let flags_env_var_value = self.getenv_with_target_prefixes(flags_env_var_name); if let Ok(_) = flags_env_var_value { true } else { @@ -2006,20 +2220,33 @@ impl Build { let mut cmd = windows_registry::find(&target, tool).unwrap_or_else(|| self.cmd(tool)); cmd.arg("-nologo"); // undocumented, yet working with armasm[64] for directory in self.include_directories.iter() { - cmd.arg("-I").arg(directory); + cmd.arg("-I").arg(&**directory); } if target.contains("aarch64") || target.contains("arm") { if self.get_debug() { cmd.arg("-g"); } - println!("cargo:warning=The MSVC ARM assemblers do not support -D flags"); + for (key, value) in self.definitions.iter() { + cmd.arg("-PreDefine"); + if let Some(ref value) = *value { + if let Ok(i) = value.parse::<i32>() { + cmd.arg(&format!("{} SETA {}", key, i)); + } else if value.starts_with('"') && value.ends_with('"') { + cmd.arg(&format!("{} SETS {}", key, value)); + } else { + cmd.arg(&format!("{} SETS \"{}\"", key, value)); + } + } else { + cmd.arg(&format!("{} SETL {}", key, "{TRUE}")); + } + } } else { if self.get_debug() { cmd.arg("-Zi"); } - for &(ref key, ref value) in self.definitions.iter() { + for (key, value) in self.definitions.iter() { if let Some(ref value) = *value { cmd.arg(&format!("-D{}={}", key, value)); } else { @@ -2031,9 +2258,6 @@ impl Build { if target.contains("i686") || target.contains("i586") { cmd.arg("-safeseh"); } - for flag in self.flags.iter() { - cmd.arg(flag); - } Ok((cmd, tool.to_string())) } @@ -2041,15 +2265,15 @@ impl Build { fn assemble(&self, lib_name: &str, dst: &Path, objs: &[Object]) -> Result<(), Error> { // Delete the destination if it exists as we want to // create on the first iteration instead of appending. - let _ = fs::remove_file(&dst); + let _ = fs::remove_file(dst); // Add objects to the archive in limited-length batches. This helps keep // the length of the command line within a reasonable length to avoid // blowing system limits on limiting platforms like Windows. let objs: Vec<_> = objs .iter() - .map(|o| o.dst.clone()) - .chain(self.objects.clone()) + .map(|o| o.dst.as_path()) + .chain(self.objects.iter().map(std::ops::Deref::deref)) .collect(); for chunk in objs.chunks(100) { self.assemble_progressive(dst, chunk)?; @@ -2062,12 +2286,9 @@ impl Build { 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.clone()) - .arg(dst); - run(&mut nvcc, "nvcc")?; - self.assemble_progressive(dst, &[dlink])?; + nvcc.arg("--device-link").arg("-o").arg(&dlink).arg(dst); + run(&mut nvcc, "nvcc", &self.cargo_output)?; + self.assemble_progressive(dst, &[dlink.as_path()])?; } let target = self.get_target()?; @@ -2078,9 +2299,9 @@ impl Build { let lib_dst = dst.with_file_name(format!("{}.lib", lib_name)); let _ = fs::remove_file(&lib_dst); - match fs::hard_link(&dst, &lib_dst).or_else(|_| { + match fs::hard_link(dst, &lib_dst).or_else(|_| { // if hard-link fails, just copy (ignoring the number of bytes written) - fs::copy(&dst, &lib_dst).map(|_| ()) + fs::copy(dst, &lib_dst).map(|_| ()) }) { Ok(_) => (), Err(_) => { @@ -2094,23 +2315,31 @@ impl Build { // Non-msvc targets (those using `ar`) need a separate step to add // the symbol table to archives since our construction command of // `cq` doesn't add it for us. - let (mut ar, cmd) = self.get_ar()?; - run(ar.arg("s").arg(dst), &cmd)?; + let (mut ar, cmd, _any_flags) = self.get_ar()?; + + // NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s` + // here represents a _mode_, not an arbitrary flag. Further discussion of this choice + // can be seen in https://github.com/rust-lang/cc-rs/pull/763. + run(ar.arg("s").arg(dst), &cmd, &self.cargo_output)?; } Ok(()) } - fn assemble_progressive(&self, dst: &Path, objs: &[PathBuf]) -> Result<(), Error> { + fn assemble_progressive(&self, dst: &Path, objs: &[&Path]) -> Result<(), Error> { let target = self.get_target()?; if target.contains("msvc") { - let (mut cmd, program) = self.get_ar()?; + let (mut cmd, program, any_flags) = self.get_ar()?; + // NOTE: -out: here is an I/O flag, and so must be included even if $ARFLAGS/ar_flag is + // in use. -nologo on the other hand is just a regular flag, and one that we'll skip if + // the caller has explicitly dictated the flags they want. See + // https://github.com/rust-lang/cc-rs/pull/763 for further discussion. let mut out = OsString::from("-out:"); out.push(dst); - cmd.arg(out).arg("-nologo"); - for flag in self.ar_flags.iter() { - cmd.arg(flag); + cmd.arg(out); + if !any_flags { + cmd.arg("-nologo"); } // If the library file already exists, add the library name // as an argument to let lib.exe know we are appending the objs. @@ -2118,9 +2347,9 @@ impl Build { cmd.arg(dst); } cmd.args(objs); - run(&mut cmd, &program)?; + run(&mut cmd, &program, &self.cargo_output)?; } else { - let (mut ar, cmd) = self.get_ar()?; + let (mut ar, cmd, _any_flags) = self.get_ar()?; // Set an environment variable to tell the OSX archiver to ensure // that all dates listed in the archive are zero, improving @@ -2145,46 +2374,35 @@ impl Build { // In any case if this doesn't end up getting read, it shouldn't // cause that many issues! ar.env("ZERO_AR_DATE", "1"); - for flag in self.ar_flags.iter() { - ar.arg(flag); - } - run(ar.arg("cq").arg(dst).args(objs), &cmd)?; + + // NOTE: We add cq here regardless of whether $ARFLAGS/ar_flag have been used because + // it dictates the _mode_ ar runs in, which the setter of $ARFLAGS/ar_flag can't + // dictate. See https://github.com/rust-lang/cc-rs/pull/763 for further discussion. + run(ar.arg("cq").arg(dst).args(objs), &cmd, &self.cargo_output)?; } Ok(()) } - fn ios_watchos_flags(&self, cmd: &mut Tool) -> Result<(), Error> { - enum ArchSpec { - Device(&'static str), - Simulator(&'static str), - Catalyst(&'static str), - } - - enum Os { - Ios, - WatchOs, - } - impl Display for Os { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Os::Ios => f.write_str("iOS"), - Os::WatchOs => f.write_str("WatchOS"), - } - } - } - - let target = self.get_target()?; - let os = if target.contains("-watchos") { - Os::WatchOs + fn apple_flags(&self, cmd: &mut Tool, target: &str) -> Result<(), Error> { + let os = if target.contains("-darwin") { + AppleOs::MacOs + } else if target.contains("-watchos") { + AppleOs::WatchOs + } else if target.contains("-tvos") { + AppleOs::TvOs } else { - Os::Ios + AppleOs::Ios + }; + let is_mac = match os { + AppleOs::MacOs => true, + _ => false, }; - let arch = target.split('-').nth(0).ok_or_else(|| { + let arch_str = target.split('-').nth(0).ok_or_else(|| { Error::new( ErrorKind::ArchitectureInvalid, - format!("Unknown architecture for {} target.", os).as_str(), + format!("Unknown architecture for {:?} target.", os), ) })?; @@ -2193,16 +2411,27 @@ impl Build { None => false, }; - let is_sim = match target.split('-').nth(3) { + let is_arm_sim = match target.split('-').nth(3) { Some(v) => v == "sim", None => false, }; - let arch = if is_catalyst { - match arch { - "arm64e" => ArchSpec::Catalyst("arm64e"), - "arm64" | "aarch64" => ArchSpec::Catalyst("arm64"), - "x86_64" => ArchSpec::Catalyst("-m64"), + let arch = if is_mac { + match arch_str { + "i686" => AppleArchSpec::Device("-m32"), + "x86_64" | "x86_64h" | "aarch64" => AppleArchSpec::Device("-m64"), + _ => { + return Err(Error::new( + ErrorKind::ArchitectureInvalid, + "Unknown architecture for macOS target.", + )); + } + } + } else if is_catalyst { + match arch_str { + "arm64e" => AppleArchSpec::Catalyst("arm64e"), + "arm64" | "aarch64" => AppleArchSpec::Catalyst("arm64"), + "x86_64" | "x86_64h" => AppleArchSpec::Catalyst("-m64"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, @@ -2210,105 +2439,192 @@ impl Build { )); } } - } else if is_sim { - match arch { - "arm64" | "aarch64" => ArchSpec::Simulator("-arch arm64"), - "x86_64" => ArchSpec::Simulator("-m64"), + } else if is_arm_sim { + match arch_str { + "arm64" | "aarch64" => AppleArchSpec::Simulator("arm64"), + "x86_64" | "x86_64h" => AppleArchSpec::Simulator("-m64"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, - "Unknown architecture for iOS simulator target.", + "Unknown architecture for simulator target.", )); } } } else { - match arch { - "arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7"), - "armv7k" => ArchSpec::Device("armv7k"), - "armv7s" | "thumbv7s" => ArchSpec::Device("armv7s"), - "arm64e" => ArchSpec::Device("arm64e"), - "arm64" | "aarch64" => ArchSpec::Device("arm64"), - "arm64_32" => ArchSpec::Device("arm64_32"), - "i386" | "i686" => ArchSpec::Simulator("-m32"), - "x86_64" => ArchSpec::Simulator("-m64"), + match arch_str { + "arm" | "armv7" | "thumbv7" => AppleArchSpec::Device("armv7"), + "armv7k" => AppleArchSpec::Device("armv7k"), + "armv7s" | "thumbv7s" => AppleArchSpec::Device("armv7s"), + "arm64e" => AppleArchSpec::Device("arm64e"), + "arm64" | "aarch64" => AppleArchSpec::Device("arm64"), + "arm64_32" => AppleArchSpec::Device("arm64_32"), + "i386" | "i686" => AppleArchSpec::Simulator("-m32"), + "x86_64" | "x86_64h" => AppleArchSpec::Simulator("-m64"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, - format!("Unknown architecture for {} target.", os).as_str(), + format!("Unknown architecture for {:?} target.", os), )); } } }; - let (sdk_prefix, sim_prefix, min_version) = match os { - Os::Ios => ( - "iphone", - "ios-", - std::env::var("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|_| "7.0".into()), - ), - Os::WatchOs => ( - "watch", - "watch", - std::env::var("WATCHOS_DEPLOYMENT_TARGET").unwrap_or_else(|_| "2.0".into()), - ), - }; + let sdk_details = apple_os_sdk_parts(os, &arch); + let min_version = self.apple_deployment_version(os, Some(arch_str), &sdk_details.sdk); - let sdk = match arch { - ArchSpec::Device(arch) => { - cmd.args.push("-arch".into()); - cmd.args.push(arch.into()); + match arch { + AppleArchSpec::Device(_) if is_mac => { cmd.args - .push(format!("-m{}os-version-min={}", sdk_prefix, min_version).into()); - format!("{}os", sdk_prefix) + .push(format!("-mmacosx-version-min={}", min_version).into()); } - ArchSpec::Simulator(arch) => { + AppleArchSpec::Device(arch) => { + cmd.args.push("-arch".into()); cmd.args.push(arch.into()); - cmd.args - .push(format!("-m{}simulator-version-min={}", sim_prefix, min_version).into()); - format!("{}simulator", sdk_prefix) + cmd.args.push( + format!("-m{}os-version-min={}", sdk_details.sdk_prefix, min_version).into(), + ); } - ArchSpec::Catalyst(_) => "macosx".to_owned(), + AppleArchSpec::Simulator(arch) => { + if arch.starts_with('-') { + // -m32 or -m64 + cmd.args.push(arch.into()); + } else { + cmd.args.push("-arch".into()); + cmd.args.push(arch.into()); + } + cmd.args.push( + format!( + "-m{}simulator-version-min={}", + sdk_details.sim_prefix, min_version + ) + .into(), + ); + } + AppleArchSpec::Catalyst(_) => {} }; - self.print(&format!("Detecting {} SDK path for {}", os, sdk)); - let sdk_path = if let Some(sdkroot) = env::var_os("SDKROOT") { - sdkroot - } else { - self.apple_sdk_root(sdk.as_str())? - }; + // AppleClang sometimes requires sysroot even for darwin + if cmd.is_xctoolchain_clang() || !target.ends_with("-darwin") { + self.cargo_output.print_metadata(&format_args!( + "Detecting {:?} SDK path for {}", + os, sdk_details.sdk + )); + let sdk_path = self.apple_sdk_root(&sdk_details.sdk)?; - cmd.args.push("-isysroot".into()); - cmd.args.push(sdk_path); - cmd.args.push("-fembed-bitcode".into()); - /* - * TODO we probably ultimately want the -fembed-bitcode-marker flag - * but can't have it now because of an issue in LLVM: - * https://github.com/rust-lang/cc-rs/issues/301 - * https://github.com/rust-lang/rust/pull/48896#comment-372192660 - */ - /* - if self.get_opt_level()? == "0" { - cmd.args.push("-fembed-bitcode-marker".into()); - } - */ + cmd.args.push("-isysroot".into()); + cmd.args.push(sdk_path); + } + + match cmd.family { + ToolFamily::Gnu => { + if target.contains("darwin") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args.push("-arch".into()); + cmd.args.push(arch.into()); + } + } + } + ToolFamily::Clang => { + if target.contains("darwin") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args + .push(format!("--target={}-apple-darwin", arch).into()); + } + } else if target.contains("macabi") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args + .push(format!("--target={}-apple-ios-macabi", arch).into()); + } + } else if target.contains("ios-sim") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args.push( + format!("--target={}-apple-ios{}-simulator", arch, min_version).into(), + ); + } + } else if target.contains("watchos-sim") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args.push( + format!("--target={}-apple-watchos{}-simulator", arch, min_version) + .into(), + ); + } + } else if target.contains("tvos-sim") || target.contains("x86_64-apple-tvos") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args.push( + format!("--target={}-apple-tvos{}-simulator", arch, min_version).into(), + ); + } + } else if target.contains("aarch64-apple-tvos") { + if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) + { + cmd.args + .push(format!("--target={}-apple-tvos{}", arch, min_version).into()); + } + } + } + _ => unreachable!("unexpected compiler for apple architectures"), + } + + if let AppleArchSpec::Catalyst(_) = arch { + // Mac Catalyst uses the macOS SDK, but to compile against and + // link to iOS-specific frameworks, we should have the support + // library stubs in the include and library search path. + let sdk_path = self.apple_sdk_root(&sdk_details.sdk)?; + let ios_support = PathBuf::from(sdk_path).join("/System/iOSSupport"); + + cmd.args.extend([ + // Header search path + OsString::from("-isystem"), + ios_support.join("/usr/include").into(), + // Framework header search path + OsString::from("-iframework"), + ios_support.join("/System/Library/Frameworks").into(), + // Library search path + { + let mut s = OsString::from("-L"); + s.push(&ios_support.join("/usr/lib")); + s + }, + // Framework linker search path + { + // Technically, we _could_ avoid emitting `-F`, as + // `-iframework` implies it, but let's keep it in for + // clarity. + 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); - for &(ref a, ref b) in self.env.iter() { + for (a, b) in self.env.iter() { cmd.env(a, b); } cmd } fn get_base_compiler(&self) -> Result<Tool, Error> { - if let Some(ref c) = self.compiler { - return Ok(Tool::new(c.clone())); + if let Some(c) = &self.compiler { + return Ok(Tool::new( + (**c).to_owned(), + &self.cached_compiler_family, + &self.cargo_output, + )); } let host = self.get_host()?; let target = self.get_target()?; + let target = &*target; let (env, msvc, gnu, traditional, clang) = if self.cpp { ("CXX", "cl.exe", "g++", "c++", "clang++") } else { @@ -2325,7 +2641,7 @@ impl Build { traditional }; - let cl_exe = windows_registry::find_tool(&target, "cl.exe"); + let cl_exe = windows_registry::find_tool(target, "cl.exe"); let tool_opt: Option<Tool> = self .env_tool(env) @@ -2340,7 +2656,12 @@ impl Build { // semi-buggy build scripts which are shared in // makefiles/configure scripts (where spaces are far more // lenient) - let mut t = Tool::with_clang_driver(PathBuf::from(tool.trim()), driver_mode); + let mut t = Tool::with_clang_driver( + tool, + driver_mode, + &self.cached_compiler_family, + &self.cargo_output, + ); if let Some(cc_wrapper) = wrapper { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); } @@ -2354,12 +2675,20 @@ impl Build { let tool = if self.cpp { "em++" } else { "emcc" }; // Windows uses bat file so we have to be a bit more specific if cfg!(windows) { - let mut t = Tool::new(PathBuf::from("cmd")); + let mut t = Tool::new( + PathBuf::from("cmd"), + &self.cached_compiler_family, + &self.cargo_output, + ); t.args.push("/c".into()); t.args.push(format!("{}.bat", tool).into()); Some(t) } else { - Some(Tool::new(PathBuf::from(tool))) + Some(Tool::new( + PathBuf::from(tool), + &self.cached_compiler_family, + &self.cargo_output, + )) } } else { None @@ -2377,12 +2706,13 @@ impl Build { let cc = if target.contains("llvm") { clang } else { gnu }; format!("{}.exe", cc) } - } else if target.contains("apple-ios") { - clang.to_string() - } else if target.contains("apple-watchos") { + } else if target.contains("apple-ios") + | target.contains("apple-watchos") + | target.contains("apple-tvos") + { clang.to_string() } else if target.contains("android") { - autodetect_android_compiler(&target, &host, gnu, clang) + autodetect_android_compiler(target, &host, gnu, clang) } else if target.contains("cloudabi") { format!("{}-{}", target, traditional) } else if target == "wasm32-wasi" @@ -2400,8 +2730,8 @@ impl Build { format!("arm-kmc-eabi-{}", gnu) } else if target.starts_with("aarch64-kmc-solid_") { format!("aarch64-kmc-elf-{}", gnu) - } else if self.get_host()? != target { - let prefix = self.prefix_for_target(&target); + } else if &*self.get_host()? != target { + let prefix = self.prefix_for_target(target); match prefix { Some(prefix) => { let cc = if target.contains("llvm") { clang } else { gnu }; @@ -2413,7 +2743,11 @@ impl Build { default.to_string() }; - let mut t = Tool::new(PathBuf::from(compiler)); + let mut t = Tool::new( + PathBuf::from(compiler), + &self.cached_compiler_family, + &self.cargo_output, + ); if let Some(cc_wrapper) = Self::rustc_wrapper_fallback() { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); } @@ -2426,11 +2760,17 @@ impl Build { tool.args.is_empty(), "CUDA compilation currently assumes empty pre-existing args" ); - let nvcc = match self.get_var("NVCC") { - Err(_) => "nvcc".into(), - Ok(nvcc) => nvcc, + 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(PathBuf::from(nvcc), None, self.cuda); + let mut nvcc_tool = Tool::with_features( + nvcc, + None, + self.cuda, + &self.cached_compiler_family, + &self.cargo_output, + ); nvcc_tool .args .push(format!("-ccbin={}", tool.path.display()).into()); @@ -2455,16 +2795,17 @@ impl Build { { if let Some(path) = tool.path.file_name() { let file_name = path.to_str().unwrap().to_owned(); - let (target, clang) = file_name.split_at(file_name.rfind("-").unwrap()); + let (target, clang) = file_name.split_at(file_name.rfind('-').unwrap()); - tool.path.set_file_name(clang.trim_start_matches("-")); + 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()); // Additionally, shell scripts for target i686-linux-android versions 16 to 24 // pass the `mstackrealign` option so we do that here as well. if target.contains("i686-linux-android") { - let (_, version) = target.split_at(target.rfind("d").unwrap() + 1); + let (_, version) = target.split_at(target.rfind('d').unwrap() + 1); if let Ok(version) = version.parse::<u32>() { if version > 15 && version < 25 { tool.args.push("-mstackrealign".into()); @@ -2489,41 +2830,18 @@ impl Build { && tool.env.len() == 0 && target.contains("msvc") { - for &(ref k, ref v) in cl_exe.env.iter() { + for (k, v) in cl_exe.env.iter() { tool.env.push((k.to_owned(), v.to_owned())); } } } - Ok(tool) - } - - fn get_var(&self, var_base: &str) -> Result<String, Error> { - let target = self.get_target()?; - let host = self.get_host()?; - let kind = if host == target { "HOST" } else { "TARGET" }; - let target_u = target.replace("-", "_"); - let res = self - .getenv(&format!("{}_{}", var_base, target)) - .or_else(|| self.getenv(&format!("{}_{}", var_base, target_u))) - .or_else(|| self.getenv(&format!("{}_{}", kind, var_base))) - .or_else(|| self.getenv(var_base)); - - match res { - Some(res) => Ok(res), - None => Err(Error::new( - ErrorKind::EnvVarNotFound, - &format!("Could not find environment variable {}.", var_base), - )), + if target.contains("msvc") && tool.family == ToolFamily::Gnu { + self.cargo_output + .print_warning(&"GNU compiler is not supported for this target"); } - } - fn envflags(&self, name: &str) -> Vec<String> { - self.get_var(name) - .unwrap_or(String::new()) - .split_ascii_whitespace() - .map(|slice| slice.to_string()) - .collect() + Ok(tool) } /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER` @@ -2545,8 +2863,8 @@ impl Build { } /// Returns compiler path, optional modifier name from whitelist, and arguments vec - fn env_tool(&self, name: &str) -> Option<(String, Option<String>, Vec<String>)> { - let tool = match self.get_var(name) { + fn env_tool(&self, name: &str) -> Option<(PathBuf, Option<String>, Vec<String>)> { + let tool = match self.getenv_with_target_prefixes(name) { Ok(tool) => tool, Err(_) => return None, }; @@ -2554,8 +2872,12 @@ impl Build { // If this is an exact path on the filesystem we don't want to do any // interpretation at all, just pass it on through. This'll hopefully get // us to support spaces-in-paths. - if Path::new(&tool).exists() { - return Some((tool, None, Vec::new())); + if Path::new(&*tool).exists() { + return Some(( + PathBuf::from(&*tool), + Self::rustc_wrapper_fallback(), + Vec::new(), + )); } // Ok now we want to handle a couple of scenarios. We'll assume from @@ -2594,7 +2916,7 @@ impl Build { if known_wrappers.contains(&file_stem) { if let Some(compiler) = parts.next() { return Some(( - compiler.to_string(), + compiler.into(), Some(maybe_wrapper.to_string()), parts.map(|s| s.to_string()).collect(), )); @@ -2602,36 +2924,37 @@ impl Build { } Some(( - maybe_wrapper.to_string(), + maybe_wrapper.into(), Self::rustc_wrapper_fallback(), parts.map(|s| s.to_string()).collect(), )) } /// Returns the C++ standard library: - /// 1. If [cpp_link_stdlib](cc::Build::cpp_link_stdlib) is set, uses its value. + /// 1. If [`cpp_link_stdlib`](cc::Build::cpp_link_stdlib) is set, uses its value. /// 2. Else if the `CXXSTDLIB` environment variable is set, uses its value. /// 3. Else the default is `libc++` for OS X and BSDs, `libc++_shared` for Android, /// `None` for MSVC and `libstdc++` for anything else. fn get_cpp_link_stdlib(&self) -> Result<Option<String>, Error> { - match self.cpp_link_stdlib.clone() { - Some(s) => Ok(s), + match &self.cpp_link_stdlib { + Some(s) => Ok(s.as_ref().map(|s| (*s).to_string())), None => { - if let Ok(stdlib) = self.get_var("CXXSTDLIB") { + if let Ok(stdlib) = self.getenv_with_target_prefixes("CXXSTDLIB") { if stdlib.is_empty() { Ok(None) } else { - Ok(Some(stdlib)) + Ok(Some(stdlib.to_string())) } } else { let target = self.get_target()?; if target.contains("msvc") { Ok(None) - } else if target.contains("apple") { - Ok(Some("c++".to_string())) - } else if target.contains("freebsd") { - Ok(Some("c++".to_string())) - } else if target.contains("openbsd") { + } else if target.contains("apple") + | target.contains("freebsd") + | target.contains("openbsd") + | target.contains("aix") + | target.contains("linux-ohos") + { Ok(Some("c++".to_string())) } else if target.contains("android") { Ok(Some("c++_shared".to_string())) @@ -2643,101 +2966,243 @@ impl Build { } } - fn get_ar(&self) -> Result<(Command, String), Error> { - if let Some(ref p) = self.archiver { - let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("ar"); - return Ok((self.cmd(p), name.to_string())); + fn get_ar(&self) -> Result<(Command, String, bool), Error> { + self.try_get_archiver_and_flags() + } + + /// Get the archiver (ar) that's in use for this configuration. + /// + /// You can use [`Command::get_program`] to get just the path to the command. + /// + /// This method will take into account all configuration such as debug + /// information, optimization level, include directories, defines, etc. + /// Additionally, the compiler binary in use follows the standard + /// conventions for this path, e.g. looking at the explicitly set compiler, + /// environment variables (a number of which are inspected here), and then + /// falling back to the default configuration. + /// + /// # Panics + /// + /// Panics if an error occurred while determining the architecture. + pub fn get_archiver(&self) -> Command { + match self.try_get_archiver() { + Ok(tool) => tool, + Err(e) => fail(&e.message), } - if let Ok(p) = self.get_var("AR") { - return Ok((self.cmd(&p), p)); + } + + /// Get the archiver that's in use for this configuration. + /// + /// This will return a result instead of panicking; + /// see [`Self::get_archiver`] for the complete description. + 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, String, bool), Error> { + let (mut cmd, name) = self.get_base_archiver()?; + let mut any_flags = false; + if let Ok(flags) = self.envflags("ARFLAGS") { + any_flags = any_flags | !flags.is_empty(); + cmd.args(flags); } - let target = self.get_target()?; - let default_ar = "ar".to_string(); - let program = if target.contains("android") { - format!("{}-ar", target.replace("armv7", "arm")) - } else if target.contains("emscripten") { - // Windows use bat files so we have to be a bit more specific - if cfg!(windows) { - let mut cmd = self.cmd("cmd"); - cmd.arg("/c").arg("emar.bat"); - return Ok((cmd, "emar.bat".to_string())); - } + for flag in &self.ar_flags { + any_flags = true; + cmd.arg(&**flag); + } + Ok((cmd, name, any_flags)) + } - "emar".to_string() - } else if target.contains("msvc") { - let compiler = self.get_base_compiler()?; - let mut lib = String::new(); - if compiler.family == (ToolFamily::Msvc { clang_cl: true }) { - // See if there is 'llvm-lib' next to 'clang-cl' - // Another possibility could be to see if there is 'clang' - // next to 'clang-cl' and use 'search_programs()' to locate - // 'llvm-lib'. This is because 'clang-cl' doesn't support - // the -print-search-dirs option. - if let Some(mut cmd) = which(&compiler.path) { - cmd.pop(); - cmd.push("llvm-lib.exe"); - if let Some(llvm_lib) = which(&cmd) { - lib = llvm_lib.to_str().unwrap().to_owned(); + fn get_base_archiver(&self) -> Result<(Command, String), Error> { + if let Some(ref a) = self.archiver { + return Ok((self.cmd(&**a), a.to_string_lossy().into_owned())); + } + + self.get_base_archiver_variant("AR", "ar") + } + + /// Get the ranlib that's in use for this configuration. + /// + /// You can use [`Command::get_program`] to get just the path to the command. + /// + /// This method will take into account all configuration such as debug + /// information, optimization level, include directories, defines, etc. + /// Additionally, the compiler binary in use follows the standard + /// conventions for this path, e.g. looking at the explicitly set compiler, + /// environment variables (a number of which are inspected here), and then + /// falling back to the default configuration. + /// + /// # Panics + /// + /// Panics if an error occurred while determining the architecture. + pub fn get_ranlib(&self) -> Command { + match self.try_get_ranlib() { + Ok(tool) => tool, + Err(e) => fail(&e.message), + } + } + + /// Get the ranlib that's in use for this configuration. + /// + /// This will return a result instead of panicking; + /// see [`Self::get_ranlib`] for the complete description. + pub fn try_get_ranlib(&self) -> Result<Command, Error> { + let mut cmd = self.get_base_ranlib()?; + if let Ok(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, String), Error> { + let target = self.get_target()?; + let mut name = String::new(); + let tool_opt: Option<Command> = self + .env_tool(env) + .map(|(tool, _wrapper, args)| { + let mut cmd = self.cmd(tool); + cmd.args(args); + cmd + }) + .or_else(|| { + if target.contains("emscripten") { + // Windows use bat files so we have to be a bit more specific + if cfg!(windows) { + let mut cmd = self.cmd("cmd"); + name = format!("em{}.bat", tool); + cmd.arg("/c").arg(&name); + Some(cmd) + } else { + name = format!("em{}", tool); + Some(self.cmd(&name)) } + } else if target.starts_with("wasm32") { + // Formally speaking one should be able to use this approach, + // parsing -print-search-dirs output, to cover all clang targets, + // including Android SDKs and other cross-compilation scenarios... + // And even extend it to gcc targets by searching for "ar" instead + // of "llvm-ar"... + let compiler = self.get_base_compiler().ok()?; + if compiler.family == ToolFamily::Clang { + name = format!("llvm-{}", tool); + search_programs(&mut self.cmd(&compiler.path), &name, &self.cargo_output) + .map(|name| self.cmd(name)) + } else { + None + } + } else { + None } - } - if lib.is_empty() { - lib = match windows_registry::find(&target, "lib.exe") { - Some(t) => return Ok((t, "lib.exe".to_string())), - None => "lib.exe".to_string(), - } - } - lib - } else if target.contains("illumos") { - // The default 'ar' on illumos uses a non-standard flags, - // but the OS comes bundled with a GNU-compatible variant. - // - // Use the GNU-variant to match other Unix systems. - "gar".to_string() - } else if self.get_host()? != target { - match self.prefix_for_target(&target) { - Some(p) => { - // GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both. - // Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be - // outright broken (such as when targetting freebsd with `--disable-lto` - // toolchain where the archiver attempts to load the LTO plugin anyway but - // fails to find one). - let mut ar = default_ar; - for &infix in &["", "-gcc"] { - let target_ar = format!("{}{}-ar", p, infix); - if Command::new(&target_ar).output().is_ok() { - ar = target_ar; - break; + }); + + let default = tool.to_string(); + let tool = match tool_opt { + Some(t) => t, + None => { + if target.contains("android") { + name = format!("llvm-{}", tool); + match Command::new(&name).arg("--version").status() { + Ok(status) if status.success() => (), + _ => name = format!("{}-{}", target.replace("armv7", "arm"), tool), + } + self.cmd(&name) + } else if target.contains("msvc") { + // NOTE: There isn't really a ranlib on msvc, so arguably we should return + // `None` somehow here. But in general, callers will already have to be aware + // of not running ranlib on Windows anyway, so it feels okay to return lib.exe + // here. + + let compiler = self.get_base_compiler()?; + let mut lib = String::new(); + if compiler.family == (ToolFamily::Msvc { clang_cl: true }) { + // See if there is 'llvm-lib' next to 'clang-cl' + // Another possibility could be to see if there is 'clang' + // next to 'clang-cl' and use 'search_programs()' to locate + // 'llvm-lib'. This is because 'clang-cl' doesn't support + // the -print-search-dirs option. + if let Some(mut cmd) = which(&compiler.path, None) { + cmd.pop(); + cmd.push("llvm-lib.exe"); + if let Some(llvm_lib) = which(&cmd, None) { + lib = llvm_lib.to_str().unwrap().to_owned(); + } + } + } + + if lib.is_empty() { + name = String::from("lib.exe"); + let mut cmd = match windows_registry::find(&target, "lib.exe") { + Some(t) => t, + None => self.cmd("lib.exe"), + }; + if target.contains("arm64ec") { + cmd.arg("/machine:arm64ec"); } + cmd + } else { + name = lib; + self.cmd(&name) } - ar + } else if target.contains("illumos") { + // The default 'ar' on illumos uses a non-standard flags, + // but the OS comes bundled with a GNU-compatible variant. + // + // Use the GNU-variant to match other Unix systems. + name = format!("g{}", tool); + self.cmd(&name) + } else if self.get_host()? != target { + match self.prefix_for_target(&target) { + Some(p) => { + // GCC uses $target-gcc-ar, whereas binutils uses $target-ar -- try both. + // Prefer -ar if it exists, as builds of `-gcc-ar` have been observed to be + // outright broken (such as when targeting freebsd with `--disable-lto` + // toolchain where the archiver attempts to load the LTO plugin anyway but + // fails to find one). + // + // The same applies to ranlib. + let mut chosen = default; + for &infix in &["", "-gcc"] { + let target_p = format!("{}{}-{}", p, infix, tool); + if Command::new(&target_p).output().is_ok() { + chosen = target_p; + break; + } + } + name = chosen; + self.cmd(&name) + } + None => { + name = default; + self.cmd(&name) + } + } + } else { + name = default; + self.cmd(&name) } - None => default_ar, } - } else { - default_ar }; - Ok((self.cmd(&program), program)) + + Ok((tool, name)) } fn prefix_for_target(&self, target: &str) -> Option<String> { - // Put aside RUSTC_LINKER's prefix to be used as last resort - let rustc_linker = self.getenv("RUSTC_LINKER").unwrap_or("".to_string()); - // let linker_prefix = rustc_linker.strip_suffix("-gcc"); // >=1.45.0 - let linker_prefix = if rustc_linker.len() > 4 { - let (prefix, suffix) = rustc_linker.split_at(rustc_linker.len() - 4); - if suffix == "-gcc" { - Some(prefix) - } else { - None - } - } else { - None - }; + // Put aside RUSTC_LINKER's prefix to be used as second choice, after CROSS_COMPILE + let linker_prefix = self + .getenv("RUSTC_LINKER") + .and_then(|var| var.strip_suffix("-gcc").map(str::to_string)); // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" let cc_env = self.getenv("CROSS_COMPILE"); let cross_compile = cc_env.as_ref().map(|s| s.trim_end_matches('-').to_owned()); - cross_compile.or(match &target[..] { + cross_compile.or(linker_prefix).or(match &target[..] { // Note: there is no `aarch64-pc-windows-gnu` target, only `-gnullvm` "aarch64-pc-windows-gnullvm" => Some("aarch64-w64-mingw32"), "aarch64-uwp-windows-gnu" => Some("aarch64-w64-mingw32"), @@ -2774,6 +3239,7 @@ impl Build { ]), // explicit None if not found, so caller knows to fall back "i686-unknown-linux-musl" => Some("musl"), "i686-unknown-netbsd" => Some("i486--netbsdelf"), + "loongarch64-unknown-linux-gnu" => Some("loongarch64-linux-gnu"), "mips-unknown-linux-gnu" => Some("mips-linux-gnu"), "mips-unknown-linux-musl" => Some("mips-linux-musl"), "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu"), @@ -2794,6 +3260,7 @@ impl Build { "riscv64-unknown-elf", "riscv-none-embed", ]), + "riscv32imac-esp-espidf" => Some("riscv32-esp-elf"), "riscv32imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ "riscv32-unknown-elf", "riscv64-unknown-elf", @@ -2804,6 +3271,7 @@ impl Build { "riscv64-unknown-elf", "riscv-none-embed", ]), + "riscv32imc-esp-espidf" => Some("riscv32-esp-elf"), "riscv32imc-unknown-none-elf" => self.find_working_gnu_prefix(&[ "riscv32-unknown-elf", "riscv64-unknown-elf", @@ -2823,6 +3291,7 @@ impl Build { "riscv32gc-unknown-linux-gnu" => Some("riscv32-linux-gnu"), "riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl"), "riscv32gc-unknown-linux-musl" => Some("riscv32-linux-musl"), + "riscv64gc-unknown-netbsd" => Some("riscv64--netbsd"), "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"), "sparc-unknown-linux-gnu" => Some("sparc-linux-gnu"), "sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu"), @@ -2834,6 +3303,7 @@ impl Build { "armebv7r-none-eabihf" => Some("arm-none-eabi"), "armv7r-none-eabi" => Some("arm-none-eabi"), "armv7r-none-eabihf" => Some("arm-none-eabi"), + "armv8r-none-eabihf" => Some("arm-none-eabi"), "thumbv6m-none-eabi" => Some("arm-none-eabi"), "thumbv7em-none-eabi" => Some("arm-none-eabi"), "thumbv7em-none-eabihf" => Some("arm-none-eabi"), @@ -2850,7 +3320,7 @@ impl Build { ]), // explicit None if not found, so caller knows to fall back "x86_64-unknown-linux-musl" => Some("musl"), "x86_64-unknown-netbsd" => Some("x86_64--netbsd"), - _ => linker_prefix, + _ => None, } .map(|x| x.to_owned())) } @@ -2887,30 +3357,30 @@ impl Build { prefixes.first().map(|prefix| *prefix)) } - fn get_target(&self) -> Result<String, Error> { - match self.target.clone() { - Some(t) => Ok(t), - None => Ok(self.getenv_unwrap("TARGET")?), + fn get_target(&self) -> Result<Arc<str>, Error> { + match &self.target { + Some(t) => Ok(t.clone()), + None => self.getenv_unwrap("TARGET"), } } - fn get_host(&self) -> Result<String, Error> { - match self.host.clone() { - Some(h) => Ok(h), - None => Ok(self.getenv_unwrap("HOST")?), + fn get_host(&self) -> Result<Arc<str>, Error> { + match &self.host { + Some(h) => Ok(h.clone()), + None => self.getenv_unwrap("HOST"), } } - fn get_opt_level(&self) -> Result<String, Error> { - match self.opt_level.as_ref().cloned() { - Some(ol) => Ok(ol), - None => Ok(self.getenv_unwrap("OPT_LEVEL")?), + fn get_opt_level(&self) -> Result<Arc<str>, Error> { + match &self.opt_level { + Some(ol) => Ok(ol.clone()), + None => self.getenv_unwrap("OPT_LEVEL"), } } fn get_debug(&self) -> bool { self.debug.unwrap_or_else(|| match self.getenv("DEBUG") { - Some(s) => s != "false", + Some(s) => &*s != "false", None => false, }) } @@ -2938,19 +3408,22 @@ impl Build { self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) } - fn get_out_dir(&self) -> Result<PathBuf, Error> { - match self.out_dir.clone() { - Some(p) => Ok(p), - None => Ok(env::var_os("OUT_DIR").map(PathBuf::from).ok_or_else(|| { - Error::new( - ErrorKind::EnvVarNotFound, - "Environment variable OUT_DIR not defined.", - ) - })?), + fn get_out_dir(&self) -> Result<Cow<'_, Path>, Error> { + match &self.out_dir { + Some(p) => Ok(Cow::Borrowed(&**p)), + None => env::var_os("OUT_DIR") + .map(PathBuf::from) + .map(Cow::Owned) + .ok_or_else(|| { + Error::new( + ErrorKind::EnvVarNotFound, + "Environment variable OUT_DIR not defined.", + ) + }), } } - fn getenv(&self, v: &str) -> Option<String> { + fn getenv(&self, v: &str) -> Option<Arc<str>> { // Returns true for environment variables cargo sets for build scripts: // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts // @@ -2970,47 +3443,58 @@ impl Build { return val.clone(); } if self.emit_rerun_if_env_changed && !provided_by_cargo(v) { - self.print(&format!("cargo:rerun-if-env-changed={}", v)); + self.cargo_output + .print_metadata(&format_args!("cargo:rerun-if-env-changed={}", v)); } - let r = env::var(v).ok(); - self.print(&format!("{} = {:?}", v, r)); + let r = env::var(v).ok().map(Arc::from); + self.cargo_output + .print_metadata(&format_args!("{} = {:?}", v, r)); cache.insert(v.to_string(), r.clone()); r } - fn getenv_unwrap(&self, v: &str) -> Result<String, Error> { + fn getenv_unwrap(&self, v: &str) -> Result<Arc<str>, Error> { match self.getenv(v) { Some(s) => Ok(s), None => Err(Error::new( ErrorKind::EnvVarNotFound, - &format!("Environment variable {} not defined.", v.to_string()), + format!("Environment variable {} not defined.", v), )), } } - fn print(&self, s: &str) { - if self.cargo_metadata { - println!("{}", s); + fn getenv_with_target_prefixes(&self, var_base: &str) -> Result<Arc<str>, Error> { + let target = self.get_target()?; + let host = self.get_host()?; + let kind = if host == target { "HOST" } else { "TARGET" }; + let target_u = target.replace('-', "_"); + let res = self + .getenv(&format!("{}_{}", var_base, target)) + .or_else(|| self.getenv(&format!("{}_{}", var_base, target_u))) + .or_else(|| self.getenv(&format!("{}_{}", kind, var_base))) + .or_else(|| self.getenv(var_base)); + + match res { + Some(res) => Ok(res), + None => Err(Error::new( + ErrorKind::EnvVarNotFound, + format!("Could not find environment variable {}.", var_base), + )), } } + fn envflags(&self, name: &str) -> Result<Vec<String>, Error> { + Ok(self + .getenv_with_target_prefixes(name)? + .split_ascii_whitespace() + .map(|slice| slice.to_string()) + .collect()) + } + fn fix_env_for_apple_os(&self, cmd: &mut Command) -> Result<(), Error> { let target = self.get_target()?; let host = self.get_host()?; if host.contains("apple-darwin") && target.contains("apple-darwin") { - // If, for example, `cargo` runs during the build of an XCode project, then `SDKROOT` environment variable - // would represent the current target, and this is the problem for us, if we want to compile something - // for the host, when host != target. - // We can not just remove `SDKROOT`, because, again, for example, XCode add to PATH - // /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin - // and `cc` from this path can not find system include files, like `pthread.h`, if `SDKROOT` - // is not set - if let Ok(sdkroot) = env::var("SDKROOT") { - if !sdkroot.contains("MacOSX") { - let macos_sdk = self.apple_sdk_root("macosx")?; - cmd.env("SDKROOT", macos_sdk); - } - } // Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", // although this is apparently ignored when using the linker at "/usr/bin/ld". @@ -3020,6 +3504,10 @@ impl Build { } fn apple_sdk_root(&self, sdk: &str) -> Result<OsString, Error> { + if let Some(sdkroot) = env::var_os("SDKROOT") { + return Ok(sdkroot); + } + let mut cache = self .apple_sdk_root_cache .lock() @@ -3034,6 +3522,7 @@ impl Build { .arg("--sdk") .arg(sdk), "xcrun", + &self.cargo_output, )?; let sdk_path = match String::from_utf8(sdk_path) { @@ -3050,362 +3539,199 @@ impl Build { Ok(ret) } - fn cuda_file_count(&self) -> usize { - self.files - .iter() - .filter(|file| file.extension() == Some(OsStr::new("cu"))) - .count() - } -} + fn apple_deployment_version(&self, os: AppleOs, arch_str: Option<&str>, sdk: &str) -> String { + let default_deployment_from_sdk = || { + let mut cache = self + .apple_versions_cache + .lock() + .expect("apple_versions_cache lock failed"); -impl Default for Build { - fn default() -> Build { - Build::new() - } -} + if let Some(ret) = cache.get(sdk) { + return Some(ret.clone()); + } -impl Tool { - fn new(path: PathBuf) -> Self { - Tool::with_features(path, None, false) - } + let version = run_output( + self.cmd("xcrun") + .arg("--show-sdk-platform-version") + .arg("--sdk") + .arg(sdk), + "xcrun", + &self.cargo_output, + ) + .ok()?; - fn with_clang_driver(path: PathBuf, clang_driver: Option<&str>) -> Self { - Self::with_features(path, clang_driver, false) - } + let version = std::str::from_utf8(&version).ok()?.trim().to_owned(); - #[cfg(windows)] - /// Explicitly set the `ToolFamily`, skipping name-based detection. - fn with_family(path: PathBuf, family: ToolFamily) -> Self { - Self { - path: path, - cc_wrapper_path: None, - cc_wrapper_args: Vec::new(), - args: Vec::new(), - env: Vec::new(), - family: family, - cuda: false, - removed_args: Vec::new(), - } - } + cache.insert(sdk.into(), version.clone()); + Some(version) + }; - fn with_features(path: PathBuf, clang_driver: Option<&str>, cuda: bool) -> Self { - // Try to detect family of the tool from its name, falling back to Gnu. - let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { - if fname.contains("clang-cl") { - ToolFamily::Msvc { clang_cl: true } - } else if fname.ends_with("cl") || fname == "cl.exe" { - ToolFamily::Msvc { clang_cl: false } - } else if fname.contains("clang") { - match clang_driver { - Some("cl") => ToolFamily::Msvc { clang_cl: true }, - _ => ToolFamily::Clang, - } + let deployment_from_env = |name: &str| { + // note this isn't hit in production codepaths, its mostly just for tests which don't + // set the real env + if let Some((_, v)) = self.env.iter().find(|(k, _)| &**k == OsStr::new(name)) { + Some(v.to_str().unwrap().to_string()) } else { - ToolFamily::Gnu + env::var(name).ok() } - } else { - ToolFamily::Gnu }; - Tool { - path: path, - cc_wrapper_path: None, - cc_wrapper_args: Vec::new(), - args: Vec::new(), - env: Vec::new(), - family: family, - cuda: cuda, - removed_args: Vec::new(), - } - } - - /// Add an argument to be stripped from the final command arguments. - fn remove_arg(&mut self, flag: OsString) { - self.removed_args.push(flag); - } - - /// Add a flag, and optionally prepend the NVCC wrapper flag "-Xcompiler". - /// - /// Currently this is only used for compiling CUDA sources, since NVCC only - /// accepts a limited set of GNU-like flags, and the rest must be prefixed - /// with a "-Xcompiler" flag to get passed to the underlying C++ compiler. - fn push_cc_arg(&mut self, flag: OsString) { - if self.cuda { - self.args.push("-Xcompiler".into()); - } - self.args.push(flag); - } - - fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool { - let flag = flag.to_str().unwrap(); - let mut chars = flag.chars(); - - // Only duplicate check compiler flags - if self.is_like_msvc() { - if chars.next() != Some('/') { - return false; - } - } else if self.is_like_gnu() || self.is_like_clang() { - if chars.next() != Some('-') { - return false; + // Determines if the acquired deployment target is too low to support modern C++ on some Apple platform. + // + // A long time ago they used libstdc++, but since macOS 10.9 and iOS 7 libc++ has been the library the SDKs provide to link against. + // If a `cc`` config wants to use C++, we round up to these versions as the baseline. + let maybe_cpp_version_baseline = |deployment_target_ver: String| -> Option<String> { + if !self.cpp { + return Some(deployment_target_ver); } - } - // Check for existing optimization flags (-O, /O) - if chars.next() == Some('O') { - return self - .args() - .iter() - .any(|ref a| a.to_str().unwrap_or("").chars().nth(1) == Some('O')); - } - - // TODO Check for existing -m..., -m...=..., /arch:... flags - return false; - } - - /// Don't push optimization arg if it conflicts with existing args - fn push_opt_unless_duplicate(&mut self, flag: OsString) { - if self.is_duplicate_opt_arg(&flag) { - println!("Info: Ignoring duplicate arg {:?}", &flag); - } else { - self.push_cc_arg(flag); - } - } - - /// Converts this compiler into a `Command` that's ready to be run. - /// - /// This is useful for when the compiler needs to be executed and the - /// command returned will already have the initial arguments and environment - /// variables configured. - pub fn to_command(&self) -> Command { - let mut cmd = match self.cc_wrapper_path { - Some(ref cc_wrapper_path) => { - let mut cmd = Command::new(&cc_wrapper_path); - cmd.arg(&self.path); - cmd + let mut deployment_target = deployment_target_ver + .split('.') + .map(|v| v.parse::<u32>().expect("integer version")); + + match os { + AppleOs::MacOs => { + let major = deployment_target.next().unwrap_or(0); + let minor = deployment_target.next().unwrap_or(0); + + // If below 10.9, we ignore it and let the SDK's target definitions handle it. + if major == 10 && minor < 9 { + self.cargo_output.print_warning(&format_args!( + "macOS deployment target ({}) too low, it will be increased", + deployment_target_ver + )); + return None; + } + } + AppleOs::Ios => { + let major = deployment_target.next().unwrap_or(0); + + // If below 10.7, we ignore it and let the SDK's target definitions handle it. + if major < 7 { + self.cargo_output.print_warning(&format_args!( + "iOS deployment target ({}) too low, it will be increased", + deployment_target_ver + )); + return None; + } + } + // watchOS, tvOS, and others are all new enough that libc++ is their baseline. + _ => {} } - None => Command::new(&self.path), - }; - cmd.args(&self.cc_wrapper_args); - let value = self - .args - .iter() - .filter(|a| !self.removed_args.contains(a)) - .collect::<Vec<_>>(); - cmd.args(&value); - - for &(ref k, ref v) in self.env.iter() { - cmd.env(k, v); - } - cmd - } + // If the deployment target met or exceeded the C++ baseline + Some(deployment_target_ver) + }; - /// Returns the path for this compiler. - /// - /// Note that this may not be a path to a file on the filesystem, e.g. "cc", - /// but rather something which will be resolved when a process is spawned. - pub fn path(&self) -> &Path { - &self.path - } + // The hardcoded minimums here are subject to change in a future compiler release, + // and only exist as last resort fallbacks. Don't consider them stable. + // `cc` doesn't use rustc's `--print deployment-target`` because the compiler's defaults + // don't align well with Apple's SDKs and other third-party libraries that require ~generally~ higher + // deployment targets. rustc isn't interested in those by default though so its fine to be different here. + // + // If no explicit target is passed, `cc` defaults to the current Xcode SDK's `DefaultDeploymentTarget` for better + // compatibility. This is also the crate's historical behavior and what has become a relied-on value. + // + // The ordering of env -> XCode SDK -> old rustc defaults is intentional for performance when using + // an explicit target. + match os { + AppleOs::MacOs => deployment_from_env("MACOSX_DEPLOYMENT_TARGET") + .and_then(maybe_cpp_version_baseline) + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| { + if arch_str == Some("aarch64") { + "11.0".into() + } else { + let default = "10.7"; + maybe_cpp_version_baseline(default.into()).unwrap_or_else(|| default.into()) + } + }), - /// Returns the default set of arguments to the compiler needed to produce - /// executables for the target this compiler generates. - pub fn args(&self) -> &[OsString] { - &self.args - } + AppleOs::Ios => deployment_from_env("IPHONEOS_DEPLOYMENT_TARGET") + .and_then(maybe_cpp_version_baseline) + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| "7.0".into()), - /// Returns the set of environment variables needed for this compiler to - /// operate. - /// - /// This is typically only used for MSVC compilers currently. - pub fn env(&self) -> &[(OsString, OsString)] { - &self.env - } + AppleOs::WatchOs => deployment_from_env("WATCHOS_DEPLOYMENT_TARGET") + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| "5.0".into()), - /// Returns the compiler command in format of CC environment variable. - /// Or empty string if CC env was not present - /// - /// This is typically used by configure script - pub fn cc_env(&self) -> OsString { - match self.cc_wrapper_path { - Some(ref cc_wrapper_path) => { - let mut cc_env = cc_wrapper_path.as_os_str().to_owned(); - cc_env.push(" "); - cc_env.push(self.path.to_path_buf().into_os_string()); - for arg in self.cc_wrapper_args.iter() { - cc_env.push(" "); - cc_env.push(arg); - } - cc_env - } - None => OsString::from(""), + AppleOs::TvOs => deployment_from_env("TVOS_DEPLOYMENT_TARGET") + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| "9.0".into()), } } - /// Returns the compiler flags in format of CFLAGS environment variable. - /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS - /// This is typically used by configure script - pub fn cflags_env(&self) -> OsString { - let mut flags = OsString::new(); - for (i, arg) in self.args.iter().enumerate() { - if i > 0 { - flags.push(" "); - } - flags.push(arg); - } - flags + fn cuda_file_count(&self) -> usize { + self.files + .iter() + .filter(|file| file.extension() == Some(OsStr::new("cu"))) + .count() } +} - /// Whether the tool is GNU Compiler Collection-like. - pub fn is_like_gnu(&self) -> bool { - self.family == ToolFamily::Gnu +impl Default for Build { + fn default() -> Build { + Build::new() } +} - /// Whether the tool is Clang-like. - pub fn is_like_clang(&self) -> bool { - self.family == ToolFamily::Clang - } +fn fail(s: &str) -> ! { + eprintln!("\n\nerror occurred: {}\n\n", s); + std::process::exit(1); +} - /// Whether the tool is MSVC-like. - pub fn is_like_msvc(&self) -> bool { - match self.family { - ToolFamily::Msvc { .. } => true, - _ => false, +#[derive(Clone, Copy, PartialEq)] +enum AppleOs { + MacOs, + Ios, + WatchOs, + TvOs, +} +impl std::fmt::Debug for AppleOs { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + AppleOs::MacOs => f.write_str("macOS"), + AppleOs::Ios => f.write_str("iOS"), + AppleOs::WatchOs => f.write_str("WatchOS"), + AppleOs::TvOs => f.write_str("AppleTVOS"), } } } -fn run(cmd: &mut Command, program: &str) -> Result<(), Error> { - let (mut child, print) = spawn(cmd, program)?; - let status = match child.wait() { - Ok(s) => s, - Err(_) => { - return Err(Error::new( - ErrorKind::ToolExecError, - &format!( - "Failed to wait on spawned child process, command {:?} with args {:?}.", - cmd, program - ), - )); - } - }; - print.join().unwrap(); - println!("{}", status); - - if status.success() { - Ok(()) - } else { - Err(Error::new( - ErrorKind::ToolExecError, - &format!( - "Command {:?} with args {:?} did not execute successfully (status code {}).", - cmd, program, status - ), - )) - } +struct AppleSdkTargetParts { + sdk_prefix: &'static str, + sim_prefix: &'static str, + sdk: Cow<'static, str>, } -fn run_output(cmd: &mut Command, program: &str) -> Result<Vec<u8>, Error> { - cmd.stdout(Stdio::piped()); - let (mut child, print) = spawn(cmd, program)?; - let mut stdout = vec![]; - child - .stdout - .take() - .unwrap() - .read_to_end(&mut stdout) - .unwrap(); - let status = match child.wait() { - Ok(s) => s, - Err(_) => { - return Err(Error::new( - ErrorKind::ToolExecError, - &format!( - "Failed to wait on spawned child process, command {:?} with args {:?}.", - cmd, program - ), - )); - } +fn apple_os_sdk_parts(os: AppleOs, arch: &AppleArchSpec) -> AppleSdkTargetParts { + let (sdk_prefix, sim_prefix) = match os { + AppleOs::MacOs => ("macosx", ""), + AppleOs::Ios => ("iphone", "ios-"), + AppleOs::WatchOs => ("watch", "watch"), + AppleOs::TvOs => ("appletv", "appletv"), + }; + let sdk = match arch { + AppleArchSpec::Device(_) if os == AppleOs::MacOs => Cow::Borrowed("macosx"), + AppleArchSpec::Device(_) => format!("{}os", sdk_prefix).into(), + AppleArchSpec::Simulator(_) => format!("{}simulator", sdk_prefix).into(), + AppleArchSpec::Catalyst(_) => Cow::Borrowed("macosx"), }; - print.join().unwrap(); - println!("{}", status); - - if status.success() { - Ok(stdout) - } else { - Err(Error::new( - ErrorKind::ToolExecError, - &format!( - "Command {:?} with args {:?} did not execute successfully (status code {}).", - cmd, program, status - ), - )) - } -} -fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Error> { - println!("running: {:?}", cmd); - - // Capture the standard error coming from these programs, and write it out - // with cargo:warning= prefixes. Note that this is a bit wonky to avoid - // requiring the output to be UTF-8, we instead just ship bytes from one - // location to another. - match cmd.stderr(Stdio::piped()).spawn() { - Ok(mut child) => { - let stderr = BufReader::new(child.stderr.take().unwrap()); - let print = thread::spawn(move || { - for line in stderr.split(b'\n').filter_map(|l| l.ok()) { - print!("cargo:warning="); - std::io::stdout().write_all(&line).unwrap(); - println!(""); - } - }); - Ok((child, print)) - } - Err(ref e) if e.kind() == io::ErrorKind::NotFound => { - let extra = if cfg!(windows) { - " (see https://github.com/rust-lang/cc-rs#compile-time-requirements \ - for help)" - } else { - "" - }; - Err(Error::new( - ErrorKind::ToolNotFound, - &format!("Failed to find tool. Is `{}` installed?{}", program, extra), - )) - } - Err(ref e) => Err(Error::new( - ErrorKind::ToolExecError, - &format!( - "Command {:?} with args {:?} failed to start: {:?}", - cmd, program, e - ), - )), + AppleSdkTargetParts { + sdk_prefix, + sim_prefix, + sdk, } } -fn fail(s: &str) -> ! { - eprintln!("\n\nerror occurred: {}\n\n", s); - std::process::exit(1); -} - -fn command_add_output_file( - cmd: &mut Command, - dst: &Path, - cuda: bool, - msvc: bool, - clang: bool, - is_asm: bool, - is_arm: bool, -) { - if msvc && !clang && !cuda && !(is_asm && is_arm) { - let mut s = OsString::from("-Fo"); - s.push(&dst); - cmd.arg(s); - } else { - cmd.arg("-o").arg(&dst); - } +#[allow(dead_code)] +enum AppleArchSpec { + Device(&'static str), + Simulator(&'static str), + #[allow(dead_code)] + Catalyst(&'static str), } // Use by default minimum available API level @@ -3429,13 +3755,12 @@ static NEW_STANDALONE_ANDROID_COMPILERS: [&str; 4] = [ 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() { - filename_str.contains("android") - } else { - false + if let Some(idx) = filename_str.rfind('-') { + return filename_str.split_at(idx).0.contains("android"); + } } - } else { - false } + false } #[test] @@ -3449,6 +3774,9 @@ fn test_android_clang_compiler_uses_target_arg_internally() { )); } 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( @@ -3505,7 +3833,9 @@ fn autodetect_android_compiler(target: &str, host: &str, gnu: &str, clang: &str) // Rust and clang/cc don't agree on how to name the target. fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option<&'static str> { - if target.contains("x86_64") { + if target.contains("x86_64h") { + Some("x86_64h") + } else if target.contains("x86_64") { Some("x86_64") } else if target.contains("arm64e") { Some("arm64e") @@ -3522,7 +3852,7 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option< } } -fn which(tool: &Path) -> Option<PathBuf> { +fn which(tool: &Path, path_entries: Option<OsString>) -> Option<PathBuf> { fn check_exe(exe: &mut PathBuf) -> bool { let exe_ext = std::env::consts::EXE_EXTENSION; exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists()) @@ -3535,13 +3865,37 @@ fn which(tool: &Path) -> Option<PathBuf> { } // Loop through PATH entries searching for the |tool|. - let path_entries = env::var_os("PATH")?; + let path_entries = path_entries.or(env::var_os("PATH"))?; env::split_paths(&path_entries).find_map(|path_entry| { let mut exe = path_entry.join(tool); - return if check_exe(&mut exe) { Some(exe) } else { None }; + if check_exe(&mut exe) { + Some(exe) + } else { + None + } }) } +// search for |prog| on 'programs' path in '|cc| -print-search-dirs' output +fn search_programs(cc: &mut Command, prog: &str, cargo_output: &CargoOutput) -> Option<PathBuf> { + let search_dirs = run_output( + cc.arg("-print-search-dirs"), + "cc", + // this doesn't concern the compilation so we always want to show warnings. + cargo_output, + ) + .ok()?; + // clang driver appears to be forcing UTF-8 output even on Windows, + // hence from_utf8 is assumed to be usable in all cases. + let search_dirs = std::str::from_utf8(&search_dirs).ok()?; + for dirs in search_dirs.split(|c| c == '\r' || c == '\n') { + if let Some(path) = dirs.strip_prefix("programs: =") { + return which(Path::new(prog), Some(OsString::from(path))); + } + } + None +} + #[derive(Clone, Copy, PartialEq)] enum AsmFileExt { /// `.asm` files. On MSVC targets, we assume these should be passed to MASM |