From 2f7038b9d915527ef2301c8b4a5b161c0ae0f783 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 27 Feb 2024 21:11:25 +0000 Subject: [PATCH] Bug 1882202 - Upgrade the cc crate to 1.0.88. r=emilio,supply-chain-reviewers Differential Revision: https://phabricator.services.mozilla.com/D202771 --- Cargo.lock | 6 +- supply-chain/audits.toml | 6 + supply-chain/imports.lock | 7 + third_party/rust/cc/.cargo-checksum.json | 2 +- third_party/rust/cc/Cargo.lock | 110 - third_party/rust/cc/Cargo.toml | 20 +- third_party/rust/cc/README.md | 210 +- third_party/rust/cc/src/bin/gcc-shim.rs | 48 - third_party/rust/cc/src/command_helpers.rs | 433 +++ third_party/rust/cc/src/lib.rs | 2576 ++++++++++------- .../rust/cc/src/parallel/async_executor.rs | 118 + .../rust/cc/src/parallel/job_token/mod.rs | 257 ++ .../rust/cc/src/parallel/job_token/unix.rs | 176 ++ .../rust/cc/src/parallel/job_token/windows.rs | 68 + third_party/rust/cc/src/parallel/mod.rs | 20 + third_party/rust/cc/src/parallel/stderr.rs | 100 + third_party/rust/cc/src/tool.rs | 400 +++ third_party/rust/cc/src/{ => windows}/com.rs | 42 +- .../find_tools.rs} | 351 ++- third_party/rust/cc/src/windows/mod.rs | 20 + .../rust/cc/src/{ => windows}/registry.rs | 94 +- .../rust/cc/src/{ => windows}/setup_config.rs | 34 +- .../rust/cc/src/{ => windows}/vs_instances.rs | 2 +- .../rust/cc/src/{ => windows}/winapi.rs | 102 +- .../rust/cc/src/windows/windows_sys.rs | 223 ++ third_party/rust/cc/tests/cc_env.rs | 118 - third_party/rust/cc/tests/cflags.rs | 15 - third_party/rust/cc/tests/cxxflags.rs | 15 - third_party/rust/cc/tests/support/mod.rs | 172 -- third_party/rust/cc/tests/test.rs | 461 --- 30 files changed, 3659 insertions(+), 2547 deletions(-) delete mode 100644 third_party/rust/cc/Cargo.lock delete mode 100644 third_party/rust/cc/src/bin/gcc-shim.rs create mode 100644 third_party/rust/cc/src/command_helpers.rs create mode 100644 third_party/rust/cc/src/parallel/async_executor.rs create mode 100644 third_party/rust/cc/src/parallel/job_token/mod.rs create mode 100644 third_party/rust/cc/src/parallel/job_token/unix.rs create mode 100644 third_party/rust/cc/src/parallel/job_token/windows.rs create mode 100644 third_party/rust/cc/src/parallel/mod.rs create mode 100644 third_party/rust/cc/src/parallel/stderr.rs create mode 100644 third_party/rust/cc/src/tool.rs rename third_party/rust/cc/src/{ => windows}/com.rs (83%) rename third_party/rust/cc/src/{windows_registry.rs => windows/find_tools.rs} (72%) create mode 100644 third_party/rust/cc/src/windows/mod.rs rename third_party/rust/cc/src/{ => windows}/registry.rs (74%) rename third_party/rust/cc/src/{ => windows}/setup_config.rs (93%) rename third_party/rust/cc/src/{ => windows}/vs_instances.rs (98%) rename third_party/rust/cc/src/{ => windows}/winapi.rs (62%) create mode 100644 third_party/rust/cc/src/windows/windows_sys.rs delete mode 100644 third_party/rust/cc/tests/cc_env.rs delete mode 100644 third_party/rust/cc/tests/cflags.rs delete mode 100644 third_party/rust/cc/tests/cxxflags.rs delete mode 100644 third_party/rust/cc/tests/support/mod.rs delete mode 100644 third_party/rust/cc/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index a0e90dfe62dd..9bc8b8b5e260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,11 +646,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" dependencies = [ - "jobserver", + "libc", ] [[package]] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 2cb2ab1bdccb..38b9834c46ff 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -4754,6 +4754,12 @@ user-id = 6741 # Alice Ryhl (Darksonn) start = "2021-01-11" end = "2024-05-05" +[[trusted.cc]] +criteria = "safe-to-deploy" +user-id = 2915 # Amanieu d'Antras (Amanieu) +start = "2024-02-20" +end = "2025-02-26" + [[trusted.clap]] criteria = "safe-to-deploy" user-id = 6743 # Ed Page (epage) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index a326a8e2d3f7..2816295a6490 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -71,6 +71,13 @@ user-id = 6741 user-login = "Darksonn" user-name = "Alice Ryhl" +[[publisher.cc]] +version = "1.0.88" +when = "2024-02-25" +user-id = 2915 +user-login = "Amanieu" +user-name = "Amanieu d'Antras" + [[publisher.cexpr]] version = "0.6.0" when = "2021-10-11" diff --git a/third_party/rust/cc/.cargo-checksum.json b/third_party/rust/cc/.cargo-checksum.json index 4dc2fe2390fc..257e071e0710 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":"20e23a82fa9c03b73bee3a466a08b7388d75a7329dec60cb053f1687f5d17240","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"f1ddbede208a5b78333a25dac0a7598e678e9b601a7d99a791069bddaf180dfe","src/command_helpers.rs":"3ef95bdcd79a43406fdab275d8a8f45ba787876399b54df34068955ec0109e69","src/lib.rs":"23c2575733eb162a0540ebb28846f4167de700fcc6338cc242af0f7531c8ce96","src/parallel/async_executor.rs":"4ce24435fff6b6555b43fee042c16bd65d4150d0346567f246b9190d85b45983","src/parallel/job_token/mod.rs":"98bc764cf5dcef7786114ce4ee2497ffccf4b4c3a4d761a5d7e50ac4cd79f21f","src/parallel/job_token/unix.rs":"c8feb8075d27719af8a124dad065b1a22d6d657961799e9a13e053ee866814d1","src/parallel/job_token/windows.rs":"88fda34547a4bbe0076b02b1d6f13bfeb75e0e251d6c7d07911b80ed4bfeaafa","src/parallel/mod.rs":"aaffed5ad3dc0d28641533ab0d6f522bf34a059d4b1a239dc4d217cb5d58e232","src/parallel/stderr.rs":"7544f41e1ee8978311470e77892cf6670c293859b52fd53ea197e8baff432638","src/tool.rs":"bc5a63b9d553408b46a7bc1958059034e38b6ccd76d599295432ae32b8d866f7","src/windows/com.rs":"be1564756c9f3ef1398eafeed7b54ba610caba28e8f6258d28a997737ebf9535","src/windows/find_tools.rs":"61dffce9e0529ec64a023f354158c0b39cbca38ec60ae3467cd64a7965ce4777","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":"02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"} \ 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 2d065bc6a879..000000000000 --- 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 c4ec0bf79d71..8fb79eed9507 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.88" authors = ["Alex Crichton "] -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 @@ -28,12 +33,13 @@ categories = ["development-tools::build-utils"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/cc-rs" -[dependencies.jobserver] -version = "0.1.16" -optional = true - [dev-dependencies.tempfile] version = "3" [features] -parallel = ["jobserver"] +parallel = ["libc"] + +[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 863540d2d941..33d4bb40f8e8 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. +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. -[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. `_` - for example, `CC_x86_64-unknown-linux-gnu` -2. `_` - for example, `CC_x86_64_unknown_linux_gnu` -3. `_` - for example, `HOST_CC` or `TARGET_CFLAGS` -4. `` - 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"); -} -``` +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 1731df82ea5c..000000000000 --- 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 000000000000..919d276c84ec --- /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, +} + +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)>, + #[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], dst: &Path) -> Result, 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, 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 { + 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, 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 1ebd2cc7a577..f2b34748e997 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: //! -//! This crate will automatically detect situations such as cross compilation or -//! other environment variables set by Cargo and will build code appropriately. +//! ```rust,no_run +//! // build.rs //! -//! 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. +//! fn main() { +//! cc::Build::new() +//! .file("foo.c") +//! .file("bar.c") +//! .compile("foo"); +//! } +//! ``` //! -//! [`Build`]: struct.Build.html +//! 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 //! -//! # Parallelism +//! ```c +//! void foo_function(void) { ... } +//! ``` //! -//! To parallelize computation, enable the `parallel` feature for the crate. +//! 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; +//! } +//! +//! pub fn call() { +//! unsafe { +//! foo_function(); +//! bar_function(42); +//! } +//! } +//! +//! fn main() { +//! call(); +//! } +//! ``` +//! +//! 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. +//! * `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. `_` - for example, `CC_x86_64-unknown-linux-gnu` +//! 2. `_` - for example, `CC_x86_64_unknown_linux_gnu` +//! 3. `_` - for example, `HOST_CC` or `TARGET_CFLAGS` +//! 4. `` - 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. //! -//! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can -//! also specify the build parallelism. +//! # Compile-time Requirements //! -//! # Examples +//! 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: //! -//! Use the `Build` struct to compile `src/foo.c`: +//! * 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. //! -//! ```no_run +//! [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() -//! .file("src/foo.c") -//! .define("FOO", Some("bar")) -//! .include("src") +//! .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() +//! .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; +#[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; -pub mod windows_registry; +mod command_helpers; +use command_helpers::*; + +mod tool; +pub use tool::Tool; +use tool::ToolFamily; /// A builder for compilation of a native library. /// @@ -91,32 +254,34 @@ pub mod windows_registry; /// documentation on each method itself. #[derive(Clone, Debug)] pub struct Build { - include_directories: Vec, - definitions: Vec<(String, Option)>, - objects: Vec, - flags: Vec, - flags_supported: Vec, + include_directories: Vec>, + definitions: Vec<(Arc, Option>)>, + objects: Vec>, + flags: Vec>, + flags_supported: Vec>, known_flag_support_status: Arc>>, - ar_flags: Vec, - asm_flags: Vec, + ar_flags: Vec>, + asm_flags: Vec>, no_default_flags: bool, - files: Vec, + files: Vec>, cpp: bool, - cpp_link_stdlib: Option>, - cpp_set_stdlib: Option, + cpp_link_stdlib: Option>>, + cpp_set_stdlib: Option>, cuda: bool, - cudart: Option, - target: Option, - host: Option, - out_dir: Option, - opt_level: Option, + cudart: Option>, + std: Option>, + target: Option>, + host: Option>, + out_dir: Option>, + opt_level: Option>, debug: Option, force_frame_pointer: Option, - env: Vec<(OsString, OsString)>, - compiler: Option, - archiver: Option, - cargo_metadata: bool, - link_lib_modifiers: Vec, + env: Vec<(Arc, Arc)>, + compiler: Option>, + archiver: Option>, + ranlib: Option>, + cargo_output: CargoOutput, + link_lib_modifiers: Vec>, pic: Option, use_plt: Option, static_crt: Option, @@ -125,9 +290,11 @@ pub struct Build { warnings_into_errors: bool, warnings: Option, extra_warnings: Option, - env_cache: Arc>>>, + env_cache: Arc>>>>, apple_sdk_root_cache: Arc>>, + apple_versions_cache: Arc>>, emit_rerun_if_env_changed: bool, + cached_compiler_family: Arc, ToolFamily>>>, } /// Represents the types of errors that may occur while using cc-rs. @@ -153,21 +320,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>) -> Error { Error { - kind: kind, - message: message.to_owned(), + kind, + message: message.into(), } } } impl From for Error { fn from(e: io::Error) -> Error { - Error::new(ErrorKind::IOError, &format!("{}", e)) + Error::new(ErrorKind::IOError, format!("{}", e)) } } @@ -179,97 +346,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, - cc_wrapper_args: Vec, - args: Vec, - env: Vec<(OsString, OsString)>, - family: ToolFamily, - cuda: bool, - removed_args: Vec, -} - -/// 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) { - 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 +358,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 +387,7 @@ impl Build { cpp_set_stdlib: None, cuda: false, cudart: None, + std: None, target: None, host: None, out_dir: None, @@ -320,7 +397,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 +408,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 +430,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn include>(&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 +476,13 @@ impl Build { /// ``` pub fn define<'a, V: Into>>(&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>(&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 +497,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 +531,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 +550,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 +597,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 +614,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 +659,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 +742,7 @@ impl Build { /// Add a file which will be compiled pub fn file>(&mut self, p: P) -> &mut Build { - self.files.push(p.as_ref().to_path_buf()); + self.files.push(p.as_ref().into()); self } @@ -630,6 +762,12 @@ impl Build { /// /// 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 +775,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 + /// 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 +800,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 +907,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 +995,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 +1013,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 +1022,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 +1031,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 +1062,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>(&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 +1072,7 @@ impl Build { /// number of environment variables, so it's not required to call this /// function. pub fn compiler>(&mut self, compiler: P) -> &mut Build { - self.compiler = Some(compiler.as_ref().to_owned()); + self.compiler = Some(compiler.as_ref().into()); self } @@ -913,9 +1082,20 @@ impl Build { /// number of environment variables, so it's not required to call this /// function. pub fn archiver>(&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>(&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 +1108,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 /// 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 +1191,13 @@ impl Build { A: AsRef, B: AsRef, { - 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 +1215,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 +1231,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 +1240,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 +1248,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 +1304,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 +1316,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 +1367,48 @@ impl Build { } } - #[cfg(feature = "parallel")] - fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> { - use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; - use std::sync::Once; + /// 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 { + match self.try_compile_intermediates() { + Ok(v) => v, + Err(e) => fail(&e.message), + } + } - // 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(); + /// 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, 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(&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 +1422,160 @@ 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) }; - 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); - } - drop(token); // make sure our jobserver token is released after the compile - return result; - }); - threads.push(JoinOnDrop(Some(thread))); - } + // we wait on all the processes and propagate the results of compilation. - for mut thread in threads { - if let Some(thread) = thread.0.take() { - thread.join().expect("thread should not panic")?; - } - } + 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); - // Reacquire our process's token before we proceed, which we released - // before entering the loop above. - if reacquire { - server.acquire_raw()?; - } + let wait_future = async { + let mut error = None; + // Buffer the stdout + let mut stdout = io::BufWriter::with_capacity(128, io::stdout()); - return Ok(()); + 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. - /// 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, - } + let mut pendings_is_empty = false; - /// 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 = None; + 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); - fn _assert_sync() {} - _assert_sync::(); + if self.cargo_output.warnings { + let _ = writeln!(stdout, "cargo:warning={}", err); + } + error = Some(err); - unsafe { - INIT.call_once(|| { - let server = default_jobserver(); - JOBSERVER = Some(server); + false + } + } + }, + ); + pendings_is_empty = pendings.is_empty(); + 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; - } - - // ... 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; + if pendings_is_empty && is_disconnected.get() { + break if let Some(err) = error { + Err(err) + } else { + Ok(()) + }; } + + YieldOnce::default().await; } + }; + let spawn_future = async { + for obj in objs { + let (mut cmd, program) = self.create_compile_object_cmd(obj)?; + let token = loop { + if let Some(token) = tokens.try_acquire()? { + break token; + } else { + YieldOnce::default().await + } + }; + let mut child = spawn(&mut cmd, &program, &self.cargo_output)?; + let mut stderr_forwarder = StderrForwarder::new(&mut child); + stderr_forwarder.set_non_blocking()?; - // 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; - } + cell_update(&pendings, |mut pendings| { + pendings.push((cmd, program, KillOnDrop(child, stderr_forwarder), token)); + pendings + }); - struct JoinOnDrop(Option>>); + has_made_progress.set(true); + } + is_disconnected.set(true); - impl Drop for JoinOnDrop { + Ok::<_, Error>(()) + }; + + return block_on(wait_future, spawn_future, &has_made_progress); + + struct KillOnDrop(Child, StderrForwarder); + + 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(cell: &Cell, 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 +1589,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 +1614,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, 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 +1631,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 +1655,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 +1704,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 { 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 +1721,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 +1745,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 +1798,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 +1826,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 +1842,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,12 +1879,20 @@ 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 match cmd.family { ToolFamily::Clang => { - if !(target.contains("android") - && android_clang_compiler_uses_target_arg_internally(&cmd.path)) - { + if !(target.contains("android") && cmd.has_internal_target_arg) { if target.contains("darwin") { if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) @@ -1669,8 +1911,10 @@ impl Build { 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()); + let sdk_details = + apple_os_sdk_parts(AppleOs::Ios, &AppleArchSpec::Simulator("")); + let deployment_target = + self.apple_deployment_version(AppleOs::Ios, None, &sdk_details.sdk); cmd.args.push( format!( "--target={}-apple-ios{}-simulator", @@ -1683,8 +1927,13 @@ impl Build { 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()); + let sdk_details = + apple_os_sdk_parts(AppleOs::WatchOs, &AppleArchSpec::Simulator("")); + let deployment_target = self.apple_deployment_version( + AppleOs::WatchOs, + None, + &sdk_details.sdk, + ); cmd.args.push( format!( "--target={}-apple-watchos{}-simulator", @@ -1693,6 +1942,40 @@ impl Build { .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) + { + let sdk_details = + apple_os_sdk_parts(AppleOs::TvOs, &AppleArchSpec::Simulator("")); + let deployment_target = self.apple_deployment_version( + AppleOs::TvOs, + None, + &sdk_details.sdk, + ); + cmd.args.push( + format!( + "--target={}-apple-tvos{}-simulator", + arch, deployment_target + ) + .into(), + ); + } + } else if target.contains("aarch64-apple-tvos") { + if let Some(arch) = + map_darwin_target_from_rust_to_compiler_architecture(target) + { + let sdk_details = + apple_os_sdk_parts(AppleOs::TvOs, &AppleArchSpec::Device("")); + let deployment_target = self.apple_deployment_version( + AppleOs::TvOs, + None, + &sdk_details.sdk, + ); + cmd.args.push( + format!("--target={}-apple-tvos{}", arch, deployment_target).into(), + ); + } } else if target.starts_with("riscv64gc-") { cmd.args.push( format!("--target={}", target.replace("riscv64gc", "riscv64")).into(), @@ -1709,6 +1992,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 +2039,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,14 +2059,6 @@ 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) { @@ -1771,9 +2072,8 @@ impl Build { } 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,33 +2227,36 @@ 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 target.contains("-apple-") { + self.apple_flags(cmd)?; } if self.static_flag.unwrap_or(false) { @@ -1970,11 +2273,7 @@ impl Build { 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 - ); + self.cargo_output.print_warning(&format_args!("cpp_set_stdlib is specified, but the {:?} compiler does not support this option, ignored", cmd.family)); } } } @@ -1984,7 +2283,7 @@ impl Build { 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 +2305,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::() { + 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 +2343,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 +2350,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 +2371,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 +2384,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 +2400,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 +2432,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 +2459,36 @@ 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"), - } - } - } - + fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> { let target = self.get_target()?; - let os = if target.contains("-watchos") { - Os::WatchOs + 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 +2497,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 +2525,136 @@ 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) => { + match arch { + AppleArchSpec::Device(_) if is_mac => { + cmd.args + .push(format!("-mmacosx-version-min={}", min_version).into()); + } + AppleArchSpec::Device(arch) => { cmd.args.push("-arch".into()); cmd.args.push(arch.into()); - cmd.args - .push(format!("-m{}os-version-min={}", sdk_prefix, min_version).into()); - format!("{}os", sdk_prefix) + cmd.args.push( + format!("-m{}os-version-min={}", sdk_details.sdk_prefix, min_version).into(), + ); } - ArchSpec::Simulator(arch) => { - cmd.args.push(arch.into()); - cmd.args - .push(format!("-m{}simulator-version-min={}", sim_prefix, min_version).into()); - format!("{}simulator", sdk_prefix) + 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(), + ); } - ArchSpec::Catalyst(_) => "macosx".to_owned(), + 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); + } + + 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>(&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 { - 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 +2671,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 = self .env_tool(env) @@ -2340,7 +2686,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 +2705,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 +2736,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 +2760,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 +2773,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 +2790,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 +2825,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::() { if version > 15 && version < 25 { tool.args.push("-mstackrealign".into()); @@ -2489,41 +2860,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 { - 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 { - 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 +2893,8 @@ impl Build { } /// Returns compiler path, optional modifier name from whitelist, and arguments vec - fn env_tool(&self, name: &str) -> Option<(String, Option, Vec)> { - let tool = match self.get_var(name) { + fn env_tool(&self, name: &str) -> Option<(PathBuf, Option, Vec)> { + let tool = match self.getenv_with_target_prefixes(name) { Ok(tool) => tool, Err(_) => return None, }; @@ -2554,8 +2902,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 +2946,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 +2954,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, 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 +2996,239 @@ 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())); - } - if let Ok(p) = self.get_var("AR") { - return Ok((self.cmd(&p), p)); - } - 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())); - } + fn get_ar(&self) -> Result<(Command, String, bool), Error> { + self.try_get_archiver_and_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(); + /// 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), + } + } + + /// 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 { + 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); + } + for flag in &self.ar_flags { + any_flags = true; + cmd.arg(&**flag); + } + Ok((cmd, name, any_flags)) + } + + 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 { + 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 { + 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 = 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!("{}-{}", 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(); + } } } - ar + + 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) + } + } 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 { - // 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 +3265,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 +3286,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 +3297,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 +3317,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 +3329,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 +3346,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 +3383,30 @@ impl Build { prefixes.first().map(|prefix| *prefix)) } - fn get_target(&self) -> Result { - match self.target.clone() { - Some(t) => Ok(t), - None => Ok(self.getenv_unwrap("TARGET")?), + fn get_target(&self) -> Result, Error> { + match &self.target { + Some(t) => Ok(t.clone()), + None => self.getenv_unwrap("TARGET"), } } - fn get_host(&self) -> Result { - match self.host.clone() { - Some(h) => Ok(h), - None => Ok(self.getenv_unwrap("HOST")?), + fn get_host(&self) -> Result, Error> { + match &self.host { + Some(h) => Ok(h.clone()), + None => self.getenv_unwrap("HOST"), } } - fn get_opt_level(&self) -> Result { - match self.opt_level.as_ref().cloned() { - Some(ol) => Ok(ol), - None => Ok(self.getenv_unwrap("OPT_LEVEL")?), + fn get_opt_level(&self) -> Result, 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 +3434,22 @@ impl Build { self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) } - fn get_out_dir(&self) -> Result { - 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, 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 { + fn getenv(&self, v: &str) -> Option> { // 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 +3469,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 { + fn getenv_unwrap(&self, v: &str) -> Result, 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, 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, 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 +3530,10 @@ impl Build { } fn apple_sdk_root(&self, sdk: &str) -> Result { + if let Some(sdkroot) = env::var_os("SDKROOT") { + return Ok(sdkroot); + } + let mut cache = self .apple_sdk_root_cache .lock() @@ -3034,6 +3548,7 @@ impl Build { .arg("--sdk") .arg(sdk), "xcrun", + &self.cargo_output, )?; let sdk_path = match String::from_utf8(sdk_path) { @@ -3050,6 +3565,129 @@ impl Build { Ok(ret) } + 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"); + + if let Some(ret) = cache.get(sdk) { + return Some(ret.clone()); + } + + let version = run_output( + self.cmd("xcrun") + .arg("--show-sdk-platform-version") + .arg("--sdk") + .arg(sdk), + "xcrun", + &self.cargo_output, + ) + .ok()?; + + let version = std::str::from_utf8(&version).ok()?.trim().to_owned(); + + cache.insert(sdk.into(), version.clone()); + Some(version) + }; + + 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 { + env::var(name).ok() + } + }; + + // 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 { + if !self.cpp { + return Some(deployment_target_ver); + } + + let mut deployment_target = deployment_target_ver + .split('.') + .map(|v| v.parse::().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. + _ => {} + } + + // If the deployment target met or exceeded the C++ baseline + Some(deployment_target_ver) + }; + + // 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()) + } + }), + + 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()), + + AppleOs::WatchOs => deployment_from_env("WATCHOS_DEPLOYMENT_TARGET") + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| "5.0".into()), + + AppleOs::TvOs => deployment_from_env("TVOS_DEPLOYMENT_TARGET") + .or_else(default_deployment_from_sdk) + .unwrap_or_else(|| "9.0".into()), + } + } + fn cuda_file_count(&self) -> usize { self.files .iter() @@ -3064,350 +3702,64 @@ impl Default for Build { } } -impl Tool { - fn new(path: PathBuf) -> Self { - Tool::with_features(path, None, false) - } - - fn with_clang_driver(path: PathBuf, clang_driver: Option<&str>) -> Self { - Self::with_features(path, clang_driver, false) - } - - #[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(), - } - } - - 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, - } - } else { - ToolFamily::Gnu - } - } 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; - } - } - - // 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 - } - None => Command::new(&self.path), - }; - cmd.args(&self.cc_wrapper_args); - - let value = self - .args - .iter() - .filter(|a| !self.removed_args.contains(a)) - .collect::>(); - cmd.args(&value); - - for &(ref k, ref 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 MSVC-like. - pub fn is_like_msvc(&self) -> bool { - match self.family { - ToolFamily::Msvc { .. } => true, - _ => false, - } - } -} - -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 - ), - )) - } -} - -fn run_output(cmd: &mut Command, program: &str) -> Result, 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 - ), - )); - } - }; - 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 - ), - )), - } -} - 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); +#[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"), + } } } +struct AppleSdkTargetParts { + sdk_prefix: &'static str, + sim_prefix: &'static str, + sdk: Cow<'static, str>, +} + +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"), + }; + + AppleSdkTargetParts { + sdk_prefix, + sim_prefix, + sdk, + } +} + +#[allow(dead_code)] +enum AppleArchSpec { + Device(&'static str), + Simulator(&'static str), + #[allow(dead_code)] + Catalyst(&'static str), +} + // Use by default minimum available API level // See note about naming here // https://android.googlesource.com/platform/ndk/+/refs/heads/ndk-release-r21/docs/BuildSystemMaintainers.md#Clang @@ -3429,13 +3781,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] @@ -3448,6 +3799,9 @@ fn test_android_clang_compiler_uses_target_arg_internally() { &PathBuf::from(format!("armv7a-linux-androideabi{}-clang++", version)) )); } + assert!(!android_clang_compiler_uses_target_arg_internally( + &PathBuf::from("clang-i686-linux-android") + )); assert!(!android_clang_compiler_uses_target_arg_internally( &PathBuf::from("clang") )); @@ -3505,7 +3859,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 +3878,7 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option< } } -fn which(tool: &Path) -> Option { +fn which(tool: &Path, path_entries: Option) -> Option { 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 +3891,37 @@ fn which(tool: &Path) -> Option { } // 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 { + 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 000000000000..9ebd1ad56205 --- /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( + mut fut1: Fut1, + mut fut2: Fut2, + has_made_progress: &Cell, +) -> Result<(), Error> +where + Fut1: Future>, + Fut2: Future>, +{ + // 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/mod.rs b/third_party/rust/cc/src/parallel/job_token/mod.rs new file mode 100644 index 000000000000..a04d7625b3bc --- /dev/null +++ b/third_party/rust/cc/src/parallel/job_token/mod.rs @@ -0,0 +1,257 @@ +use std::{mem::MaybeUninit, sync::Once}; + +use crate::Error; + +#[cfg(unix)] +#[path = "unix.rs"] +mod sys; + +#[cfg(windows)] +#[path = "windows.rs"] +mod sys; + +pub(crate) struct JobToken(); + +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 = 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) struct ActiveJobTokenServer(&'static JobTokenServer); + +impl ActiveJobTokenServer { + pub(crate) fn new() -> Result { + let jobserver = JobTokenServer::new(); + + #[cfg(unix)] + if let JobTokenServer::Inherited(inherited_jobserver) = &jobserver { + inherited_jobserver.enter_active()?; + } + + Ok(Self(jobserver)) + } + + pub(crate) fn try_acquire(&self) -> Result, Error> { + match &self.0 { + JobTokenServer::Inherited(jobserver) => jobserver.try_acquire(), + JobTokenServer::InProcess(jobserver) => Ok(jobserver.try_acquire()), + } + } +} + +impl Drop for ActiveJobTokenServer { + fn drop(&mut self) { + #[cfg(unix)] + if let JobTokenServer::Inherited(inherited_jobserver) = &self.0 { + inherited_jobserver.exit_active(); + } + } +} + +mod inherited_jobserver { + use super::{sys, Error, JobToken}; + + use std::{ + env::var_os, + sync::atomic::{ + AtomicBool, + Ordering::{AcqRel, Acquire}, + }, + }; + + #[cfg(unix)] + use std::sync::{Mutex, MutexGuard, PoisonError}; + + pub(crate) 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. + global_implicit_token: AtomicBool, + inner: sys::JobServerClient, + /// number of active clients is required to know when it is safe to clear non-blocking + /// flags + #[cfg(unix)] + active_clients_cnt: Mutex, + } + + impl JobServer { + pub(super) unsafe fn from_env() -> Option { + let var = var_os("CARGO_MAKEFLAGS") + .or_else(|| var_os("MAKEFLAGS")) + .or_else(|| var_os("MFLAGS"))?; + + #[cfg(unix)] + let var = std::os::unix::ffi::OsStrExt::as_bytes(var.as_os_str()); + #[cfg(not(unix))] + let var = var.to_str()?.as_bytes(); + + let makeflags = var.split(u8::is_ascii_whitespace); + + // `--jobserver-auth=` is the only documented makeflags. + // `--jobserver-fds=` is actually an internal only makeflags, so we should + // always prefer `--jobserver-auth=`. + // + // Also, according to doc of makeflags, if there are multiple `--jobserver-auth=` + // the last one is used + if let Some(flag) = makeflags + .clone() + .filter_map(|s| s.strip_prefix(b"--jobserver-auth=")) + .last() + { + sys::JobServerClient::open(flag) + } else { + sys::JobServerClient::open( + makeflags + .filter_map(|s| s.strip_prefix(b"--jobserver-fds=")) + .last()?, + ) + } + .map(|inner| Self { + inner, + global_implicit_token: AtomicBool::new(true), + #[cfg(unix)] + active_clients_cnt: Mutex::new(0), + }) + } + + #[cfg(unix)] + fn get_locked_active_cnt(&self) -> MutexGuard<'_, usize> { + self.active_clients_cnt + .lock() + .unwrap_or_else(PoisonError::into_inner) + } + + #[cfg(unix)] + pub(super) fn enter_active(&self) -> Result<(), Error> { + let mut active_cnt = self.get_locked_active_cnt(); + if *active_cnt == 0 { + self.inner.prepare_for_acquires()?; + } + + *active_cnt += 1; + + Ok(()) + } + + #[cfg(unix)] + pub(super) fn exit_active(&self) { + let mut active_cnt = self.get_locked_active_cnt(); + *active_cnt -= 1; + + if *active_cnt == 0 { + self.inner.done_acquires(); + } + } + + pub(super) fn try_acquire(&self) -> Result, Error> { + if !self.global_implicit_token.swap(false, AcqRel) { + // Cold path, no global implicit token, obtain one + if self.inner.try_acquire()?.is_none() { + return Ok(None); + } + } + Ok(Some(JobToken())) + } + + pub(super) fn release_token_raw(&self) { + // All tokens will be put back into the jobserver immediately + // and they cannot be cached, since Rust does not call `Drop::drop` + // on global variables. + if self + .global_implicit_token + .compare_exchange(false, true, AcqRel, Acquire) + .is_err() + { + // There's already a global implicit token, so this token must + // be released back into jobserver + let _ = self.inner.release(); + } + } + } +} + +mod inprocess_jobserver { + use super::JobToken; + + 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) fn try_acquire(&self) -> Option { + let res = self + .0 + .fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1)); + + res.ok().map(|_| JobToken()) + } + + pub(super) fn release_token_raw(&self) { + self.0.fetch_add(1, AcqRel); + } + } +} diff --git a/third_party/rust/cc/src/parallel/job_token/unix.rs b/third_party/rust/cc/src/parallel/job_token/unix.rs new file mode 100644 index 000000000000..7f8e1b88100a --- /dev/null +++ b/third_party/rust/cc/src/parallel/job_token/unix.rs @@ -0,0 +1,176 @@ +use std::{ + ffi::OsStr, + fs::{self, File}, + io::{self, Read, Write}, + mem::ManuallyDrop, + os::{raw::c_int, unix::prelude::*}, + path::Path, +}; + +use crate::parallel::stderr::{set_blocking, set_non_blocking}; + +pub(super) struct JobServerClient { + read: File, + write: Option, +} + +impl JobServerClient { + pub(super) unsafe fn open(var: &[u8]) -> Option { + if let Some(fifo) = var.strip_prefix(b"fifo:") { + Self::from_fifo(Path::new(OsStr::from_bytes(fifo))) + } else { + Self::from_pipe(OsStr::from_bytes(var).to_str()?) + } + } + + /// `--jobserver-auth=fifo:PATH` + fn from_fifo(path: &Path) -> Option { + let file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(path) + .ok()?; + + if is_pipe(&file)? { + // File in Rust is always closed-on-exec as long as it's opened by + // `File::open` or `fs::OpenOptions::open`. + set_non_blocking(&file).ok()?; + + Some(Self { + read: file, + write: None, + }) + } else { + None + } + } + + /// `--jobserver-auth=fd-for-R,fd-for-W` + unsafe fn from_pipe(s: &str) -> Option { + let (read, write) = s.split_once(',')?; + + let read = read.parse().ok()?; + let write = write.parse().ok()?; + + let read = ManuallyDrop::new(File::from_raw_fd(read)); + let write = ManuallyDrop::new(File::from_raw_fd(write)); + + // Ok so we've got two integers that look like file descriptors, but + // for extra sanity checking let's see if they actually look like + // instances of a pipe before we return the client. + // + // If we're called from `make` *without* the leading + on our rule + // then we'll have `MAKEFLAGS` env vars but won't actually have + // access to the file descriptors. + match ( + is_pipe(&read), + is_pipe(&write), + get_access_mode(&read), + get_access_mode(&write), + ) { + ( + Some(true), + Some(true), + Some(libc::O_RDONLY) | Some(libc::O_RDWR), + Some(libc::O_WRONLY) | Some(libc::O_RDWR), + ) => { + // Optimization: Try converting it to a fifo by using /dev/fd + if let Some(jobserver) = + Self::from_fifo(Path::new(&format!("/dev/fd/{}", read.as_raw_fd()))) + { + return Some(jobserver); + } + + let read = read.try_clone().ok()?; + let write = write.try_clone().ok()?; + + Some(Self { + read, + write: Some(write), + }) + } + _ => None, + } + } + + pub(super) fn prepare_for_acquires(&self) -> Result<(), crate::Error> { + if let Some(write) = self.write.as_ref() { + set_non_blocking(&self.read)?; + set_non_blocking(write)?; + } + + Ok(()) + } + + pub(super) fn done_acquires(&self) { + if let Some(write) = self.write.as_ref() { + let _ = set_blocking(&self.read); + let _ = set_blocking(write); + } + } + + /// Must call `prepare_for_acquire` before using it. + pub(super) fn try_acquire(&self) -> io::Result> { + let mut fds = [libc::pollfd { + fd: self.read.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }]; + + let ret = cvt(unsafe { libc::poll(fds.as_mut_ptr(), 1, 0) })?; + if ret == 1 { + let mut buf = [0]; + match (&self.read).read(&mut buf) { + Ok(1) => Ok(Some(())), + Ok(_) => Ok(None), // 0, eof + Err(e) + if e.kind() == io::ErrorKind::Interrupted + || e.kind() == io::ErrorKind::WouldBlock => + { + Ok(None) + } + Err(e) => Err(e), + } + } else { + Ok(None) + } + } + + pub(super) fn release(&self) -> io::Result<()> { + // For write to block, this would mean that pipe is full. + // If all every release are pair with an acquire, then this cannot + // happen. + // + // If it does happen, it is likely a bug in the program using this + // crate or some other programs that use the same jobserver have a + // bug in their code. + // + // If that turns out to not be the case we'll get an error anyway! + let mut write = self.write.as_ref().unwrap_or(&self.read); + match write.write(&[b'+'])? { + 1 => Ok(()), + _ => Err(io::Error::from(io::ErrorKind::UnexpectedEof)), + } + } +} + +fn cvt(t: c_int) -> io::Result { + if t == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} + +fn is_pipe(file: &File) -> Option { + Some(file.metadata().ok()?.file_type().is_fifo()) +} + +fn get_access_mode(file: &File) -> Option { + let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) }; + if ret == -1 { + return None; + } + + Some(ret & libc::O_ACCMODE) +} diff --git a/third_party/rust/cc/src/parallel/job_token/windows.rs b/third_party/rust/cc/src/parallel/job_token/windows.rs new file mode 100644 index 000000000000..434fe169ed28 --- /dev/null +++ b/third_party/rust/cc/src/parallel/job_token/windows.rs @@ -0,0 +1,68 @@ +use std::{ffi::CString, io, ptr, str}; + +use crate::windows::windows_sys::{ + OpenSemaphoreA, ReleaseSemaphore, WaitForSingleObject, FALSE, HANDLE, SEMAPHORE_MODIFY_STATE, + THREAD_SYNCHRONIZE, WAIT_ABANDONED, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, +}; + +pub(super) struct JobServerClient { + sem: HANDLE, +} + +unsafe impl Sync for JobServerClient {} +unsafe impl Send for JobServerClient {} + +impl JobServerClient { + pub(super) unsafe fn open(var: &[u8]) -> Option { + let var = str::from_utf8(var).ok()?; + if !var.is_ascii() { + // `OpenSemaphoreA` only accepts ASCII, not utf-8. + // + // Upstream implementation jobserver and jobslot also uses the + // same function and they works without problem, so there's no + // motivation to support utf-8 here using `OpenSemaphoreW` + // which only makes the code harder to maintain by making it more + // different than upstream. + return None; + } + + let name = CString::new(var).ok()?; + + let sem = OpenSemaphoreA( + THREAD_SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, + FALSE, + name.as_bytes().as_ptr(), + ); + if sem != ptr::null_mut() { + Some(Self { sem }) + } else { + None + } + } + + pub(super) fn try_acquire(&self) -> io::Result> { + match unsafe { WaitForSingleObject(self.sem, 0) } { + WAIT_OBJECT_0 => Ok(Some(())), + WAIT_TIMEOUT => Ok(None), + WAIT_FAILED => Err(io::Error::last_os_error()), + // We believe this should be impossible for a semaphore, but still + // check the error code just in case it happens. + WAIT_ABANDONED => Err(io::Error::new( + io::ErrorKind::Other, + "Wait on jobserver semaphore returned WAIT_ABANDONED", + )), + _ => unreachable!("Unexpected return value from WaitForSingleObject"), + } + } + + pub(super) fn release(&self) -> io::Result<()> { + // SAFETY: ReleaseSemaphore will write to prev_count is it is Some + // and release semaphore self.sem by 1. + let r = unsafe { ReleaseSemaphore(self.sem, 1, ptr::null_mut()) }; + if r != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } +} 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 000000000000..d69146dc59a1 --- /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(vec: &mut Vec, 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 000000000000..2b85772ad899 --- /dev/null +++ b/third_party/rust/cc/src/parallel/stderr.rs @@ -0,0 +1,100 @@ +/// 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 { + 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_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)) +} + +#[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 { + 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 000000000000..39131eaef5c8 --- /dev/null +++ b/third_party/rust/cc/src/tool.rs @@ -0,0 +1,400 @@ +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, + pub(crate) cc_wrapper_args: Vec, + pub(crate) args: Vec, + pub(crate) env: Vec<(OsString, OsString)>, + pub(crate) family: ToolFamily, + pub(crate) cuda: bool, + pub(crate) removed_args: Vec, + pub(crate) has_internal_target_arg: bool, +} + +impl Tool { + pub(crate) fn new( + path: PathBuf, + cached_compiler_family: &Mutex, 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, ToolFamily>>, + cargo_output: &CargoOutput, + ) -> Self { + Self::with_features( + path, + clang_driver, + false, + cached_compiler_family, + cargo_output, + ) + } + + #[cfg(windows)] + /// 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, 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::>(); + 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) { + 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 similarity index 83% rename from third_party/rust/cc/src/com.rs rename to third_party/rust/cc/src/windows/com.rs index 843247e5884b..e81bb1d3c34f 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(*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(&self) -> Result, 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 similarity index 72% rename from third_party/rust/cc/src/windows_registry.rs rename to third_party/rust/cc/src/windows/find_tools.rs index 276688b03f50..9c6511555a0f 100644 --- a/third_party/rust/cc/src/windows_registry.rs +++ b/third_party/rust/cc/src/windows/find_tools.rs @@ -11,6 +11,8 @@ //! A helper module to probe the Windows Registry when looking for //! windows-specific tools. +#![allow(clippy::upper_case_acronyms)] + use std::process::Command; use crate::Tool; @@ -53,6 +55,9 @@ pub fn find_tool(target: &str, tool: &str) -> Option { return None; } + // Split the target to get the arch. + let target = impl_::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. @@ -71,15 +76,16 @@ pub fn find_tool(target: &str, tool: &str) -> Option { // 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,13 +97,6 @@ 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 @@ -106,7 +105,7 @@ pub enum VsVers { /// generator. #[cfg(not(windows))] pub fn find_vs_version() -> Result { - Err(format!("not windows")) + Err("not windows".to_string()) } /// Documented above @@ -160,10 +159,14 @@ pub fn find_vs_version() -> Result { #[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,10 +177,27 @@ 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 crate::Tool; + #[derive(Copy, Clone)] + pub struct TargetArch<'a>(pub &'a str); + + impl PartialEq<&str> for TargetArch<'_> { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } + } + + impl<'a> From> for &'a str { + fn from(target: TargetArch<'a>) -> Self { + target.0 + } + } + struct MsvcTool { tool: PathBuf, libs: Vec, @@ -185,10 +205,75 @@ mod impl_ { include: Vec, } + struct LibraryHandle(HMODULE); + + impl LibraryHandle { + fn new(name: &[u8]) -> Option { + 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(&self, name: &[u8]) -> Option { + 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 { + // 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::(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 +287,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 +297,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 { + fn is_vscmd_target(target: TargetArch<'_>) -> Option { 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 +312,7 @@ mod impl_ { } /// Attempt to find the tool using environment variables set by vcvars. - pub fn find_msvc_environment(tool: &str, target: &str) -> Option { + pub fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option { // Early return if the environment doesn't contain a VC install. if env::var_os("VCINSTALLDIR").is_none() { return None; @@ -248,16 +332,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 { + fn find_msbuild_vs17(target: TargetArch<'_>) -> Option { 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> { + fn vs16plus_instances( + target: TargetArch<'_>, + version: &'static str, + ) -> Box> { let instances = if let Some(instances) = vs15plus_instances(target) { instances } else { @@ -275,7 +362,11 @@ mod impl_ { })) } - fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option { + fn find_tool_in_vs16plus_path( + tool: &str, + target: TargetArch<'_>, + version: &'static str, + ) -> Option { vs16plus_instances(target, version) .filter_map(|path| { let path = path.join(tool); @@ -283,10 +374,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 +385,7 @@ mod impl_ { .next() } - fn find_msbuild_vs16(target: &str) -> Option { + fn find_msbuild_vs16(target: TargetArch<'_>) -> Option { find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16") } @@ -310,7 +401,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 { + fn vs15plus_instances(target: TargetArch<'_>) -> Option { vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) } @@ -323,7 +414,7 @@ mod impl_ { Some(VsInstances::ComBased(enum_setup_instances)) } - fn vs15plus_instances_using_vswhere(target: &str) -> Option { + fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option { let program_files_path: PathBuf = env::var("ProgramFiles(x86)") .or_else(|_| env::var("ProgramFiles")) .ok()? @@ -336,11 +427,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 +464,7 @@ mod impl_ { .collect() } - pub fn find_msvc_15plus(tool: &str, target: &str) -> Option { + pub fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option { let iter = vs15plus_instances(target)?; iter.into_iter() .filter_map(|instance| { @@ -394,13 +484,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 { + fn find_tool_in_vs15_path(tool: &str, target: TargetArch<'_>) -> Option { 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 +506,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 +517,10 @@ mod impl_ { fn tool_from_vs15plus_instance( tool: &str, - target: &str, + target: TargetArch<'_>, instance_path: &PathBuf, ) -> Option { - 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 +530,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 +547,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)> { + 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 { + // 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 +649,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 { + pub fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option { 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 +699,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 { + pub fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option { let vcdir = get_vc_dir("12.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -571,7 +715,7 @@ mod impl_ { } // For MSVC 11 we need to find the Windows 8 SDK. - pub fn find_msvc_11(tool: &str, target: &str) -> Option { + pub fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option { let vcdir = get_vc_dir("11.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -596,7 +740,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 { + fn get_tool(tool: &str, path: &Path, target: TargetArch<'_>) -> Option { bin_subdir(target) .into_iter() .map(|(sub, host)| { @@ -605,7 +749,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 +878,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 +890,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 +954,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, @@ -831,19 +972,19 @@ mod impl_ { pub 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 +996,20 @@ mod impl_ { } } - pub fn find_devenv(target: &str) -> Option { - find_devenv_vs15(&target) + pub fn find_devenv(target: TargetArch<'_>) -> Option { + find_devenv_vs15(target) } - fn find_devenv_vs15(target: &str) -> Option { + fn find_devenv_vs15(target: TargetArch<'_>) -> Option { 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 { + pub fn find_msbuild(target: TargetArch<'_>) -> Option { // 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 +1018,11 @@ mod impl_ { } } - fn find_msbuild_vs15(target: &str) -> Option { + fn find_msbuild_vs15(target: TargetArch<'_>) -> Option { find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) } - fn find_old_msbuild(target: &str) -> Option { + fn find_old_msbuild(target: TargetArch<'_>) -> Option { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE .open(key.as_ref()) @@ -891,7 +1034,7 @@ 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 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 000000000000..9b6f297e1a6d --- /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 similarity index 74% rename from third_party/rust/cc/src/registry.rs rename to third_party/rust/cc/src/windows/registry.rs index cae32219c7fb..83983032deea 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 { let key = key.encode_wide().chain(Some(0)).collect::>(); - 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 similarity index 93% rename from third_party/rust/cc/src/setup_config.rs rename to third_party/rust/cc/src/windows/setup_config.rs index 030051ca6963..5739ecf7d686 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 { - 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 { - 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 { - 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 { - 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 { - let mut s = null_mut(); + let mut s = null(); let this = self.0.cast::()?; 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 similarity index 98% rename from third_party/rust/cc/src/vs_instances.rs rename to third_party/rust/cc/src/windows/vs_instances.rs index 31d3dd1470fa..e863dadabbb5 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 similarity index 62% rename from third_party/rust/cc/src/winapi.rs rename to third_party/rust/cc/src/windows/winapi.rs index 8e04ce9cbd91..09965daa8901 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 000000000000..8b98ce97f8c6 --- /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 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 43eb689f0fbf..000000000000 --- 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 caec6ea4ed4c..000000000000 --- 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 c524c7da4e9f..000000000000 --- 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 f3c04405a3f7..000000000000 --- 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, -} - -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::>(); - 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>(&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>(&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, Q: AsRef>(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, Q: AsRef>(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 161abd8ab7e1..000000000000 --- 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"); -}