diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:33 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:33 +0000 |
commit | 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 (patch) | |
tree | a4f824bd33cb075dd5aa3eb5a0a94af221bbe83a /third_party/rust/cc | |
parent | Adding debian version 124.0.1-1. (diff) | |
download | firefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.tar.xz firefox-086c044dc34dfc0f74fbe41f4ecb402b2cd34884.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cc')
25 files changed, 3506 insertions, 2625 deletions
diff --git a/third_party/rust/cc/.cargo-checksum.json b/third_party/rust/cc/.cargo-checksum.json index 4dc2fe2390..f7d42ebd81 100644 --- a/third_party/rust/cc/.cargo-checksum.json +++ b/third_party/rust/cc/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.lock":"23c26d62ba5114f5ac6e7ffa3ea233cea77e5cb7f98d9f056f40fe2c49971f67","Cargo.toml":"fd4b39488866b6717476fadc460ff91c89511628080769516eec452c0def8bc7","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"58af5106352aafa62175a90f8a5f25fa114028bf909220dc0735d79745999ec1","src/bin/gcc-shim.rs":"b77907875029494b6288841c3aed2e4939ed40708c7f597fca5c9e2570490ca6","src/com.rs":"29d0dee08a656ab1a4cc3e5fe24542e0fab5c1373cbc9b05059f7572cf9b8313","src/lib.rs":"e0cc228db97675d6a0d86b219a20e9e48925a1ccbfd9e9fd038ccf6ef129957e","src/registry.rs":"98ae2b71781acc49297e5544fa0cf059f735636f8f1338edef8dbf7232443945","src/setup_config.rs":"72deaf1927c0b713fd5c2b2d5b8f0ea3a303a00fda1579427895cac26a94122d","src/vs_instances.rs":"2d3f8278a803b0e7052f4eeb1979b29f963dd0143f4458e2cb5f33c4e5f0963b","src/winapi.rs":"e128e95b2d39ae7a02f54a7e25d33c488c14759b9f1a50a449e10545856950c3","src/windows_registry.rs":"c0340379c1f540cf96f45bbd4cf8fc28db555826f30ac937b75b87e4377b716b","tests/cc_env.rs":"e02b3b0824ad039b47e4462c5ef6dbe6c824c28e7953af94a0f28f7b5158042e","tests/cflags.rs":"57f06eb5ce1557e5b4a032d0c4673e18fbe6f8d26c1deb153126e368b96b41b3","tests/cxxflags.rs":"c2c6c6d8a0d7146616fa1caed26876ee7bc9fcfffd525eb4743593cade5f3371","tests/support/mod.rs":"a3c8d116973bb16066bf6ec4de5143183f97de7aad085d85f8118a2eaac3e1e0","tests/test.rs":"61fb35ae6dd5cf506ada000bdd82c92e9f8eac9cc053b63e83d3f897436fbf8f"},"package":"a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"}
\ No newline at end of file +{"files":{"Cargo.toml":"1288f536f4ddf6bcdc664a91a070aad2ebd7c6edc32ce24e8d6bc04c2cd64d49","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"f1ddbede208a5b78333a25dac0a7598e678e9b601a7d99a791069bddaf180dfe","src/command_helpers.rs":"3ef95bdcd79a43406fdab275d8a8f45ba787876399b54df34068955ec0109e69","src/lib.rs":"91efa8f9242266752658edd66ee607ce30635f4c30710508a99eb62e7b3c54da","src/parallel/async_executor.rs":"4ce24435fff6b6555b43fee042c16bd65d4150d0346567f246b9190d85b45983","src/parallel/job_token.rs":"0676c3177b5be9d7ede483bf4bd45c5ca0f5511073e4d1c9f181a0bc83db05dc","src/parallel/mod.rs":"aaffed5ad3dc0d28641533ab0d6f522bf34a059d4b1a239dc4d217cb5d58e232","src/parallel/stderr.rs":"a2d18ba3f2e04deb9047ece9ab7ca5452d9a76b515afbe20a76307e31597f34b","src/tool.rs":"172cfcbecd7c6a363ea841a48a10a75b0a01e83b83c0691107c601598b68dedf","src/windows/com.rs":"be1564756c9f3ef1398eafeed7b54ba610caba28e8f6258d28a997737ebf9535","src/windows/find_tools.rs":"9234fe7ab27b0259c6fa9fb47826e7d1a3d1d2c7c4042ef7153ab90ccb9a3412","src/windows/mod.rs":"42f1ad7fee35a17686b003e6aa520d3d1940d47d2f531d626e9ae0c48ba49005","src/windows/registry.rs":"c521b72c825e8095843e73482ffa810ed066ad8bb9f86e6db0c5c143c171aba1","src/windows/setup_config.rs":"754439cbab492afd44c9755abcbec1a41c9b2c358131cee2df13c0e996dbbec8","src/windows/vs_instances.rs":"76e3cee74b5fd38ddaf533bba11fe401667c50dda5f9d064099840893eaa7587","src/windows/winapi.rs":"250d51c1826d1a2329e9889dd9f058cfce253dbf2a678b076147c6cdb5db046c","src/windows/windows_sys.rs":"f6b90b87f23e446284bde86749b53858c0d37b8a43515ed8d0e90b1ac8cf7771"},"package":"a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"}
\ No newline at end of file diff --git a/third_party/rust/cc/Cargo.lock b/third_party/rust/cc/Cargo.lock deleted file mode 100644 index 2d065bc6a8..0000000000 --- a/third_party/rust/cc/Cargo.lock +++ /dev/null @@ -1,110 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "cc" -version = "1.0.78" -dependencies = [ - "jobserver", - "tempfile", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "jobserver" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" -dependencies = [ - "libc", -] - -[[package]] -name = "libc" -version = "0.2.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/third_party/rust/cc/Cargo.toml b/third_party/rust/cc/Cargo.toml index c4ec0bf79d..5d974e076f 100644 --- a/third_party/rust/cc/Cargo.toml +++ b/third_party/rust/cc/Cargo.toml @@ -11,10 +11,15 @@ [package] edition = "2018" +rust-version = "1.53" name = "cc" -version = "1.0.78" +version = "1.0.89" authors = ["Alex Crichton <alex@alexcrichton.com>"] -exclude = ["/.github"] +exclude = [ + "/.github", + "tests", + "src/bin", +] description = """ A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust @@ -29,11 +34,20 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/cc-rs" [dependencies.jobserver] -version = "0.1.16" +version = "0.1.20" optional = true +default-features = false [dev-dependencies.tempfile] version = "3" [features] -parallel = ["jobserver"] +parallel = [ + "libc", + "jobserver", +] + +[target."cfg(unix)".dependencies.libc] +version = "0.2.62" +optional = true +default-features = false diff --git a/third_party/rust/cc/README.md b/third_party/rust/cc/README.md index 863540d2d9..33d4bb40f8 100644 --- a/third_party/rust/cc/README.md +++ b/third_party/rust/cc/README.md @@ -1,209 +1,13 @@ # cc-rs -A library to compile C/C++/assembly into a Rust library/application. - -[Documentation](https://docs.rs/cc) - -A simple library meant to be used as a build dependency with Cargo packages in -order to build a set of C/C++ files into a static archive. This crate calls out -to the most relevant compiler for a platform, for example using `cl` on MSVC. - -## Using cc-rs - -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" -``` - -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 { - fn foo_function(); - fn bar_function(x: i32) -> i32; -} - -pub fn call() { - unsafe { - foo_function(); - bar_function(42); - } -} - -fn main() { - // ... -} -``` - -See [the Rustonomicon](https://doc.rust-lang.org/nomicon/ffi.html) for more details. - -## External configuration via environment variables - -To control the programs and flags used for building, the builder can set a -number of different environment variables. - -* `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. -* `CXX...` - see [C++ Support](#c-support). - -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"] } -``` - -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: - -* 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 `cl.exe` to be available and in `PATH`. This is typically found in - standard Visual Studio installations and the `PATH` 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. - -[msys2-help]: https://github.com/rust-lang/rust#building-on-windows - -## C++ support - -`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("libfoo.a"); -} -``` - -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() - .cpp(true) - .file("foo.cpp") - .cpp_link_stdlib("stdc++") // use libstdc++ - .compile("libfoo.a"); - } - ``` -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` (currently for GNU/Clang toolchains only): - -```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") - .file("bar.cu") - .compile("libbar.a"); -} -``` +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 and will build code appropriately. + +Refer to the [documentation](https://docs.rs/cc) for detailed usage instructions. ## License diff --git a/third_party/rust/cc/src/bin/gcc-shim.rs b/third_party/rust/cc/src/bin/gcc-shim.rs deleted file mode 100644 index 1731df82ea..0000000000 --- a/third_party/rust/cc/src/bin/gcc-shim.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![cfg_attr(test, allow(dead_code))] - -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; - -fn main() { - let mut args = env::args(); - let program = args.next().expect("Unexpected empty args"); - - let out_dir = PathBuf::from( - env::var_os("GCCTEST_OUT_DIR").expect(&format!("{}: GCCTEST_OUT_DIR not found", program)), - ); - - // Find the first nonexistent candidate file to which the program's args can be written. - for i in 0.. { - let candidate = &out_dir.join(format!("out{}", i)); - - // If the file exists, commands have already run. Try again. - if candidate.exists() { - continue; - } - - // Create a file and record the args passed to the command. - let mut f = File::create(candidate).expect(&format!( - "{}: can't create candidate: {}", - program, - candidate.to_string_lossy() - )); - for arg in args { - writeln!(f, "{}", arg).expect(&format!( - "{}: can't write to candidate: {}", - program, - candidate.to_string_lossy() - )); - } - break; - } - - // Create a file used by some tests. - let path = &out_dir.join("libfoo.a"); - File::create(path).expect(&format!( - "{}: can't create libfoo.a: {}", - program, - path.to_string_lossy() - )); -} diff --git a/third_party/rust/cc/src/command_helpers.rs b/third_party/rust/cc/src/command_helpers.rs new file mode 100644 index 0000000000..919d276c84 --- /dev/null +++ b/third_party/rust/cc/src/command_helpers.rs @@ -0,0 +1,433 @@ +//! Miscellaneous helpers for running commands + +use std::{ + collections::hash_map, + ffi::OsString, + fmt::Display, + fs, + hash::Hasher, + io::{self, Read, Write}, + path::Path, + process::{Child, ChildStderr, Command, Stdio}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use crate::{Error, ErrorKind, Object}; + +#[derive(Clone, Debug)] +pub(crate) struct CargoOutput { + pub(crate) metadata: bool, + pub(crate) warnings: bool, + pub(crate) debug: bool, + checked_dbg_var: Arc<AtomicBool>, +} + +impl CargoOutput { + pub(crate) fn new() -> Self { + Self { + metadata: true, + warnings: true, + debug: std::env::var_os("CC_ENABLE_DEBUG_OUTPUT").is_some(), + checked_dbg_var: Arc::new(AtomicBool::new(false)), + } + } + + pub(crate) fn print_metadata(&self, s: &dyn Display) { + if self.metadata { + println!("{}", s); + } + } + + pub(crate) fn print_warning(&self, arg: &dyn Display) { + if self.warnings { + println!("cargo:warning={}", arg); + } + } + + pub(crate) fn print_debug(&self, arg: &dyn Display) { + if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) { + self.checked_dbg_var.store(true, Ordering::Relaxed); + println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT"); + } + if self.debug { + println!("{}", arg); + } + } + + fn stdio_for_warnings(&self) -> Stdio { + if self.warnings { + Stdio::piped() + } else { + Stdio::null() + } + } +} + +pub(crate) struct StderrForwarder { + inner: Option<(ChildStderr, Vec<u8>)>, + #[cfg(feature = "parallel")] + is_non_blocking: bool, + #[cfg(feature = "parallel")] + bytes_available_failed: bool, +} + +const MIN_BUFFER_CAPACITY: usize = 100; + +impl StderrForwarder { + pub(crate) fn new(child: &mut Child) -> Self { + Self { + inner: child + .stderr + .take() + .map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))), + #[cfg(feature = "parallel")] + is_non_blocking: false, + #[cfg(feature = "parallel")] + bytes_available_failed: false, + } + } + + #[allow(clippy::uninit_vec)] + fn forward_available(&mut self) -> bool { + if let Some((stderr, buffer)) = self.inner.as_mut() { + loop { + let old_data_end = buffer.len(); + + // For non-blocking we check to see if there is data available, so we should try to + // read at least that much. For blocking, always read at least the minimum amount. + #[cfg(not(feature = "parallel"))] + let to_reserve = MIN_BUFFER_CAPACITY; + #[cfg(feature = "parallel")] + let to_reserve = if self.is_non_blocking && !self.bytes_available_failed { + match crate::parallel::stderr::bytes_available(stderr) { + #[cfg(windows)] + Ok(0) => return false, + #[cfg(unix)] + Ok(0) => { + // On Unix, depending on the implementation, we may sometimes get 0 in a + // loop (either there is data available or the pipe is broken), so + // continue with the non-blocking read anyway. + MIN_BUFFER_CAPACITY + } + #[cfg(windows)] + Err(_) => { + // On Windows, if we get an error then the pipe is broken, so flush + // the buffer and bail. + if !buffer.is_empty() { + write_warning(&buffer[..]); + } + self.inner = None; + return true; + } + #[cfg(unix)] + Err(_) => { + // On Unix, depending on the implementation, we may get spurious + // errors so make a note not to use bytes_available again and try + // the non-blocking read anyway. + self.bytes_available_failed = true; + MIN_BUFFER_CAPACITY + } + Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available), + } + } else { + MIN_BUFFER_CAPACITY + }; + buffer.reserve(to_reserve); + + // SAFETY: 1) the length is set to the capacity, so we are never using memory beyond + // the underlying buffer and 2) we always call `truncate` below to set the len back + // to the initialized data. + unsafe { + buffer.set_len(buffer.capacity()); + } + match stderr.read(&mut buffer[old_data_end..]) { + Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { + // No data currently, yield back. + buffer.truncate(old_data_end); + return false; + } + Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { + // Interrupted, try again. + buffer.truncate(old_data_end); + } + Ok(0) | Err(_) => { + // End of stream: flush remaining data and bail. + if old_data_end > 0 { + write_warning(&buffer[..old_data_end]); + } + self.inner = None; + return true; + } + Ok(bytes_read) => { + buffer.truncate(old_data_end + bytes_read); + let mut consumed = 0; + for line in buffer.split_inclusive(|&b| b == b'\n') { + // Only forward complete lines, leave the rest in the buffer. + if let Some((b'\n', line)) = line.split_last() { + consumed += line.len() + 1; + write_warning(line); + } + } + buffer.drain(..consumed); + } + } + } + } else { + true + } + } + + #[cfg(feature = "parallel")] + pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> { + assert!(!self.is_non_blocking); + + #[cfg(unix)] + if let Some((stderr, _)) = self.inner.as_ref() { + crate::parallel::stderr::set_non_blocking(stderr)?; + } + + self.is_non_blocking = true; + Ok(()) + } + + #[cfg(feature = "parallel")] + fn forward_all(&mut self) { + while !self.forward_available() {} + } + + #[cfg(not(feature = "parallel"))] + fn forward_all(&mut self) { + let forward_result = self.forward_available(); + assert!(forward_result, "Should have consumed all data"); + } +} + +fn write_warning(line: &[u8]) { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + stdout.write_all(b"cargo:warning=").unwrap(); + stdout.write_all(line).unwrap(); + stdout.write_all(b"\n").unwrap(); +} + +fn wait_on_child( + cmd: &Command, + program: &str, + child: &mut Child, + cargo_output: &CargoOutput, +) -> Result<(), Error> { + StderrForwarder::new(child).forward_all(); + + let status = match child.wait() { + Ok(s) => s, + Err(e) => { + return Err(Error::new( + ErrorKind::ToolExecError, + format!( + "Failed to wait on spawned child process, command {:?} with args {:?}: {}.", + cmd, program, e + ), + )); + } + }; + + cargo_output.print_debug(&status); + + if status.success() { + Ok(()) + } else { + Err(Error::new( + ErrorKind::ToolExecError, + format!( + "Command {:?} with args {:?} did not execute successfully (status code {}).", + cmd, program, status + ), + )) + } +} + +/// Find the destination object path for each file in the input source files, +/// and store them in the output Object. +pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> { + let mut objects = Vec::with_capacity(files.len()); + for file in files { + let basename = file + .file_name() + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidArgument, + "No file_name for object file path!", + ) + })? + .to_string_lossy(); + let dirname = file + .parent() + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidArgument, + "No parent for object file path!", + ) + })? + .to_string_lossy(); + + // Hash the dirname. This should prevent conflicts if we have multiple + // object files with the same filename in different subfolders. + let mut hasher = hash_map::DefaultHasher::new(); + hasher.write(dirname.to_string().as_bytes()); + let obj = dst + .join(format!("{:016x}-{}", hasher.finish(), basename)) + .with_extension("o"); + + match obj.parent() { + Some(s) => fs::create_dir_all(s)?, + None => { + return Err(Error::new( + ErrorKind::InvalidArgument, + "dst is an invalid path with no parent", + )); + } + }; + + objects.push(Object::new(file.to_path_buf(), obj)); + } + + Ok(objects) +} + +pub(crate) fn run( + cmd: &mut Command, + program: &str, + cargo_output: &CargoOutput, +) -> Result<(), Error> { + let mut child = spawn(cmd, program, cargo_output)?; + wait_on_child(cmd, program, &mut child, cargo_output) +} + +pub(crate) fn run_output( + cmd: &mut Command, + program: &str, + cargo_output: &CargoOutput, +) -> Result<Vec<u8>, Error> { + cmd.stdout(Stdio::piped()); + + let mut child = spawn(cmd, program, cargo_output)?; + + let mut stdout = vec![]; + child + .stdout + .take() + .unwrap() + .read_to_end(&mut stdout) + .unwrap(); + + wait_on_child(cmd, program, &mut child, cargo_output)?; + + Ok(stdout) +} + +pub(crate) fn spawn( + cmd: &mut Command, + program: &str, + cargo_output: &CargoOutput, +) -> Result<Child, Error> { + struct ResetStderr<'cmd>(&'cmd mut Command); + + impl Drop for ResetStderr<'_> { + fn drop(&mut self) { + // Reset stderr to default to release pipe_writer so that print thread will + // not block forever. + self.0.stderr(Stdio::inherit()); + } + } + + cargo_output.print_debug(&format_args!("running: {:?}", cmd)); + + let cmd = ResetStderr(cmd); + let child = cmd.0.stderr(cargo_output.stdio_for_warnings()).spawn(); + match child { + Ok(child) => Ok(child), + 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(e) => Err(Error::new( + ErrorKind::ToolExecError, + format!( + "Command {:?} with args {:?} failed to start: {:?}", + cmd.0, program, e + ), + )), + } +} + +pub(crate) fn command_add_output_file( + cmd: &mut Command, + dst: &Path, + cuda: bool, + msvc: bool, + clang: bool, + gnu: bool, + is_asm: bool, + is_arm: bool, +) { + if msvc && !clang && !gnu && !cuda && !(is_asm && is_arm) { + let mut s = OsString::from("-Fo"); + s.push(dst); + cmd.arg(s); + } else { + cmd.arg("-o").arg(dst); + } +} + +#[cfg(feature = "parallel")] +pub(crate) fn try_wait_on_child( + cmd: &Command, + program: &str, + child: &mut Child, + stdout: &mut dyn io::Write, + stderr_forwarder: &mut StderrForwarder, +) -> Result<Option<()>, Error> { + stderr_forwarder.forward_available(); + + match child.try_wait() { + Ok(Some(status)) => { + stderr_forwarder.forward_all(); + + let _ = writeln!(stdout, "{}", status); + + if status.success() { + Ok(Some(())) + } else { + Err(Error::new( + ErrorKind::ToolExecError, + format!( + "Command {:?} with args {:?} did not execute successfully (status code {}).", + cmd, program, status + ), + )) + } + } + Ok(None) => Ok(None), + Err(e) => { + stderr_forwarder.forward_all(); + Err(Error::new( + ErrorKind::ToolExecError, + format!( + "Failed to wait on spawned child process, command {:?} with args {:?}: {}.", + cmd, program, e + ), + )) + } + } +} 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 diff --git a/third_party/rust/cc/src/parallel/async_executor.rs b/third_party/rust/cc/src/parallel/async_executor.rs new file mode 100644 index 0000000000..9ebd1ad562 --- /dev/null +++ b/third_party/rust/cc/src/parallel/async_executor.rs @@ -0,0 +1,118 @@ +use std::{ + cell::Cell, + future::Future, + pin::Pin, + ptr, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + thread, + time::Duration, +}; + +use crate::Error; + +const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new( + // Cloning just returns a new no-op raw waker + |_| NOOP_RAW_WAKER, + // `wake` does nothing + |_| {}, + // `wake_by_ref` does nothing + |_| {}, + // Dropping does nothing as we don't allocate anything + |_| {}, +); +const NOOP_RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE); + +#[derive(Default)] +pub(crate) struct YieldOnce(bool); + +impl Future for YieldOnce { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + let flag = &mut std::pin::Pin::into_inner(self).0; + if !*flag { + *flag = true; + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +/// Execute the futures and return when they are all done. +/// +/// Here we use our own homebrew async executor since cc is used in the build +/// script of many popular projects, pulling in additional dependencies would +/// significantly slow down its compilation. +pub(crate) fn block_on<Fut1, Fut2>( + mut fut1: Fut1, + mut fut2: Fut2, + has_made_progress: &Cell<bool>, +) -> Result<(), Error> +where + Fut1: Future<Output = Result<(), Error>>, + Fut2: Future<Output = Result<(), Error>>, +{ + // Shadows the future so that it can never be moved and is guaranteed + // to be pinned. + // + // The same trick used in `pin!` macro. + // + // TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!` + let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) }); + let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) }); + + // TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version + // which it is stablised, replace this with `Waker::noop`. + let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) }; + let mut context = Context::from_waker(&waker); + + let mut backoff_cnt = 0; + + loop { + has_made_progress.set(false); + + if let Some(fut) = fut2.as_mut() { + if let Poll::Ready(res) = fut.as_mut().poll(&mut context) { + fut2 = None; + res?; + } + } + + if let Some(fut) = fut1.as_mut() { + if let Poll::Ready(res) = fut.as_mut().poll(&mut context) { + fut1 = None; + res?; + } + } + + if fut1.is_none() && fut2.is_none() { + return Ok(()); + } + + if !has_made_progress.get() { + if backoff_cnt > 3 { + // We have yielded at least three times without making' + // any progress, so we will sleep for a while. + let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10)); + thread::sleep(duration); + } else { + // Given that we spawned a lot of compilation tasks, it is unlikely + // that OS cannot find other ready task to execute. + // + // If all of them are done, then we will yield them and spawn more, + // or simply return. + // + // Thus this will not be turned into a busy-wait loop and it will not + // waste CPU resource. + thread::yield_now(); + } + } + + backoff_cnt = if has_made_progress.get() { + 0 + } else { + backoff_cnt + 1 + }; + } +} diff --git a/third_party/rust/cc/src/parallel/job_token.rs b/third_party/rust/cc/src/parallel/job_token.rs new file mode 100644 index 0000000000..4fec982f85 --- /dev/null +++ b/third_party/rust/cc/src/parallel/job_token.rs @@ -0,0 +1,255 @@ +use std::{marker::PhantomData, mem::MaybeUninit, sync::Once}; + +use crate::Error; + +pub(crate) struct JobToken(PhantomData<()>); + +impl JobToken { + fn new() -> Self { + Self(PhantomData) + } +} + +impl Drop for JobToken { + fn drop(&mut self) { + match JobTokenServer::new() { + JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(), + JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(), + } + } +} + +enum JobTokenServer { + Inherited(inherited_jobserver::JobServer), + InProcess(inprocess_jobserver::JobServer), +} + +impl JobTokenServer { + /// This function returns a static reference to the jobserver because + /// - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might + /// be closed by other jobserver users in the process) and better do it + /// at the start of the program. + /// - in case a jobserver cannot be created from env (e.g. it's not + /// present), we will create a global in-process only jobserver + /// that has to be static so that it will be shared by all cc + /// compilation. + fn new() -> &'static Self { + static INIT: Once = Once::new(); + static mut JOBSERVER: MaybeUninit<JobTokenServer> = MaybeUninit::uninit(); + + unsafe { + INIT.call_once(|| { + let server = inherited_jobserver::JobServer::from_env() + .map(Self::Inherited) + .unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new())); + JOBSERVER = MaybeUninit::new(server); + }); + // TODO: Poor man's assume_init_ref, as that'd require a MSRV of 1.55. + &*JOBSERVER.as_ptr() + } + } +} + +pub(crate) enum ActiveJobTokenServer { + Inherited(inherited_jobserver::ActiveJobServer<'static>), + InProcess(&'static inprocess_jobserver::JobServer), +} + +impl ActiveJobTokenServer { + pub(crate) fn new() -> Result<Self, Error> { + match JobTokenServer::new() { + JobTokenServer::Inherited(inherited_jobserver) => { + inherited_jobserver.enter_active().map(Self::Inherited) + } + JobTokenServer::InProcess(inprocess_jobserver) => { + Ok(Self::InProcess(inprocess_jobserver)) + } + } + } + + pub(crate) async fn acquire(&self) -> Result<JobToken, Error> { + match &self { + Self::Inherited(jobserver) => jobserver.acquire().await, + Self::InProcess(jobserver) => Ok(jobserver.acquire().await), + } + } +} + +mod inherited_jobserver { + use super::JobToken; + + use crate::{parallel::async_executor::YieldOnce, Error, ErrorKind}; + + use std::{ + io, mem, + sync::{mpsc, Mutex, MutexGuard, PoisonError}, + }; + + pub(super) struct JobServer { + /// Implicit token for this process which is obtained and will be + /// released in parent. Since JobTokens only give back what they got, + /// there should be at most one global implicit token in the wild. + /// + /// Since Rust does not execute any `Drop` for global variables, + /// we can't just put it back to jobserver and then re-acquire it at + /// the end of the process. + /// + /// Use `Mutex` to avoid race between acquire and release. + /// If an `AtomicBool` is used, then it's possible for: + /// - `release_token_raw`: Tries to set `global_implicit_token` to true, but it is already + /// set to `true`, continue to release it to jobserver + /// - `acquire` takes the global implicit token, set `global_implicit_token` to false + /// - `release_token_raw` now writes the token back into the jobserver, while + /// `global_implicit_token` is `false` + /// + /// If the program exits here, then cc effectively increases parallelism by one, which is + /// incorrect, hence we use a `Mutex` here. + global_implicit_token: Mutex<bool>, + inner: jobserver::Client, + } + + impl JobServer { + pub(super) unsafe fn from_env() -> Option<Self> { + jobserver::Client::from_env().map(|inner| Self { + inner, + global_implicit_token: Mutex::new(true), + }) + } + + fn get_global_implicit_token(&self) -> MutexGuard<'_, bool> { + self.global_implicit_token + .lock() + .unwrap_or_else(PoisonError::into_inner) + } + + /// All tokens except for the global implicit token will be put back into the jobserver + /// immediately and they cannot be cached, since Rust does not call `Drop::drop` on + /// global variables. + pub(super) fn release_token_raw(&self) { + let mut global_implicit_token = self.get_global_implicit_token(); + + if *global_implicit_token { + // There's already a global implicit token, so this token must + // be released back into jobserver. + // + // `release_raw` should not block + let _ = self.inner.release_raw(); + } else { + *global_implicit_token = true; + } + } + + pub(super) fn enter_active(&self) -> Result<ActiveJobServer<'_>, Error> { + ActiveJobServer::new(self) + } + } + + pub(crate) struct ActiveJobServer<'a> { + jobserver: &'a JobServer, + helper_thread: jobserver::HelperThread, + /// When rx is dropped, all the token stored within it will be dropped. + rx: mpsc::Receiver<io::Result<jobserver::Acquired>>, + } + + impl<'a> ActiveJobServer<'a> { + fn new(jobserver: &'a JobServer) -> Result<Self, Error> { + let (tx, rx) = mpsc::channel(); + + Ok(Self { + rx, + helper_thread: jobserver.inner.clone().into_helper_thread(move |res| { + let _ = tx.send(res); + })?, + jobserver, + }) + } + + pub(super) async fn acquire(&self) -> Result<JobToken, Error> { + let mut has_requested_token = false; + + loop { + // Fast path + if mem::replace(&mut *self.jobserver.get_global_implicit_token(), false) { + break Ok(JobToken::new()); + } + + // Cold path, no global implicit token, obtain one + match self.rx.try_recv() { + Ok(res) => { + let acquired = res?; + acquired.drop_without_releasing(); + break Ok(JobToken::new()); + } + Err(mpsc::TryRecvError::Disconnected) => { + break Err(Error::new( + ErrorKind::JobserverHelpThreadError, + "jobserver help thread has returned before ActiveJobServer is dropped", + )) + } + Err(mpsc::TryRecvError::Empty) => { + if !has_requested_token { + self.helper_thread.request_token(); + has_requested_token = true; + } + YieldOnce::default().await + } + } + } + } + } +} + +mod inprocess_jobserver { + use super::JobToken; + + use crate::parallel::async_executor::YieldOnce; + + use std::{ + env::var, + sync::atomic::{ + AtomicU32, + Ordering::{AcqRel, Acquire}, + }, + }; + + pub(crate) struct JobServer(AtomicU32); + + impl JobServer { + pub(super) fn new() -> Self { + // 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; + // TODO: Use std::thread::available_parallelism as an upper bound + // when MSRV is bumped. + if let Ok(amt) = var("NUM_JOBS") { + if let Ok(amt) = amt.parse() { + parallelism = amt; + } + } + + Self(AtomicU32::new(parallelism)) + } + + pub(super) async fn acquire(&self) -> JobToken { + loop { + let res = self + .0 + .fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1)); + + if res.is_ok() { + break JobToken::new(); + } + + YieldOnce::default().await + } + } + + pub(super) fn release_token_raw(&self) { + self.0.fetch_add(1, AcqRel); + } + } +} diff --git a/third_party/rust/cc/src/parallel/mod.rs b/third_party/rust/cc/src/parallel/mod.rs new file mode 100644 index 0000000000..d69146dc59 --- /dev/null +++ b/third_party/rust/cc/src/parallel/mod.rs @@ -0,0 +1,20 @@ +pub(crate) mod async_executor; +pub(crate) mod job_token; +pub(crate) mod stderr; + +/// Remove all element in `vec` which `f(element)` returns `false`. +/// +/// TODO: Remove this once the MSRV is bumped to v1.61 +pub(crate) fn retain_unordered_mut<T, F>(vec: &mut Vec<T>, mut f: F) +where + F: FnMut(&mut T) -> bool, +{ + let mut i = 0; + while i < vec.len() { + if f(&mut vec[i]) { + i += 1; + } else { + vec.swap_remove(i); + } + } +} diff --git a/third_party/rust/cc/src/parallel/stderr.rs b/third_party/rust/cc/src/parallel/stderr.rs new file mode 100644 index 0000000000..47fa085dba --- /dev/null +++ b/third_party/rust/cc/src/parallel/stderr.rs @@ -0,0 +1,90 @@ +/// Helpers functions for [ChildStderr]. +use std::{convert::TryInto, process::ChildStderr}; + +use crate::{Error, ErrorKind}; + +#[cfg(all(not(unix), not(windows)))] +compile_error!("Only unix and windows support non-blocking pipes! For other OSes, disable the parallel feature."); + +#[cfg(unix)] +fn get_flags(fd: std::os::unix::io::RawFd) -> Result<i32, Error> { + let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) }; + if flags == -1 { + Err(Error::new( + ErrorKind::IOError, + format!( + "Failed to get flags for pipe {}: {}", + fd, + std::io::Error::last_os_error() + ), + )) + } else { + Ok(flags) + } +} + +#[cfg(unix)] +fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result<(), Error> { + if unsafe { libc::fcntl(fd, libc::F_SETFL, flags) } == -1 { + Err(Error::new( + ErrorKind::IOError, + format!( + "Failed to set flags for pipe {}: {}", + fd, + std::io::Error::last_os_error() + ), + )) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn set_non_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> { + // On Unix, switch the pipe to non-blocking mode. + // On Windows, we have a different way to be non-blocking. + let fd = pipe.as_raw_fd(); + + let flags = get_flags(fd)?; + set_flags(fd, flags | libc::O_NONBLOCK) +} + +pub fn bytes_available(stderr: &mut ChildStderr) -> Result<usize, Error> { + let mut bytes_available = 0; + #[cfg(windows)] + { + use crate::windows::windows_sys::PeekNamedPipe; + use std::os::windows::io::AsRawHandle; + use std::ptr::null_mut; + if unsafe { + PeekNamedPipe( + stderr.as_raw_handle(), + null_mut(), + 0, + null_mut(), + &mut bytes_available, + null_mut(), + ) + } == 0 + { + return Err(Error::new( + ErrorKind::IOError, + format!( + "PeekNamedPipe failed with {}", + std::io::Error::last_os_error() + ), + )); + } + } + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + if unsafe { libc::ioctl(stderr.as_raw_fd(), libc::FIONREAD, &mut bytes_available) } != 0 { + return Err(Error::new( + ErrorKind::IOError, + format!("ioctl failed with {}", std::io::Error::last_os_error()), + )); + } + } + Ok(bytes_available.try_into().unwrap()) +} diff --git a/third_party/rust/cc/src/tool.rs b/third_party/rust/cc/src/tool.rs new file mode 100644 index 0000000000..a193a90ff7 --- /dev/null +++ b/third_party/rust/cc/src/tool.rs @@ -0,0 +1,399 @@ +use std::{ + collections::HashMap, + ffi::OsString, + path::{Path, PathBuf}, + process::Command, + sync::Mutex, +}; + +use crate::command_helpers::{run_output, CargoOutput}; + +/// Configuration used to represent an invocation of a C compiler. +/// +/// This can be used to figure out what compiler is in use, what the arguments +/// to it are, and what the environment variables look like for the compiler. +/// This can be used to further configure other build systems (e.g. forward +/// along CC and/or CFLAGS) or the `to_command` method can be used to run the +/// compiler itself. +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub struct Tool { + pub(crate) path: PathBuf, + pub(crate) cc_wrapper_path: Option<PathBuf>, + pub(crate) cc_wrapper_args: Vec<OsString>, + pub(crate) args: Vec<OsString>, + pub(crate) env: Vec<(OsString, OsString)>, + pub(crate) family: ToolFamily, + pub(crate) cuda: bool, + pub(crate) removed_args: Vec<OsString>, + pub(crate) has_internal_target_arg: bool, +} + +impl Tool { + pub(crate) fn new( + path: PathBuf, + cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, + cargo_output: &CargoOutput, + ) -> Self { + Self::with_features(path, None, false, cached_compiler_family, cargo_output) + } + + pub(crate) fn with_clang_driver( + path: PathBuf, + clang_driver: Option<&str>, + cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, + cargo_output: &CargoOutput, + ) -> Self { + Self::with_features( + path, + clang_driver, + false, + cached_compiler_family, + cargo_output, + ) + } + + /// Explicitly set the `ToolFamily`, skipping name-based detection. + pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self { + Self { + path, + cc_wrapper_path: None, + cc_wrapper_args: Vec::new(), + args: Vec::new(), + env: Vec::new(), + family, + cuda: false, + removed_args: Vec::new(), + has_internal_target_arg: false, + } + } + + pub(crate) fn with_features( + path: PathBuf, + clang_driver: Option<&str>, + cuda: bool, + cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>, + cargo_output: &CargoOutput, + ) -> Self { + fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily { + let mut cmd = Command::new(path); + cmd.arg("--version"); + + let stdout = match run_output( + &mut cmd, + &path.to_string_lossy(), + // tool detection issues should always be shown as warnings + cargo_output, + ) + .ok() + .and_then(|o| String::from_utf8(o).ok()) + { + Some(s) => s, + None => { + // --version failed. fallback to gnu + cargo_output.print_warning(&format_args!("Failed to run: {:?}", cmd)); + return ToolFamily::Gnu; + } + }; + if stdout.contains("clang") { + ToolFamily::Clang + } else if stdout.contains("GCC") { + ToolFamily::Gnu + } else { + // --version doesn't include clang for GCC + cargo_output.print_warning(&format_args!( + "Compiler version doesn't include clang or GCC: {:?}", + cmd + )); + ToolFamily::Gnu + } + } + let detect_family = |path: &Path| -> ToolFamily { + if let Some(family) = cached_compiler_family.lock().unwrap().get(path) { + return *family; + } + + let family = detect_family_inner(path, cargo_output); + cached_compiler_family + .lock() + .unwrap() + .insert(path.into(), family); + family + }; + + // Try to detect family of the tool from its name, falling back to Gnu. + let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { + if fname.contains("clang-cl") { + ToolFamily::Msvc { clang_cl: true } + } else if fname.ends_with("cl") || fname == "cl.exe" { + ToolFamily::Msvc { clang_cl: false } + } else if fname.contains("clang") { + match clang_driver { + Some("cl") => ToolFamily::Msvc { clang_cl: true }, + _ => ToolFamily::Clang, + } + } else { + detect_family(&path) + } + } else { + detect_family(&path) + }; + + Tool { + path, + cc_wrapper_path: None, + cc_wrapper_args: Vec::new(), + args: Vec::new(), + env: Vec::new(), + family, + cuda, + removed_args: Vec::new(), + has_internal_target_arg: false, + } + } + + /// Add an argument to be stripped from the final command arguments. + pub(crate) fn remove_arg(&mut self, flag: OsString) { + self.removed_args.push(flag); + } + + /// Push an "exotic" flag to the end of the compiler's arguments list. + /// + /// Nvidia compiler accepts only the most common compiler flags like `-D`, + /// `-I`, `-c`, etc. Options meant specifically for the underlying + /// host C++ compiler have to be prefixed with `-Xcompiler`. + /// [Another possible future application for this function is passing + /// clang-specific flags to clang-cl, which otherwise accepts only + /// MSVC-specific options.] + pub(crate) fn push_cc_arg(&mut self, flag: OsString) { + if self.cuda { + self.args.push("-Xcompiler".into()); + } + self.args.push(flag); + } + + /// Checks if an argument or flag has already been specified or conflicts. + /// + /// Currently only checks optimization flags. + pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool { + let flag = flag.to_str().unwrap(); + let mut chars = flag.chars(); + + // Only duplicate check compiler flags + if self.is_like_msvc() { + if chars.next() != Some('/') { + return false; + } + } else if self.is_like_gnu() || self.is_like_clang() { + if chars.next() != Some('-') { + return false; + } + } + + // Check for existing optimization flags (-O, /O) + if chars.next() == Some('O') { + return self + .args() + .iter() + .any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O')); + } + + // TODO Check for existing -m..., -m...=..., /arch:... flags + false + } + + /// Don't push optimization arg if it conflicts with existing args. + pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) { + if self.is_duplicate_opt_arg(&flag) { + println!("Info: Ignoring duplicate arg {:?}", &flag); + } else { + self.push_cc_arg(flag); + } + } + + /// Converts this compiler into a `Command` that's ready to be run. + /// + /// This is useful for when the compiler needs to be executed and the + /// command returned will already have the initial arguments and environment + /// variables configured. + pub fn to_command(&self) -> Command { + let mut cmd = match self.cc_wrapper_path { + Some(ref cc_wrapper_path) => { + let mut cmd = Command::new(cc_wrapper_path); + cmd.arg(&self.path); + cmd + } + None => Command::new(&self.path), + }; + cmd.args(&self.cc_wrapper_args); + + let value = self + .args + .iter() + .filter(|a| !self.removed_args.contains(a)) + .collect::<Vec<_>>(); + cmd.args(&value); + + for (k, v) in self.env.iter() { + cmd.env(k, v); + } + cmd + } + + /// Returns the path for this compiler. + /// + /// Note that this may not be a path to a file on the filesystem, e.g. "cc", + /// but rather something which will be resolved when a process is spawned. + pub fn path(&self) -> &Path { + &self.path + } + + /// Returns the default set of arguments to the compiler needed to produce + /// executables for the target this compiler generates. + pub fn args(&self) -> &[OsString] { + &self.args + } + + /// Returns the set of environment variables needed for this compiler to + /// operate. + /// + /// This is typically only used for MSVC compilers currently. + pub fn env(&self) -> &[(OsString, OsString)] { + &self.env + } + + /// Returns the compiler command in format of CC environment variable. + /// Or empty string if CC env was not present + /// + /// This is typically used by configure script + pub fn cc_env(&self) -> OsString { + match self.cc_wrapper_path { + Some(ref cc_wrapper_path) => { + let mut cc_env = cc_wrapper_path.as_os_str().to_owned(); + cc_env.push(" "); + cc_env.push(self.path.to_path_buf().into_os_string()); + for arg in self.cc_wrapper_args.iter() { + cc_env.push(" "); + cc_env.push(arg); + } + cc_env + } + None => OsString::from(""), + } + } + + /// Returns the compiler flags in format of CFLAGS environment variable. + /// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS + /// This is typically used by configure script + pub fn cflags_env(&self) -> OsString { + let mut flags = OsString::new(); + for (i, arg) in self.args.iter().enumerate() { + if i > 0 { + flags.push(" "); + } + flags.push(arg); + } + flags + } + + /// Whether the tool is GNU Compiler Collection-like. + pub fn is_like_gnu(&self) -> bool { + self.family == ToolFamily::Gnu + } + + /// Whether the tool is Clang-like. + pub fn is_like_clang(&self) -> bool { + self.family == ToolFamily::Clang + } + + /// Whether the tool is AppleClang under .xctoolchain + #[cfg(target_vendor = "apple")] + pub(crate) fn is_xctoolchain_clang(&self) -> bool { + let path = self.path.to_string_lossy(); + path.contains(".xctoolchain/") + } + #[cfg(not(target_vendor = "apple"))] + pub(crate) fn is_xctoolchain_clang(&self) -> bool { + false + } + + /// Whether the tool is MSVC-like. + pub fn is_like_msvc(&self) -> bool { + match self.family { + ToolFamily::Msvc { .. } => true, + _ => false, + } + } +} + +/// Represents the family of tools this tool belongs to. +/// +/// Each family of tools differs in how and what arguments they accept. +/// +/// Detection of a family is done on best-effort basis and may not accurately reflect the tool. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ToolFamily { + /// Tool is GNU Compiler Collection-like. + Gnu, + /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags + /// and its cross-compilation approach is different. + Clang, + /// Tool is the MSVC cl.exe. + Msvc { clang_cl: bool }, +} + +impl ToolFamily { + /// What the flag to request debug info for this family of tools look like + pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) { + match *self { + ToolFamily::Msvc { .. } => { + cmd.push_cc_arg("-Z7".into()); + } + ToolFamily::Gnu | ToolFamily::Clang => { + cmd.push_cc_arg( + dwarf_version + .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v)) + .into(), + ); + } + } + } + + /// What the flag to force frame pointers. + pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) { + match *self { + ToolFamily::Gnu | ToolFamily::Clang => { + cmd.push_cc_arg("-fno-omit-frame-pointer".into()); + } + _ => (), + } + } + + /// What the flags to enable all warnings + pub(crate) fn warnings_flags(&self) -> &'static str { + match *self { + ToolFamily::Msvc { .. } => "-W4", + ToolFamily::Gnu | ToolFamily::Clang => "-Wall", + } + } + + /// What the flags to enable extra warnings + pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> { + match *self { + ToolFamily::Msvc { .. } => None, + ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"), + } + } + + /// What the flag to turn warning into errors + pub(crate) fn warnings_to_errors_flag(&self) -> &'static str { + match *self { + ToolFamily::Msvc { .. } => "-WX", + ToolFamily::Gnu | ToolFamily::Clang => "-Werror", + } + } + + pub(crate) fn verbose_stderr(&self) -> bool { + *self == ToolFamily::Clang + } +} diff --git a/third_party/rust/cc/src/com.rs b/third_party/rust/cc/src/windows/com.rs index 843247e588..e81bb1d3c3 100644 --- a/third_party/rust/cc/src/com.rs +++ b/third_party/rust/cc/src/windows/com.rs @@ -7,27 +7,31 @@ #![allow(unused)] -use crate::winapi::CoInitializeEx; -use crate::winapi::IUnknown; -use crate::winapi::Interface; -use crate::winapi::BSTR; -use crate::winapi::COINIT_MULTITHREADED; -use crate::winapi::{SysFreeString, SysStringLen}; -use crate::winapi::{HRESULT, S_FALSE, S_OK}; -use std::ffi::{OsStr, OsString}; -use std::mem::forget; -use std::ops::Deref; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::ptr::null_mut; -use std::slice::from_raw_parts; +use crate::windows::{ + winapi::{IUnknown, Interface}, + windows_sys::{ + CoInitializeEx, SysFreeString, SysStringLen, BSTR, COINIT_MULTITHREADED, HRESULT, S_FALSE, + S_OK, + }, +}; +use std::{ + convert::TryInto, + ffi::{OsStr, OsString}, + mem::ManuallyDrop, + ops::Deref, + os::windows::ffi::{OsStrExt, OsStringExt}, + ptr::{null, null_mut}, + slice::from_raw_parts, +}; pub fn initialize() -> Result<(), HRESULT> { - let err = unsafe { CoInitializeEx(null_mut(), COINIT_MULTITHREADED) }; + let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) }; if err != S_OK && err != S_FALSE { // S_FALSE just means COM is already initialized - return Err(err); + Err(err) + } else { + Ok(()) } - Ok(()) } pub struct ComPtr<T>(*mut T) @@ -55,15 +59,13 @@ where /// Extracts the raw pointer. /// You are now responsible for releasing it yourself. pub fn into_raw(self) -> *mut T { - let p = self.0; - forget(self); - p + ManuallyDrop::new(self).0 } /// For internal use only. fn as_unknown(&self) -> &IUnknown { unsafe { &*(self.0 as *mut IUnknown) } } - /// Performs QueryInterface fun. + /// Performs `QueryInterface` fun. pub fn cast<U>(&self) -> Result<ComPtr<U>, i32> where U: Interface, diff --git a/third_party/rust/cc/src/windows_registry.rs b/third_party/rust/cc/src/windows/find_tools.rs index 276688b03f..4f0ed87411 100644 --- a/third_party/rust/cc/src/windows_registry.rs +++ b/third_party/rust/cc/src/windows/find_tools.rs @@ -8,18 +8,34 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! A helper module to probe the Windows Registry when looking for -//! windows-specific tools. +//! A helper module to looking for windows-specific tools: +//! 1. On Windows host, probe the Windows Registry if needed; +//! 2. On non-Windows host, check specified environment variables. + +#![allow(clippy::upper_case_acronyms)] use std::process::Command; use crate::Tool; -#[cfg(windows)] use crate::ToolFamily; -#[cfg(windows)] const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; +#[derive(Copy, Clone)] +struct TargetArch<'a>(pub &'a str); + +impl PartialEq<&str> for TargetArch<'_> { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +impl<'a> From<TargetArch<'a>> for &'a str { + fn from(target: TargetArch<'a>) -> Self { + target.0 + } +} + /// Attempts to find a tool within an MSVC installation using the Windows /// registry as a point to search from. /// @@ -39,13 +55,6 @@ pub fn find(target: &str, tool: &str) -> Option<Command> { /// Similar to the `find` function above, this function will attempt the same /// operation (finding a MSVC tool in a local install) but instead returns a /// `Tool` which may be introspected. -#[cfg(not(windows))] -pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> { - None -} - -/// Documented above. -#[cfg(windows)] pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { // This logic is all tailored for MSVC, if we're not that then bail out // early. @@ -53,13 +62,17 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { return None; } + // Split the target to get the arch. + let target = TargetArch(target.split_once('-')?.0); + // Looks like msbuild isn't located in the same location as other tools like - // cl.exe and lib.exe. To handle this we probe for it manually with - // dedicated registry keys. + // cl.exe and lib.exe. if tool.contains("msbuild") { return impl_::find_msbuild(target); } + // Looks like devenv isn't located in the same location as other tools like + // cl.exe and lib.exe. if tool.contains("devenv") { return impl_::find_devenv(target); } @@ -71,15 +84,16 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that // the tool is actually usable. - return impl_::find_msvc_environment(tool, target) + impl_::find_msvc_environment(tool, target) .or_else(|| impl_::find_msvc_15plus(tool, target)) .or_else(|| impl_::find_msvc_14(tool, target)) .or_else(|| impl_::find_msvc_12(tool, target)) - .or_else(|| impl_::find_msvc_11(tool, target)); + .or_else(|| impl_::find_msvc_11(tool, target)) } /// A version of Visual Studio #[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[non_exhaustive] pub enum VsVers { /// Visual Studio 12 (2013) Vs12, @@ -91,30 +105,14 @@ pub enum VsVers { Vs16, /// Visual Studio 17 (2022) Vs17, - - /// Hidden variant that should not be matched on. Callers that want to - /// handle an enumeration of `VsVers` instances should always have a default - /// case meaning that it's a VS version they don't understand. - #[doc(hidden)] - #[allow(bad_style)] - __Nonexhaustive_do_not_match_this_or_your_code_will_break, } /// Find the most recent installed version of Visual Studio /// /// This is used by the cmake crate to figure out the correct /// generator. -#[cfg(not(windows))] pub fn find_vs_version() -> Result<VsVers, String> { - Err(format!("not windows")) -} - -/// Documented above -#[cfg(windows)] -pub fn find_vs_version() -> Result<VsVers, String> { - use std::env; - - match env::var("VisualStudioVersion") { + match std::env::var("VisualStudioVersion") { Ok(version) => match &version[..] { "17.0" => Ok(VsVers::Vs17), "16.0" => Ok(VsVers::Vs16), @@ -158,12 +156,17 @@ pub fn find_vs_version() -> Result<VsVers, String> { } } +/// Windows Implementation. #[cfg(windows)] mod impl_ { - use crate::com; - use crate::registry::{RegistryKey, LOCAL_MACHINE}; - use crate::setup_config::SetupConfiguration; - use crate::vs_instances::{VsInstances, VswhereInstance}; + use crate::windows::com; + use crate::windows::registry::{RegistryKey, LOCAL_MACHINE}; + use crate::windows::setup_config::SetupConfiguration; + use crate::windows::vs_instances::{VsInstances, VswhereInstance}; + use crate::windows::windows_sys::{ + FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, + IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK, + }; use std::convert::TryFrom; use std::env; use std::ffi::OsString; @@ -174,8 +177,10 @@ mod impl_ { use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Once; - use super::MSVC_FAMILY; + use super::{TargetArch, MSVC_FAMILY}; use crate::Tool; struct MsvcTool { @@ -185,10 +190,75 @@ mod impl_ { include: Vec<PathBuf>, } + struct LibraryHandle(HMODULE); + + impl LibraryHandle { + fn new(name: &[u8]) -> Option<Self> { + let handle = unsafe { LoadLibraryA(name.as_ptr() as _) }; + (!handle.is_null()).then(|| Self(handle)) + } + + /// Get a function pointer to a function in the library. + /// SAFETY: The caller must ensure that the function signature matches the actual function. + /// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the + /// generated function for `func_signature`. + unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> { + let symbol = unsafe { GetProcAddress(self.0, name.as_ptr() as _) }; + symbol.map(|symbol| unsafe { mem::transmute_copy(&symbol) }) + } + } + + impl Drop for LibraryHandle { + fn drop(&mut self) { + unsafe { FreeLibrary(self.0) }; + } + } + + type GetMachineTypeAttributesFuncType = + unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32; + const _: () = { + // Ensure that our hand-written signature matches the actual function signature. + // We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to + // it, which will fail to load on older versions of Windows. + let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes; + }; + + fn is_amd64_emulation_supported_inner() -> Option<bool> { + // GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it. + let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?; + // SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature. + let get_machine_type_attributes = unsafe { + kernel32 + .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0") + }?; + let mut attributes = Default::default(); + if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK + { + Some((attributes & UserEnabled) != 0) + } else { + Some(false) + } + } + + fn is_amd64_emulation_supported() -> bool { + // TODO: Replace with a OnceLock once MSRV is 1.70. + static LOAD_VALUE: Once = Once::new(); + static IS_SUPPORTED: AtomicBool = AtomicBool::new(false); + + // Using Relaxed ordering since the Once is providing synchronization. + LOAD_VALUE.call_once(|| { + IS_SUPPORTED.store( + is_amd64_emulation_supported_inner().unwrap_or(false), + Ordering::Relaxed, + ); + }); + IS_SUPPORTED.load(Ordering::Relaxed) + } + impl MsvcTool { fn new(tool: PathBuf) -> MsvcTool { MsvcTool { - tool: tool, + tool, libs: Vec::new(), path: Vec::new(), include: Vec::new(), @@ -202,7 +272,7 @@ mod impl_ { path, include, } = self; - let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY); + let mut tool = Tool::with_family(tool, MSVC_FAMILY); add_env(&mut tool, "LIB", libs); add_env(&mut tool, "PATH", path); add_env(&mut tool, "INCLUDE", include); @@ -212,15 +282,14 @@ mod impl_ { /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the /// given target's arch. Returns `None` if the variable does not exist. - #[cfg(windows)] - fn is_vscmd_target(target: &str) -> Option<bool> { + fn is_vscmd_target(target: TargetArch<'_>) -> Option<bool> { let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?; // Convert the Rust target arch to its VS arch equivalent. - let arch = match target.split("-").next() { - Some("x86_64") => "x64", - Some("aarch64") => "arm64", - Some("i686") | Some("i586") => "x86", - Some("thumbv7a") => "arm", + let arch = match target.into() { + "x86_64" => "x64", + "aarch64" | "arm64ec" => "arm64", + "i686" | "i586" => "x86", + "thumbv7a" => "arm", // An unrecognized arch. _ => return Some(false), }; @@ -228,7 +297,7 @@ mod impl_ { } /// Attempt to find the tool using environment variables set by vcvars. - pub fn find_msvc_environment(tool: &str, target: &str) -> Option<Tool> { + pub(super) fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> { // Early return if the environment doesn't contain a VC install. if env::var_os("VCINSTALLDIR").is_none() { return None; @@ -248,16 +317,19 @@ mod impl_ { .map(|p| p.join(tool)) .find(|p| p.exists()) }) - .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) + .map(|path| Tool::with_family(path, MSVC_FAMILY)) } } - fn find_msbuild_vs17(target: &str) -> Option<Tool> { + fn find_msbuild_vs17(target: TargetArch<'_>) -> Option<Tool> { find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17") } #[allow(bare_trait_objects)] - fn vs16plus_instances(target: &str, version: &'static str) -> Box<Iterator<Item = PathBuf>> { + fn vs16plus_instances( + target: TargetArch<'_>, + version: &'static str, + ) -> Box<Iterator<Item = PathBuf>> { let instances = if let Some(instances) = vs15plus_instances(target) { instances } else { @@ -275,7 +347,11 @@ mod impl_ { })) } - fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option<Tool> { + fn find_tool_in_vs16plus_path( + tool: &str, + target: TargetArch<'_>, + version: &'static str, + ) -> Option<Tool> { vs16plus_instances(target, version) .filter_map(|path| { let path = path.join(tool); @@ -283,10 +359,10 @@ mod impl_ { return None; } let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target.contains("x86_64") { + if target == "x86_64" { tool.env.push(("Platform".into(), "X64".into())); } - if target.contains("aarch64") { + if target == "aarch64" || target == "arm64ec" { tool.env.push(("Platform".into(), "ARM64".into())); } Some(tool) @@ -294,7 +370,7 @@ mod impl_ { .next() } - fn find_msbuild_vs16(target: &str) -> Option<Tool> { + fn find_msbuild_vs16(target: TargetArch<'_>) -> Option<Tool> { find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16") } @@ -310,7 +386,7 @@ mod impl_ { // // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. // Hence, as the last resort we try to use vswhere.exe to list available instances. - fn vs15plus_instances(target: &str) -> Option<VsInstances> { + fn vs15plus_instances(target: TargetArch<'_>) -> Option<VsInstances> { vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) } @@ -323,7 +399,7 @@ mod impl_ { Some(VsInstances::ComBased(enum_setup_instances)) } - fn vs15plus_instances_using_vswhere(target: &str) -> Option<VsInstances> { + fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option<VsInstances> { let program_files_path: PathBuf = env::var("ProgramFiles(x86)") .or_else(|_| env::var("ProgramFiles")) .ok()? @@ -336,11 +412,10 @@ mod impl_ { return None; } - let arch = target.split('-').next().unwrap(); - let tools_arch = match arch { + let tools_arch = match target.into() { "i586" | "i686" | "x86_64" => Some("x86.x64"), "arm" | "thumbv7a" => Some("ARM"), - "aarch64" => Some("ARM64"), + "aarch64" | "arm64ec" => Some("ARM64"), _ => None, }; @@ -374,7 +449,7 @@ mod impl_ { .collect() } - pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> { + pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> { let iter = vs15plus_instances(target)?; iter.into_iter() .filter_map(|instance| { @@ -394,13 +469,13 @@ mod impl_ { // we keep the registry method as a fallback option. // // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 - fn find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool> { + fn find_tool_in_vs15_path(tool: &str, target: TargetArch<'_>) -> Option<Tool> { let mut path = match vs15plus_instances(target) { Some(instances) => instances .into_iter() .filter_map(|instance| instance.installation_path()) .map(|path| path.join(tool)) - .find(|ref path| path.is_file()), + .find(|path| path.is_file()), None => None, }; @@ -416,10 +491,9 @@ mod impl_ { path.map(|path| { let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target.contains("x86_64") { + if target == "x86_64" { tool.env.push(("Platform".into(), "X64".into())); - } - if target.contains("aarch64") { + } else if target == "aarch64" { tool.env.push(("Platform".into(), "ARM64".into())); } tool @@ -428,10 +502,10 @@ mod impl_ { fn tool_from_vs15plus_instance( tool: &str, - target: &str, + target: TargetArch<'_>, instance_path: &PathBuf, ) -> Option<Tool> { - let (root_path, bin_path, host_dylib_path, lib_path, include_path) = + let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) = vs15plus_vc_paths(target, instance_path)?; let tool_path = bin_path.join(tool); if !tool_path.exists() { @@ -441,6 +515,9 @@ mod impl_ { let mut tool = MsvcTool::new(tool_path); tool.path.push(bin_path.clone()); tool.path.push(host_dylib_path); + if let Some(alt_lib_path) = alt_lib_path { + tool.libs.push(alt_lib_path); + } tool.libs.push(lib_path); tool.include.push(include_path); @@ -455,45 +532,97 @@ mod impl_ { } fn vs15plus_vc_paths( - target: &str, - instance_path: &PathBuf, - ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, PathBuf)> { - let version_path = - instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); - let mut version_file = File::open(version_path).ok()?; - let mut version = String::new(); - version_file.read_to_string(&mut version).ok()?; - let version = version.trim(); - let host = match host_arch() { - X86 => "X86", - X86_64 => "X64", - // There is no natively hosted compiler on ARM64. - // Instead, use the x86 toolchain under emulation (there is no x64 emulation). - AARCH64 => "X86", + target: TargetArch<'_>, + instance_path: &Path, + ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> { + let version = vs15plus_vc_read_version(instance_path)?; + + let hosts = match host_arch() { + X86 => &["X86"], + X86_64 => &["X64"], + // Starting with VS 17.4, there is a natively hosted compiler on ARM64: + // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/ + // On older versions of VS, we use x64 if running under emulation is supported, + // otherwise use x86. + AARCH64 => { + if is_amd64_emulation_supported() { + &["ARM64", "X64", "X86"][..] + } else { + &["ARM64", "X86"] + } + } _ => return None, }; let target = lib_subdir(target)?; // The directory layout here is MSVC/bin/Host$host/$target/ let path = instance_path.join(r"VC\Tools\MSVC").join(version); + // We use the first available host architecture that can build for the target + let (host_path, host) = hosts.iter().find_map(|&x| { + let candidate = path.join("bin").join(format!("Host{}", x)); + if candidate.join(target).exists() { + Some((candidate, x)) + } else { + None + } + })?; // This is the path to the toolchain for a particular target, running // on a given host - let bin_path = path - .join("bin") - .join(&format!("Host{}", host)) - .join(&target); + let bin_path = host_path.join(target); // But! we also need PATH to contain the target directory for the host // architecture, because it contains dlls like mspdb140.dll compiled for // the host architecture. - let host_dylib_path = path - .join("bin") - .join(&format!("Host{}", host)) - .join(&host.to_lowercase()); - let lib_path = path.join("lib").join(&target); + let host_dylib_path = host_path.join(host.to_lowercase()); + let lib_path = path.join("lib").join(target); + let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec")); let include_path = path.join("include"); - Some((path, bin_path, host_dylib_path, lib_path, include_path)) + Some(( + path, + bin_path, + host_dylib_path, + lib_path, + alt_lib_path, + include_path, + )) } - fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { + fn vs15plus_vc_read_version(dir: &Path) -> Option<String> { + // Try to open the default version file. + let mut version_path: PathBuf = + dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); + let mut version_file = if let Ok(f) = File::open(&version_path) { + f + } else { + // If the default doesn't exist, search for other version files. + // These are in the form Microsoft.VCToolsVersion.v143.default.txt + // where `143` is any three decimal digit version number. + // This sorts versions by lexical order and selects the highest version. + let mut version_file = String::new(); + version_path.pop(); + for file in version_path.read_dir().ok()? { + let name = file.ok()?.file_name(); + let name = name.to_str()?; + if name.starts_with("Microsoft.VCToolsVersion.v") + && name.ends_with(".default.txt") + && name > &version_file + { + version_file.replace_range(.., name); + } + } + if version_file.is_empty() { + return None; + } + version_path.push(version_file); + File::open(version_path).ok()? + }; + + // Get the version string from the file we found. + let mut version = String::new(); + version_file.read_to_string(&mut version).ok()?; + version.truncate(version.trim_end().len()); + Some(version) + } + + fn atl_paths(target: TargetArch<'_>, path: &Path) -> Option<(PathBuf, PathBuf)> { let atl_path = path.join("atlmfc"); let sub = lib_subdir(target)?; if atl_path.exists() { @@ -505,14 +634,14 @@ mod impl_ { // For MSVC 14 we need to find the Universal CRT as well as either // the Windows 10 SDK or Windows 8.1 SDK. - pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { + pub(super) fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> { let vcdir = get_vc_dir("14.0")?; let mut tool = get_tool(tool, &vcdir, target)?; add_sdks(&mut tool, target)?; Some(tool.into_tool()) } - fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { + fn add_sdks(tool: &mut MsvcTool, target: TargetArch<'_>) -> Option<()> { let sub = lib_subdir(target)?; let (ucrt, ucrt_version) = get_ucrt_dir()?; @@ -555,7 +684,7 @@ mod impl_ { } // For MSVC 12 we need to find the Windows 8.1 SDK. - pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { + pub(super) fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> { let vcdir = get_vc_dir("12.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -571,7 +700,7 @@ mod impl_ { } // For MSVC 11 we need to find the Windows 8 SDK. - pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { + pub(super) fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option<Tool> { let vcdir = get_vc_dir("11.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -596,7 +725,7 @@ mod impl_ { // Given a possible MSVC installation directory, we look for the linker and // then add the MSVC library path. - fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> { + fn get_tool(tool: &str, path: &Path, target: TargetArch<'_>) -> Option<MsvcTool> { bin_subdir(target) .into_iter() .map(|(sub, host)| { @@ -605,7 +734,7 @@ mod impl_ { path.join("bin").join(host), ) }) - .filter(|&(ref path, _)| path.is_file()) + .filter(|(path, _)| path.is_file()) .map(|(path, host)| { let mut tool = MsvcTool::new(path); tool.path.push(host); @@ -734,9 +863,8 @@ mod impl_ { // linkers that can target the architecture we desire. The 64-bit host // linker is preferred, and hence first, due to 64-bit allowing it more // address space to work with and potentially being faster. - fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> { - let arch = target.split('-').next().unwrap(); - match (arch, host_arch()) { + fn bin_subdir(target: TargetArch<'_>) -> Vec<(&'static str, &'static str)> { + match (target.into(), host_arch()) { ("i586", X86) | ("i686", X86) => vec![("", "")], ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")], ("x86_64", X86) => vec![("x86_amd64", "")], @@ -747,21 +875,19 @@ mod impl_ { } } - fn lib_subdir(target: &str) -> Option<&'static str> { - let arch = target.split('-').next().unwrap(); - match arch { + fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { + match target.into() { "i586" | "i686" => Some("x86"), "x86_64" => Some("x64"), "arm" | "thumbv7a" => Some("arm"), - "aarch64" => Some("arm64"), + "aarch64" | "arm64ec" => Some("arm64"), _ => None, } } // MSVC's x86 libraries are not in a subfolder - fn vc_lib_subdir(target: &str) -> Option<&'static str> { - let arch = target.split('-').next().unwrap(); - match arch { + fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { + match target.into() { "i586" | "i686" => Some(""), "x86_64" => Some("amd64"), "arm" | "thumbv7a" => Some("arm"), @@ -813,7 +939,7 @@ mod impl_ { for subkey in key.iter().filter_map(|k| k.ok()) { let val = subkey .to_str() - .and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok()); + .and_then(|s| s.trim_left_matches("v").replace('.', "").parse().ok()); let val = match val { Some(s) => s, None => continue, @@ -828,22 +954,22 @@ mod impl_ { max_key } - pub fn has_msbuild_version(version: &str) -> bool { + pub(super) fn has_msbuild_version(version: &str) -> bool { match version { "17.0" => { - find_msbuild_vs17("x86_64-pc-windows-msvc").is_some() - || find_msbuild_vs17("i686-pc-windows-msvc").is_some() - || find_msbuild_vs17("aarch64-pc-windows-msvc").is_some() + find_msbuild_vs17(TargetArch("x86_64")).is_some() + || find_msbuild_vs17(TargetArch("i686")).is_some() + || find_msbuild_vs17(TargetArch("aarch64")).is_some() } "16.0" => { - find_msbuild_vs16("x86_64-pc-windows-msvc").is_some() - || find_msbuild_vs16("i686-pc-windows-msvc").is_some() - || find_msbuild_vs16("aarch64-pc-windows-msvc").is_some() + find_msbuild_vs16(TargetArch("x86_64")).is_some() + || find_msbuild_vs16(TargetArch("i686")).is_some() + || find_msbuild_vs16(TargetArch("aarch64")).is_some() } "15.0" => { - find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() - || find_msbuild_vs15("i686-pc-windows-msvc").is_some() - || find_msbuild_vs15("aarch64-pc-windows-msvc").is_some() + find_msbuild_vs15(TargetArch("x86_64")).is_some() + || find_msbuild_vs15(TargetArch("i686")).is_some() + || find_msbuild_vs15(TargetArch("aarch64")).is_some() } "12.0" | "14.0" => LOCAL_MACHINE .open(&OsString::from(format!( @@ -855,18 +981,20 @@ mod impl_ { } } - pub fn find_devenv(target: &str) -> Option<Tool> { - find_devenv_vs15(&target) + pub(super) fn find_devenv(target: TargetArch<'_>) -> Option<Tool> { + find_devenv_vs15(target) } - fn find_devenv_vs15(target: &str) -> Option<Tool> { + fn find_devenv_vs15(target: TargetArch<'_>) -> Option<Tool> { find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target) } // see http://stackoverflow.com/questions/328017/path-to-msbuild - pub fn find_msbuild(target: &str) -> Option<Tool> { + pub(super) fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> { // VS 15 (2017) changed how to locate msbuild - if let Some(r) = find_msbuild_vs16(target) { + if let Some(r) = find_msbuild_vs17(target) { + Some(r) + } else if let Some(r) = find_msbuild_vs16(target) { return Some(r); } else if let Some(r) = find_msbuild_vs15(target) { return Some(r); @@ -875,11 +1003,11 @@ mod impl_ { } } - fn find_msbuild_vs15(target: &str) -> Option<Tool> { + fn find_msbuild_vs15(target: TargetArch<'_>) -> Option<Tool> { find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) } - fn find_old_msbuild(target: &str) -> Option<Tool> { + fn find_old_msbuild(target: TargetArch<'_>) -> Option<Tool> { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE .open(key.as_ref()) @@ -891,10 +1019,82 @@ mod impl_ { let mut path = PathBuf::from(path); path.push("MSBuild.exe"); let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target.contains("x86_64") { + if target == "x86_64" { tool.env.push(("Platform".into(), "X64".into())); } tool }) } } + +/// Non-Windows Implementation. +#[cfg(not(windows))] +mod impl_ { + use std::{env, ffi::OsString}; + + use super::{TargetArch, MSVC_FAMILY}; + use crate::Tool; + + /// Finding msbuild.exe tool under unix system is not currently supported. + /// Maybe can check it using an environment variable looks like `MSBUILD_BIN`. + pub(super) fn find_msbuild(_target: TargetArch<'_>) -> Option<Tool> { + None + } + + // Finding devenv.exe tool under unix system is not currently supported. + // Maybe can check it using an environment variable looks like `DEVENV_BIN`. + pub(super) fn find_devenv(_target: TargetArch<'_>) -> Option<Tool> { + None + } + + /// Attempt to find the tool using environment variables set by vcvars. + pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option<Tool> { + // Early return if the environment doesn't contain a VC install. + let vc_install_dir = env::var_os("VCINSTALLDIR")?; + let vs_install_dir = env::var_os("VSINSTALLDIR")?; + + let get_tool = |install_dir: OsString| { + env::split_paths(&install_dir) + .map(|p| p.join(tool)) + .find(|p| p.exists()) + .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) + }; + + // Take the path of tool for the vc install directory. + get_tool(vc_install_dir) + // Take the path of tool for the vs install directory. + .or_else(|| get_tool(vs_install_dir)) + // Take the path of tool for the current path environment. + .or_else(|| env::var_os("PATH").and_then(|path| get_tool(path))) + } + + pub(super) fn find_msvc_15plus(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { + None + } + + // For MSVC 14 we need to find the Universal CRT as well as either + // the Windows 10 SDK or Windows 8.1 SDK. + pub(super) fn find_msvc_14(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { + None + } + + // For MSVC 12 we need to find the Windows 8.1 SDK. + pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { + None + } + + // For MSVC 11 we need to find the Windows 8 SDK. + pub(super) fn find_msvc_11(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> { + None + } + + pub(super) fn has_msbuild_version(version: &str) -> bool { + match version { + "17.0" => false, + "16.0" => false, + "15.0" => false, + "12.0" | "14.0" => false, + _ => false, + } + } +} diff --git a/third_party/rust/cc/src/windows/mod.rs b/third_party/rust/cc/src/windows/mod.rs new file mode 100644 index 0000000000..9b6f297e1a --- /dev/null +++ b/third_party/rust/cc/src/windows/mod.rs @@ -0,0 +1,20 @@ +//! These modules are all glue to support reading the MSVC version from +//! the registry and from COM interfaces. + +// This is used in the crate's public API, so don't use #[cfg(windows)] +pub mod find_tools; + +#[cfg(windows)] +pub(crate) mod windows_sys; + +#[cfg(windows)] +mod registry; +#[cfg(windows)] +#[macro_use] +mod winapi; +#[cfg(windows)] +mod com; +#[cfg(windows)] +mod setup_config; +#[cfg(windows)] +mod vs_instances; diff --git a/third_party/rust/cc/src/registry.rs b/third_party/rust/cc/src/windows/registry.rs index cae32219c7..83983032de 100644 --- a/third_party/rust/cc/src/registry.rs +++ b/third_party/rust/cc/src/windows/registry.rs @@ -8,63 +8,23 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::ffi::{OsStr, OsString}; -use std::io; -use std::ops::RangeFrom; -use std::os::raw; -use std::os::windows::prelude::*; +use crate::windows::windows_sys::{ + RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, RegQueryValueExW, ERROR_NO_MORE_ITEMS, + ERROR_SUCCESS, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, REG_SZ, +}; +use std::{ + ffi::{OsStr, OsString}, + io, + ops::RangeFrom, + os::windows::prelude::*, + ptr::null_mut, +}; /// Must never be `HKEY_PERFORMANCE_DATA`. pub(crate) struct RegistryKey(Repr); -type HKEY = *mut u8; +#[allow(clippy::upper_case_acronyms)] type DWORD = u32; -type LPDWORD = *mut DWORD; -type LPCWSTR = *const u16; -type LPWSTR = *mut u16; -type LONG = raw::c_long; -type PHKEY = *mut HKEY; -type PFILETIME = *mut u8; -type LPBYTE = *mut u8; -type REGSAM = u32; - -const ERROR_SUCCESS: DWORD = 0; -const ERROR_NO_MORE_ITEMS: DWORD = 259; -// Sign-extend into 64 bits if needed. -const HKEY_LOCAL_MACHINE: HKEY = 0x80000002u32 as i32 as isize as HKEY; -const REG_SZ: DWORD = 1; -const KEY_READ: DWORD = 0x20019; -const KEY_WOW64_32KEY: DWORD = 0x200; - -#[link(name = "advapi32")] -extern "system" { - fn RegOpenKeyExW( - key: HKEY, - lpSubKey: LPCWSTR, - ulOptions: DWORD, - samDesired: REGSAM, - phkResult: PHKEY, - ) -> LONG; - fn RegEnumKeyExW( - key: HKEY, - dwIndex: DWORD, - lpName: LPWSTR, - lpcName: LPDWORD, - lpReserved: LPDWORD, - lpClass: LPWSTR, - lpcClass: LPDWORD, - lpftLastWriteTime: PFILETIME, - ) -> LONG; - fn RegQueryValueExW( - hKey: HKEY, - lpValueName: LPCWSTR, - lpReserved: LPDWORD, - lpType: LPDWORD, - lpData: LPBYTE, - lpcbData: LPDWORD, - ) -> LONG; - fn RegCloseKey(hKey: HKEY) -> LONG; -} struct OwnedKey(HKEY); @@ -97,7 +57,7 @@ impl RegistryKey { /// Open a sub-key of `self`. pub fn open(&self, key: &OsStr) -> io::Result<RegistryKey> { let key = key.encode_wide().chain(Some(0)).collect::<Vec<_>>(); - let mut ret = 0 as *mut _; + let mut ret = null_mut(); let err = unsafe { RegOpenKeyExW( self.raw(), @@ -107,7 +67,7 @@ impl RegistryKey { &mut ret, ) }; - if err == ERROR_SUCCESS as LONG { + if err == ERROR_SUCCESS { Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) } else { Err(io::Error::from_raw_os_error(err as i32)) @@ -130,12 +90,12 @@ impl RegistryKey { let err = RegQueryValueExW( self.raw(), name.as_ptr(), - 0 as *mut _, + null_mut(), &mut kind, - 0 as *mut _, + null_mut(), &mut len, ); - if err != ERROR_SUCCESS as LONG { + if err != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(err as i32)); } if kind != REG_SZ { @@ -156,8 +116,8 @@ impl RegistryKey { let err = RegQueryValueExW( self.raw(), name.as_ptr(), - 0 as *mut _, - 0 as *mut _, + null_mut(), + null_mut(), v.as_mut_ptr() as *mut _, &mut len, ); @@ -165,7 +125,7 @@ impl RegistryKey { // grew between the first and second call to `RegQueryValueExW`), // both because it's extremely unlikely, and this is a bit more // defensive more defensive against weird types of registry keys. - if err != ERROR_SUCCESS as LONG { + if err != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(err as i32)); } // The length is allowed to change, but should still be even, as @@ -188,7 +148,7 @@ impl RegistryKey { if !v.is_empty() && v[v.len() - 1] == 0 { v.pop(); } - return Ok(OsString::from_wide(&v)); + Ok(OsString::from_wide(&v)) } } } @@ -213,14 +173,14 @@ impl<'a> Iterator for Iter<'a> { i, v.as_mut_ptr(), &mut len, - 0 as *mut _, - 0 as *mut _, - 0 as *mut _, - 0 as *mut _, + null_mut(), + null_mut(), + null_mut(), + null_mut(), ); - if ret == ERROR_NO_MORE_ITEMS as LONG { + if ret == ERROR_NO_MORE_ITEMS { None - } else if ret != ERROR_SUCCESS as LONG { + } else if ret != ERROR_SUCCESS { Some(Err(io::Error::from_raw_os_error(ret as i32))) } else { v.set_len(len as usize); diff --git a/third_party/rust/cc/src/setup_config.rs b/third_party/rust/cc/src/windows/setup_config.rs index 030051ca69..5739ecf7d6 100644 --- a/third_party/rust/cc/src/setup_config.rs +++ b/third_party/rust/cc/src/windows/setup_config.rs @@ -8,19 +8,19 @@ #![allow(bad_style)] #![allow(unused)] -use crate::winapi::Interface; -use crate::winapi::BSTR; -use crate::winapi::LPCOLESTR; -use crate::winapi::LPSAFEARRAY; -use crate::winapi::S_FALSE; -use crate::winapi::{CoCreateInstance, CLSCTX_ALL}; -use crate::winapi::{IUnknown, IUnknownVtbl}; -use crate::winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG}; -use crate::winapi::{LPFILETIME, ULONG}; -use std::ffi::OsString; -use std::ptr::null_mut; +use crate::windows::{ + com::{BStr, ComPtr}, + winapi::{ + IUnknown, IUnknownVtbl, Interface, LCID, LPCOLESTR, LPCWSTR, LPFILETIME, LPSAFEARRAY, + PULONGLONG, ULONG, + }, + windows_sys::{CoCreateInstance, BSTR, CLSCTX_ALL, HRESULT, S_FALSE}, +}; -use crate::com::{BStr, ComPtr}; +use std::{ + ffi::OsString, + ptr::{null, null_mut}, +}; // Bindings to the Setup.Configuration stuff pub type InstanceState = u32; @@ -212,7 +212,7 @@ impl SetupInstance { SetupInstance(ComPtr::from_raw(obj)) } pub fn instance_id(&self) -> Result<OsString, i32> { - let mut s = null_mut(); + let mut s = null(); let err = unsafe { self.0.GetInstanceId(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; if err < 0 { @@ -221,7 +221,7 @@ impl SetupInstance { Ok(bstr.to_osstring()) } pub fn installation_name(&self) -> Result<OsString, i32> { - let mut s = null_mut(); + let mut s = null(); let err = unsafe { self.0.GetInstallationName(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; if err < 0 { @@ -230,7 +230,7 @@ impl SetupInstance { Ok(bstr.to_osstring()) } pub fn installation_path(&self) -> Result<OsString, i32> { - let mut s = null_mut(); + let mut s = null(); let err = unsafe { self.0.GetInstallationPath(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; if err < 0 { @@ -239,7 +239,7 @@ impl SetupInstance { Ok(bstr.to_osstring()) } pub fn installation_version(&self) -> Result<OsString, i32> { - let mut s = null_mut(); + let mut s = null(); let err = unsafe { self.0.GetInstallationVersion(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; if err < 0 { @@ -248,7 +248,7 @@ impl SetupInstance { Ok(bstr.to_osstring()) } pub fn product_path(&self) -> Result<OsString, i32> { - let mut s = null_mut(); + let mut s = null(); let this = self.0.cast::<ISetupInstance2>()?; let err = unsafe { this.GetProductPath(&mut s) }; let bstr = unsafe { BStr::from_raw(s) }; diff --git a/third_party/rust/cc/src/vs_instances.rs b/third_party/rust/cc/src/windows/vs_instances.rs index 31d3dd1470..e863dadabb 100644 --- a/third_party/rust/cc/src/vs_instances.rs +++ b/third_party/rust/cc/src/windows/vs_instances.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use std::io::BufRead; use std::path::PathBuf; -use crate::setup_config::{EnumSetupInstances, SetupInstance}; +use crate::windows::setup_config::{EnumSetupInstances, SetupInstance}; pub enum VsInstance { Com(SetupInstance), diff --git a/third_party/rust/cc/src/winapi.rs b/third_party/rust/cc/src/windows/winapi.rs index 8e04ce9cbd..09965daa89 100644 --- a/third_party/rust/cc/src/winapi.rs +++ b/third_party/rust/cc/src/windows/winapi.rs @@ -5,26 +5,19 @@ // All files in the project carrying such notice may not be copied, modified, or distributed // except according to those terms. -#![allow(bad_style)] +#![allow(bad_style, clippy::upper_case_acronyms)] use std::os::raw; pub type wchar_t = u16; -pub type UINT = raw::c_uint; -pub type LPUNKNOWN = *mut IUnknown; +pub use crate::windows::windows_sys::{FILETIME, GUID, HRESULT, SAFEARRAY}; + pub type REFIID = *const IID; pub type IID = GUID; -pub type REFCLSID = *const IID; -pub type PVOID = *mut raw::c_void; -pub type USHORT = raw::c_ushort; pub type ULONG = raw::c_ulong; -pub type LONG = raw::c_long; pub type DWORD = u32; -pub type LPVOID = *mut raw::c_void; -pub type HRESULT = raw::c_long; pub type LPFILETIME = *mut FILETIME; -pub type BSTR = *mut OLECHAR; pub type OLECHAR = WCHAR; pub type WCHAR = wchar_t; pub type LPCOLESTR = *const OLECHAR; @@ -33,75 +26,10 @@ pub type LPCWSTR = *const WCHAR; pub type PULONGLONG = *mut ULONGLONG; pub type ULONGLONG = u64; -pub const S_OK: HRESULT = 0; -pub const S_FALSE: HRESULT = 1; -pub const COINIT_MULTITHREADED: u32 = 0x0; - -pub type CLSCTX = u32; - -pub const CLSCTX_INPROC_SERVER: CLSCTX = 0x1; -pub const CLSCTX_INPROC_HANDLER: CLSCTX = 0x2; -pub const CLSCTX_LOCAL_SERVER: CLSCTX = 0x4; -pub const CLSCTX_REMOTE_SERVER: CLSCTX = 0x10; - -pub const CLSCTX_ALL: CLSCTX = - CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct GUID { - pub Data1: raw::c_ulong, - pub Data2: raw::c_ushort, - pub Data3: raw::c_ushort, - pub Data4: [raw::c_uchar; 8], -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct FILETIME { - pub dwLowDateTime: DWORD, - pub dwHighDateTime: DWORD, -} - pub trait Interface { fn uuidof() -> GUID; } -#[link(name = "ole32")] -#[link(name = "oleaut32")] -extern "C" {} - -extern "system" { - pub fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) -> HRESULT; - pub fn CoCreateInstance( - rclsid: REFCLSID, - pUnkOuter: LPUNKNOWN, - dwClsContext: DWORD, - riid: REFIID, - ppv: *mut LPVOID, - ) -> HRESULT; - pub fn SysFreeString(bstrString: BSTR); - pub fn SysStringLen(pbstr: BSTR) -> UINT; -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct SAFEARRAYBOUND { - pub cElements: ULONG, - pub lLbound: LONG, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct SAFEARRAY { - pub cDims: USHORT, - pub fFeatures: USHORT, - pub cbElements: ULONG, - pub cLocks: ULONG, - pub pvData: PVOID, - pub rgsabound: [SAFEARRAYBOUND; 1], -} - pub type LPSAFEARRAY = *mut SAFEARRAY; macro_rules! DEFINE_GUID { @@ -109,11 +37,11 @@ macro_rules! DEFINE_GUID { $name:ident, $l:expr, $w1:expr, $w2:expr, $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr ) => { - pub const $name: $crate::winapi::GUID = $crate::winapi::GUID { - Data1: $l, - Data2: $w1, - Data3: $w2, - Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], + pub const $name: $crate::windows::winapi::GUID = $crate::windows::winapi::GUID { + data1: $l, + data2: $w1, + data3: $w2, + data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], }; }; } @@ -193,14 +121,14 @@ macro_rules! RIDL { $l:expr, $w1:expr, $w2:expr, $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr ) => ( - impl $crate::winapi::Interface for $interface { + impl $crate::windows::winapi::Interface for $interface { #[inline] - fn uuidof() -> $crate::winapi::GUID { - $crate::winapi::GUID { - Data1: $l, - Data2: $w1, - Data3: $w2, - Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], + fn uuidof() -> $crate::windows::winapi::GUID { + $crate::windows::winapi::GUID { + data1: $l, + data2: $w1, + data3: $w2, + data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], } } } diff --git a/third_party/rust/cc/src/windows/windows_sys.rs b/third_party/rust/cc/src/windows/windows_sys.rs new file mode 100644 index 0000000000..8b98ce97f8 --- /dev/null +++ b/third_party/rust/cc/src/windows/windows_sys.rs @@ -0,0 +1,223 @@ +// This file is autogenerated. +// +// To add bindings, edit windows_sys.lst then run: +// +// ``` +// cd generate-windows-sys/ +// cargo run +// ``` +// Bindings generated by `windows-bindgen` 0.53.0 + +#![allow( + non_snake_case, + non_upper_case_globals, + non_camel_case_types, + dead_code, + clippy::all +)] +#[link(name = "advapi32")] +extern "system" { + pub fn RegCloseKey(hkey: HKEY) -> WIN32_ERROR; +} +#[link(name = "advapi32")] +extern "system" { + pub fn RegEnumKeyExW( + hkey: HKEY, + dwindex: u32, + lpname: PWSTR, + lpcchname: *mut u32, + lpreserved: *const u32, + lpclass: PWSTR, + lpcchclass: *mut u32, + lpftlastwritetime: *mut FILETIME, + ) -> WIN32_ERROR; +} +#[link(name = "advapi32")] +extern "system" { + pub fn RegOpenKeyExW( + hkey: HKEY, + lpsubkey: PCWSTR, + uloptions: u32, + samdesired: REG_SAM_FLAGS, + phkresult: *mut HKEY, + ) -> WIN32_ERROR; +} +#[link(name = "advapi32")] +extern "system" { + pub fn RegQueryValueExW( + hkey: HKEY, + lpvaluename: PCWSTR, + lpreserved: *const u32, + lptype: *mut REG_VALUE_TYPE, + lpdata: *mut u8, + lpcbdata: *mut u32, + ) -> WIN32_ERROR; +} +#[link(name = "kernel32")] +extern "system" { + pub fn FreeLibrary(hlibmodule: HMODULE) -> BOOL; +} +#[link(name = "kernel32")] +extern "system" { + pub fn GetMachineTypeAttributes( + machine: u16, + machinetypeattributes: *mut MACHINE_ATTRIBUTES, + ) -> HRESULT; +} +#[link(name = "kernel32")] +extern "system" { + pub fn GetProcAddress(hmodule: HMODULE, lpprocname: PCSTR) -> FARPROC; +} +#[link(name = "kernel32")] +extern "system" { + pub fn LoadLibraryA(lplibfilename: PCSTR) -> HMODULE; +} +#[link(name = "kernel32")] +extern "system" { + pub fn OpenSemaphoreA(dwdesiredaccess: u32, binherithandle: BOOL, lpname: PCSTR) -> HANDLE; +} +#[link(name = "kernel32")] +extern "system" { + pub fn PeekNamedPipe( + hnamedpipe: HANDLE, + lpbuffer: *mut ::core::ffi::c_void, + nbuffersize: u32, + lpbytesread: *mut u32, + lptotalbytesavail: *mut u32, + lpbytesleftthismessage: *mut u32, + ) -> BOOL; +} +#[link(name = "kernel32")] +extern "system" { + pub fn ReleaseSemaphore( + hsemaphore: HANDLE, + lreleasecount: i32, + lppreviouscount: *mut i32, + ) -> BOOL; +} +#[link(name = "kernel32")] +extern "system" { + pub fn WaitForSingleObject(hhandle: HANDLE, dwmilliseconds: u32) -> WAIT_EVENT; +} +#[link(name = "ole32")] +extern "system" { + pub fn CoCreateInstance( + rclsid: *const GUID, + punkouter: *mut ::core::ffi::c_void, + dwclscontext: CLSCTX, + riid: *const GUID, + ppv: *mut *mut ::core::ffi::c_void, + ) -> HRESULT; +} +#[link(name = "ole32")] +extern "system" { + pub fn CoInitializeEx(pvreserved: *const ::core::ffi::c_void, dwcoinit: u32) -> HRESULT; +} +#[link(name = "oleaut32")] +extern "system" { + pub fn SysFreeString(bstrstring: BSTR); +} +#[link(name = "oleaut32")] +extern "system" { + pub fn SysStringLen(pbstr: BSTR) -> u32; +} +pub type ADVANCED_FEATURE_FLAGS = u16; +pub type BOOL = i32; +pub type BSTR = *const u16; +pub type CLSCTX = u32; +pub const CLSCTX_ALL: CLSCTX = 23u32; +pub type COINIT = i32; +pub const COINIT_MULTITHREADED: COINIT = 0i32; +pub const ERROR_NO_MORE_ITEMS: WIN32_ERROR = 259u32; +pub const ERROR_SUCCESS: WIN32_ERROR = 0u32; +pub const FALSE: BOOL = 0i32; +pub type FARPROC = ::core::option::Option<unsafe extern "system" fn() -> isize>; +#[repr(C)] +pub struct FILETIME { + pub dwLowDateTime: u32, + pub dwHighDateTime: u32, +} +impl ::core::marker::Copy for FILETIME {} +impl ::core::clone::Clone for FILETIME { + fn clone(&self) -> Self { + *self + } +} +#[repr(C)] +pub struct GUID { + pub data1: u32, + pub data2: u16, + pub data3: u16, + pub data4: [u8; 8], +} +impl ::core::marker::Copy for GUID {} +impl ::core::clone::Clone for GUID { + fn clone(&self) -> Self { + *self + } +} +impl GUID { + pub const fn from_u128(uuid: u128) -> Self { + Self { + data1: (uuid >> 96) as u32, + data2: (uuid >> 80 & 0xffff) as u16, + data3: (uuid >> 64 & 0xffff) as u16, + data4: (uuid as u64).to_be_bytes(), + } + } +} +pub type HANDLE = *mut ::core::ffi::c_void; +pub type HKEY = *mut ::core::ffi::c_void; +pub const HKEY_LOCAL_MACHINE: HKEY = -2147483646i32 as _; +pub type HMODULE = *mut ::core::ffi::c_void; +pub type HRESULT = i32; +pub type IMAGE_FILE_MACHINE = u16; +pub const IMAGE_FILE_MACHINE_AMD64: IMAGE_FILE_MACHINE = 34404u16; +pub const KEY_READ: REG_SAM_FLAGS = 131097u32; +pub const KEY_WOW64_32KEY: REG_SAM_FLAGS = 512u32; +pub type MACHINE_ATTRIBUTES = i32; +pub type PCSTR = *const u8; +pub type PCWSTR = *const u16; +pub type PWSTR = *mut u16; +pub type REG_SAM_FLAGS = u32; +pub const REG_SZ: REG_VALUE_TYPE = 1u32; +pub type REG_VALUE_TYPE = u32; +#[repr(C)] +pub struct SAFEARRAY { + pub cDims: u16, + pub fFeatures: ADVANCED_FEATURE_FLAGS, + pub cbElements: u32, + pub cLocks: u32, + pub pvData: *mut ::core::ffi::c_void, + pub rgsabound: [SAFEARRAYBOUND; 1], +} +impl ::core::marker::Copy for SAFEARRAY {} +impl ::core::clone::Clone for SAFEARRAY { + fn clone(&self) -> Self { + *self + } +} +#[repr(C)] +pub struct SAFEARRAYBOUND { + pub cElements: u32, + pub lLbound: i32, +} +impl ::core::marker::Copy for SAFEARRAYBOUND {} +impl ::core::clone::Clone for SAFEARRAYBOUND { + fn clone(&self) -> Self { + *self + } +} +pub const SEMAPHORE_MODIFY_STATE: SYNCHRONIZATION_ACCESS_RIGHTS = 2u32; +pub type SYNCHRONIZATION_ACCESS_RIGHTS = u32; +pub const S_FALSE: HRESULT = 0x1_u32 as _; +pub const S_OK: HRESULT = 0x0_u32 as _; +pub type THREAD_ACCESS_RIGHTS = u32; +pub const THREAD_SYNCHRONIZE: THREAD_ACCESS_RIGHTS = 1048576u32; +pub const UserEnabled: MACHINE_ATTRIBUTES = 1i32; +pub const WAIT_ABANDONED: WAIT_EVENT = 128u32; +pub type WAIT_EVENT = u32; +pub const WAIT_FAILED: WAIT_EVENT = 4294967295u32; +pub const WAIT_OBJECT_0: WAIT_EVENT = 0u32; +pub const WAIT_TIMEOUT: WAIT_EVENT = 258u32; +pub type WIN32_ERROR = u32; diff --git a/third_party/rust/cc/tests/cc_env.rs b/third_party/rust/cc/tests/cc_env.rs deleted file mode 100644 index 43eb689f0f..0000000000 --- a/third_party/rust/cc/tests/cc_env.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::env; -use std::ffi::OsString; -use std::path::Path; - -mod support; -use crate::support::Test; - -#[test] -fn main() { - ccache(); - distcc(); - ccache_spaces(); - ccache_env_flags(); - leading_spaces(); - extra_flags(); - path_to_ccache(); - more_spaces(); -} - -fn ccache() { - let test = Test::gnu(); - - env::set_var("CC", "ccache cc"); - let compiler = test.gcc().file("foo.c").get_compiler(); - - assert_eq!(compiler.path(), Path::new("cc")); -} - -fn ccache_spaces() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", "ccache cc"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("cc")); -} - -fn distcc() { - let test = Test::gnu(); - test.shim("distcc"); - - env::set_var("CC", "distcc cc"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("cc")); -} - -fn ccache_env_flags() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", "ccache lol-this-is-not-a-compiler"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("lol-this-is-not-a-compiler")); - assert_eq!( - compiler.cc_env(), - OsString::from("ccache lol-this-is-not-a-compiler") - ); - assert!( - compiler - .cflags_env() - .into_string() - .unwrap() - .contains("ccache") - == false - ); - assert!( - compiler - .cflags_env() - .into_string() - .unwrap() - .contains(" lol-this-is-not-a-compiler") - == false - ); - - env::set_var("CC", ""); -} - -fn leading_spaces() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", " test "); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("test")); - - env::set_var("CC", ""); -} - -fn extra_flags() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", "ccache cc -m32"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("cc")); -} - -fn path_to_ccache() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", "/path/to/ccache.exe cc -m32"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("cc")); - assert_eq!( - compiler.cc_env(), - OsString::from("/path/to/ccache.exe cc -m32"), - ); -} - -fn more_spaces() { - let test = Test::gnu(); - test.shim("ccache"); - - env::set_var("CC", "cc -m32"); - let compiler = test.gcc().file("foo.c").get_compiler(); - assert_eq!(compiler.path(), Path::new("cc")); -} diff --git a/third_party/rust/cc/tests/cflags.rs b/third_party/rust/cc/tests/cflags.rs deleted file mode 100644 index caec6ea4ed..0000000000 --- a/third_party/rust/cc/tests/cflags.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod support; - -use crate::support::Test; -use std::env; - -/// This test is in its own module because it modifies the environment and would affect other tests -/// when run in parallel with them. -#[test] -fn gnu_no_warnings_if_cflags() { - env::set_var("CFLAGS", "-arbitrary"); - let test = Test::gnu(); - test.gcc().file("foo.c").compile("foo"); - - test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra"); -} diff --git a/third_party/rust/cc/tests/cxxflags.rs b/third_party/rust/cc/tests/cxxflags.rs deleted file mode 100644 index c524c7da4e..0000000000 --- a/third_party/rust/cc/tests/cxxflags.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod support; - -use crate::support::Test; -use std::env; - -/// This test is in its own module because it modifies the environment and would affect other tests -/// when run in parallel with them. -#[test] -fn gnu_no_warnings_if_cxxflags() { - env::set_var("CXXFLAGS", "-arbitrary"); - let test = Test::gnu(); - test.gcc().file("foo.cpp").cpp(true).compile("foo"); - - test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra"); -} diff --git a/third_party/rust/cc/tests/support/mod.rs b/third_party/rust/cc/tests/support/mod.rs deleted file mode 100644 index f3c04405a3..0000000000 --- a/third_party/rust/cc/tests/support/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![allow(dead_code)] - -use std::env; -use std::ffi::{OsStr, OsString}; -use std::fs::{self, File}; -use std::io; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; - -use cc; -use tempfile::{Builder, TempDir}; - -pub struct Test { - pub td: TempDir, - pub gcc: PathBuf, - pub msvc: bool, -} - -pub struct Execution { - args: Vec<String>, -} - -impl Test { - pub fn new() -> Test { - // This is ugly: `sccache` needs to introspect the compiler it is - // executing, as it adjusts its behavior depending on the - // language/compiler. This crate's test driver uses mock compilers that - // are obviously not supported by sccache, so the tests fail if - // RUSTC_WRAPPER is set. rust doesn't build test dependencies with - // the `test` feature enabled, so we can't conditionally disable the - // usage of `sccache` if running in a test environment, at least not - // without setting an environment variable here and testing for it - // there. Explicitly deasserting RUSTC_WRAPPER here seems to be the - // lesser of the two evils. - env::remove_var("RUSTC_WRAPPER"); - - let mut gcc = PathBuf::from(env::current_exe().unwrap()); - gcc.pop(); - if gcc.ends_with("deps") { - gcc.pop(); - } - let td = Builder::new().prefix("gcc-test").tempdir_in(&gcc).unwrap(); - gcc.push(format!("gcc-shim{}", env::consts::EXE_SUFFIX)); - Test { - td: td, - gcc: gcc, - msvc: false, - } - } - - pub fn gnu() -> Test { - let t = Test::new(); - t.shim("cc").shim("c++").shim("ar"); - t - } - - pub fn msvc() -> Test { - let mut t = Test::new(); - t.shim("cl").shim("lib.exe"); - t.msvc = true; - t - } - - pub fn shim(&self, name: &str) -> &Test { - let name = if name.ends_with(env::consts::EXE_SUFFIX) { - name.to_string() - } else { - format!("{}{}", name, env::consts::EXE_SUFFIX) - }; - link_or_copy(&self.gcc, self.td.path().join(name)).unwrap(); - self - } - - pub fn gcc(&self) -> cc::Build { - let mut cfg = cc::Build::new(); - let target = if self.msvc { - "x86_64-pc-windows-msvc" - } else { - "x86_64-unknown-linux-gnu" - }; - - cfg.target(target) - .host(target) - .opt_level(2) - .debug(false) - .out_dir(self.td.path()) - .__set_env("PATH", self.path()) - .__set_env("GCCTEST_OUT_DIR", self.td.path()); - if self.msvc { - cfg.compiler(self.td.path().join("cl")); - cfg.archiver(self.td.path().join("lib.exe")); - } - cfg - } - - fn path(&self) -> OsString { - let mut path = env::split_paths(&env::var_os("PATH").unwrap()).collect::<Vec<_>>(); - path.insert(0, self.td.path().to_owned()); - env::join_paths(path).unwrap() - } - - pub fn cmd(&self, i: u32) -> Execution { - let mut s = String::new(); - File::open(self.td.path().join(format!("out{}", i))) - .unwrap() - .read_to_string(&mut s) - .unwrap(); - Execution { - args: s.lines().map(|s| s.to_string()).collect(), - } - } -} - -impl Execution { - pub fn must_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution { - if !self.has(p.as_ref()) { - panic!("didn't find {:?} in {:?}", p.as_ref(), self.args); - } else { - self - } - } - - pub fn must_not_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution { - if self.has(p.as_ref()) { - panic!("found {:?}", p.as_ref()); - } else { - self - } - } - - pub fn has(&self, p: &OsStr) -> bool { - self.args.iter().any(|arg| OsStr::new(arg) == p) - } - - pub fn must_have_in_order(&self, before: &str, after: &str) -> &Execution { - let before_position = self - .args - .iter() - .rposition(|x| OsStr::new(x) == OsStr::new(before)); - let after_position = self - .args - .iter() - .rposition(|x| OsStr::new(x) == OsStr::new(after)); - match (before_position, after_position) { - (Some(b), Some(a)) if b < a => {} - (b, a) => panic!( - "{:?} (last position: {:?}) did not appear before {:?} (last position: {:?})", - before, b, after, a - ), - }; - self - } -} - -/// Hard link an executable or copy it if that fails. -/// -/// We first try to hard link an executable to save space. If that fails (as on Windows with -/// different mount points, issue #60), we copy. -#[cfg(not(target_os = "macos"))] -fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> { - let from = from.as_ref(); - let to = to.as_ref(); - fs::hard_link(from, to).or_else(|_| fs::copy(from, to).map(|_| ())) -} - -/// Copy an executable. -/// -/// On macOS, hard linking the executable leads to strange failures (issue #419), so we just copy. -#[cfg(target_os = "macos")] -fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> { - fs::copy(from, to).map(|_| ()) -} diff --git a/third_party/rust/cc/tests/test.rs b/third_party/rust/cc/tests/test.rs deleted file mode 100644 index 161abd8ab7..0000000000 --- a/third_party/rust/cc/tests/test.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crate::support::Test; - -mod support; - -// Some tests check that a flag is *not* present. These tests might fail if the flag is set in the -// CFLAGS or CXXFLAGS environment variables. This function clears the CFLAGS and CXXFLAGS -// variables to make sure that the tests can run correctly. -fn reset_env() { - std::env::set_var("CFLAGS", ""); - std::env::set_var("CXXFLAGS", ""); -} - -#[test] -fn gnu_smoke() { - reset_env(); - - let test = Test::gnu(); - test.gcc().file("foo.c").compile("foo"); - - test.cmd(0) - .must_have("-O2") - .must_have("foo.c") - .must_not_have("-gdwarf-4") - .must_have("-c") - .must_have("-ffunction-sections") - .must_have("-fdata-sections"); - test.cmd(1).must_have(test.td.path().join("foo.o")); -} - -#[test] -fn gnu_opt_level_1() { - reset_env(); - - let test = Test::gnu(); - test.gcc().opt_level(1).file("foo.c").compile("foo"); - - test.cmd(0).must_have("-O1").must_not_have("-O2"); -} - -#[test] -fn gnu_opt_level_s() { - reset_env(); - - let test = Test::gnu(); - test.gcc().opt_level_str("s").file("foo.c").compile("foo"); - - test.cmd(0) - .must_have("-Os") - .must_not_have("-O1") - .must_not_have("-O2") - .must_not_have("-O3") - .must_not_have("-Oz"); -} - -#[test] -fn gnu_debug() { - let test = Test::gnu(); - test.gcc().debug(true).file("foo.c").compile("foo"); - test.cmd(0).must_have("-gdwarf-4"); - - let test = Test::gnu(); - test.gcc() - .target("x86_64-apple-darwin") - .debug(true) - .file("foo.c") - .compile("foo"); - test.cmd(0).must_have("-gdwarf-2"); -} - -#[test] -fn gnu_debug_fp_auto() { - let test = Test::gnu(); - test.gcc().debug(true).file("foo.c").compile("foo"); - test.cmd(0).must_have("-gdwarf-4"); - test.cmd(0).must_have("-fno-omit-frame-pointer"); -} - -#[test] -fn gnu_debug_fp() { - let test = Test::gnu(); - test.gcc().debug(true).file("foo.c").compile("foo"); - test.cmd(0).must_have("-gdwarf-4"); - test.cmd(0).must_have("-fno-omit-frame-pointer"); -} - -#[test] -fn gnu_debug_nofp() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .debug(true) - .force_frame_pointer(false) - .file("foo.c") - .compile("foo"); - test.cmd(0).must_have("-gdwarf-4"); - test.cmd(0).must_not_have("-fno-omit-frame-pointer"); - - let test = Test::gnu(); - test.gcc() - .force_frame_pointer(false) - .debug(true) - .file("foo.c") - .compile("foo"); - test.cmd(0).must_have("-gdwarf-4"); - test.cmd(0).must_not_have("-fno-omit-frame-pointer"); -} - -#[test] -fn gnu_warnings_into_errors() { - let test = Test::gnu(); - test.gcc() - .warnings_into_errors(true) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-Werror"); -} - -#[test] -fn gnu_warnings() { - let test = Test::gnu(); - test.gcc() - .warnings(true) - .flag("-Wno-missing-field-initializers") - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-Wall").must_have("-Wextra"); -} - -#[test] -fn gnu_extra_warnings0() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .warnings(true) - .extra_warnings(false) - .flag("-Wno-missing-field-initializers") - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-Wall").must_not_have("-Wextra"); -} - -#[test] -fn gnu_extra_warnings1() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .warnings(false) - .extra_warnings(true) - .flag("-Wno-missing-field-initializers") - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_not_have("-Wall").must_have("-Wextra"); -} - -#[test] -fn gnu_warnings_overridable() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .warnings(true) - .flag("-Wno-missing-field-initializers") - .file("foo.c") - .compile("foo"); - - test.cmd(0) - .must_have_in_order("-Wall", "-Wno-missing-field-initializers"); -} - -#[test] -fn gnu_x86_64() { - for vendor in &["unknown-linux-gnu", "apple-darwin"] { - let target = format!("x86_64-{}", vendor); - let test = Test::gnu(); - test.gcc() - .target(&target) - .host(&target) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-fPIC").must_have("-m64"); - } -} - -#[test] -fn gnu_x86_64_no_pic() { - reset_env(); - - for vendor in &["unknown-linux-gnu", "apple-darwin"] { - let target = format!("x86_64-{}", vendor); - let test = Test::gnu(); - test.gcc() - .pic(false) - .target(&target) - .host(&target) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_not_have("-fPIC"); - } -} - -#[test] -fn gnu_i686() { - for vendor in &["unknown-linux-gnu", "apple-darwin"] { - let target = format!("i686-{}", vendor); - let test = Test::gnu(); - test.gcc() - .target(&target) - .host(&target) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-m32"); - } -} - -#[test] -fn gnu_i686_pic() { - for vendor in &["unknown-linux-gnu", "apple-darwin"] { - let target = format!("i686-{}", vendor); - let test = Test::gnu(); - test.gcc() - .pic(true) - .target(&target) - .host(&target) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-fPIC"); - } -} - -#[test] -fn gnu_x86_64_no_plt() { - let target = "x86_64-unknown-linux-gnu"; - let test = Test::gnu(); - test.gcc() - .pic(true) - .use_plt(false) - .target(&target) - .host(&target) - .file("foo.c") - .compile("foo"); - test.cmd(0).must_have("-fno-plt"); -} - -#[test] -fn gnu_set_stdlib() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .cpp_set_stdlib(Some("foo")) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_not_have("-stdlib=foo"); -} - -#[test] -fn gnu_include() { - let test = Test::gnu(); - test.gcc().include("foo/bar").file("foo.c").compile("foo"); - - test.cmd(0).must_have("-I").must_have("foo/bar"); -} - -#[test] -fn gnu_define() { - let test = Test::gnu(); - test.gcc() - .define("FOO", "bar") - .define("BAR", None) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-DFOO=bar").must_have("-DBAR"); -} - -#[test] -fn gnu_compile_assembly() { - let test = Test::gnu(); - test.gcc().file("foo.S").compile("foo"); - test.cmd(0).must_have("foo.S"); -} - -#[test] -fn gnu_shared() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .file("foo.c") - .shared_flag(true) - .static_flag(false) - .compile("foo"); - - test.cmd(0).must_have("-shared").must_not_have("-static"); -} - -#[test] -fn gnu_flag_if_supported() { - reset_env(); - - if cfg!(windows) { - return; - } - let test = Test::gnu(); - test.gcc() - .file("foo.c") - .flag("-v") - .flag_if_supported("-Wall") - .flag_if_supported("-Wflag-does-not-exist") - .flag_if_supported("-std=c++11") - .compile("foo"); - - test.cmd(0) - .must_have("-v") - .must_have("-Wall") - .must_not_have("-Wflag-does-not-exist") - .must_not_have("-std=c++11"); -} - -#[test] -fn gnu_flag_if_supported_cpp() { - if cfg!(windows) { - return; - } - let test = Test::gnu(); - test.gcc() - .cpp(true) - .file("foo.cpp") - .flag_if_supported("-std=c++11") - .compile("foo"); - - test.cmd(0).must_have("-std=c++11"); -} - -#[test] -fn gnu_static() { - reset_env(); - - let test = Test::gnu(); - test.gcc() - .file("foo.c") - .shared_flag(false) - .static_flag(true) - .compile("foo"); - - test.cmd(0).must_have("-static").must_not_have("-shared"); -} - -#[test] -fn gnu_no_dash_dash() { - let test = Test::gnu(); - test.gcc().file("foo.c").compile("foo"); - - test.cmd(0).must_not_have("--"); -} - -#[test] -fn msvc_smoke() { - reset_env(); - - let test = Test::msvc(); - test.gcc().file("foo.c").compile("foo"); - - test.cmd(0) - .must_have("-O2") - .must_have("foo.c") - .must_not_have("-Z7") - .must_have("-c") - .must_have("-MD"); - test.cmd(1).must_have(test.td.path().join("foo.o")); -} - -#[test] -fn msvc_opt_level_0() { - reset_env(); - - let test = Test::msvc(); - test.gcc().opt_level(0).file("foo.c").compile("foo"); - - test.cmd(0).must_not_have("-O2"); -} - -#[test] -fn msvc_debug() { - let test = Test::msvc(); - test.gcc().debug(true).file("foo.c").compile("foo"); - test.cmd(0).must_have("-Z7"); -} - -#[test] -fn msvc_include() { - let test = Test::msvc(); - test.gcc().include("foo/bar").file("foo.c").compile("foo"); - - test.cmd(0).must_have("-I").must_have("foo/bar"); -} - -#[test] -fn msvc_define() { - let test = Test::msvc(); - test.gcc() - .define("FOO", "bar") - .define("BAR", None) - .file("foo.c") - .compile("foo"); - - test.cmd(0).must_have("-DFOO=bar").must_have("-DBAR"); -} - -#[test] -fn msvc_static_crt() { - let test = Test::msvc(); - test.gcc().static_crt(true).file("foo.c").compile("foo"); - - test.cmd(0).must_have("-MT"); -} - -#[test] -fn msvc_no_static_crt() { - let test = Test::msvc(); - test.gcc().static_crt(false).file("foo.c").compile("foo"); - - test.cmd(0).must_have("-MD"); -} - -#[test] -fn msvc_no_dash_dash() { - let test = Test::msvc(); - test.gcc().file("foo.c").compile("foo"); - - test.cmd(0).must_not_have("--"); -} - -// Disable this test with the parallel feature because the execution -// order is not deterministic. -#[cfg(not(feature = "parallel"))] -#[test] -fn asm_flags() { - let test = Test::gnu(); - test.gcc() - .file("foo.c") - .file("x86_64.asm") - .file("x86_64.S") - .asm_flag("--abc") - .compile("foo"); - test.cmd(0).must_not_have("--abc"); - test.cmd(1).must_have("--abc"); - test.cmd(2).must_have("--abc"); -} |