From b33d008247f8549c48e188a1e9e94b3622113600 Mon Sep 17 00:00:00 2001 From: Iulian Moraru Date: Wed, 28 Feb 2024 02:00:56 +0200 Subject: [PATCH] Backed out changeset b0a288cd4c4d (bug 1882202) for causing OSX build bustages. CLOSED TREE --- 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/{windows => }/com.rs | 42 +- third_party/rust/cc/src/command_helpers.rs | 433 --- third_party/rust/cc/src/lib.rs | 2608 +++++++---------- .../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 - .../rust/cc/src/{windows => }/registry.rs | 94 +- .../rust/cc/src/{windows => }/setup_config.rs | 34 +- third_party/rust/cc/src/tool.rs | 400 --- .../rust/cc/src/{windows => }/vs_instances.rs | 2 +- .../rust/cc/src/{windows => }/winapi.rs | 102 +- third_party/rust/cc/src/windows/mod.rs | 20 - .../rust/cc/src/windows/windows_sys.rs | 223 -- .../find_tools.rs => windows_registry.rs} | 351 +-- 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, 2563 insertions(+), 3675 deletions(-) create mode 100644 third_party/rust/cc/Cargo.lock create mode 100644 third_party/rust/cc/src/bin/gcc-shim.rs rename third_party/rust/cc/src/{windows => }/com.rs (83%) delete mode 100644 third_party/rust/cc/src/command_helpers.rs delete mode 100644 third_party/rust/cc/src/parallel/async_executor.rs delete mode 100644 third_party/rust/cc/src/parallel/job_token/mod.rs delete mode 100644 third_party/rust/cc/src/parallel/job_token/unix.rs delete mode 100644 third_party/rust/cc/src/parallel/job_token/windows.rs delete mode 100644 third_party/rust/cc/src/parallel/mod.rs delete mode 100644 third_party/rust/cc/src/parallel/stderr.rs rename third_party/rust/cc/src/{windows => }/registry.rs (74%) rename third_party/rust/cc/src/{windows => }/setup_config.rs (93%) delete mode 100644 third_party/rust/cc/src/tool.rs rename third_party/rust/cc/src/{windows => }/vs_instances.rs (98%) rename third_party/rust/cc/src/{windows => }/winapi.rs (62%) delete mode 100644 third_party/rust/cc/src/windows/mod.rs delete mode 100644 third_party/rust/cc/src/windows/windows_sys.rs rename third_party/rust/cc/src/{windows/find_tools.rs => windows_registry.rs} (72%) create mode 100644 third_party/rust/cc/tests/cc_env.rs create mode 100644 third_party/rust/cc/tests/cflags.rs create mode 100644 third_party/rust/cc/tests/cxxflags.rs create mode 100644 third_party/rust/cc/tests/support/mod.rs create mode 100644 third_party/rust/cc/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 9bc8b8b5e260..a0e90dfe62dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -646,11 +646,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.88" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" dependencies = [ - "libc", + "jobserver", ] [[package]] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 38b9834c46ff..2cb2ab1bdccb 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -4754,12 +4754,6 @@ 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 2816295a6490..a326a8e2d3f7 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -71,13 +71,6 @@ 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 257e071e0710..4dc2fe2390fc 100644 --- a/third_party/rust/cc/.cargo-checksum.json +++ b/third_party/rust/cc/.cargo-checksum.json @@ -1 +1 @@ -{"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 +{"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 diff --git a/third_party/rust/cc/Cargo.lock b/third_party/rust/cc/Cargo.lock new file mode 100644 index 000000000000..2d065bc6a879 --- /dev/null +++ b/third_party/rust/cc/Cargo.lock @@ -0,0 +1,110 @@ +# 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 8fb79eed9507..c4ec0bf79d71 100644 --- a/third_party/rust/cc/Cargo.toml +++ b/third_party/rust/cc/Cargo.toml @@ -11,15 +11,10 @@ [package] edition = "2018" -rust-version = "1.53" name = "cc" -version = "1.0.88" +version = "1.0.78" authors = ["Alex Crichton "] -exclude = [ - "/.github", - "tests", - "src/bin", -] +exclude = ["/.github"] 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 @@ -33,13 +28,12 @@ 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 = ["libc"] - -[target."cfg(unix)".dependencies.libc] -version = "0.2.62" -optional = true -default-features = false +parallel = ["jobserver"] diff --git a/third_party/rust/cc/README.md b/third_party/rust/cc/README.md index 33d4bb40f8e8..863540d2d941 100644 --- a/third_party/rust/cc/README.md +++ b/third_party/rust/cc/README.md @@ -1,13 +1,209 @@ # cc-rs -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. +A library to compile C/C++/assembly into a Rust library/application. -Refer to the [documentation](https://docs.rs/cc) for detailed usage instructions. +[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"); +} +``` ## License diff --git a/third_party/rust/cc/src/bin/gcc-shim.rs b/third_party/rust/cc/src/bin/gcc-shim.rs new file mode 100644 index 000000000000..1731df82ea5c --- /dev/null +++ b/third_party/rust/cc/src/bin/gcc-shim.rs @@ -0,0 +1,48 @@ +#![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/windows/com.rs b/third_party/rust/cc/src/com.rs similarity index 83% rename from third_party/rust/cc/src/windows/com.rs rename to third_party/rust/cc/src/com.rs index e81bb1d3c34f..843247e5884b 100644 --- a/third_party/rust/cc/src/windows/com.rs +++ b/third_party/rust/cc/src/com.rs @@ -7,31 +7,27 @@ #![allow(unused)] -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, -}; +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; pub fn initialize() -> Result<(), HRESULT> { - let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) }; + let err = unsafe { CoInitializeEx(null_mut(), COINIT_MULTITHREADED) }; if err != S_OK && err != S_FALSE { // S_FALSE just means COM is already initialized - Err(err) - } else { - Ok(()) + return Err(err); } + Ok(()) } pub struct ComPtr(*mut T) @@ -59,13 +55,15 @@ where /// Extracts the raw pointer. /// You are now responsible for releasing it yourself. pub fn into_raw(self) -> *mut T { - ManuallyDrop::new(self).0 + let p = self.0; + forget(self); + p } /// 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/command_helpers.rs b/third_party/rust/cc/src/command_helpers.rs deleted file mode 100644 index 919d276c84ec..000000000000 --- a/third_party/rust/cc/src/command_helpers.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! 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 f2b34748e997..1ebd2cc7a577 100644 --- a/third_party/rust/cc/src/lib.rs +++ b/third_party/rust/cc/src/lib.rs @@ -1,251 +1,88 @@ -//! 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. +//! A library for build scripts to compile custom C code //! -//! # 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: +//! This library is intended to be used as a `build-dependencies` entry in +//! `Cargo.toml`: //! //! ```toml //! [build-dependencies] //! cc = "1.0" //! ``` //! -//! Next up, you'll want to write a build script like so: +//! 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. //! -//! ```rust,no_run -//! // build.rs +//! This crate will automatically detect situations such as cross compilation or +//! other environment variables set by Cargo and will build code appropriately. //! -//! fn main() { -//! cc::Build::new() -//! .file("foo.c") -//! .file("bar.c") -//! .compile("foo"); -//! } -//! ``` +//! 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. //! -//! 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 +//! [`Build`]: struct.Build.html //! -//! ```c -//! void foo_function(void) { ... } -//! ``` +//! # Parallelism //! -//! 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: +//! To parallelize computation, enable the `parallel` feature for the crate. //! //! ```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. //! -//! 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. +//! Cargo will also set this environment variable when executed with the `-jN` flag. //! -//! # Compile-time Requirements +//! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can +//! also specify the build parallelism. //! -//! 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: +//! # Examples //! -//! * 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. +//! Use the `Build` struct to compile `src/foo.c`: //! -//! [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 +//! ```no_run //! fn main() { //! cc::Build::new() -//! .cpp(true) // Switch to C++ library compilation. -//! .file("foo.cpp") +//! .file("src/foo.c") +//! .define("FOO", Some("bar")) +//! .include("src") //! .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::borrow::Cow; -use std::collections::HashMap; +use std::collections::{hash_map, HashMap}; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt::{self, Display, Formatter}; use std::fs; -use std::io::{self, Write}; +use std::hash::Hasher; +use std::io::{self, BufRead, BufReader, Read, Write}; use std::path::{Component, Path, PathBuf}; -#[cfg(feature = "parallel")] -use std::process::Child; -use std::process::Command; +use std::process::{Child, Command, Stdio}; use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; -#[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; +// 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; -mod command_helpers; -use command_helpers::*; - -mod tool; -pub use tool::Tool; -use tool::ToolFamily; +pub mod windows_registry; /// A builder for compilation of a native library. /// @@ -254,34 +91,32 @@ use tool::ToolFamily; /// documentation on each method itself. #[derive(Clone, Debug)] pub struct Build { - include_directories: Vec>, - definitions: Vec<(Arc, Option>)>, - objects: Vec>, - flags: Vec>, - flags_supported: Vec>, + include_directories: Vec, + definitions: Vec<(String, 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>, - std: Option>, - target: Option>, - host: Option>, - out_dir: Option>, - opt_level: Option>, + cudart: Option, + target: Option, + host: Option, + out_dir: Option, + opt_level: Option, debug: Option, force_frame_pointer: Option, - env: Vec<(Arc, Arc)>, - compiler: Option>, - archiver: Option>, - ranlib: Option>, - cargo_output: CargoOutput, - link_lib_modifiers: Vec>, + env: Vec<(OsString, OsString)>, + compiler: Option, + archiver: Option, + cargo_metadata: bool, + link_lib_modifiers: Vec, pic: Option, use_plt: Option, static_crt: Option, @@ -290,11 +125,9 @@ 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. @@ -320,21 +153,21 @@ pub struct Error { /// Describes the kind of error that occurred. kind: ErrorKind, /// More explanation of error that occurred. - message: Cow<'static, str>, + message: String, } impl Error { - fn new(kind: ErrorKind, message: impl Into>) -> Error { + fn new(kind: ErrorKind, message: &str) -> Error { Error { - kind, - message: message.into(), + kind: kind, + message: message.to_owned(), } } } impl From for Error { fn from(e: io::Error) -> Error { - Error::new(ErrorKind::IOError, format!("{}", e)) + Error::new(ErrorKind::IOError, &format!("{}", e)) } } @@ -346,6 +179,97 @@ 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. @@ -358,7 +282,7 @@ struct Object { impl Object { /// Create a new source file -> object file pair. fn new(src: PathBuf, dst: PathBuf) -> Object { - Object { src, dst } + Object { src: src, dst: dst } } } @@ -387,7 +311,6 @@ impl Build { cpp_set_stdlib: None, cuda: false, cudart: None, - std: None, target: None, host: None, out_dir: None, @@ -397,8 +320,7 @@ impl Build { env: Vec::new(), compiler: None, archiver: None, - ranlib: None, - cargo_output: CargoOutput::new(), + cargo_metadata: true, link_lib_modifiers: Vec::new(), pic: None, use_plt: None, @@ -408,9 +330,7 @@ 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(), } } @@ -430,7 +350,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn include>(&mut self, dir: P) -> &mut Build { - self.include_directories.push(dir.as_ref().into()); + self.include_directories.push(dir.as_ref().to_path_buf()); self } @@ -476,13 +396,13 @@ impl Build { /// ``` pub fn define<'a, V: Into>>(&mut self, var: &str, val: V) -> &mut Build { self.definitions - .push((var.into(), val.into().map(Into::into))); + .push((var.to_string(), val.into().map(|s| s.to_string()))); self } /// Add an arbitrary object file to link in pub fn object>(&mut self, obj: P) -> &mut Build { - self.objects.push(obj.as_ref().into()); + self.objects.push(obj.as_ref().to_path_buf()); self } @@ -497,25 +417,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn flag(&mut self, flag: &str) -> &mut Build { - 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.flags.push(flag.to_string()); self } @@ -531,7 +433,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn ar_flag(&mut self, flag: &str) -> &mut Build { - self.ar_flags.push(flag.into()); + self.ar_flags.push(flag.to_string()); self } @@ -550,7 +452,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn asm_flag(&mut self, flag: &str) -> &mut Build { - self.asm_flags.push(flag.into()); + self.asm_flags.push(flag.to_string()); self } @@ -597,7 +499,6 @@ 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) @@ -614,34 +515,30 @@ 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, ); - // Checking for compiler flags does not require linking - cmd.arg("-c"); + // 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"); + } cmd.arg(&src); let output = cmd.output()?; - let is_supported = output.status.success() && output.stderr.is_empty(); + let is_supported = output.stderr.is_empty(); known_status.insert(flag.to_owned(), is_supported); Ok(is_supported) @@ -659,39 +556,10 @@ impl Build { /// .compile("foo"); /// ``` pub fn flag_if_supported(&mut self, flag: &str) -> &mut Build { - self.flags_supported.push(flag.into()); + self.flags_supported.push(flag.to_string()); 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 @@ -742,7 +610,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().into()); + self.files.push(p.as_ref().to_path_buf()); self } @@ -762,12 +630,6 @@ 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 @@ -775,19 +637,17 @@ impl Build { /// Set CUDA C++ support. /// - /// 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. + /// 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. /// /// 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".into()); + self.cudart = Some("static".to_string()); } self } @@ -800,42 +660,11 @@ 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.into()); + self.cudart = Some(cudart.to_string()); } 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. @@ -907,6 +736,8 @@ 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. @@ -995,7 +826,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn target(&mut self, target: &str) -> &mut Build { - self.target = Some(target.into()); + self.target = Some(target.to_string()); self } @@ -1013,7 +844,7 @@ impl Build { /// .compile("foo"); /// ``` pub fn host(&mut self, host: &str) -> &mut Build { - self.host = Some(host.into()); + self.host = Some(host.to_string()); self } @@ -1022,7 +853,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().into()); + self.opt_level = Some(opt_level.to_string()); self } @@ -1031,7 +862,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.into()); + self.opt_level = Some(opt_level.to_string()); self } @@ -1062,7 +893,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().into()); + self.out_dir = Some(out_dir.as_ref().to_owned()); self } @@ -1072,7 +903,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().into()); + self.compiler = Some(compiler.as_ref().to_owned()); self } @@ -1082,20 +913,9 @@ 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().into()); + self.archiver = Some(archiver.as_ref().to_owned()); 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`. /// @@ -1108,37 +928,17 @@ 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_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.cargo_metadata = cargo_metadata; 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 + /// See https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library /// for the list of modifiers accepted by rustc. pub fn link_lib_modifier(&mut self, link_lib_modifier: &str) -> &mut Build { - self.link_lib_modifiers.push(link_lib_modifier.into()); + self.link_lib_modifiers.push(link_lib_modifier.to_string()); self } @@ -1191,13 +991,14 @@ impl Build { A: AsRef, B: AsRef, { - self.env.push((a.as_ref().into(), b.as_ref().into())); + self.env + .push((a.as_ref().to_owned(), b.as_ref().to_owned())); self } /// Run the compiler, generating the file `output` /// - /// This will return a result instead of panicking; see compile() for the complete description. + /// This will return a result instead of panicing; 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()) { @@ -1215,14 +1016,52 @@ 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 objects = objects_from_files(&self.files, &dst)?; + 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 + }; + 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)?; @@ -1231,8 +1070,8 @@ impl Build { let atlmfc_lib = compiler .env() .iter() - .find(|&(var, _)| var.as_os_str() == OsStr::new("LIB")) - .and_then(|(_, lib_paths)| { + .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB")) + .and_then(|&(_, ref 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)) @@ -1240,7 +1079,7 @@ impl Build { }); if let Some(atlmfc_lib) = atlmfc_lib { - self.cargo_output.print_metadata(&format_args!( + self.print(&format!( "cargo:rustc-link-search=native={}", atlmfc_lib.display() )); @@ -1248,34 +1087,26 @@ impl Build { } if self.link_lib_modifiers.is_empty() { - self.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib=static={}", lib_name)); + self.print(&format!("cargo:rustc-link-lib=static={}", lib_name)); } else { let m = self.link_lib_modifiers.join(","); - self.cargo_output.print_metadata(&format_args!( - "cargo:rustc-link-lib=static:{}={}", - m, lib_name - )); + self.print(&format!("cargo:rustc-link-lib=static:{}={}", m, lib_name)); } - self.cargo_output.print_metadata(&format_args!( - "cargo:rustc-link-search=native={}", - dst.display() - )); + self.print(&format!("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.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib)); + self.print(&format!("cargo:rustc-link-lib={}", stdlib)); } } let cudart = match &self.cudart { - Some(opt) => &*opt, // {none|shared|static} + Some(opt) => opt.as_str(), // {none|shared|static} None => "none", }; if cudart != "none" { - if let Some(nvcc) = which(&self.get_compiler().path, None) { + if let Some(nvcc) = which(&self.get_compiler().path) { // 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. @@ -1304,10 +1135,10 @@ impl Build { } } if libtst && libdir.is_dir() { - self.cargo_output.print_metadata(&format_args!( + println!( "cargo:rustc-link-search=native={}", libdir.to_str().unwrap() - )); + ); } // And now the -l flag. @@ -1316,8 +1147,7 @@ impl Build { "static" => "cudart_static", bad => panic!("unsupported cudart option: {}", bad), }; - self.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib={}", lib)); + println!("cargo:rustc-link-lib={}", lib); } } @@ -1367,48 +1197,18 @@ impl Build { } } - /// Run the compiler, generating intermediate files, but without linking - /// them into an archive file. - /// - /// This will return a list of compiled object files, in the same order - /// as they were passed in as `file`/`files` methods. - pub fn compile_intermediates(&self) -> Vec { - match self.try_compile_intermediates() { - Ok(v) => v, - Err(e) => fail(&e.message), - } - } - - /// Run the compiler, generating intermediate files, but without linking - /// them into an archive file. - /// - /// This will return a result instead of panicking; see `compile_intermediates()` for the complete description. - pub fn try_compile_intermediates(&self) -> Result, 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; + fn compile_objects<'me>(&'me self, objs: &[Object]) -> Result<(), Error> { + use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + use std::sync::Once; - 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()?; + // 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(); // When compiling objects in parallel we do a few dirty tricks to speed // things up: @@ -1422,160 +1222,153 @@ 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 spawn the process to actually compile objects in - // parallel after we've acquired a token to perform some work + // * 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. // // 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 wait on all the processes and propagate the results of compilation. - - let pendings = Cell::new(Vec::<( - Command, - String, - KillOnDrop, - parallel::job_token::JobToken, - )>::new()); - let is_disconnected = Cell::new(false); - let has_made_progress = Cell::new(false); - - let wait_future = async { - let mut error = None; - // Buffer the stdout - let mut stdout = io::BufWriter::with_capacity(128, io::stdout()); - - loop { - // If the other end of the pipe is already disconnected, then we're not gonna get any new jobs, - // so it doesn't make sense to reuse the tokens; in fact, - // releasing them as soon as possible (once we know that the other end is disconnected) is beneficial. - // Imagine that the last file built takes an hour to finish; in this scenario, - // by not releasing the tokens before that last file is done we would effectively block other processes from - // starting sooner - even though we only need one token for that last file, not N others that were acquired. - - let mut pendings_is_empty = false; - - cell_update(&pendings, |mut pendings| { - // Try waiting on them. - parallel::retain_unordered_mut( - &mut pendings, - |(cmd, program, child, _token)| { - match try_wait_on_child( - cmd, - program, - &mut child.0, - &mut stdout, - &mut child.1, - ) { - Ok(Some(())) => { - // Task done, remove the entry - has_made_progress.set(true); - false - } - Ok(None) => true, // Task still not finished, keep the entry - Err(err) => { - // Task fail, remove the entry. - // Since we can only return one error, log the error to make - // sure users always see all the compilation failures. - has_made_progress.set(true); - - if self.cargo_output.warnings { - let _ = writeln!(stdout, "cargo:warning={}", err); - } - error = Some(err); - - false - } - } - }, - ); - pendings_is_empty = pendings.is_empty(); - pendings - }); - - if pendings_is_empty && is_disconnected.get() { - break if let Some(err) = error { - Err(err) - } else { - Ok(()) - }; + // 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))); + } - 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()?; - - cell_update(&pendings, |mut pendings| { - pendings.push((cmd, program, KillOnDrop(child, stderr_forwarder), token)); - pendings - }); - - has_made_progress.set(true); - } - is_disconnected.set(true); - - Ok::<_, Error>(()) - }; - - return block_on(wait_future, spawn_future, &has_made_progress); - - struct KillOnDrop(Child, StderrForwarder); - - impl Drop for KillOnDrop { - fn drop(&mut self) { - let child = &mut self.0; - - child.kill().ok(); + for mut thread in threads { + if let Some(thread) = thread.0.take() { + thread.join().expect("thread should not panic")?; } } - 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); + // Reacquire our process's token before we proceed, which we released + // before entering the loop above. + if reacquire { + server.acquire_raw()?; + } + + return Ok(()); + + /// Shared state from the parent thread to the child thread. This + /// package of pointers is temporarily transmuted to a `'static` + /// lifetime to cross the thread boundary and then once the thread is + /// running we erase the `'static` to go back to an anonymous lifetime. + struct State<'a> { + build: &'a Build, + obj: &'a Object, + error: &'a AtomicBool, + } + + /// Returns a suitable `jobserver::Client` used to coordinate + /// parallelism between build scripts. + fn jobserver() -> &'static jobserver::Client { + static INIT: Once = Once::new(); + static mut JOBSERVER: Option = None; + + fn _assert_sync() {} + _assert_sync::(); + + unsafe { + INIT.call_once(|| { + let server = default_jobserver(); + JOBSERVER = Some(server); + }); + 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 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; + } + + struct JoinOnDrop(Option>>); + + impl Drop for JoinOnDrop { + fn drop(&mut self) { + if let Some(thread) = self.0.take() { + drop(thread.join()); + } + } } } #[cfg(not(feature = "parallel"))] fn compile_objects(&self, objs: &[Object]) -> Result<(), Error> { for obj in objs { - let (mut cmd, name) = self.create_compile_object_cmd(obj)?; - run(&mut cmd, &name, &self.cargo_output)?; + self.compile_object(obj)?; } - Ok(()) } - fn create_compile_object_cmd(&self, obj: &Object) -> Result<(Command, String), Error> { + fn compile_object(&self, obj: &Object) -> Result<(), 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 is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm); - let (mut cmd, name) = if is_assembler_msvc { + let (mut cmd, name) = if msvc && asm_ext == Some(AsmFileExt::DotAsm) { self.msvc_macro_assembler()? } else { let mut cmd = compiler.to_command(); - for (a, b) in self.env.iter() { + for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); } ( @@ -1589,20 +1382,18 @@ impl Build { ) }; let is_arm = target.contains("aarch64") || target.contains("arm"); - command_add_output_file( - &mut cmd, &obj.dst, self.cuda, msvc, clang, gnu, is_asm, is_arm, - ); + command_add_output_file(&mut cmd, &obj.dst, self.cuda, msvc, clang, is_asm, is_arm); // armasm and armasm64 don't requrie -c option - if !is_assembler_msvc || !is_arm { + if !msvc || !is_asm || !is_arm { cmd.arg("-c"); } if self.cuda && self.cuda_file_count() > 1 { cmd.arg("--device-c"); } if is_asm { - cmd.args(self.asm_flags.iter().map(std::ops::Deref::deref)); + cmd.args(&self.asm_flags); } - if compiler.family == (ToolFamily::Msvc { clang_cl: true }) && !is_assembler_msvc { + 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 @@ -1614,14 +1405,15 @@ impl Build { self.fix_env_for_apple_os(&mut cmd)?; } - Ok((cmd, name)) + run(&mut cmd, &name)?; + Ok(()) } - /// This will return a result instead of panicking; see expand() for the complete description. + /// This will return a result instead of panicing; 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 (a, b) in self.env.iter() { + for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); } cmd.arg("-E"); @@ -1631,23 +1423,10 @@ impl Build { "Expand may only be called for a single 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("--"); + for file in self.files.iter() { + cmd.arg(file); } - cmd.args(self.files.iter().map(std::ops::Deref::deref)); - let name = compiler .path .file_name() @@ -1655,7 +1434,7 @@ impl Build { .to_string_lossy() .into_owned(); - Ok(run_output(&mut cmd, &name, &self.cargo_output)?) + Ok(run_output(&mut cmd, &name)?) } /// Run the compiler, returning the macro-expanded version of the input files. @@ -1704,13 +1483,13 @@ impl Build { /// Get the compiler that's in use for this configuration. /// - /// This will return a result instead of panicking; see - /// [`get_compiler()`](Self::get_compiler) for the complete description. + /// This will return a result instead of panicing; see 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(); @@ -1721,23 +1500,13 @@ impl Build { println!("Info: default compiler flags are disabled"); } - 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 arg in envflags { + cmd.push_cc_arg(arg.into()); } for directory in self.include_directories.iter() { cmd.args.push("-I".into()); - cmd.args.push(directory.as_os_str().into()); + cmd.args.push(directory.into()); } // If warnings and/or extra_warnings haven't been explicitly set, @@ -1745,28 +1514,34 @@ impl Build { // CFLAGS/CXXFLAGS, since those variables presumably already contain // the desired set of warnings flags. - if self.warnings.unwrap_or(!self.has_flags()) { + if self + .warnings + .unwrap_or(if self.has_flags() { false } else { true }) + { let wflags = cmd.family.warnings_flags().into(); cmd.push_cc_arg(wflags); } - if self.extra_warnings.unwrap_or(!self.has_flags()) { + if self + .extra_warnings + .unwrap_or(if self.has_flags() { false } else { true }) + { 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 (key, value) in self.definitions.iter() { + for &(ref key, ref value) in self.definitions.iter() { if let Some(ref value) = *value { cmd.args.push(format!("-D{}={}", key, value).into()); } else { @@ -1798,8 +1573,9 @@ impl Build { Some(true) => "-MT", Some(false) => "-MD", None => { - let features = self.getenv("CARGO_CFG_TARGET_FEATURE"); - let features = features.as_deref().unwrap_or_default(); + let features = self + .getenv("CARGO_CFG_TARGET_FEATURE") + .unwrap_or(String::new()); if features.contains("crt-static") { "-MT" } else { @@ -1826,13 +1602,6 @@ 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) @@ -1842,10 +1611,7 @@ impl Build { cmd.push_opt_unless_duplicate("-DANDROID".into()); } - if !target.contains("apple-ios") - && !target.contains("apple-watchos") - && !target.contains("apple-tvos") - { + if !target.contains("apple-ios") && !target.contains("apple-watchos") { cmd.push_cc_arg("-ffunction-sections".into()); cmd.push_cc_arg("-fdata-sections".into()); } @@ -1879,20 +1645,12 @@ 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") && cmd.has_internal_target_arg) { + if !(target.contains("android") + && android_clang_compiler_uses_target_arg_internally(&cmd.path)) + { if target.contains("darwin") { if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) @@ -1911,10 +1669,8 @@ impl Build { if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) { - let sdk_details = - apple_os_sdk_parts(AppleOs::Ios, &AppleArchSpec::Simulator("")); - let deployment_target = - self.apple_deployment_version(AppleOs::Ios, None, &sdk_details.sdk); + let deployment_target = env::var("IPHONEOS_DEPLOYMENT_TARGET") + .unwrap_or_else(|_| "7.0".into()); cmd.args.push( format!( "--target={}-apple-ios{}-simulator", @@ -1927,13 +1683,8 @@ impl Build { if let Some(arch) = map_darwin_target_from_rust_to_compiler_architecture(target) { - let sdk_details = - apple_os_sdk_parts(AppleOs::WatchOs, &AppleArchSpec::Simulator("")); - let deployment_target = self.apple_deployment_version( - AppleOs::WatchOs, - None, - &sdk_details.sdk, - ); + let deployment_target = env::var("WATCHOS_DEPLOYMENT_TARGET") + .unwrap_or_else(|_| "5.0".into()); cmd.args.push( format!( "--target={}-apple-watchos{}-simulator", @@ -1942,40 +1693,6 @@ 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(), @@ -1992,30 +1709,6 @@ 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()); } @@ -2039,8 +1732,6 @@ 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()); } } @@ -2059,6 +1750,14 @@ 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) { @@ -2072,8 +1771,9 @@ impl Build { } if self.static_flag.is_none() { - let features = self.getenv("CARGO_CFG_TARGET_FEATURE"); - let features = features.as_deref().unwrap_or_default(); + let features = self + .getenv("CARGO_CFG_TARGET_FEATURE") + .unwrap_or(String::new()); if features.contains("crt-static") { cmd.args.push("-static".into()); } @@ -2227,36 +1927,33 @@ impl Build { let mut parts = target.split('-'); if let Some(arch) = parts.next() { let arch = &arch[5..]; - 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()); - } + 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()); } else { - cmd.args.push("-mcmodel=medany".into()); + cmd.args.push(("-march=rv".to_owned() + arch).into()); + cmd.args.push("-mabi=ilp32".into()); } + cmd.args.push("-mcmodel=medany".into()); } } } } - if target.contains("-apple-") { - self.apple_flags(cmd)?; + if target.contains("apple-ios") || target.contains("apple-watchos") { + self.ios_watchos_flags(cmd)?; } if self.static_flag.unwrap_or(false) { @@ -2273,7 +1970,11 @@ impl Build { cmd.push_cc_arg(format!("-stdlib=lib{}", stdlib).into()); } _ => { - self.cargo_output.print_warning(&format_args!("cpp_set_stdlib is specified, but the {:?} compiler does not support this option, ignored", cmd.family)); + println!( + "cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \ + does not support this option, ignored", + cmd.family + ); } } } @@ -2283,7 +1984,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.getenv_with_target_prefixes(flags_env_var_name); + let flags_env_var_value = self.get_var(flags_env_var_name); if let Ok(_) = flags_env_var_value { true } else { @@ -2305,33 +2006,20 @@ 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"); } - 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}")); - } - } + println!("cargo:warning=The MSVC ARM assemblers do not support -D flags"); } else { if self.get_debug() { cmd.arg("-Zi"); } - for (key, value) in self.definitions.iter() { + for &(ref key, ref value) in self.definitions.iter() { if let Some(ref value) = *value { cmd.arg(&format!("-D{}={}", key, value)); } else { @@ -2343,6 +2031,9 @@ 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())) } @@ -2350,15 +2041,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.as_path()) - .chain(self.objects.iter().map(std::ops::Deref::deref)) + .map(|o| o.dst.clone()) + .chain(self.objects.clone()) .collect(); for chunk in objs.chunks(100) { self.assemble_progressive(dst, chunk)?; @@ -2371,9 +2062,12 @@ 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).arg(dst); - run(&mut nvcc, "nvcc", &self.cargo_output)?; - self.assemble_progressive(dst, &[dlink.as_path()])?; + nvcc.arg("--device-link") + .arg("-o") + .arg(dlink.clone()) + .arg(dst); + run(&mut nvcc, "nvcc")?; + self.assemble_progressive(dst, &[dlink])?; } let target = self.get_target()?; @@ -2384,9 +2078,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(_) => { @@ -2400,31 +2094,23 @@ 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, _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)?; + let (mut ar, cmd) = self.get_ar()?; + run(ar.arg("s").arg(dst), &cmd)?; } Ok(()) } - fn assemble_progressive(&self, dst: &Path, objs: &[&Path]) -> Result<(), Error> { + fn assemble_progressive(&self, dst: &Path, objs: &[PathBuf]) -> Result<(), Error> { let target = self.get_target()?; if target.contains("msvc") { - 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 cmd, program) = self.get_ar()?; let mut out = OsString::from("-out:"); out.push(dst); - cmd.arg(out); - if !any_flags { - cmd.arg("-nologo"); + cmd.arg(out).arg("-nologo"); + for flag in self.ar_flags.iter() { + cmd.arg(flag); } // If the library file already exists, add the library name // as an argument to let lib.exe know we are appending the objs. @@ -2432,9 +2118,9 @@ impl Build { cmd.arg(dst); } cmd.args(objs); - run(&mut cmd, &program, &self.cargo_output)?; + run(&mut cmd, &program)?; } else { - let (mut ar, cmd, _any_flags) = self.get_ar()?; + let (mut ar, cmd) = self.get_ar()?; // Set an environment variable to tell the OSX archiver to ensure // that all dates listed in the archive are zero, improving @@ -2459,36 +2145,46 @@ 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"); - - // 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)?; + for flag in self.ar_flags.iter() { + ar.arg(flag); + } + run(ar.arg("cq").arg(dst).args(objs), &cmd)?; } Ok(()) } - fn apple_flags(&self, cmd: &mut Tool) -> Result<(), Error> { + fn ios_watchos_flags(&self, cmd: &mut Tool) -> Result<(), Error> { + enum ArchSpec { + Device(&'static str), + Simulator(&'static str), + Catalyst(&'static str), + } + + enum Os { + Ios, + WatchOs, + } + impl Display for Os { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Os::Ios => f.write_str("iOS"), + Os::WatchOs => f.write_str("WatchOS"), + } + } + } + let target = self.get_target()?; - let os = if target.contains("-darwin") { - AppleOs::MacOs - } else if target.contains("-watchos") { - AppleOs::WatchOs - } else if target.contains("-tvos") { - AppleOs::TvOs + let os = if target.contains("-watchos") { + Os::WatchOs } else { - AppleOs::Ios - }; - let is_mac = match os { - AppleOs::MacOs => true, - _ => false, + Os::Ios }; - let arch_str = target.split('-').nth(0).ok_or_else(|| { + let arch = target.split('-').nth(0).ok_or_else(|| { Error::new( ErrorKind::ArchitectureInvalid, - format!("Unknown architecture for {:?} target.", os), + format!("Unknown architecture for {} target.", os).as_str(), ) })?; @@ -2497,27 +2193,16 @@ impl Build { None => false, }; - let is_arm_sim = match target.split('-').nth(3) { + let is_sim = match target.split('-').nth(3) { Some(v) => v == "sim", None => false, }; - 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"), + let arch = if is_catalyst { + match arch { + "arm64e" => ArchSpec::Catalyst("arm64e"), + "arm64" | "aarch64" => ArchSpec::Catalyst("arm64"), + "x86_64" => ArchSpec::Catalyst("-m64"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, @@ -2525,136 +2210,105 @@ impl Build { )); } } - } else if is_arm_sim { - match arch_str { - "arm64" | "aarch64" => AppleArchSpec::Simulator("arm64"), - "x86_64" | "x86_64h" => AppleArchSpec::Simulator("-m64"), + } else if is_sim { + match arch { + "arm64" | "aarch64" => ArchSpec::Simulator("-arch arm64"), + "x86_64" => ArchSpec::Simulator("-m64"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, - "Unknown architecture for simulator target.", + "Unknown architecture for iOS simulator target.", )); } } } else { - 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"), + 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"), _ => { return Err(Error::new( ErrorKind::ArchitectureInvalid, - format!("Unknown architecture for {:?} target.", os), + format!("Unknown architecture for {} target.", os).as_str(), )); } } }; - let sdk_details = apple_os_sdk_parts(os, &arch); - let min_version = self.apple_deployment_version(os, Some(arch_str), &sdk_details.sdk); - - 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_details.sdk_prefix, min_version).into(), - ); - } - AppleArchSpec::Simulator(arch) => { - if arch.starts_with('-') { - // -m32 or -m64 - cmd.args.push(arch.into()); - } else { - cmd.args.push("-arch".into()); - cmd.args.push(arch.into()); - } - cmd.args.push( - format!( - "-m{}simulator-version-min={}", - sdk_details.sim_prefix, min_version - ) - .into(), - ); - } - AppleArchSpec::Catalyst(_) => {} + 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()), + ), }; - // 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)?; + let sdk = match arch { + ArchSpec::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) + } + 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) + } + ArchSpec::Catalyst(_) => "macosx".to_owned(), + }; - 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 - }, - ]); + 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())? + }; + + 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()); } + */ Ok(()) } fn cmd>(&self, prog: P) -> Command { let mut cmd = Command::new(prog); - for (a, b) in self.env.iter() { + for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); } cmd } fn get_base_compiler(&self) -> Result { - if let Some(c) = &self.compiler { - return Ok(Tool::new( - (**c).to_owned(), - &self.cached_compiler_family, - &self.cargo_output, - )); + if let Some(ref c) = self.compiler { + return Ok(Tool::new(c.clone())); } 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 { @@ -2671,7 +2325,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) @@ -2686,12 +2340,7 @@ 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( - tool, - driver_mode, - &self.cached_compiler_family, - &self.cargo_output, - ); + let mut t = Tool::with_clang_driver(PathBuf::from(tool.trim()), driver_mode); if let Some(cc_wrapper) = wrapper { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); } @@ -2705,20 +2354,12 @@ 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"), - &self.cached_compiler_family, - &self.cargo_output, - ); + let mut t = Tool::new(PathBuf::from("cmd")); t.args.push("/c".into()); t.args.push(format!("{}.bat", tool).into()); Some(t) } else { - Some(Tool::new( - PathBuf::from(tool), - &self.cached_compiler_family, - &self.cargo_output, - )) + Some(Tool::new(PathBuf::from(tool))) } } else { None @@ -2736,13 +2377,12 @@ impl Build { let cc = if target.contains("llvm") { clang } else { gnu }; format!("{}.exe", cc) } - } else if target.contains("apple-ios") - | target.contains("apple-watchos") - | target.contains("apple-tvos") - { + } else if target.contains("apple-ios") { + clang.to_string() + } else if target.contains("apple-watchos") { 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" @@ -2760,8 +2400,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 }; @@ -2773,11 +2413,7 @@ impl Build { default.to_string() }; - let mut t = Tool::new( - PathBuf::from(compiler), - &self.cached_compiler_family, - &self.cargo_output, - ); + let mut t = Tool::new(PathBuf::from(compiler)); if let Some(cc_wrapper) = Self::rustc_wrapper_fallback() { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); } @@ -2790,17 +2426,11 @@ impl Build { tool.args.is_empty(), "CUDA compilation currently assumes empty pre-existing args" ); - let nvcc = match self.getenv_with_target_prefixes("NVCC") { - Err(_) => PathBuf::from("nvcc"), - Ok(nvcc) => PathBuf::from(&*nvcc), + let nvcc = match self.get_var("NVCC") { + Err(_) => "nvcc".into(), + Ok(nvcc) => nvcc, }; - let mut nvcc_tool = Tool::with_features( - nvcc, - None, - self.cuda, - &self.cached_compiler_family, - &self.cargo_output, - ); + let mut nvcc_tool = Tool::with_features(PathBuf::from(nvcc), None, self.cuda); nvcc_tool .args .push(format!("-ccbin={}", tool.path.display()).into()); @@ -2825,17 +2455,16 @@ 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.has_internal_target_arg = true; - tool.path.set_file_name(clang.trim_start_matches('-')); + 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()); @@ -2860,20 +2489,43 @@ impl Build { && tool.env.len() == 0 && target.contains("msvc") { - for (k, v) in cl_exe.env.iter() { + for &(ref k, ref v) in cl_exe.env.iter() { tool.env.push((k.to_owned(), v.to_owned())); } } } - if target.contains("msvc") && tool.family == ToolFamily::Gnu { - self.cargo_output - .print_warning(&"GNU compiler is not supported for this target"); - } - 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), + )), + } + } + + fn envflags(&self, name: &str) -> Vec { + self.get_var(name) + .unwrap_or(String::new()) + .split_ascii_whitespace() + .map(|slice| slice.to_string()) + .collect() + } + /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER` fn rustc_wrapper_fallback() -> Option { // No explicit CC wrapper was detected, but check if RUSTC_WRAPPER @@ -2893,8 +2545,8 @@ impl Build { } /// Returns compiler path, optional modifier name from whitelist, and arguments vec - fn env_tool(&self, name: &str) -> Option<(PathBuf, Option, Vec)> { - let tool = match self.getenv_with_target_prefixes(name) { + fn env_tool(&self, name: &str) -> Option<(String, Option, Vec)> { + let tool = match self.get_var(name) { Ok(tool) => tool, Err(_) => return None, }; @@ -2902,12 +2554,8 @@ 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(( - PathBuf::from(&*tool), - Self::rustc_wrapper_fallback(), - Vec::new(), - )); + if Path::new(&tool).exists() { + return Some((tool, None, Vec::new())); } // Ok now we want to handle a couple of scenarios. We'll assume from @@ -2946,7 +2594,7 @@ impl Build { if known_wrappers.contains(&file_stem) { if let Some(compiler) = parts.next() { return Some(( - compiler.into(), + compiler.to_string(), Some(maybe_wrapper.to_string()), parts.map(|s| s.to_string()).collect(), )); @@ -2954,37 +2602,36 @@ impl Build { } Some(( - maybe_wrapper.into(), + maybe_wrapper.to_string(), 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 { - Some(s) => Ok(s.as_ref().map(|s| (*s).to_string())), + match self.cpp_link_stdlib.clone() { + Some(s) => Ok(s), None => { - if let Ok(stdlib) = self.getenv_with_target_prefixes("CXXSTDLIB") { + if let Ok(stdlib) = self.get_var("CXXSTDLIB") { if stdlib.is_empty() { Ok(None) } else { - Ok(Some(stdlib.to_string())) + Ok(Some(stdlib)) } } else { let target = self.get_target()?; if target.contains("msvc") { Ok(None) - } else if target.contains("apple") - | target.contains("freebsd") - | target.contains("openbsd") - | target.contains("aix") - | target.contains("linux-ohos") - { + } 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") { Ok(Some("c++".to_string())) } else if target.contains("android") { Ok(Some("c++_shared".to_string())) @@ -2996,239 +2643,101 @@ impl Build { } } - fn get_ar(&self) -> Result<(Command, String, bool), Error> { - self.try_get_archiver_and_flags() - } - - /// Get the archiver (ar) that's in use for this configuration. - /// - /// You can use [`Command::get_program`] to get just the path to the command. - /// - /// This method will take into account all configuration such as debug - /// information, optimization level, include directories, defines, etc. - /// Additionally, the compiler binary in use follows the standard - /// conventions for this path, e.g. looking at the explicitly set compiler, - /// environment variables (a number of which are inspected here), and then - /// falling back to the default configuration. - /// - /// # Panics - /// - /// Panics if an error occurred while determining the architecture. - pub fn get_archiver(&self) -> Command { - match self.try_get_archiver() { - Ok(tool) => tool, - Err(e) => fail(&e.message), + 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())); } - } - - /// 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); + if let Ok(p) = self.get_var("AR") { + return Ok((self.cmd(&p), p)); } - 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 - } - }); + 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())); + } - 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(); - } - } + "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(); } - - 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) } } + 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; + } + } + ar + } + None => default_ar, + } + } else { + default_ar }; - - Ok((tool, name)) + Ok((self.cmd(&program), program)) } fn prefix_for_target(&self, target: &str) -> Option { - // 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)); + // 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 + }; // 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(linker_prefix).or(match &target[..] { + cross_compile.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"), @@ -3265,7 +2774,6 @@ 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"), @@ -3286,7 +2794,6 @@ 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", @@ -3297,7 +2804,6 @@ 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", @@ -3317,7 +2823,6 @@ 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"), @@ -3329,7 +2834,6 @@ 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"), @@ -3346,7 +2850,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"), - _ => None, + _ => linker_prefix, } .map(|x| x.to_owned())) } @@ -3383,30 +2887,30 @@ impl Build { prefixes.first().map(|prefix| *prefix)) } - fn get_target(&self) -> Result, Error> { - match &self.target { - Some(t) => Ok(t.clone()), - None => self.getenv_unwrap("TARGET"), + fn get_target(&self) -> Result { + match self.target.clone() { + Some(t) => Ok(t), + None => Ok(self.getenv_unwrap("TARGET")?), } } - fn get_host(&self) -> Result, Error> { - match &self.host { - Some(h) => Ok(h.clone()), - None => self.getenv_unwrap("HOST"), + fn get_host(&self) -> Result { + match self.host.clone() { + Some(h) => Ok(h), + None => Ok(self.getenv_unwrap("HOST")?), } } - fn get_opt_level(&self) -> Result, Error> { - match &self.opt_level { - Some(ol) => Ok(ol.clone()), - None => self.getenv_unwrap("OPT_LEVEL"), + 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_debug(&self) -> bool { self.debug.unwrap_or_else(|| match self.getenv("DEBUG") { - Some(s) => &*s != "false", + Some(s) => s != "false", None => false, }) } @@ -3434,22 +2938,19 @@ impl Build { self.force_frame_pointer.unwrap_or_else(|| self.get_debug()) } - 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 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 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 // @@ -3469,58 +2970,47 @@ impl Build { return val.clone(); } if self.emit_rerun_if_env_changed && !provided_by_cargo(v) { - self.cargo_output - .print_metadata(&format_args!("cargo:rerun-if-env-changed={}", v)); + self.print(&format!("cargo:rerun-if-env-changed={}", v)); } - let r = env::var(v).ok().map(Arc::from); - self.cargo_output - .print_metadata(&format_args!("{} = {:?}", v, r)); + let r = env::var(v).ok(); + self.print(&format!("{} = {:?}", v, r)); cache.insert(v.to_string(), r.clone()); r } - fn getenv_unwrap(&self, v: &str) -> Result, Error> { + fn getenv_unwrap(&self, v: &str) -> Result { match self.getenv(v) { Some(s) => Ok(s), None => Err(Error::new( ErrorKind::EnvVarNotFound, - format!("Environment variable {} not defined.", v), + &format!("Environment variable {} not defined.", v.to_string()), )), } } - 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 print(&self, s: &str) { + if self.cargo_metadata { + println!("{}", s); } } - 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". @@ -3530,10 +3020,6 @@ 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() @@ -3548,7 +3034,6 @@ impl Build { .arg("--sdk") .arg(sdk), "xcrun", - &self.cargo_output, )?; let sdk_path = match String::from_utf8(sdk_path) { @@ -3565,129 +3050,6 @@ 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() @@ -3702,64 +3064,350 @@ 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); } -#[derive(Clone, Copy, PartialEq)] -enum AppleOs { - MacOs, - Ios, - WatchOs, - TvOs, -} -impl std::fmt::Debug for AppleOs { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - AppleOs::MacOs => f.write_str("macOS"), - AppleOs::Ios => f.write_str("iOS"), - AppleOs::WatchOs => f.write_str("WatchOS"), - AppleOs::TvOs => f.write_str("AppleTVOS"), - } +fn 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); } } -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 @@ -3781,12 +3429,13 @@ 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() { - if let Some(idx) = filename_str.rfind('-') { - return filename_str.split_at(idx).0.contains("android"); - } + filename_str.contains("android") + } else { + false } + } else { + false } - false } #[test] @@ -3799,9 +3448,6 @@ 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") )); @@ -3859,9 +3505,7 @@ 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_64h") { - Some("x86_64h") - } else if target.contains("x86_64") { + if target.contains("x86_64") { Some("x86_64") } else if target.contains("arm64e") { Some("arm64e") @@ -3878,7 +3522,7 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option< } } -fn which(tool: &Path, path_entries: Option) -> Option { +fn which(tool: &Path) -> 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()) @@ -3891,37 +3535,13 @@ fn which(tool: &Path, path_entries: Option) -> Option { } // Loop through PATH entries searching for the |tool|. - let path_entries = path_entries.or(env::var_os("PATH"))?; + let path_entries = env::var_os("PATH")?; env::split_paths(&path_entries).find_map(|path_entry| { let mut exe = path_entry.join(tool); - if check_exe(&mut exe) { - Some(exe) - } else { - None - } + return 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 deleted file mode 100644 index 9ebd1ad56205..000000000000 --- a/third_party/rust/cc/src/parallel/async_executor.rs +++ /dev/null @@ -1,118 +0,0 @@ -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 deleted file mode 100644 index a04d7625b3bc..000000000000 --- a/third_party/rust/cc/src/parallel/job_token/mod.rs +++ /dev/null @@ -1,257 +0,0 @@ -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 deleted file mode 100644 index 7f8e1b88100a..000000000000 --- a/third_party/rust/cc/src/parallel/job_token/unix.rs +++ /dev/null @@ -1,176 +0,0 @@ -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 deleted file mode 100644 index 434fe169ed28..000000000000 --- a/third_party/rust/cc/src/parallel/job_token/windows.rs +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index d69146dc59a1..000000000000 --- a/third_party/rust/cc/src/parallel/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 2b85772ad899..000000000000 --- a/third_party/rust/cc/src/parallel/stderr.rs +++ /dev/null @@ -1,100 +0,0 @@ -/// 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/windows/registry.rs b/third_party/rust/cc/src/registry.rs similarity index 74% rename from third_party/rust/cc/src/windows/registry.rs rename to third_party/rust/cc/src/registry.rs index 83983032deea..cae32219c7fb 100644 --- a/third_party/rust/cc/src/windows/registry.rs +++ b/third_party/rust/cc/src/registry.rs @@ -8,23 +8,63 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -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, -}; +use std::ffi::{OsStr, OsString}; +use std::io; +use std::ops::RangeFrom; +use std::os::raw; +use std::os::windows::prelude::*; /// Must never be `HKEY_PERFORMANCE_DATA`. pub(crate) struct RegistryKey(Repr); -#[allow(clippy::upper_case_acronyms)] +type HKEY = *mut u8; 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); @@ -57,7 +97,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 = null_mut(); + let mut ret = 0 as *mut _; let err = unsafe { RegOpenKeyExW( self.raw(), @@ -67,7 +107,7 @@ impl RegistryKey { &mut ret, ) }; - if err == ERROR_SUCCESS { + if err == ERROR_SUCCESS as LONG { Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) } else { Err(io::Error::from_raw_os_error(err as i32)) @@ -90,12 +130,12 @@ impl RegistryKey { let err = RegQueryValueExW( self.raw(), name.as_ptr(), - null_mut(), + 0 as *mut _, &mut kind, - null_mut(), + 0 as *mut _, &mut len, ); - if err != ERROR_SUCCESS { + if err != ERROR_SUCCESS as LONG { return Err(io::Error::from_raw_os_error(err as i32)); } if kind != REG_SZ { @@ -116,8 +156,8 @@ impl RegistryKey { let err = RegQueryValueExW( self.raw(), name.as_ptr(), - null_mut(), - null_mut(), + 0 as *mut _, + 0 as *mut _, v.as_mut_ptr() as *mut _, &mut len, ); @@ -125,7 +165,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 { + if err != ERROR_SUCCESS as LONG { return Err(io::Error::from_raw_os_error(err as i32)); } // The length is allowed to change, but should still be even, as @@ -148,7 +188,7 @@ impl RegistryKey { if !v.is_empty() && v[v.len() - 1] == 0 { v.pop(); } - Ok(OsString::from_wide(&v)) + return Ok(OsString::from_wide(&v)); } } } @@ -173,14 +213,14 @@ impl<'a> Iterator for Iter<'a> { i, v.as_mut_ptr(), &mut len, - null_mut(), - null_mut(), - null_mut(), - null_mut(), + 0 as *mut _, + 0 as *mut _, + 0 as *mut _, + 0 as *mut _, ); - if ret == ERROR_NO_MORE_ITEMS { + if ret == ERROR_NO_MORE_ITEMS as LONG { None - } else if ret != ERROR_SUCCESS { + } else if ret != ERROR_SUCCESS as LONG { 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/windows/setup_config.rs b/third_party/rust/cc/src/setup_config.rs similarity index 93% rename from third_party/rust/cc/src/windows/setup_config.rs rename to third_party/rust/cc/src/setup_config.rs index 5739ecf7d686..030051ca6963 100644 --- a/third_party/rust/cc/src/windows/setup_config.rs +++ b/third_party/rust/cc/src/setup_config.rs @@ -8,19 +8,19 @@ #![allow(bad_style)] #![allow(unused)] -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::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 std::{ - ffi::OsString, - ptr::{null, null_mut}, -}; +use crate::com::{BStr, ComPtr}; // 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(); + let mut s = null_mut(); 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(); + let mut s = null_mut(); 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(); + let mut s = null_mut(); 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(); + let mut s = null_mut(); 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(); + let mut s = null_mut(); 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/tool.rs b/third_party/rust/cc/src/tool.rs deleted file mode 100644 index 39131eaef5c8..000000000000 --- a/third_party/rust/cc/src/tool.rs +++ /dev/null @@ -1,400 +0,0 @@ -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/windows/vs_instances.rs b/third_party/rust/cc/src/vs_instances.rs similarity index 98% rename from third_party/rust/cc/src/windows/vs_instances.rs rename to third_party/rust/cc/src/vs_instances.rs index e863dadabbb5..31d3dd1470fa 100644 --- a/third_party/rust/cc/src/windows/vs_instances.rs +++ b/third_party/rust/cc/src/vs_instances.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use std::io::BufRead; use std::path::PathBuf; -use crate::windows::setup_config::{EnumSetupInstances, SetupInstance}; +use crate::setup_config::{EnumSetupInstances, SetupInstance}; pub enum VsInstance { Com(SetupInstance), diff --git a/third_party/rust/cc/src/windows/winapi.rs b/third_party/rust/cc/src/winapi.rs similarity index 62% rename from third_party/rust/cc/src/windows/winapi.rs rename to third_party/rust/cc/src/winapi.rs index 09965daa8901..8e04ce9cbd91 100644 --- a/third_party/rust/cc/src/windows/winapi.rs +++ b/third_party/rust/cc/src/winapi.rs @@ -5,19 +5,26 @@ // All files in the project carrying such notice may not be copied, modified, or distributed // except according to those terms. -#![allow(bad_style, clippy::upper_case_acronyms)] +#![allow(bad_style)] use std::os::raw; pub type wchar_t = u16; -pub use crate::windows::windows_sys::{FILETIME, GUID, HRESULT, SAFEARRAY}; - +pub type UINT = raw::c_uint; +pub type LPUNKNOWN = *mut IUnknown; 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; @@ -26,10 +33,75 @@ 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 { @@ -37,11 +109,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::windows::winapi::GUID = $crate::windows::winapi::GUID { - data1: $l, - data2: $w1, - data3: $w2, - data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], + pub const $name: $crate::winapi::GUID = $crate::winapi::GUID { + Data1: $l, + Data2: $w1, + Data3: $w2, + Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], }; }; } @@ -121,14 +193,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::windows::winapi::Interface for $interface { + impl $crate::winapi::Interface for $interface { #[inline] - 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], + fn uuidof() -> $crate::winapi::GUID { + $crate::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/mod.rs b/third_party/rust/cc/src/windows/mod.rs deleted file mode 100644 index 9b6f297e1a6d..000000000000 --- a/third_party/rust/cc/src/windows/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! 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/windows/windows_sys.rs b/third_party/rust/cc/src/windows/windows_sys.rs deleted file mode 100644 index 8b98ce97f8c6..000000000000 --- a/third_party/rust/cc/src/windows/windows_sys.rs +++ /dev/null @@ -1,223 +0,0 @@ -// 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/src/windows/find_tools.rs b/third_party/rust/cc/src/windows_registry.rs similarity index 72% rename from third_party/rust/cc/src/windows/find_tools.rs rename to third_party/rust/cc/src/windows_registry.rs index 9c6511555a0f..276688b03f50 100644 --- a/third_party/rust/cc/src/windows/find_tools.rs +++ b/third_party/rust/cc/src/windows_registry.rs @@ -11,8 +11,6 @@ //! 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; @@ -55,9 +53,6 @@ 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. @@ -76,16 +71,15 @@ pub fn find_tool(target: &str, tool: &str) -> Option { // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that // the tool is actually usable. - impl_::find_msvc_environment(tool, target) + return 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, @@ -97,6 +91,13 @@ 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 @@ -105,7 +106,7 @@ pub enum VsVers { /// generator. #[cfg(not(windows))] pub fn find_vs_version() -> Result { - Err("not windows".to_string()) + Err(format!("not windows")) } /// Documented above @@ -159,14 +160,10 @@ pub fn find_vs_version() -> Result { #[cfg(windows)] mod impl_ { - 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 crate::com; + use crate::registry::{RegistryKey, LOCAL_MACHINE}; + use crate::setup_config::SetupConfiguration; + use crate::vs_instances::{VsInstances, VswhereInstance}; use std::convert::TryFrom; use std::env; use std::ffi::OsString; @@ -177,27 +174,10 @@ mod impl_ { use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Once; use super::MSVC_FAMILY; use 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, @@ -205,75 +185,10 @@ 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(), @@ -287,7 +202,7 @@ mod impl_ { path, include, } = self; - let mut tool = Tool::with_family(tool, MSVC_FAMILY); + let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY); add_env(&mut tool, "LIB", libs); add_env(&mut tool, "PATH", path); add_env(&mut tool, "INCLUDE", include); @@ -297,14 +212,15 @@ 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. - fn is_vscmd_target(target: TargetArch<'_>) -> Option { + #[cfg(windows)] + fn is_vscmd_target(target: &str) -> 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.into() { - "x86_64" => "x64", - "aarch64" | "arm64ec" => "arm64", - "i686" | "i586" => "x86", - "thumbv7a" => "arm", + let arch = match target.split("-").next() { + Some("x86_64") => "x64", + Some("aarch64") => "arm64", + Some("i686") | Some("i586") => "x86", + Some("thumbv7a") => "arm", // An unrecognized arch. _ => return Some(false), }; @@ -312,7 +228,7 @@ mod impl_ { } /// Attempt to find the tool using environment variables set by vcvars. - pub fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option { + pub fn find_msvc_environment(tool: &str, target: &str) -> Option { // Early return if the environment doesn't contain a VC install. if env::var_os("VCINSTALLDIR").is_none() { return None; @@ -332,19 +248,16 @@ mod impl_ { .map(|p| p.join(tool)) .find(|p| p.exists()) }) - .map(|path| Tool::with_family(path, MSVC_FAMILY)) + .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) } } - fn find_msbuild_vs17(target: TargetArch<'_>) -> Option { + fn find_msbuild_vs17(target: &str) -> Option { find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17") } #[allow(bare_trait_objects)] - fn vs16plus_instances( - target: TargetArch<'_>, - version: &'static str, - ) -> Box> { + fn vs16plus_instances(target: &str, version: &'static str) -> Box> { let instances = if let Some(instances) = vs15plus_instances(target) { instances } else { @@ -362,11 +275,7 @@ mod impl_ { })) } - fn find_tool_in_vs16plus_path( - tool: &str, - target: TargetArch<'_>, - version: &'static str, - ) -> Option { + fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option { vs16plus_instances(target, version) .filter_map(|path| { let path = path.join(tool); @@ -374,10 +283,10 @@ mod impl_ { return None; } let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target == "x86_64" { + if target.contains("x86_64") { tool.env.push(("Platform".into(), "X64".into())); } - if target == "aarch64" || target == "arm64ec" { + if target.contains("aarch64") { tool.env.push(("Platform".into(), "ARM64".into())); } Some(tool) @@ -385,7 +294,7 @@ mod impl_ { .next() } - fn find_msbuild_vs16(target: TargetArch<'_>) -> Option { + fn find_msbuild_vs16(target: &str) -> Option { find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16") } @@ -401,7 +310,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: TargetArch<'_>) -> Option { + fn vs15plus_instances(target: &str) -> Option { vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) } @@ -414,7 +323,7 @@ mod impl_ { Some(VsInstances::ComBased(enum_setup_instances)) } - fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option { + fn vs15plus_instances_using_vswhere(target: &str) -> Option { let program_files_path: PathBuf = env::var("ProgramFiles(x86)") .or_else(|_| env::var("ProgramFiles")) .ok()? @@ -427,10 +336,11 @@ mod impl_ { return None; } - let tools_arch = match target.into() { + let arch = target.split('-').next().unwrap(); + let tools_arch = match arch { "i586" | "i686" | "x86_64" => Some("x86.x64"), "arm" | "thumbv7a" => Some("ARM"), - "aarch64" | "arm64ec" => Some("ARM64"), + "aarch64" => Some("ARM64"), _ => None, }; @@ -464,7 +374,7 @@ mod impl_ { .collect() } - pub fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option { + pub fn find_msvc_15plus(tool: &str, target: &str) -> Option { let iter = vs15plus_instances(target)?; iter.into_iter() .filter_map(|instance| { @@ -484,13 +394,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: TargetArch<'_>) -> Option { + fn find_tool_in_vs15_path(tool: &str, target: &str) -> 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(|path| path.is_file()), + .find(|ref path| path.is_file()), None => None, }; @@ -506,9 +416,10 @@ mod impl_ { path.map(|path| { let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target == "x86_64" { + if target.contains("x86_64") { tool.env.push(("Platform".into(), "X64".into())); - } else if target == "aarch64" { + } + if target.contains("aarch64") { tool.env.push(("Platform".into(), "ARM64".into())); } tool @@ -517,10 +428,10 @@ mod impl_ { fn tool_from_vs15plus_instance( tool: &str, - target: TargetArch<'_>, + target: &str, instance_path: &PathBuf, ) -> Option { - let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) = + let (root_path, bin_path, host_dylib_path, lib_path, include_path) = vs15plus_vc_paths(target, instance_path)?; let tool_path = bin_path.join(tool); if !tool_path.exists() { @@ -530,9 +441,6 @@ 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); @@ -547,97 +455,45 @@ mod impl_ { } fn vs15plus_vc_paths( - 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"] - } - } + 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", _ => 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 = host_path.join(target); + let bin_path = path + .join("bin") + .join(&format!("Host{}", host)) + .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 = 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 host_dylib_path = path + .join("bin") + .join(&format!("Host{}", host)) + .join(&host.to_lowercase()); + let lib_path = path.join("lib").join(&target); let include_path = path.join("include"); - Some(( - path, - bin_path, - host_dylib_path, - lib_path, - alt_lib_path, - include_path, - )) + Some((path, bin_path, host_dylib_path, lib_path, include_path)) } - 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)> { + fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { let atl_path = path.join("atlmfc"); let sub = lib_subdir(target)?; if atl_path.exists() { @@ -649,14 +505,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: TargetArch<'_>) -> Option { + pub fn find_msvc_14(tool: &str, target: &str) -> 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: TargetArch<'_>) -> Option<()> { + fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { let sub = lib_subdir(target)?; let (ucrt, ucrt_version) = get_ucrt_dir()?; @@ -699,7 +555,7 @@ mod impl_ { } // For MSVC 12 we need to find the Windows 8.1 SDK. - pub fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option { + pub fn find_msvc_12(tool: &str, target: &str) -> Option { let vcdir = get_vc_dir("12.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -715,7 +571,7 @@ mod impl_ { } // For MSVC 11 we need to find the Windows 8 SDK. - pub fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option { + pub fn find_msvc_11(tool: &str, target: &str) -> Option { let vcdir = get_vc_dir("11.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -740,7 +596,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: TargetArch<'_>) -> Option { + fn get_tool(tool: &str, path: &Path, target: &str) -> Option { bin_subdir(target) .into_iter() .map(|(sub, host)| { @@ -749,7 +605,7 @@ mod impl_ { path.join("bin").join(host), ) }) - .filter(|(path, _)| path.is_file()) + .filter(|&(ref path, _)| path.is_file()) .map(|(path, host)| { let mut tool = MsvcTool::new(path); tool.path.push(host); @@ -878,8 +734,9 @@ 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: TargetArch<'_>) -> Vec<(&'static str, &'static str)> { - match (target.into(), host_arch()) { + fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> { + let arch = target.split('-').next().unwrap(); + match (arch, host_arch()) { ("i586", X86) | ("i686", X86) => vec![("", "")], ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")], ("x86_64", X86) => vec![("x86_amd64", "")], @@ -890,19 +747,21 @@ mod impl_ { } } - fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { - match target.into() { + fn lib_subdir(target: &str) -> Option<&'static str> { + let arch = target.split('-').next().unwrap(); + match arch { "i586" | "i686" => Some("x86"), "x86_64" => Some("x64"), "arm" | "thumbv7a" => Some("arm"), - "aarch64" | "arm64ec" => Some("arm64"), + "aarch64" => Some("arm64"), _ => None, } } // MSVC's x86 libraries are not in a subfolder - fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> { - match target.into() { + fn vc_lib_subdir(target: &str) -> Option<&'static str> { + let arch = target.split('-').next().unwrap(); + match arch { "i586" | "i686" => Some(""), "x86_64" => Some("amd64"), "arm" | "thumbv7a" => Some("arm"), @@ -954,7 +813,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, @@ -972,19 +831,19 @@ mod impl_ { pub fn has_msbuild_version(version: &str) -> bool { match version { "17.0" => { - find_msbuild_vs17(TargetArch("x86_64")).is_some() - || find_msbuild_vs17(TargetArch("i686")).is_some() - || find_msbuild_vs17(TargetArch("aarch64")).is_some() + 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() } "16.0" => { - find_msbuild_vs16(TargetArch("x86_64")).is_some() - || find_msbuild_vs16(TargetArch("i686")).is_some() - || find_msbuild_vs16(TargetArch("aarch64")).is_some() + 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() } "15.0" => { - find_msbuild_vs15(TargetArch("x86_64")).is_some() - || find_msbuild_vs15(TargetArch("i686")).is_some() - || find_msbuild_vs15(TargetArch("aarch64")).is_some() + 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() } "12.0" | "14.0" => LOCAL_MACHINE .open(&OsString::from(format!( @@ -996,20 +855,18 @@ mod impl_ { } } - pub fn find_devenv(target: TargetArch<'_>) -> Option { - find_devenv_vs15(target) + pub fn find_devenv(target: &str) -> Option { + find_devenv_vs15(&target) } - fn find_devenv_vs15(target: TargetArch<'_>) -> Option { + fn find_devenv_vs15(target: &str) -> 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: TargetArch<'_>) -> Option { + pub fn find_msbuild(target: &str) -> Option { // VS 15 (2017) changed how to locate msbuild - if let Some(r) = find_msbuild_vs17(target) { - Some(r) - } else if let Some(r) = find_msbuild_vs16(target) { + if let Some(r) = find_msbuild_vs16(target) { return Some(r); } else if let Some(r) = find_msbuild_vs15(target) { return Some(r); @@ -1018,11 +875,11 @@ mod impl_ { } } - fn find_msbuild_vs15(target: TargetArch<'_>) -> Option { + fn find_msbuild_vs15(target: &str) -> Option { find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) } - fn find_old_msbuild(target: TargetArch<'_>) -> Option { + fn find_old_msbuild(target: &str) -> Option { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE .open(key.as_ref()) @@ -1034,7 +891,7 @@ mod impl_ { let mut path = PathBuf::from(path); path.push("MSBuild.exe"); let mut tool = Tool::with_family(path, MSVC_FAMILY); - if target == "x86_64" { + if target.contains("x86_64") { tool.env.push(("Platform".into(), "X64".into())); } tool diff --git a/third_party/rust/cc/tests/cc_env.rs b/third_party/rust/cc/tests/cc_env.rs new file mode 100644 index 000000000000..43eb689f0fbf --- /dev/null +++ b/third_party/rust/cc/tests/cc_env.rs @@ -0,0 +1,118 @@ +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 new file mode 100644 index 000000000000..caec6ea4ed4c --- /dev/null +++ b/third_party/rust/cc/tests/cflags.rs @@ -0,0 +1,15 @@ +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 new file mode 100644 index 000000000000..c524c7da4e9f --- /dev/null +++ b/third_party/rust/cc/tests/cxxflags.rs @@ -0,0 +1,15 @@ +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 new file mode 100644 index 000000000000..f3c04405a3f7 --- /dev/null +++ b/third_party/rust/cc/tests/support/mod.rs @@ -0,0 +1,172 @@ +#![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 new file mode 100644 index 000000000000..161abd8ab7e1 --- /dev/null +++ b/third_party/rust/cc/tests/test.rs @@ -0,0 +1,461 @@ +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"); +}