зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1882202 - Upgrade the cc crate to 1.0.88. r=emilio,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D202771
This commit is contained in:
Родитель
f909cd37c4
Коммит
2f7038b9d9
|
@ -646,11 +646,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -4754,6 +4754,12 @@ user-id = 6741 # Alice Ryhl (Darksonn)
|
|||
start = "2021-01-11"
|
||||
end = "2024-05-05"
|
||||
|
||||
[[trusted.cc]]
|
||||
criteria = "safe-to-deploy"
|
||||
user-id = 2915 # Amanieu d'Antras (Amanieu)
|
||||
start = "2024-02-20"
|
||||
end = "2025-02-26"
|
||||
|
||||
[[trusted.clap]]
|
||||
criteria = "safe-to-deploy"
|
||||
user-id = 6743 # Ed Page (epage)
|
||||
|
|
|
@ -71,6 +71,13 @@ user-id = 6741
|
|||
user-login = "Darksonn"
|
||||
user-name = "Alice Ryhl"
|
||||
|
||||
[[publisher.cc]]
|
||||
version = "1.0.88"
|
||||
when = "2024-02-25"
|
||||
user-id = 2915
|
||||
user-login = "Amanieu"
|
||||
user-name = "Amanieu d'Antras"
|
||||
|
||||
[[publisher.cexpr]]
|
||||
version = "0.6.0"
|
||||
when = "2021-10-11"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.lock":"23c26d62ba5114f5ac6e7ffa3ea233cea77e5cb7f98d9f056f40fe2c49971f67","Cargo.toml":"fd4b39488866b6717476fadc460ff91c89511628080769516eec452c0def8bc7","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"58af5106352aafa62175a90f8a5f25fa114028bf909220dc0735d79745999ec1","src/bin/gcc-shim.rs":"b77907875029494b6288841c3aed2e4939ed40708c7f597fca5c9e2570490ca6","src/com.rs":"29d0dee08a656ab1a4cc3e5fe24542e0fab5c1373cbc9b05059f7572cf9b8313","src/lib.rs":"e0cc228db97675d6a0d86b219a20e9e48925a1ccbfd9e9fd038ccf6ef129957e","src/registry.rs":"98ae2b71781acc49297e5544fa0cf059f735636f8f1338edef8dbf7232443945","src/setup_config.rs":"72deaf1927c0b713fd5c2b2d5b8f0ea3a303a00fda1579427895cac26a94122d","src/vs_instances.rs":"2d3f8278a803b0e7052f4eeb1979b29f963dd0143f4458e2cb5f33c4e5f0963b","src/winapi.rs":"e128e95b2d39ae7a02f54a7e25d33c488c14759b9f1a50a449e10545856950c3","src/windows_registry.rs":"c0340379c1f540cf96f45bbd4cf8fc28db555826f30ac937b75b87e4377b716b","tests/cc_env.rs":"e02b3b0824ad039b47e4462c5ef6dbe6c824c28e7953af94a0f28f7b5158042e","tests/cflags.rs":"57f06eb5ce1557e5b4a032d0c4673e18fbe6f8d26c1deb153126e368b96b41b3","tests/cxxflags.rs":"c2c6c6d8a0d7146616fa1caed26876ee7bc9fcfffd525eb4743593cade5f3371","tests/support/mod.rs":"a3c8d116973bb16066bf6ec4de5143183f97de7aad085d85f8118a2eaac3e1e0","tests/test.rs":"61fb35ae6dd5cf506ada000bdd82c92e9f8eac9cc053b63e83d3f897436fbf8f"},"package":"a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"}
|
||||
{"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"}
|
|
@ -1,110 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -11,10 +11,15 @@
|
|||
|
||||
[package]
|
||||
edition = "2018"
|
||||
rust-version = "1.53"
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
version = "1.0.88"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
exclude = ["/.github"]
|
||||
exclude = [
|
||||
"/.github",
|
||||
"tests",
|
||||
"src/bin",
|
||||
]
|
||||
description = """
|
||||
A build-time dependency for Cargo build scripts to assist in invoking the native
|
||||
C compiler to compile native C code into a static archive to be linked into Rust
|
||||
|
@ -28,12 +33,13 @@ categories = ["development-tools::build-utils"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/cc-rs"
|
||||
|
||||
[dependencies.jobserver]
|
||||
version = "0.1.16"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.tempfile]
|
||||
version = "3"
|
||||
|
||||
[features]
|
||||
parallel = ["jobserver"]
|
||||
parallel = ["libc"]
|
||||
|
||||
[target."cfg(unix)".dependencies.libc]
|
||||
version = "0.2.62"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
|
|
@ -1,209 +1,13 @@
|
|||
# cc-rs
|
||||
|
||||
A library to compile C/C++/assembly into a Rust library/application.
|
||||
A library for [Cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
|
||||
to compile a set of C/C++/assembly/CUDA files into a static archive for Cargo
|
||||
to link into the crate being built. This crate does not compile code itself;
|
||||
it calls out to the default compiler for the platform. This crate will
|
||||
automatically detect situations such as cross compilation and
|
||||
various environment variables and will build code appropriately.
|
||||
|
||||
[Documentation](https://docs.rs/cc)
|
||||
|
||||
A simple library meant to be used as a build dependency with Cargo packages in
|
||||
order to build a set of C/C++ files into a static archive. This crate calls out
|
||||
to the most relevant compiler for a platform, for example using `cl` on MSVC.
|
||||
|
||||
## Using cc-rs
|
||||
|
||||
First, you'll want to both add a build script for your crate (`build.rs`) and
|
||||
also add this crate to your `Cargo.toml` via:
|
||||
|
||||
```toml
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
```
|
||||
|
||||
Next up, you'll want to write a build script like so:
|
||||
|
||||
```rust,no_run
|
||||
// build.rs
|
||||
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("foo.c")
|
||||
.file("bar.c")
|
||||
.compile("foo");
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! Running `cargo build` should take care of the rest and your Rust
|
||||
application will now have the C files `foo.c` and `bar.c` compiled into a file
|
||||
named `libfoo.a`. If the C files contain
|
||||
|
||||
```c
|
||||
void foo_function(void) { ... }
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```c
|
||||
int32_t bar_function(int32_t x) { ... }
|
||||
```
|
||||
|
||||
you can call them from Rust by declaring them in
|
||||
your Rust code like so:
|
||||
|
||||
```rust,no_run
|
||||
extern {
|
||||
fn foo_function();
|
||||
fn bar_function(x: i32) -> i32;
|
||||
}
|
||||
|
||||
pub fn call() {
|
||||
unsafe {
|
||||
foo_function();
|
||||
bar_function(42);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
See [the Rustonomicon](https://doc.rust-lang.org/nomicon/ffi.html) for more details.
|
||||
|
||||
## External configuration via environment variables
|
||||
|
||||
To control the programs and flags used for building, the builder can set a
|
||||
number of different environment variables.
|
||||
|
||||
* `CFLAGS` - a series of space separated flags passed to compilers. Note that
|
||||
individual flags cannot currently contain spaces, so doing
|
||||
something like: `-L=foo\ bar` is not possible.
|
||||
* `CC` - the actual C compiler used. Note that this is used as an exact
|
||||
executable name, so (for example) no extra flags can be passed inside
|
||||
this variable, and the builder must ensure that there aren't any
|
||||
trailing spaces. This compiler must understand the `-c` flag. For
|
||||
certain `TARGET`s, it also is assumed to know about other flags (most
|
||||
common is `-fPIC`).
|
||||
* `AR` - the `ar` (archiver) executable to use to build the static library.
|
||||
* `CRATE_CC_NO_DEFAULTS` - the default compiler flags may cause conflicts in some cross compiling scenarios. Setting this variable will disable the generation of default compiler flags.
|
||||
* `CXX...` - see [C++ Support](#c-support).
|
||||
|
||||
Each of these variables can also be supplied with certain prefixes and suffixes,
|
||||
in the following prioritized order:
|
||||
|
||||
1. `<var>_<target>` - for example, `CC_x86_64-unknown-linux-gnu`
|
||||
2. `<var>_<target_with_underscores>` - for example, `CC_x86_64_unknown_linux_gnu`
|
||||
3. `<build-kind>_<var>` - for example, `HOST_CC` or `TARGET_CFLAGS`
|
||||
4. `<var>` - a plain `CC`, `AR` as above.
|
||||
|
||||
If none of these variables exist, cc-rs uses built-in defaults
|
||||
|
||||
In addition to the above optional environment variables, `cc-rs` has some
|
||||
functions with hard requirements on some variables supplied by [cargo's
|
||||
build-script driver][cargo] that it has the `TARGET`, `OUT_DIR`, `OPT_LEVEL`,
|
||||
and `HOST` variables.
|
||||
|
||||
[cargo]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script
|
||||
|
||||
## Optional features
|
||||
|
||||
### Parallel
|
||||
|
||||
Currently cc-rs supports parallel compilation (think `make -jN`) but this
|
||||
feature is turned off by default. To enable cc-rs to compile C/C++ in parallel,
|
||||
you can change your dependency to:
|
||||
|
||||
```toml
|
||||
[build-dependencies]
|
||||
cc = { version = "1.0", features = ["parallel"] }
|
||||
```
|
||||
|
||||
By default cc-rs will limit parallelism to `$NUM_JOBS`, or if not present it
|
||||
will limit it to the number of cpus on the machine. If you are using cargo,
|
||||
use `-jN` option of `build`, `test` and `run` commands as `$NUM_JOBS`
|
||||
is supplied by cargo.
|
||||
|
||||
## Compile-time Requirements
|
||||
|
||||
To work properly this crate needs access to a C compiler when the build script
|
||||
is being run. This crate does not ship a C compiler with it. The compiler
|
||||
required varies per platform, but there are three broad categories:
|
||||
|
||||
* Unix platforms require `cc` to be the C compiler. This can be found by
|
||||
installing cc/clang on Linux distributions and Xcode on macOS, for example.
|
||||
* Windows platforms targeting MSVC (e.g. your target triple ends in `-msvc`)
|
||||
require `cl.exe` to be available and in `PATH`. This is typically found in
|
||||
standard Visual Studio installations and the `PATH` can be set up by running
|
||||
the appropriate developer tools shell.
|
||||
* Windows platforms targeting MinGW (e.g. your target triple ends in `-gnu`)
|
||||
require `cc` to be available in `PATH`. We recommend the
|
||||
[MinGW-w64](https://www.mingw-w64.org/) distribution, which is using the
|
||||
[Win-builds](http://win-builds.org/) installation system.
|
||||
You may also acquire it via
|
||||
[MSYS2](https://www.msys2.org/), as explained [here][msys2-help]. Make sure
|
||||
to install the appropriate architecture corresponding to your installation of
|
||||
rustc. GCC from older [MinGW](http://www.mingw.org/) project is compatible
|
||||
only with 32-bit rust compiler.
|
||||
|
||||
[msys2-help]: https://github.com/rust-lang/rust#building-on-windows
|
||||
|
||||
## C++ support
|
||||
|
||||
`cc-rs` supports C++ libraries compilation by using the `cpp` method on
|
||||
`Build`:
|
||||
|
||||
```rust,no_run
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.cpp(true) // Switch to C++ library compilation.
|
||||
.file("foo.cpp")
|
||||
.compile("libfoo.a");
|
||||
}
|
||||
```
|
||||
|
||||
For C++ libraries, the `CXX` and `CXXFLAGS` environment variables are used instead of `CC` and `CFLAGS`.
|
||||
|
||||
The C++ standard library may be linked to the crate target. By default it's `libc++` for macOS, FreeBSD, and OpenBSD, `libc++_shared` for Android, nothing for MSVC, and `libstdc++` for anything else. It can be changed in one of two ways:
|
||||
|
||||
1. by using the `cpp_link_stdlib` method on `Build`:
|
||||
```rust,no-run
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.file("foo.cpp")
|
||||
.cpp_link_stdlib("stdc++") // use libstdc++
|
||||
.compile("libfoo.a");
|
||||
}
|
||||
```
|
||||
2. by setting the `CXXSTDLIB` environment variable.
|
||||
|
||||
In particular, for Android you may want to [use `c++_static` if you have at most one shared library](https://developer.android.com/ndk/guides/cpp-support).
|
||||
|
||||
Remember that C++ does name mangling so `extern "C"` might be required to enable Rust linker to find your functions.
|
||||
|
||||
## CUDA C++ support
|
||||
|
||||
`cc-rs` also supports compiling CUDA C++ libraries by using the `cuda` method
|
||||
on `Build` (currently for GNU/Clang toolchains only):
|
||||
|
||||
```rust,no_run
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
// Switch to CUDA C++ library compilation using NVCC.
|
||||
.cuda(true)
|
||||
.cudart("static")
|
||||
// Generate code for Maxwell (GTX 970, 980, 980 Ti, Titan X).
|
||||
.flag("-gencode").flag("arch=compute_52,code=sm_52")
|
||||
// Generate code for Maxwell (Jetson TX1).
|
||||
.flag("-gencode").flag("arch=compute_53,code=sm_53")
|
||||
// Generate code for Pascal (GTX 1070, 1080, 1080 Ti, Titan Xp).
|
||||
.flag("-gencode").flag("arch=compute_61,code=sm_61")
|
||||
// Generate code for Pascal (Tesla P100).
|
||||
.flag("-gencode").flag("arch=compute_60,code=sm_60")
|
||||
// Generate code for Pascal (Jetson TX2).
|
||||
.flag("-gencode").flag("arch=compute_62,code=sm_62")
|
||||
.file("bar.cu")
|
||||
.compile("libbar.a");
|
||||
}
|
||||
```
|
||||
Refer to the [documentation](https://docs.rs/cc) for detailed usage instructions.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
#![cfg_attr(test, allow(dead_code))]
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let mut args = env::args();
|
||||
let program = args.next().expect("Unexpected empty args");
|
||||
|
||||
let out_dir = PathBuf::from(
|
||||
env::var_os("GCCTEST_OUT_DIR").expect(&format!("{}: GCCTEST_OUT_DIR not found", program)),
|
||||
);
|
||||
|
||||
// Find the first nonexistent candidate file to which the program's args can be written.
|
||||
for i in 0.. {
|
||||
let candidate = &out_dir.join(format!("out{}", i));
|
||||
|
||||
// If the file exists, commands have already run. Try again.
|
||||
if candidate.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a file and record the args passed to the command.
|
||||
let mut f = File::create(candidate).expect(&format!(
|
||||
"{}: can't create candidate: {}",
|
||||
program,
|
||||
candidate.to_string_lossy()
|
||||
));
|
||||
for arg in args {
|
||||
writeln!(f, "{}", arg).expect(&format!(
|
||||
"{}: can't write to candidate: {}",
|
||||
program,
|
||||
candidate.to_string_lossy()
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Create a file used by some tests.
|
||||
let path = &out_dir.join("libfoo.a");
|
||||
File::create(path).expect(&format!(
|
||||
"{}: can't create libfoo.a: {}",
|
||||
program,
|
||||
path.to_string_lossy()
|
||||
));
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
//! Miscellaneous helpers for running commands
|
||||
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
ffi::OsString,
|
||||
fmt::Display,
|
||||
fs,
|
||||
hash::Hasher,
|
||||
io::{self, Read, Write},
|
||||
path::Path,
|
||||
process::{Child, ChildStderr, Command, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{Error, ErrorKind, Object};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct CargoOutput {
|
||||
pub(crate) metadata: bool,
|
||||
pub(crate) warnings: bool,
|
||||
pub(crate) debug: bool,
|
||||
checked_dbg_var: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CargoOutput {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
metadata: true,
|
||||
warnings: true,
|
||||
debug: std::env::var_os("CC_ENABLE_DEBUG_OUTPUT").is_some(),
|
||||
checked_dbg_var: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn print_metadata(&self, s: &dyn Display) {
|
||||
if self.metadata {
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn print_warning(&self, arg: &dyn Display) {
|
||||
if self.warnings {
|
||||
println!("cargo:warning={}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn print_debug(&self, arg: &dyn Display) {
|
||||
if self.metadata && !self.checked_dbg_var.load(Ordering::Relaxed) {
|
||||
self.checked_dbg_var.store(true, Ordering::Relaxed);
|
||||
println!("cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT");
|
||||
}
|
||||
if self.debug {
|
||||
println!("{}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
fn stdio_for_warnings(&self) -> Stdio {
|
||||
if self.warnings {
|
||||
Stdio::piped()
|
||||
} else {
|
||||
Stdio::null()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StderrForwarder {
|
||||
inner: Option<(ChildStderr, Vec<u8>)>,
|
||||
#[cfg(feature = "parallel")]
|
||||
is_non_blocking: bool,
|
||||
#[cfg(feature = "parallel")]
|
||||
bytes_available_failed: bool,
|
||||
}
|
||||
|
||||
const MIN_BUFFER_CAPACITY: usize = 100;
|
||||
|
||||
impl StderrForwarder {
|
||||
pub(crate) fn new(child: &mut Child) -> Self {
|
||||
Self {
|
||||
inner: child
|
||||
.stderr
|
||||
.take()
|
||||
.map(|stderr| (stderr, Vec::with_capacity(MIN_BUFFER_CAPACITY))),
|
||||
#[cfg(feature = "parallel")]
|
||||
is_non_blocking: false,
|
||||
#[cfg(feature = "parallel")]
|
||||
bytes_available_failed: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::uninit_vec)]
|
||||
fn forward_available(&mut self) -> bool {
|
||||
if let Some((stderr, buffer)) = self.inner.as_mut() {
|
||||
loop {
|
||||
let old_data_end = buffer.len();
|
||||
|
||||
// For non-blocking we check to see if there is data available, so we should try to
|
||||
// read at least that much. For blocking, always read at least the minimum amount.
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let to_reserve = MIN_BUFFER_CAPACITY;
|
||||
#[cfg(feature = "parallel")]
|
||||
let to_reserve = if self.is_non_blocking && !self.bytes_available_failed {
|
||||
match crate::parallel::stderr::bytes_available(stderr) {
|
||||
#[cfg(windows)]
|
||||
Ok(0) => return false,
|
||||
#[cfg(unix)]
|
||||
Ok(0) => {
|
||||
// On Unix, depending on the implementation, we may sometimes get 0 in a
|
||||
// loop (either there is data available or the pipe is broken), so
|
||||
// continue with the non-blocking read anyway.
|
||||
MIN_BUFFER_CAPACITY
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Err(_) => {
|
||||
// On Windows, if we get an error then the pipe is broken, so flush
|
||||
// the buffer and bail.
|
||||
if !buffer.is_empty() {
|
||||
write_warning(&buffer[..]);
|
||||
}
|
||||
self.inner = None;
|
||||
return true;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Err(_) => {
|
||||
// On Unix, depending on the implementation, we may get spurious
|
||||
// errors so make a note not to use bytes_available again and try
|
||||
// the non-blocking read anyway.
|
||||
self.bytes_available_failed = true;
|
||||
MIN_BUFFER_CAPACITY
|
||||
}
|
||||
Ok(bytes_available) => MIN_BUFFER_CAPACITY.max(bytes_available),
|
||||
}
|
||||
} else {
|
||||
MIN_BUFFER_CAPACITY
|
||||
};
|
||||
buffer.reserve(to_reserve);
|
||||
|
||||
// SAFETY: 1) the length is set to the capacity, so we are never using memory beyond
|
||||
// the underlying buffer and 2) we always call `truncate` below to set the len back
|
||||
// to the initialized data.
|
||||
unsafe {
|
||||
buffer.set_len(buffer.capacity());
|
||||
}
|
||||
match stderr.read(&mut buffer[old_data_end..]) {
|
||||
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
// No data currently, yield back.
|
||||
buffer.truncate(old_data_end);
|
||||
return false;
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
|
||||
// Interrupted, try again.
|
||||
buffer.truncate(old_data_end);
|
||||
}
|
||||
Ok(0) | Err(_) => {
|
||||
// End of stream: flush remaining data and bail.
|
||||
if old_data_end > 0 {
|
||||
write_warning(&buffer[..old_data_end]);
|
||||
}
|
||||
self.inner = None;
|
||||
return true;
|
||||
}
|
||||
Ok(bytes_read) => {
|
||||
buffer.truncate(old_data_end + bytes_read);
|
||||
let mut consumed = 0;
|
||||
for line in buffer.split_inclusive(|&b| b == b'\n') {
|
||||
// Only forward complete lines, leave the rest in the buffer.
|
||||
if let Some((b'\n', line)) = line.split_last() {
|
||||
consumed += line.len() + 1;
|
||||
write_warning(line);
|
||||
}
|
||||
}
|
||||
buffer.drain(..consumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
pub(crate) fn set_non_blocking(&mut self) -> Result<(), Error> {
|
||||
assert!(!self.is_non_blocking);
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some((stderr, _)) = self.inner.as_ref() {
|
||||
crate::parallel::stderr::set_non_blocking(stderr)?;
|
||||
}
|
||||
|
||||
self.is_non_blocking = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
fn forward_all(&mut self) {
|
||||
while !self.forward_available() {}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
fn forward_all(&mut self) {
|
||||
let forward_result = self.forward_available();
|
||||
assert!(forward_result, "Should have consumed all data");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_warning(line: &[u8]) {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
stdout.write_all(b"cargo:warning=").unwrap();
|
||||
stdout.write_all(line).unwrap();
|
||||
stdout.write_all(b"\n").unwrap();
|
||||
}
|
||||
|
||||
fn wait_on_child(
|
||||
cmd: &Command,
|
||||
program: &str,
|
||||
child: &mut Child,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Result<(), Error> {
|
||||
StderrForwarder::new(child).forward_all();
|
||||
|
||||
let status = match child.wait() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::ToolExecError,
|
||||
format!(
|
||||
"Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
|
||||
cmd, program, e
|
||||
),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
cargo_output.print_debug(&status);
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(
|
||||
ErrorKind::ToolExecError,
|
||||
format!(
|
||||
"Command {:?} with args {:?} did not execute successfully (status code {}).",
|
||||
cmd, program, status
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the destination object path for each file in the input source files,
|
||||
/// and store them in the output Object.
|
||||
pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<Object>, Error> {
|
||||
let mut objects = Vec::with_capacity(files.len());
|
||||
for file in files {
|
||||
let basename = file
|
||||
.file_name()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
ErrorKind::InvalidArgument,
|
||||
"No file_name for object file path!",
|
||||
)
|
||||
})?
|
||||
.to_string_lossy();
|
||||
let dirname = file
|
||||
.parent()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
ErrorKind::InvalidArgument,
|
||||
"No parent for object file path!",
|
||||
)
|
||||
})?
|
||||
.to_string_lossy();
|
||||
|
||||
// Hash the dirname. This should prevent conflicts if we have multiple
|
||||
// object files with the same filename in different subfolders.
|
||||
let mut hasher = hash_map::DefaultHasher::new();
|
||||
hasher.write(dirname.to_string().as_bytes());
|
||||
let obj = dst
|
||||
.join(format!("{:016x}-{}", hasher.finish(), basename))
|
||||
.with_extension("o");
|
||||
|
||||
match obj.parent() {
|
||||
Some(s) => fs::create_dir_all(s)?,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidArgument,
|
||||
"dst is an invalid path with no parent",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
objects.push(Object::new(file.to_path_buf(), obj));
|
||||
}
|
||||
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
pub(crate) fn run(
|
||||
cmd: &mut Command,
|
||||
program: &str,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Result<(), Error> {
|
||||
let mut child = spawn(cmd, program, cargo_output)?;
|
||||
wait_on_child(cmd, program, &mut child, cargo_output)
|
||||
}
|
||||
|
||||
pub(crate) fn run_output(
|
||||
cmd: &mut Command,
|
||||
program: &str,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
cmd.stdout(Stdio::piped());
|
||||
|
||||
let mut child = spawn(cmd, program, cargo_output)?;
|
||||
|
||||
let mut stdout = vec![];
|
||||
child
|
||||
.stdout
|
||||
.take()
|
||||
.unwrap()
|
||||
.read_to_end(&mut stdout)
|
||||
.unwrap();
|
||||
|
||||
wait_on_child(cmd, program, &mut child, cargo_output)?;
|
||||
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
pub(crate) fn spawn(
|
||||
cmd: &mut Command,
|
||||
program: &str,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Result<Child, Error> {
|
||||
struct ResetStderr<'cmd>(&'cmd mut Command);
|
||||
|
||||
impl Drop for ResetStderr<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Reset stderr to default to release pipe_writer so that print thread will
|
||||
// not block forever.
|
||||
self.0.stderr(Stdio::inherit());
|
||||
}
|
||||
}
|
||||
|
||||
cargo_output.print_debug(&format_args!("running: {:?}", cmd));
|
||||
|
||||
let cmd = ResetStderr(cmd);
|
||||
let child = cmd.0.stderr(cargo_output.stdio_for_warnings()).spawn();
|
||||
match child {
|
||||
Ok(child) => Ok(child),
|
||||
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
let extra = if cfg!(windows) {
|
||||
" (see https://github.com/rust-lang/cc-rs#compile-time-requirements \
|
||||
for help)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
Err(Error::new(
|
||||
ErrorKind::ToolNotFound,
|
||||
format!("Failed to find tool. Is `{}` installed?{}", program, extra),
|
||||
))
|
||||
}
|
||||
Err(e) => Err(Error::new(
|
||||
ErrorKind::ToolExecError,
|
||||
format!(
|
||||
"Command {:?} with args {:?} failed to start: {:?}",
|
||||
cmd.0, program, e
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn command_add_output_file(
|
||||
cmd: &mut Command,
|
||||
dst: &Path,
|
||||
cuda: bool,
|
||||
msvc: bool,
|
||||
clang: bool,
|
||||
gnu: bool,
|
||||
is_asm: bool,
|
||||
is_arm: bool,
|
||||
) {
|
||||
if msvc && !clang && !gnu && !cuda && !(is_asm && is_arm) {
|
||||
let mut s = OsString::from("-Fo");
|
||||
s.push(dst);
|
||||
cmd.arg(s);
|
||||
} else {
|
||||
cmd.arg("-o").arg(dst);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
pub(crate) fn try_wait_on_child(
|
||||
cmd: &Command,
|
||||
program: &str,
|
||||
child: &mut Child,
|
||||
stdout: &mut dyn io::Write,
|
||||
stderr_forwarder: &mut StderrForwarder,
|
||||
) -> Result<Option<()>, Error> {
|
||||
stderr_forwarder.forward_available();
|
||||
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
stderr_forwarder.forward_all();
|
||||
|
||||
let _ = writeln!(stdout, "{}", status);
|
||||
|
||||
if status.success() {
|
||||
Ok(Some(()))
|
||||
} else {
|
||||
Err(Error::new(
|
||||
ErrorKind::ToolExecError,
|
||||
format!(
|
||||
"Command {:?} with args {:?} did not execute successfully (status code {}).",
|
||||
cmd, program, status
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => {
|
||||
stderr_forwarder.forward_all();
|
||||
Err(Error::new(
|
||||
ErrorKind::ToolExecError,
|
||||
format!(
|
||||
"Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
|
||||
cmd, program, e
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,118 @@
|
|||
use std::{
|
||||
cell::Cell,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
ptr,
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||
// Cloning just returns a new no-op raw waker
|
||||
|_| NOOP_RAW_WAKER,
|
||||
// `wake` does nothing
|
||||
|_| {},
|
||||
// `wake_by_ref` does nothing
|
||||
|_| {},
|
||||
// Dropping does nothing as we don't allocate anything
|
||||
|_| {},
|
||||
);
|
||||
const NOOP_RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE);
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct YieldOnce(bool);
|
||||
|
||||
impl Future for YieldOnce {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
|
||||
let flag = &mut std::pin::Pin::into_inner(self).0;
|
||||
if !*flag {
|
||||
*flag = true;
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the futures and return when they are all done.
|
||||
///
|
||||
/// Here we use our own homebrew async executor since cc is used in the build
|
||||
/// script of many popular projects, pulling in additional dependencies would
|
||||
/// significantly slow down its compilation.
|
||||
pub(crate) fn block_on<Fut1, Fut2>(
|
||||
mut fut1: Fut1,
|
||||
mut fut2: Fut2,
|
||||
has_made_progress: &Cell<bool>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
Fut1: Future<Output = Result<(), Error>>,
|
||||
Fut2: Future<Output = Result<(), Error>>,
|
||||
{
|
||||
// Shadows the future so that it can never be moved and is guaranteed
|
||||
// to be pinned.
|
||||
//
|
||||
// The same trick used in `pin!` macro.
|
||||
//
|
||||
// TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!`
|
||||
let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) });
|
||||
let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) });
|
||||
|
||||
// TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version
|
||||
// which it is stablised, replace this with `Waker::noop`.
|
||||
let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
|
||||
let mut context = Context::from_waker(&waker);
|
||||
|
||||
let mut backoff_cnt = 0;
|
||||
|
||||
loop {
|
||||
has_made_progress.set(false);
|
||||
|
||||
if let Some(fut) = fut2.as_mut() {
|
||||
if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
|
||||
fut2 = None;
|
||||
res?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fut) = fut1.as_mut() {
|
||||
if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
|
||||
fut1 = None;
|
||||
res?;
|
||||
}
|
||||
}
|
||||
|
||||
if fut1.is_none() && fut2.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !has_made_progress.get() {
|
||||
if backoff_cnt > 3 {
|
||||
// We have yielded at least three times without making'
|
||||
// any progress, so we will sleep for a while.
|
||||
let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10));
|
||||
thread::sleep(duration);
|
||||
} else {
|
||||
// Given that we spawned a lot of compilation tasks, it is unlikely
|
||||
// that OS cannot find other ready task to execute.
|
||||
//
|
||||
// If all of them are done, then we will yield them and spawn more,
|
||||
// or simply return.
|
||||
//
|
||||
// Thus this will not be turned into a busy-wait loop and it will not
|
||||
// waste CPU resource.
|
||||
thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
backoff_cnt = if has_made_progress.get() {
|
||||
0
|
||||
} else {
|
||||
backoff_cnt + 1
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
use std::{mem::MaybeUninit, sync::Once};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[path = "unix.rs"]
|
||||
mod sys;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[path = "windows.rs"]
|
||||
mod sys;
|
||||
|
||||
pub(crate) struct JobToken();
|
||||
|
||||
impl Drop for JobToken {
|
||||
fn drop(&mut self) {
|
||||
match JobTokenServer::new() {
|
||||
JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(),
|
||||
JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum JobTokenServer {
|
||||
Inherited(inherited_jobserver::JobServer),
|
||||
InProcess(inprocess_jobserver::JobServer),
|
||||
}
|
||||
|
||||
impl JobTokenServer {
|
||||
/// This function returns a static reference to the jobserver because
|
||||
/// - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might
|
||||
/// be closed by other jobserver users in the process) and better do it
|
||||
/// at the start of the program.
|
||||
/// - in case a jobserver cannot be created from env (e.g. it's not
|
||||
/// present), we will create a global in-process only jobserver
|
||||
/// that has to be static so that it will be shared by all cc
|
||||
/// compilation.
|
||||
fn new() -> &'static Self {
|
||||
static INIT: Once = Once::new();
|
||||
static mut JOBSERVER: MaybeUninit<JobTokenServer> = MaybeUninit::uninit();
|
||||
|
||||
unsafe {
|
||||
INIT.call_once(|| {
|
||||
let server = inherited_jobserver::JobServer::from_env()
|
||||
.map(Self::Inherited)
|
||||
.unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new()));
|
||||
JOBSERVER = MaybeUninit::new(server);
|
||||
});
|
||||
// TODO: Poor man's assume_init_ref, as that'd require a MSRV of 1.55.
|
||||
&*JOBSERVER.as_ptr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ActiveJobTokenServer(&'static JobTokenServer);
|
||||
|
||||
impl ActiveJobTokenServer {
|
||||
pub(crate) fn new() -> Result<Self, Error> {
|
||||
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<Option<JobToken>, 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<usize>,
|
||||
}
|
||||
|
||||
impl JobServer {
|
||||
pub(super) unsafe fn from_env() -> Option<Self> {
|
||||
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<Option<JobToken>, 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<JobToken> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{self, File},
|
||||
io::{self, Read, Write},
|
||||
mem::ManuallyDrop,
|
||||
os::{raw::c_int, unix::prelude::*},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::parallel::stderr::{set_blocking, set_non_blocking};
|
||||
|
||||
pub(super) struct JobServerClient {
|
||||
read: File,
|
||||
write: Option<File>,
|
||||
}
|
||||
|
||||
impl JobServerClient {
|
||||
pub(super) unsafe fn open(var: &[u8]) -> Option<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Option<()>> {
|
||||
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<c_int> {
|
||||
if t == -1 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pipe(file: &File) -> Option<bool> {
|
||||
Some(file.metadata().ok()?.file_type().is_fifo())
|
||||
}
|
||||
|
||||
fn get_access_mode(file: &File) -> Option<c_int> {
|
||||
let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) };
|
||||
if ret == -1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ret & libc::O_ACCMODE)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
use std::{ffi::CString, io, ptr, str};
|
||||
|
||||
use crate::windows::windows_sys::{
|
||||
OpenSemaphoreA, ReleaseSemaphore, WaitForSingleObject, FALSE, HANDLE, SEMAPHORE_MODIFY_STATE,
|
||||
THREAD_SYNCHRONIZE, WAIT_ABANDONED, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
|
||||
};
|
||||
|
||||
pub(super) struct JobServerClient {
|
||||
sem: HANDLE,
|
||||
}
|
||||
|
||||
unsafe impl Sync for JobServerClient {}
|
||||
unsafe impl Send for JobServerClient {}
|
||||
|
||||
impl JobServerClient {
|
||||
pub(super) unsafe fn open(var: &[u8]) -> Option<Self> {
|
||||
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<Option<()>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
pub(crate) mod async_executor;
|
||||
pub(crate) mod job_token;
|
||||
pub(crate) mod stderr;
|
||||
|
||||
/// Remove all element in `vec` which `f(element)` returns `false`.
|
||||
///
|
||||
/// TODO: Remove this once the MSRV is bumped to v1.61
|
||||
pub(crate) fn retain_unordered_mut<T, F>(vec: &mut Vec<T>, mut f: F)
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
let mut i = 0;
|
||||
while i < vec.len() {
|
||||
if f(&mut vec[i]) {
|
||||
i += 1;
|
||||
} else {
|
||||
vec.swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/// Helpers functions for [ChildStderr].
|
||||
use std::{convert::TryInto, process::ChildStderr};
|
||||
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
#[cfg(all(not(unix), not(windows)))]
|
||||
compile_error!("Only unix and windows support non-blocking pipes! For other OSes, disable the parallel feature.");
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_flags(fd: std::os::unix::io::RawFd) -> Result<i32, Error> {
|
||||
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
|
||||
if flags == -1 {
|
||||
Err(Error::new(
|
||||
ErrorKind::IOError,
|
||||
format!(
|
||||
"Failed to get flags for pipe {}: {}",
|
||||
fd,
|
||||
std::io::Error::last_os_error()
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Ok(flags)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result<(), Error> {
|
||||
if unsafe { libc::fcntl(fd, libc::F_SETFL, flags) } == -1 {
|
||||
Err(Error::new(
|
||||
ErrorKind::IOError,
|
||||
format!(
|
||||
"Failed to set flags for pipe {}: {}",
|
||||
fd,
|
||||
std::io::Error::last_os_error()
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn set_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<usize, Error> {
|
||||
let mut bytes_available = 0;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::windows::windows_sys::PeekNamedPipe;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::ptr::null_mut;
|
||||
if unsafe {
|
||||
PeekNamedPipe(
|
||||
stderr.as_raw_handle(),
|
||||
null_mut(),
|
||||
0,
|
||||
null_mut(),
|
||||
&mut bytes_available,
|
||||
null_mut(),
|
||||
)
|
||||
} == 0
|
||||
{
|
||||
return Err(Error::new(
|
||||
ErrorKind::IOError,
|
||||
format!(
|
||||
"PeekNamedPipe failed with {}",
|
||||
std::io::Error::last_os_error()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::AsRawFd;
|
||||
if unsafe { libc::ioctl(stderr.as_raw_fd(), libc::FIONREAD, &mut bytes_available) } != 0 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::IOError,
|
||||
format!("ioctl failed with {}", std::io::Error::last_os_error()),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(bytes_available.try_into().unwrap())
|
||||
}
|
|
@ -0,0 +1,400 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use crate::command_helpers::{run_output, CargoOutput};
|
||||
|
||||
/// Configuration used to represent an invocation of a C compiler.
|
||||
///
|
||||
/// This can be used to figure out what compiler is in use, what the arguments
|
||||
/// to it are, and what the environment variables look like for the compiler.
|
||||
/// This can be used to further configure other build systems (e.g. forward
|
||||
/// along CC and/or CFLAGS) or the `to_command` method can be used to run the
|
||||
/// compiler itself.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Tool {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) cc_wrapper_path: Option<PathBuf>,
|
||||
pub(crate) cc_wrapper_args: Vec<OsString>,
|
||||
pub(crate) args: Vec<OsString>,
|
||||
pub(crate) env: Vec<(OsString, OsString)>,
|
||||
pub(crate) family: ToolFamily,
|
||||
pub(crate) cuda: bool,
|
||||
pub(crate) removed_args: Vec<OsString>,
|
||||
pub(crate) has_internal_target_arg: bool,
|
||||
}
|
||||
|
||||
impl Tool {
|
||||
pub(crate) fn new(
|
||||
path: PathBuf,
|
||||
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Self {
|
||||
Self::with_features(path, None, false, cached_compiler_family, cargo_output)
|
||||
}
|
||||
|
||||
pub(crate) fn with_clang_driver(
|
||||
path: PathBuf,
|
||||
clang_driver: Option<&str>,
|
||||
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Self {
|
||||
Self::with_features(
|
||||
path,
|
||||
clang_driver,
|
||||
false,
|
||||
cached_compiler_family,
|
||||
cargo_output,
|
||||
)
|
||||
}
|
||||
|
||||
#[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<HashMap<Box<Path>, ToolFamily>>,
|
||||
cargo_output: &CargoOutput,
|
||||
) -> Self {
|
||||
fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily {
|
||||
let mut cmd = Command::new(path);
|
||||
cmd.arg("--version");
|
||||
|
||||
let stdout = match run_output(
|
||||
&mut cmd,
|
||||
&path.to_string_lossy(),
|
||||
// tool detection issues should always be shown as warnings
|
||||
cargo_output,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o).ok())
|
||||
{
|
||||
Some(s) => s,
|
||||
None => {
|
||||
// --version failed. fallback to gnu
|
||||
cargo_output.print_warning(&format_args!("Failed to run: {:?}", cmd));
|
||||
return ToolFamily::Gnu;
|
||||
}
|
||||
};
|
||||
if stdout.contains("clang") {
|
||||
ToolFamily::Clang
|
||||
} else if stdout.contains("GCC") {
|
||||
ToolFamily::Gnu
|
||||
} else {
|
||||
// --version doesn't include clang for GCC
|
||||
cargo_output.print_warning(&format_args!(
|
||||
"Compiler version doesn't include clang or GCC: {:?}",
|
||||
cmd
|
||||
));
|
||||
ToolFamily::Gnu
|
||||
}
|
||||
}
|
||||
let detect_family = |path: &Path| -> ToolFamily {
|
||||
if let Some(family) = cached_compiler_family.lock().unwrap().get(path) {
|
||||
return *family;
|
||||
}
|
||||
|
||||
let family = detect_family_inner(path, cargo_output);
|
||||
cached_compiler_family
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(path.into(), family);
|
||||
family
|
||||
};
|
||||
|
||||
// Try to detect family of the tool from its name, falling back to Gnu.
|
||||
let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) {
|
||||
if fname.contains("clang-cl") {
|
||||
ToolFamily::Msvc { clang_cl: true }
|
||||
} else if fname.ends_with("cl") || fname == "cl.exe" {
|
||||
ToolFamily::Msvc { clang_cl: false }
|
||||
} else if fname.contains("clang") {
|
||||
match clang_driver {
|
||||
Some("cl") => ToolFamily::Msvc { clang_cl: true },
|
||||
_ => ToolFamily::Clang,
|
||||
}
|
||||
} else {
|
||||
detect_family(&path)
|
||||
}
|
||||
} else {
|
||||
detect_family(&path)
|
||||
};
|
||||
|
||||
Tool {
|
||||
path,
|
||||
cc_wrapper_path: None,
|
||||
cc_wrapper_args: Vec::new(),
|
||||
args: Vec::new(),
|
||||
env: Vec::new(),
|
||||
family,
|
||||
cuda,
|
||||
removed_args: Vec::new(),
|
||||
has_internal_target_arg: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an argument to be stripped from the final command arguments.
|
||||
pub(crate) fn remove_arg(&mut self, flag: OsString) {
|
||||
self.removed_args.push(flag);
|
||||
}
|
||||
|
||||
/// Push an "exotic" flag to the end of the compiler's arguments list.
|
||||
///
|
||||
/// Nvidia compiler accepts only the most common compiler flags like `-D`,
|
||||
/// `-I`, `-c`, etc. Options meant specifically for the underlying
|
||||
/// host C++ compiler have to be prefixed with `-Xcompiler`.
|
||||
/// [Another possible future application for this function is passing
|
||||
/// clang-specific flags to clang-cl, which otherwise accepts only
|
||||
/// MSVC-specific options.]
|
||||
pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
|
||||
if self.cuda {
|
||||
self.args.push("-Xcompiler".into());
|
||||
}
|
||||
self.args.push(flag);
|
||||
}
|
||||
|
||||
/// Checks if an argument or flag has already been specified or conflicts.
|
||||
///
|
||||
/// Currently only checks optimization flags.
|
||||
pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
|
||||
let flag = flag.to_str().unwrap();
|
||||
let mut chars = flag.chars();
|
||||
|
||||
// Only duplicate check compiler flags
|
||||
if self.is_like_msvc() {
|
||||
if chars.next() != Some('/') {
|
||||
return false;
|
||||
}
|
||||
} else if self.is_like_gnu() || self.is_like_clang() {
|
||||
if chars.next() != Some('-') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing optimization flags (-O, /O)
|
||||
if chars.next() == Some('O') {
|
||||
return self
|
||||
.args()
|
||||
.iter()
|
||||
.any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
|
||||
}
|
||||
|
||||
// TODO Check for existing -m..., -m...=..., /arch:... flags
|
||||
false
|
||||
}
|
||||
|
||||
/// Don't push optimization arg if it conflicts with existing args.
|
||||
pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
|
||||
if self.is_duplicate_opt_arg(&flag) {
|
||||
println!("Info: Ignoring duplicate arg {:?}", &flag);
|
||||
} else {
|
||||
self.push_cc_arg(flag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this compiler into a `Command` that's ready to be run.
|
||||
///
|
||||
/// This is useful for when the compiler needs to be executed and the
|
||||
/// command returned will already have the initial arguments and environment
|
||||
/// variables configured.
|
||||
pub fn to_command(&self) -> Command {
|
||||
let mut cmd = match self.cc_wrapper_path {
|
||||
Some(ref cc_wrapper_path) => {
|
||||
let mut cmd = Command::new(cc_wrapper_path);
|
||||
cmd.arg(&self.path);
|
||||
cmd
|
||||
}
|
||||
None => Command::new(&self.path),
|
||||
};
|
||||
cmd.args(&self.cc_wrapper_args);
|
||||
|
||||
let value = self
|
||||
.args
|
||||
.iter()
|
||||
.filter(|a| !self.removed_args.contains(a))
|
||||
.collect::<Vec<_>>();
|
||||
cmd.args(&value);
|
||||
|
||||
for (k, v) in self.env.iter() {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Returns the path for this compiler.
|
||||
///
|
||||
/// Note that this may not be a path to a file on the filesystem, e.g. "cc",
|
||||
/// but rather something which will be resolved when a process is spawned.
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Returns the default set of arguments to the compiler needed to produce
|
||||
/// executables for the target this compiler generates.
|
||||
pub fn args(&self) -> &[OsString] {
|
||||
&self.args
|
||||
}
|
||||
|
||||
/// Returns the set of environment variables needed for this compiler to
|
||||
/// operate.
|
||||
///
|
||||
/// This is typically only used for MSVC compilers currently.
|
||||
pub fn env(&self) -> &[(OsString, OsString)] {
|
||||
&self.env
|
||||
}
|
||||
|
||||
/// Returns the compiler command in format of CC environment variable.
|
||||
/// Or empty string if CC env was not present
|
||||
///
|
||||
/// This is typically used by configure script
|
||||
pub fn cc_env(&self) -> OsString {
|
||||
match self.cc_wrapper_path {
|
||||
Some(ref cc_wrapper_path) => {
|
||||
let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
|
||||
cc_env.push(" ");
|
||||
cc_env.push(self.path.to_path_buf().into_os_string());
|
||||
for arg in self.cc_wrapper_args.iter() {
|
||||
cc_env.push(" ");
|
||||
cc_env.push(arg);
|
||||
}
|
||||
cc_env
|
||||
}
|
||||
None => OsString::from(""),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the compiler flags in format of CFLAGS environment variable.
|
||||
/// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS
|
||||
/// This is typically used by configure script
|
||||
pub fn cflags_env(&self) -> OsString {
|
||||
let mut flags = OsString::new();
|
||||
for (i, arg) in self.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
flags.push(" ");
|
||||
}
|
||||
flags.push(arg);
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
/// Whether the tool is GNU Compiler Collection-like.
|
||||
pub fn is_like_gnu(&self) -> bool {
|
||||
self.family == ToolFamily::Gnu
|
||||
}
|
||||
|
||||
/// Whether the tool is Clang-like.
|
||||
pub fn is_like_clang(&self) -> bool {
|
||||
self.family == ToolFamily::Clang
|
||||
}
|
||||
|
||||
/// Whether the tool is AppleClang under .xctoolchain
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub(crate) fn is_xctoolchain_clang(&self) -> bool {
|
||||
let path = self.path.to_string_lossy();
|
||||
path.contains(".xctoolchain/")
|
||||
}
|
||||
#[cfg(not(target_vendor = "apple"))]
|
||||
pub(crate) fn is_xctoolchain_clang(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether the tool is MSVC-like.
|
||||
pub fn is_like_msvc(&self) -> bool {
|
||||
match self.family {
|
||||
ToolFamily::Msvc { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the family of tools this tool belongs to.
|
||||
///
|
||||
/// Each family of tools differs in how and what arguments they accept.
|
||||
///
|
||||
/// Detection of a family is done on best-effort basis and may not accurately reflect the tool.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ToolFamily {
|
||||
/// Tool is GNU Compiler Collection-like.
|
||||
Gnu,
|
||||
/// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags
|
||||
/// and its cross-compilation approach is different.
|
||||
Clang,
|
||||
/// Tool is the MSVC cl.exe.
|
||||
Msvc { clang_cl: bool },
|
||||
}
|
||||
|
||||
impl ToolFamily {
|
||||
/// What the flag to request debug info for this family of tools look like
|
||||
pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
|
||||
match *self {
|
||||
ToolFamily::Msvc { .. } => {
|
||||
cmd.push_cc_arg("-Z7".into());
|
||||
}
|
||||
ToolFamily::Gnu | ToolFamily::Clang => {
|
||||
cmd.push_cc_arg(
|
||||
dwarf_version
|
||||
.map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What the flag to force frame pointers.
|
||||
pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
|
||||
match *self {
|
||||
ToolFamily::Gnu | ToolFamily::Clang => {
|
||||
cmd.push_cc_arg("-fno-omit-frame-pointer".into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// What the flags to enable all warnings
|
||||
pub(crate) fn warnings_flags(&self) -> &'static str {
|
||||
match *self {
|
||||
ToolFamily::Msvc { .. } => "-W4",
|
||||
ToolFamily::Gnu | ToolFamily::Clang => "-Wall",
|
||||
}
|
||||
}
|
||||
|
||||
/// What the flags to enable extra warnings
|
||||
pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
|
||||
match *self {
|
||||
ToolFamily::Msvc { .. } => None,
|
||||
ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"),
|
||||
}
|
||||
}
|
||||
|
||||
/// What the flag to turn warning into errors
|
||||
pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
|
||||
match *self {
|
||||
ToolFamily::Msvc { .. } => "-WX",
|
||||
ToolFamily::Gnu | ToolFamily::Clang => "-Werror",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verbose_stderr(&self) -> bool {
|
||||
*self == ToolFamily::Clang
|
||||
}
|
||||
}
|
|
@ -7,27 +7,31 @@
|
|||
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::winapi::CoInitializeEx;
|
||||
use crate::winapi::IUnknown;
|
||||
use crate::winapi::Interface;
|
||||
use crate::winapi::BSTR;
|
||||
use crate::winapi::COINIT_MULTITHREADED;
|
||||
use crate::winapi::{SysFreeString, SysStringLen};
|
||||
use crate::winapi::{HRESULT, S_FALSE, S_OK};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::mem::forget;
|
||||
use std::ops::Deref;
|
||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||
use std::ptr::null_mut;
|
||||
use std::slice::from_raw_parts;
|
||||
use crate::windows::{
|
||||
winapi::{IUnknown, Interface},
|
||||
windows_sys::{
|
||||
CoInitializeEx, SysFreeString, SysStringLen, BSTR, COINIT_MULTITHREADED, HRESULT, S_FALSE,
|
||||
S_OK,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
ffi::{OsStr, OsString},
|
||||
mem::ManuallyDrop,
|
||||
ops::Deref,
|
||||
os::windows::ffi::{OsStrExt, OsStringExt},
|
||||
ptr::{null, null_mut},
|
||||
slice::from_raw_parts,
|
||||
};
|
||||
|
||||
pub fn initialize() -> Result<(), HRESULT> {
|
||||
let err = unsafe { CoInitializeEx(null_mut(), COINIT_MULTITHREADED) };
|
||||
let err = unsafe { CoInitializeEx(null(), COINIT_MULTITHREADED.try_into().unwrap()) };
|
||||
if err != S_OK && err != S_FALSE {
|
||||
// S_FALSE just means COM is already initialized
|
||||
return Err(err);
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct ComPtr<T>(*mut T)
|
||||
|
@ -55,15 +59,13 @@ where
|
|||
/// Extracts the raw pointer.
|
||||
/// You are now responsible for releasing it yourself.
|
||||
pub fn into_raw(self) -> *mut T {
|
||||
let p = self.0;
|
||||
forget(self);
|
||||
p
|
||||
ManuallyDrop::new(self).0
|
||||
}
|
||||
/// For internal use only.
|
||||
fn as_unknown(&self) -> &IUnknown {
|
||||
unsafe { &*(self.0 as *mut IUnknown) }
|
||||
}
|
||||
/// Performs QueryInterface fun.
|
||||
/// Performs `QueryInterface` fun.
|
||||
pub fn cast<U>(&self) -> Result<ComPtr<U>, i32>
|
||||
where
|
||||
U: Interface,
|
|
@ -11,6 +11,8 @@
|
|||
//! A helper module to probe the Windows Registry when looking for
|
||||
//! windows-specific tools.
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
use crate::Tool;
|
||||
|
@ -53,6 +55,9 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
|
|||
return None;
|
||||
}
|
||||
|
||||
// Split the target to get the arch.
|
||||
let target = impl_::TargetArch(target.split_once('-')?.0);
|
||||
|
||||
// Looks like msbuild isn't located in the same location as other tools like
|
||||
// cl.exe and lib.exe. To handle this we probe for it manually with
|
||||
// dedicated registry keys.
|
||||
|
@ -71,15 +76,16 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
|
|||
// environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
|
||||
// the tool is actually usable.
|
||||
|
||||
return impl_::find_msvc_environment(tool, target)
|
||||
impl_::find_msvc_environment(tool, target)
|
||||
.or_else(|| impl_::find_msvc_15plus(tool, target))
|
||||
.or_else(|| impl_::find_msvc_14(tool, target))
|
||||
.or_else(|| impl_::find_msvc_12(tool, target))
|
||||
.or_else(|| impl_::find_msvc_11(tool, target));
|
||||
.or_else(|| impl_::find_msvc_11(tool, target))
|
||||
}
|
||||
|
||||
/// A version of Visual Studio
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum VsVers {
|
||||
/// Visual Studio 12 (2013)
|
||||
Vs12,
|
||||
|
@ -91,13 +97,6 @@ pub enum VsVers {
|
|||
Vs16,
|
||||
/// Visual Studio 17 (2022)
|
||||
Vs17,
|
||||
|
||||
/// Hidden variant that should not be matched on. Callers that want to
|
||||
/// handle an enumeration of `VsVers` instances should always have a default
|
||||
/// case meaning that it's a VS version they don't understand.
|
||||
#[doc(hidden)]
|
||||
#[allow(bad_style)]
|
||||
__Nonexhaustive_do_not_match_this_or_your_code_will_break,
|
||||
}
|
||||
|
||||
/// Find the most recent installed version of Visual Studio
|
||||
|
@ -106,7 +105,7 @@ pub enum VsVers {
|
|||
/// generator.
|
||||
#[cfg(not(windows))]
|
||||
pub fn find_vs_version() -> Result<VsVers, String> {
|
||||
Err(format!("not windows"))
|
||||
Err("not windows".to_string())
|
||||
}
|
||||
|
||||
/// Documented above
|
||||
|
@ -160,10 +159,14 @@ pub fn find_vs_version() -> Result<VsVers, String> {
|
|||
|
||||
#[cfg(windows)]
|
||||
mod impl_ {
|
||||
use crate::com;
|
||||
use crate::registry::{RegistryKey, LOCAL_MACHINE};
|
||||
use crate::setup_config::SetupConfiguration;
|
||||
use crate::vs_instances::{VsInstances, VswhereInstance};
|
||||
use crate::windows::com;
|
||||
use crate::windows::registry::{RegistryKey, LOCAL_MACHINE};
|
||||
use crate::windows::setup_config::SetupConfiguration;
|
||||
use crate::windows::vs_instances::{VsInstances, VswhereInstance};
|
||||
use crate::windows::windows_sys::{
|
||||
FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE,
|
||||
IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
|
@ -174,10 +177,27 @@ mod impl_ {
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Once;
|
||||
|
||||
use super::MSVC_FAMILY;
|
||||
use crate::Tool;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TargetArch<'a>(pub &'a str);
|
||||
|
||||
impl PartialEq<&str> for TargetArch<'_> {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<TargetArch<'a>> for &'a str {
|
||||
fn from(target: TargetArch<'a>) -> Self {
|
||||
target.0
|
||||
}
|
||||
}
|
||||
|
||||
struct MsvcTool {
|
||||
tool: PathBuf,
|
||||
libs: Vec<PathBuf>,
|
||||
|
@ -185,10 +205,75 @@ mod impl_ {
|
|||
include: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
struct LibraryHandle(HMODULE);
|
||||
|
||||
impl LibraryHandle {
|
||||
fn new(name: &[u8]) -> Option<Self> {
|
||||
let handle = unsafe { LoadLibraryA(name.as_ptr() as _) };
|
||||
(!handle.is_null()).then(|| Self(handle))
|
||||
}
|
||||
|
||||
/// Get a function pointer to a function in the library.
|
||||
/// SAFETY: The caller must ensure that the function signature matches the actual function.
|
||||
/// The easiest way to do this is to add an entry to windows_sys_no_link.list and use the
|
||||
/// generated function for `func_signature`.
|
||||
unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> {
|
||||
let symbol = unsafe { GetProcAddress(self.0, name.as_ptr() as _) };
|
||||
symbol.map(|symbol| unsafe { mem::transmute_copy(&symbol) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LibraryHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe { FreeLibrary(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
type GetMachineTypeAttributesFuncType =
|
||||
unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32;
|
||||
const _: () = {
|
||||
// Ensure that our hand-written signature matches the actual function signature.
|
||||
// We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to
|
||||
// it, which will fail to load on older versions of Windows.
|
||||
let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes;
|
||||
};
|
||||
|
||||
fn is_amd64_emulation_supported_inner() -> Option<bool> {
|
||||
// GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it.
|
||||
let kernel32 = LibraryHandle::new(b"kernel32.dll\0")?;
|
||||
// SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature.
|
||||
let get_machine_type_attributes = unsafe {
|
||||
kernel32
|
||||
.get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes\0")
|
||||
}?;
|
||||
let mut attributes = Default::default();
|
||||
if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK
|
||||
{
|
||||
Some((attributes & UserEnabled) != 0)
|
||||
} else {
|
||||
Some(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_amd64_emulation_supported() -> bool {
|
||||
// TODO: Replace with a OnceLock once MSRV is 1.70.
|
||||
static LOAD_VALUE: Once = Once::new();
|
||||
static IS_SUPPORTED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Using Relaxed ordering since the Once is providing synchronization.
|
||||
LOAD_VALUE.call_once(|| {
|
||||
IS_SUPPORTED.store(
|
||||
is_amd64_emulation_supported_inner().unwrap_or(false),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
});
|
||||
IS_SUPPORTED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
impl MsvcTool {
|
||||
fn new(tool: PathBuf) -> MsvcTool {
|
||||
MsvcTool {
|
||||
tool: tool,
|
||||
tool,
|
||||
libs: Vec::new(),
|
||||
path: Vec::new(),
|
||||
include: Vec::new(),
|
||||
|
@ -202,7 +287,7 @@ mod impl_ {
|
|||
path,
|
||||
include,
|
||||
} = self;
|
||||
let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY);
|
||||
let mut tool = Tool::with_family(tool, MSVC_FAMILY);
|
||||
add_env(&mut tool, "LIB", libs);
|
||||
add_env(&mut tool, "PATH", path);
|
||||
add_env(&mut tool, "INCLUDE", include);
|
||||
|
@ -212,15 +297,14 @@ mod impl_ {
|
|||
|
||||
/// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the
|
||||
/// given target's arch. Returns `None` if the variable does not exist.
|
||||
#[cfg(windows)]
|
||||
fn is_vscmd_target(target: &str) -> Option<bool> {
|
||||
fn is_vscmd_target(target: TargetArch<'_>) -> Option<bool> {
|
||||
let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?;
|
||||
// Convert the Rust target arch to its VS arch equivalent.
|
||||
let arch = match target.split("-").next() {
|
||||
Some("x86_64") => "x64",
|
||||
Some("aarch64") => "arm64",
|
||||
Some("i686") | Some("i586") => "x86",
|
||||
Some("thumbv7a") => "arm",
|
||||
let arch = match target.into() {
|
||||
"x86_64" => "x64",
|
||||
"aarch64" | "arm64ec" => "arm64",
|
||||
"i686" | "i586" => "x86",
|
||||
"thumbv7a" => "arm",
|
||||
// An unrecognized arch.
|
||||
_ => return Some(false),
|
||||
};
|
||||
|
@ -228,7 +312,7 @@ mod impl_ {
|
|||
}
|
||||
|
||||
/// Attempt to find the tool using environment variables set by vcvars.
|
||||
pub fn find_msvc_environment(tool: &str, target: &str) -> Option<Tool> {
|
||||
pub fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
// Early return if the environment doesn't contain a VC install.
|
||||
if env::var_os("VCINSTALLDIR").is_none() {
|
||||
return None;
|
||||
|
@ -248,16 +332,19 @@ mod impl_ {
|
|||
.map(|p| p.join(tool))
|
||||
.find(|p| p.exists())
|
||||
})
|
||||
.map(|path| Tool::with_family(path.into(), MSVC_FAMILY))
|
||||
.map(|path| Tool::with_family(path, MSVC_FAMILY))
|
||||
}
|
||||
}
|
||||
|
||||
fn find_msbuild_vs17(target: &str) -> Option<Tool> {
|
||||
fn find_msbuild_vs17(target: TargetArch<'_>) -> Option<Tool> {
|
||||
find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17")
|
||||
}
|
||||
|
||||
#[allow(bare_trait_objects)]
|
||||
fn vs16plus_instances(target: &str, version: &'static str) -> Box<Iterator<Item = PathBuf>> {
|
||||
fn vs16plus_instances(
|
||||
target: TargetArch<'_>,
|
||||
version: &'static str,
|
||||
) -> Box<Iterator<Item = PathBuf>> {
|
||||
let instances = if let Some(instances) = vs15plus_instances(target) {
|
||||
instances
|
||||
} else {
|
||||
|
@ -275,7 +362,11 @@ mod impl_ {
|
|||
}))
|
||||
}
|
||||
|
||||
fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option<Tool> {
|
||||
fn find_tool_in_vs16plus_path(
|
||||
tool: &str,
|
||||
target: TargetArch<'_>,
|
||||
version: &'static str,
|
||||
) -> Option<Tool> {
|
||||
vs16plus_instances(target, version)
|
||||
.filter_map(|path| {
|
||||
let path = path.join(tool);
|
||||
|
@ -283,10 +374,10 @@ mod impl_ {
|
|||
return None;
|
||||
}
|
||||
let mut tool = Tool::with_family(path, MSVC_FAMILY);
|
||||
if target.contains("x86_64") {
|
||||
if target == "x86_64" {
|
||||
tool.env.push(("Platform".into(), "X64".into()));
|
||||
}
|
||||
if target.contains("aarch64") {
|
||||
if target == "aarch64" || target == "arm64ec" {
|
||||
tool.env.push(("Platform".into(), "ARM64".into()));
|
||||
}
|
||||
Some(tool)
|
||||
|
@ -294,7 +385,7 @@ mod impl_ {
|
|||
.next()
|
||||
}
|
||||
|
||||
fn find_msbuild_vs16(target: &str) -> Option<Tool> {
|
||||
fn find_msbuild_vs16(target: TargetArch<'_>) -> Option<Tool> {
|
||||
find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16")
|
||||
}
|
||||
|
||||
|
@ -310,7 +401,7 @@ mod impl_ {
|
|||
//
|
||||
// However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64.
|
||||
// Hence, as the last resort we try to use vswhere.exe to list available instances.
|
||||
fn vs15plus_instances(target: &str) -> Option<VsInstances> {
|
||||
fn vs15plus_instances(target: TargetArch<'_>) -> Option<VsInstances> {
|
||||
vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target))
|
||||
}
|
||||
|
||||
|
@ -323,7 +414,7 @@ mod impl_ {
|
|||
Some(VsInstances::ComBased(enum_setup_instances))
|
||||
}
|
||||
|
||||
fn vs15plus_instances_using_vswhere(target: &str) -> Option<VsInstances> {
|
||||
fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option<VsInstances> {
|
||||
let program_files_path: PathBuf = env::var("ProgramFiles(x86)")
|
||||
.or_else(|_| env::var("ProgramFiles"))
|
||||
.ok()?
|
||||
|
@ -336,11 +427,10 @@ mod impl_ {
|
|||
return None;
|
||||
}
|
||||
|
||||
let arch = target.split('-').next().unwrap();
|
||||
let tools_arch = match arch {
|
||||
let tools_arch = match target.into() {
|
||||
"i586" | "i686" | "x86_64" => Some("x86.x64"),
|
||||
"arm" | "thumbv7a" => Some("ARM"),
|
||||
"aarch64" => Some("ARM64"),
|
||||
"aarch64" | "arm64ec" => Some("ARM64"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -374,7 +464,7 @@ mod impl_ {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> {
|
||||
pub fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
let iter = vs15plus_instances(target)?;
|
||||
iter.into_iter()
|
||||
.filter_map(|instance| {
|
||||
|
@ -394,13 +484,13 @@ mod impl_ {
|
|||
// we keep the registry method as a fallback option.
|
||||
//
|
||||
// [more reliable]: https://github.com/rust-lang/cc-rs/pull/331
|
||||
fn find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool> {
|
||||
fn find_tool_in_vs15_path(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
let mut path = match vs15plus_instances(target) {
|
||||
Some(instances) => instances
|
||||
.into_iter()
|
||||
.filter_map(|instance| instance.installation_path())
|
||||
.map(|path| path.join(tool))
|
||||
.find(|ref path| path.is_file()),
|
||||
.find(|path| path.is_file()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
|
@ -416,10 +506,9 @@ mod impl_ {
|
|||
|
||||
path.map(|path| {
|
||||
let mut tool = Tool::with_family(path, MSVC_FAMILY);
|
||||
if target.contains("x86_64") {
|
||||
if target == "x86_64" {
|
||||
tool.env.push(("Platform".into(), "X64".into()));
|
||||
}
|
||||
if target.contains("aarch64") {
|
||||
} else if target == "aarch64" {
|
||||
tool.env.push(("Platform".into(), "ARM64".into()));
|
||||
}
|
||||
tool
|
||||
|
@ -428,10 +517,10 @@ mod impl_ {
|
|||
|
||||
fn tool_from_vs15plus_instance(
|
||||
tool: &str,
|
||||
target: &str,
|
||||
target: TargetArch<'_>,
|
||||
instance_path: &PathBuf,
|
||||
) -> Option<Tool> {
|
||||
let (root_path, bin_path, host_dylib_path, lib_path, include_path) =
|
||||
let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) =
|
||||
vs15plus_vc_paths(target, instance_path)?;
|
||||
let tool_path = bin_path.join(tool);
|
||||
if !tool_path.exists() {
|
||||
|
@ -441,6 +530,9 @@ mod impl_ {
|
|||
let mut tool = MsvcTool::new(tool_path);
|
||||
tool.path.push(bin_path.clone());
|
||||
tool.path.push(host_dylib_path);
|
||||
if let Some(alt_lib_path) = alt_lib_path {
|
||||
tool.libs.push(alt_lib_path);
|
||||
}
|
||||
tool.libs.push(lib_path);
|
||||
tool.include.push(include_path);
|
||||
|
||||
|
@ -455,45 +547,97 @@ mod impl_ {
|
|||
}
|
||||
|
||||
fn vs15plus_vc_paths(
|
||||
target: &str,
|
||||
instance_path: &PathBuf,
|
||||
) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, PathBuf)> {
|
||||
let version_path =
|
||||
instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
|
||||
let mut version_file = File::open(version_path).ok()?;
|
||||
let mut version = String::new();
|
||||
version_file.read_to_string(&mut version).ok()?;
|
||||
let version = version.trim();
|
||||
let host = match host_arch() {
|
||||
X86 => "X86",
|
||||
X86_64 => "X64",
|
||||
// There is no natively hosted compiler on ARM64.
|
||||
// Instead, use the x86 toolchain under emulation (there is no x64 emulation).
|
||||
AARCH64 => "X86",
|
||||
target: TargetArch<'_>,
|
||||
instance_path: &Path,
|
||||
) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
|
||||
let version = vs15plus_vc_read_version(instance_path)?;
|
||||
|
||||
let hosts = match host_arch() {
|
||||
X86 => &["X86"],
|
||||
X86_64 => &["X64"],
|
||||
// Starting with VS 17.4, there is a natively hosted compiler on ARM64:
|
||||
// https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/
|
||||
// On older versions of VS, we use x64 if running under emulation is supported,
|
||||
// otherwise use x86.
|
||||
AARCH64 => {
|
||||
if is_amd64_emulation_supported() {
|
||||
&["ARM64", "X64", "X86"][..]
|
||||
} else {
|
||||
&["ARM64", "X86"]
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let target = lib_subdir(target)?;
|
||||
// The directory layout here is MSVC/bin/Host$host/$target/
|
||||
let path = instance_path.join(r"VC\Tools\MSVC").join(version);
|
||||
// We use the first available host architecture that can build for the target
|
||||
let (host_path, host) = hosts.iter().find_map(|&x| {
|
||||
let candidate = path.join("bin").join(format!("Host{}", x));
|
||||
if candidate.join(target).exists() {
|
||||
Some((candidate, x))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
// This is the path to the toolchain for a particular target, running
|
||||
// on a given host
|
||||
let bin_path = path
|
||||
.join("bin")
|
||||
.join(&format!("Host{}", host))
|
||||
.join(&target);
|
||||
let bin_path = host_path.join(target);
|
||||
// But! we also need PATH to contain the target directory for the host
|
||||
// architecture, because it contains dlls like mspdb140.dll compiled for
|
||||
// the host architecture.
|
||||
let host_dylib_path = path
|
||||
.join("bin")
|
||||
.join(&format!("Host{}", host))
|
||||
.join(&host.to_lowercase());
|
||||
let lib_path = path.join("lib").join(&target);
|
||||
let host_dylib_path = host_path.join(host.to_lowercase());
|
||||
let lib_path = path.join("lib").join(target);
|
||||
let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec"));
|
||||
let include_path = path.join("include");
|
||||
Some((path, bin_path, host_dylib_path, lib_path, include_path))
|
||||
Some((
|
||||
path,
|
||||
bin_path,
|
||||
host_dylib_path,
|
||||
lib_path,
|
||||
alt_lib_path,
|
||||
include_path,
|
||||
))
|
||||
}
|
||||
|
||||
fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> {
|
||||
fn vs15plus_vc_read_version(dir: &Path) -> Option<String> {
|
||||
// Try to open the default version file.
|
||||
let mut version_path: PathBuf =
|
||||
dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
|
||||
let mut version_file = if let Ok(f) = File::open(&version_path) {
|
||||
f
|
||||
} else {
|
||||
// If the default doesn't exist, search for other version files.
|
||||
// These are in the form Microsoft.VCToolsVersion.v143.default.txt
|
||||
// where `143` is any three decimal digit version number.
|
||||
// This sorts versions by lexical order and selects the highest version.
|
||||
let mut version_file = String::new();
|
||||
version_path.pop();
|
||||
for file in version_path.read_dir().ok()? {
|
||||
let name = file.ok()?.file_name();
|
||||
let name = name.to_str()?;
|
||||
if name.starts_with("Microsoft.VCToolsVersion.v")
|
||||
&& name.ends_with(".default.txt")
|
||||
&& name > &version_file
|
||||
{
|
||||
version_file.replace_range(.., name);
|
||||
}
|
||||
}
|
||||
if version_file.is_empty() {
|
||||
return None;
|
||||
}
|
||||
version_path.push(version_file);
|
||||
File::open(version_path).ok()?
|
||||
};
|
||||
|
||||
// Get the version string from the file we found.
|
||||
let mut version = String::new();
|
||||
version_file.read_to_string(&mut version).ok()?;
|
||||
version.truncate(version.trim_end().len());
|
||||
Some(version)
|
||||
}
|
||||
|
||||
fn atl_paths(target: TargetArch<'_>, path: &Path) -> Option<(PathBuf, PathBuf)> {
|
||||
let atl_path = path.join("atlmfc");
|
||||
let sub = lib_subdir(target)?;
|
||||
if atl_path.exists() {
|
||||
|
@ -505,14 +649,14 @@ mod impl_ {
|
|||
|
||||
// For MSVC 14 we need to find the Universal CRT as well as either
|
||||
// the Windows 10 SDK or Windows 8.1 SDK.
|
||||
pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> {
|
||||
pub fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
let vcdir = get_vc_dir("14.0")?;
|
||||
let mut tool = get_tool(tool, &vcdir, target)?;
|
||||
add_sdks(&mut tool, target)?;
|
||||
Some(tool.into_tool())
|
||||
}
|
||||
|
||||
fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> {
|
||||
fn add_sdks(tool: &mut MsvcTool, target: TargetArch<'_>) -> Option<()> {
|
||||
let sub = lib_subdir(target)?;
|
||||
let (ucrt, ucrt_version) = get_ucrt_dir()?;
|
||||
|
||||
|
@ -555,7 +699,7 @@ mod impl_ {
|
|||
}
|
||||
|
||||
// For MSVC 12 we need to find the Windows 8.1 SDK.
|
||||
pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
|
||||
pub fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
let vcdir = get_vc_dir("12.0")?;
|
||||
let mut tool = get_tool(tool, &vcdir, target)?;
|
||||
let sub = lib_subdir(target)?;
|
||||
|
@ -571,7 +715,7 @@ mod impl_ {
|
|||
}
|
||||
|
||||
// For MSVC 11 we need to find the Windows 8 SDK.
|
||||
pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
|
||||
pub fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
|
||||
let vcdir = get_vc_dir("11.0")?;
|
||||
let mut tool = get_tool(tool, &vcdir, target)?;
|
||||
let sub = lib_subdir(target)?;
|
||||
|
@ -596,7 +740,7 @@ mod impl_ {
|
|||
|
||||
// Given a possible MSVC installation directory, we look for the linker and
|
||||
// then add the MSVC library path.
|
||||
fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
|
||||
fn get_tool(tool: &str, path: &Path, target: TargetArch<'_>) -> Option<MsvcTool> {
|
||||
bin_subdir(target)
|
||||
.into_iter()
|
||||
.map(|(sub, host)| {
|
||||
|
@ -605,7 +749,7 @@ mod impl_ {
|
|||
path.join("bin").join(host),
|
||||
)
|
||||
})
|
||||
.filter(|&(ref path, _)| path.is_file())
|
||||
.filter(|(path, _)| path.is_file())
|
||||
.map(|(path, host)| {
|
||||
let mut tool = MsvcTool::new(path);
|
||||
tool.path.push(host);
|
||||
|
@ -734,9 +878,8 @@ mod impl_ {
|
|||
// linkers that can target the architecture we desire. The 64-bit host
|
||||
// linker is preferred, and hence first, due to 64-bit allowing it more
|
||||
// address space to work with and potentially being faster.
|
||||
fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
|
||||
let arch = target.split('-').next().unwrap();
|
||||
match (arch, host_arch()) {
|
||||
fn bin_subdir(target: TargetArch<'_>) -> Vec<(&'static str, &'static str)> {
|
||||
match (target.into(), host_arch()) {
|
||||
("i586", X86) | ("i686", X86) => vec![("", "")],
|
||||
("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
|
||||
("x86_64", X86) => vec![("x86_amd64", "")],
|
||||
|
@ -747,21 +890,19 @@ mod impl_ {
|
|||
}
|
||||
}
|
||||
|
||||
fn lib_subdir(target: &str) -> Option<&'static str> {
|
||||
let arch = target.split('-').next().unwrap();
|
||||
match arch {
|
||||
fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> {
|
||||
match target.into() {
|
||||
"i586" | "i686" => Some("x86"),
|
||||
"x86_64" => Some("x64"),
|
||||
"arm" | "thumbv7a" => Some("arm"),
|
||||
"aarch64" => Some("arm64"),
|
||||
"aarch64" | "arm64ec" => Some("arm64"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// MSVC's x86 libraries are not in a subfolder
|
||||
fn vc_lib_subdir(target: &str) -> Option<&'static str> {
|
||||
let arch = target.split('-').next().unwrap();
|
||||
match arch {
|
||||
fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> {
|
||||
match target.into() {
|
||||
"i586" | "i686" => Some(""),
|
||||
"x86_64" => Some("amd64"),
|
||||
"arm" | "thumbv7a" => Some("arm"),
|
||||
|
@ -813,7 +954,7 @@ mod impl_ {
|
|||
for subkey in key.iter().filter_map(|k| k.ok()) {
|
||||
let val = subkey
|
||||
.to_str()
|
||||
.and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok());
|
||||
.and_then(|s| s.trim_left_matches("v").replace('.', "").parse().ok());
|
||||
let val = match val {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
|
@ -831,19 +972,19 @@ mod impl_ {
|
|||
pub fn has_msbuild_version(version: &str) -> bool {
|
||||
match version {
|
||||
"17.0" => {
|
||||
find_msbuild_vs17("x86_64-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs17("i686-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs17("aarch64-pc-windows-msvc").is_some()
|
||||
find_msbuild_vs17(TargetArch("x86_64")).is_some()
|
||||
|| find_msbuild_vs17(TargetArch("i686")).is_some()
|
||||
|| find_msbuild_vs17(TargetArch("aarch64")).is_some()
|
||||
}
|
||||
"16.0" => {
|
||||
find_msbuild_vs16("x86_64-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs16("i686-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs16("aarch64-pc-windows-msvc").is_some()
|
||||
find_msbuild_vs16(TargetArch("x86_64")).is_some()
|
||||
|| find_msbuild_vs16(TargetArch("i686")).is_some()
|
||||
|| find_msbuild_vs16(TargetArch("aarch64")).is_some()
|
||||
}
|
||||
"15.0" => {
|
||||
find_msbuild_vs15("x86_64-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs15("i686-pc-windows-msvc").is_some()
|
||||
|| find_msbuild_vs15("aarch64-pc-windows-msvc").is_some()
|
||||
find_msbuild_vs15(TargetArch("x86_64")).is_some()
|
||||
|| find_msbuild_vs15(TargetArch("i686")).is_some()
|
||||
|| find_msbuild_vs15(TargetArch("aarch64")).is_some()
|
||||
}
|
||||
"12.0" | "14.0" => LOCAL_MACHINE
|
||||
.open(&OsString::from(format!(
|
||||
|
@ -855,18 +996,20 @@ mod impl_ {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn find_devenv(target: &str) -> Option<Tool> {
|
||||
find_devenv_vs15(&target)
|
||||
pub fn find_devenv(target: TargetArch<'_>) -> Option<Tool> {
|
||||
find_devenv_vs15(target)
|
||||
}
|
||||
|
||||
fn find_devenv_vs15(target: &str) -> Option<Tool> {
|
||||
fn find_devenv_vs15(target: TargetArch<'_>) -> Option<Tool> {
|
||||
find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target)
|
||||
}
|
||||
|
||||
// see http://stackoverflow.com/questions/328017/path-to-msbuild
|
||||
pub fn find_msbuild(target: &str) -> Option<Tool> {
|
||||
pub fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> {
|
||||
// VS 15 (2017) changed how to locate msbuild
|
||||
if let Some(r) = find_msbuild_vs16(target) {
|
||||
if let Some(r) = find_msbuild_vs17(target) {
|
||||
Some(r)
|
||||
} else if let Some(r) = find_msbuild_vs16(target) {
|
||||
return Some(r);
|
||||
} else if let Some(r) = find_msbuild_vs15(target) {
|
||||
return Some(r);
|
||||
|
@ -875,11 +1018,11 @@ mod impl_ {
|
|||
}
|
||||
}
|
||||
|
||||
fn find_msbuild_vs15(target: &str) -> Option<Tool> {
|
||||
fn find_msbuild_vs15(target: TargetArch<'_>) -> Option<Tool> {
|
||||
find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target)
|
||||
}
|
||||
|
||||
fn find_old_msbuild(target: &str) -> Option<Tool> {
|
||||
fn find_old_msbuild(target: TargetArch<'_>) -> Option<Tool> {
|
||||
let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
|
||||
LOCAL_MACHINE
|
||||
.open(key.as_ref())
|
||||
|
@ -891,7 +1034,7 @@ mod impl_ {
|
|||
let mut path = PathBuf::from(path);
|
||||
path.push("MSBuild.exe");
|
||||
let mut tool = Tool::with_family(path, MSVC_FAMILY);
|
||||
if target.contains("x86_64") {
|
||||
if target == "x86_64" {
|
||||
tool.env.push(("Platform".into(), "X64".into()));
|
||||
}
|
||||
tool
|
|
@ -0,0 +1,20 @@
|
|||
//! These modules are all glue to support reading the MSVC version from
|
||||
//! the registry and from COM interfaces.
|
||||
|
||||
// This is used in the crate's public API, so don't use #[cfg(windows)]
|
||||
pub mod find_tools;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod windows_sys;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod registry;
|
||||
#[cfg(windows)]
|
||||
#[macro_use]
|
||||
mod winapi;
|
||||
#[cfg(windows)]
|
||||
mod com;
|
||||
#[cfg(windows)]
|
||||
mod setup_config;
|
||||
#[cfg(windows)]
|
||||
mod vs_instances;
|
|
@ -8,63 +8,23 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io;
|
||||
use std::ops::RangeFrom;
|
||||
use std::os::raw;
|
||||
use std::os::windows::prelude::*;
|
||||
use crate::windows::windows_sys::{
|
||||
RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, RegQueryValueExW, ERROR_NO_MORE_ITEMS,
|
||||
ERROR_SUCCESS, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, REG_SZ,
|
||||
};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
io,
|
||||
ops::RangeFrom,
|
||||
os::windows::prelude::*,
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
/// Must never be `HKEY_PERFORMANCE_DATA`.
|
||||
pub(crate) struct RegistryKey(Repr);
|
||||
|
||||
type HKEY = *mut u8;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type DWORD = u32;
|
||||
type LPDWORD = *mut DWORD;
|
||||
type LPCWSTR = *const u16;
|
||||
type LPWSTR = *mut u16;
|
||||
type LONG = raw::c_long;
|
||||
type PHKEY = *mut HKEY;
|
||||
type PFILETIME = *mut u8;
|
||||
type LPBYTE = *mut u8;
|
||||
type REGSAM = u32;
|
||||
|
||||
const ERROR_SUCCESS: DWORD = 0;
|
||||
const ERROR_NO_MORE_ITEMS: DWORD = 259;
|
||||
// Sign-extend into 64 bits if needed.
|
||||
const HKEY_LOCAL_MACHINE: HKEY = 0x80000002u32 as i32 as isize as HKEY;
|
||||
const REG_SZ: DWORD = 1;
|
||||
const KEY_READ: DWORD = 0x20019;
|
||||
const KEY_WOW64_32KEY: DWORD = 0x200;
|
||||
|
||||
#[link(name = "advapi32")]
|
||||
extern "system" {
|
||||
fn RegOpenKeyExW(
|
||||
key: HKEY,
|
||||
lpSubKey: LPCWSTR,
|
||||
ulOptions: DWORD,
|
||||
samDesired: REGSAM,
|
||||
phkResult: PHKEY,
|
||||
) -> LONG;
|
||||
fn RegEnumKeyExW(
|
||||
key: HKEY,
|
||||
dwIndex: DWORD,
|
||||
lpName: LPWSTR,
|
||||
lpcName: LPDWORD,
|
||||
lpReserved: LPDWORD,
|
||||
lpClass: LPWSTR,
|
||||
lpcClass: LPDWORD,
|
||||
lpftLastWriteTime: PFILETIME,
|
||||
) -> LONG;
|
||||
fn RegQueryValueExW(
|
||||
hKey: HKEY,
|
||||
lpValueName: LPCWSTR,
|
||||
lpReserved: LPDWORD,
|
||||
lpType: LPDWORD,
|
||||
lpData: LPBYTE,
|
||||
lpcbData: LPDWORD,
|
||||
) -> LONG;
|
||||
fn RegCloseKey(hKey: HKEY) -> LONG;
|
||||
}
|
||||
|
||||
struct OwnedKey(HKEY);
|
||||
|
||||
|
@ -97,7 +57,7 @@ impl RegistryKey {
|
|||
/// Open a sub-key of `self`.
|
||||
pub fn open(&self, key: &OsStr) -> io::Result<RegistryKey> {
|
||||
let key = key.encode_wide().chain(Some(0)).collect::<Vec<_>>();
|
||||
let mut ret = 0 as *mut _;
|
||||
let mut ret = null_mut();
|
||||
let err = unsafe {
|
||||
RegOpenKeyExW(
|
||||
self.raw(),
|
||||
|
@ -107,7 +67,7 @@ impl RegistryKey {
|
|||
&mut ret,
|
||||
)
|
||||
};
|
||||
if err == ERROR_SUCCESS as LONG {
|
||||
if err == ERROR_SUCCESS {
|
||||
Ok(RegistryKey(Repr::Owned(OwnedKey(ret))))
|
||||
} else {
|
||||
Err(io::Error::from_raw_os_error(err as i32))
|
||||
|
@ -130,12 +90,12 @@ impl RegistryKey {
|
|||
let err = RegQueryValueExW(
|
||||
self.raw(),
|
||||
name.as_ptr(),
|
||||
0 as *mut _,
|
||||
null_mut(),
|
||||
&mut kind,
|
||||
0 as *mut _,
|
||||
null_mut(),
|
||||
&mut len,
|
||||
);
|
||||
if err != ERROR_SUCCESS as LONG {
|
||||
if err != ERROR_SUCCESS {
|
||||
return Err(io::Error::from_raw_os_error(err as i32));
|
||||
}
|
||||
if kind != REG_SZ {
|
||||
|
@ -156,8 +116,8 @@ impl RegistryKey {
|
|||
let err = RegQueryValueExW(
|
||||
self.raw(),
|
||||
name.as_ptr(),
|
||||
0 as *mut _,
|
||||
0 as *mut _,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
v.as_mut_ptr() as *mut _,
|
||||
&mut len,
|
||||
);
|
||||
|
@ -165,7 +125,7 @@ impl RegistryKey {
|
|||
// grew between the first and second call to `RegQueryValueExW`),
|
||||
// both because it's extremely unlikely, and this is a bit more
|
||||
// defensive more defensive against weird types of registry keys.
|
||||
if err != ERROR_SUCCESS as LONG {
|
||||
if err != ERROR_SUCCESS {
|
||||
return Err(io::Error::from_raw_os_error(err as i32));
|
||||
}
|
||||
// The length is allowed to change, but should still be even, as
|
||||
|
@ -188,7 +148,7 @@ impl RegistryKey {
|
|||
if !v.is_empty() && v[v.len() - 1] == 0 {
|
||||
v.pop();
|
||||
}
|
||||
return Ok(OsString::from_wide(&v));
|
||||
Ok(OsString::from_wide(&v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,14 +173,14 @@ impl<'a> Iterator for Iter<'a> {
|
|||
i,
|
||||
v.as_mut_ptr(),
|
||||
&mut len,
|
||||
0 as *mut _,
|
||||
0 as *mut _,
|
||||
0 as *mut _,
|
||||
0 as *mut _,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
);
|
||||
if ret == ERROR_NO_MORE_ITEMS as LONG {
|
||||
if ret == ERROR_NO_MORE_ITEMS {
|
||||
None
|
||||
} else if ret != ERROR_SUCCESS as LONG {
|
||||
} else if ret != ERROR_SUCCESS {
|
||||
Some(Err(io::Error::from_raw_os_error(ret as i32)))
|
||||
} else {
|
||||
v.set_len(len as usize);
|
|
@ -8,19 +8,19 @@
|
|||
#![allow(bad_style)]
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::winapi::Interface;
|
||||
use crate::winapi::BSTR;
|
||||
use crate::winapi::LPCOLESTR;
|
||||
use crate::winapi::LPSAFEARRAY;
|
||||
use crate::winapi::S_FALSE;
|
||||
use crate::winapi::{CoCreateInstance, CLSCTX_ALL};
|
||||
use crate::winapi::{IUnknown, IUnknownVtbl};
|
||||
use crate::winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG};
|
||||
use crate::winapi::{LPFILETIME, ULONG};
|
||||
use std::ffi::OsString;
|
||||
use std::ptr::null_mut;
|
||||
use crate::windows::{
|
||||
com::{BStr, ComPtr},
|
||||
winapi::{
|
||||
IUnknown, IUnknownVtbl, Interface, LCID, LPCOLESTR, LPCWSTR, LPFILETIME, LPSAFEARRAY,
|
||||
PULONGLONG, ULONG,
|
||||
},
|
||||
windows_sys::{CoCreateInstance, BSTR, CLSCTX_ALL, HRESULT, S_FALSE},
|
||||
};
|
||||
|
||||
use crate::com::{BStr, ComPtr};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
ptr::{null, null_mut},
|
||||
};
|
||||
|
||||
// Bindings to the Setup.Configuration stuff
|
||||
pub type InstanceState = u32;
|
||||
|
@ -212,7 +212,7 @@ impl SetupInstance {
|
|||
SetupInstance(ComPtr::from_raw(obj))
|
||||
}
|
||||
pub fn instance_id(&self) -> Result<OsString, i32> {
|
||||
let mut s = null_mut();
|
||||
let mut s = null();
|
||||
let err = unsafe { self.0.GetInstanceId(&mut s) };
|
||||
let bstr = unsafe { BStr::from_raw(s) };
|
||||
if err < 0 {
|
||||
|
@ -221,7 +221,7 @@ impl SetupInstance {
|
|||
Ok(bstr.to_osstring())
|
||||
}
|
||||
pub fn installation_name(&self) -> Result<OsString, i32> {
|
||||
let mut s = null_mut();
|
||||
let mut s = null();
|
||||
let err = unsafe { self.0.GetInstallationName(&mut s) };
|
||||
let bstr = unsafe { BStr::from_raw(s) };
|
||||
if err < 0 {
|
||||
|
@ -230,7 +230,7 @@ impl SetupInstance {
|
|||
Ok(bstr.to_osstring())
|
||||
}
|
||||
pub fn installation_path(&self) -> Result<OsString, i32> {
|
||||
let mut s = null_mut();
|
||||
let mut s = null();
|
||||
let err = unsafe { self.0.GetInstallationPath(&mut s) };
|
||||
let bstr = unsafe { BStr::from_raw(s) };
|
||||
if err < 0 {
|
||||
|
@ -239,7 +239,7 @@ impl SetupInstance {
|
|||
Ok(bstr.to_osstring())
|
||||
}
|
||||
pub fn installation_version(&self) -> Result<OsString, i32> {
|
||||
let mut s = null_mut();
|
||||
let mut s = null();
|
||||
let err = unsafe { self.0.GetInstallationVersion(&mut s) };
|
||||
let bstr = unsafe { BStr::from_raw(s) };
|
||||
if err < 0 {
|
||||
|
@ -248,7 +248,7 @@ impl SetupInstance {
|
|||
Ok(bstr.to_osstring())
|
||||
}
|
||||
pub fn product_path(&self) -> Result<OsString, i32> {
|
||||
let mut s = null_mut();
|
||||
let mut s = null();
|
||||
let this = self.0.cast::<ISetupInstance2>()?;
|
||||
let err = unsafe { this.GetProductPath(&mut s) };
|
||||
let bstr = unsafe { BStr::from_raw(s) };
|
|
@ -4,7 +4,7 @@ use std::convert::TryFrom;
|
|||
use std::io::BufRead;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::setup_config::{EnumSetupInstances, SetupInstance};
|
||||
use crate::windows::setup_config::{EnumSetupInstances, SetupInstance};
|
||||
|
||||
pub enum VsInstance {
|
||||
Com(SetupInstance),
|
|
@ -5,26 +5,19 @@
|
|||
// All files in the project carrying such notice may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![allow(bad_style)]
|
||||
#![allow(bad_style, clippy::upper_case_acronyms)]
|
||||
|
||||
use std::os::raw;
|
||||
|
||||
pub type wchar_t = u16;
|
||||
|
||||
pub type UINT = raw::c_uint;
|
||||
pub type LPUNKNOWN = *mut IUnknown;
|
||||
pub use crate::windows::windows_sys::{FILETIME, GUID, HRESULT, SAFEARRAY};
|
||||
|
||||
pub type REFIID = *const IID;
|
||||
pub type IID = GUID;
|
||||
pub type REFCLSID = *const IID;
|
||||
pub type PVOID = *mut raw::c_void;
|
||||
pub type USHORT = raw::c_ushort;
|
||||
pub type ULONG = raw::c_ulong;
|
||||
pub type LONG = raw::c_long;
|
||||
pub type DWORD = u32;
|
||||
pub type LPVOID = *mut raw::c_void;
|
||||
pub type HRESULT = raw::c_long;
|
||||
pub type LPFILETIME = *mut FILETIME;
|
||||
pub type BSTR = *mut OLECHAR;
|
||||
pub type OLECHAR = WCHAR;
|
||||
pub type WCHAR = wchar_t;
|
||||
pub type LPCOLESTR = *const OLECHAR;
|
||||
|
@ -33,75 +26,10 @@ pub type LPCWSTR = *const WCHAR;
|
|||
pub type PULONGLONG = *mut ULONGLONG;
|
||||
pub type ULONGLONG = u64;
|
||||
|
||||
pub const S_OK: HRESULT = 0;
|
||||
pub const S_FALSE: HRESULT = 1;
|
||||
pub const COINIT_MULTITHREADED: u32 = 0x0;
|
||||
|
||||
pub type CLSCTX = u32;
|
||||
|
||||
pub const CLSCTX_INPROC_SERVER: CLSCTX = 0x1;
|
||||
pub const CLSCTX_INPROC_HANDLER: CLSCTX = 0x2;
|
||||
pub const CLSCTX_LOCAL_SERVER: CLSCTX = 0x4;
|
||||
pub const CLSCTX_REMOTE_SERVER: CLSCTX = 0x10;
|
||||
|
||||
pub const CLSCTX_ALL: CLSCTX =
|
||||
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct GUID {
|
||||
pub Data1: raw::c_ulong,
|
||||
pub Data2: raw::c_ushort,
|
||||
pub Data3: raw::c_ushort,
|
||||
pub Data4: [raw::c_uchar; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FILETIME {
|
||||
pub dwLowDateTime: DWORD,
|
||||
pub dwHighDateTime: DWORD,
|
||||
}
|
||||
|
||||
pub trait Interface {
|
||||
fn uuidof() -> GUID;
|
||||
}
|
||||
|
||||
#[link(name = "ole32")]
|
||||
#[link(name = "oleaut32")]
|
||||
extern "C" {}
|
||||
|
||||
extern "system" {
|
||||
pub fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) -> HRESULT;
|
||||
pub fn CoCreateInstance(
|
||||
rclsid: REFCLSID,
|
||||
pUnkOuter: LPUNKNOWN,
|
||||
dwClsContext: DWORD,
|
||||
riid: REFIID,
|
||||
ppv: *mut LPVOID,
|
||||
) -> HRESULT;
|
||||
pub fn SysFreeString(bstrString: BSTR);
|
||||
pub fn SysStringLen(pbstr: BSTR) -> UINT;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SAFEARRAYBOUND {
|
||||
pub cElements: ULONG,
|
||||
pub lLbound: LONG,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SAFEARRAY {
|
||||
pub cDims: USHORT,
|
||||
pub fFeatures: USHORT,
|
||||
pub cbElements: ULONG,
|
||||
pub cLocks: ULONG,
|
||||
pub pvData: PVOID,
|
||||
pub rgsabound: [SAFEARRAYBOUND; 1],
|
||||
}
|
||||
|
||||
pub type LPSAFEARRAY = *mut SAFEARRAY;
|
||||
|
||||
macro_rules! DEFINE_GUID {
|
||||
|
@ -109,11 +37,11 @@ macro_rules! DEFINE_GUID {
|
|||
$name:ident, $l:expr, $w1:expr, $w2:expr,
|
||||
$b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr
|
||||
) => {
|
||||
pub const $name: $crate::winapi::GUID = $crate::winapi::GUID {
|
||||
Data1: $l,
|
||||
Data2: $w1,
|
||||
Data3: $w2,
|
||||
Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
|
||||
pub const $name: $crate::windows::winapi::GUID = $crate::windows::winapi::GUID {
|
||||
data1: $l,
|
||||
data2: $w1,
|
||||
data3: $w2,
|
||||
data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -193,14 +121,14 @@ macro_rules! RIDL {
|
|||
$l:expr, $w1:expr, $w2:expr,
|
||||
$b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr
|
||||
) => (
|
||||
impl $crate::winapi::Interface for $interface {
|
||||
impl $crate::windows::winapi::Interface for $interface {
|
||||
#[inline]
|
||||
fn uuidof() -> $crate::winapi::GUID {
|
||||
$crate::winapi::GUID {
|
||||
Data1: $l,
|
||||
Data2: $w1,
|
||||
Data3: $w2,
|
||||
Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
|
||||
fn uuidof() -> $crate::windows::winapi::GUID {
|
||||
$crate::windows::winapi::GUID {
|
||||
data1: $l,
|
||||
data2: $w1,
|
||||
data3: $w2,
|
||||
data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
// This file is autogenerated.
|
||||
//
|
||||
// To add bindings, edit windows_sys.lst then run:
|
||||
//
|
||||
// ```
|
||||
// cd generate-windows-sys/
|
||||
// cargo run
|
||||
// ```
|
||||
// Bindings generated by `windows-bindgen` 0.53.0
|
||||
|
||||
#![allow(
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
non_camel_case_types,
|
||||
dead_code,
|
||||
clippy::all
|
||||
)]
|
||||
#[link(name = "advapi32")]
|
||||
extern "system" {
|
||||
pub fn RegCloseKey(hkey: HKEY) -> WIN32_ERROR;
|
||||
}
|
||||
#[link(name = "advapi32")]
|
||||
extern "system" {
|
||||
pub fn RegEnumKeyExW(
|
||||
hkey: HKEY,
|
||||
dwindex: u32,
|
||||
lpname: PWSTR,
|
||||
lpcchname: *mut u32,
|
||||
lpreserved: *const u32,
|
||||
lpclass: PWSTR,
|
||||
lpcchclass: *mut u32,
|
||||
lpftlastwritetime: *mut FILETIME,
|
||||
) -> WIN32_ERROR;
|
||||
}
|
||||
#[link(name = "advapi32")]
|
||||
extern "system" {
|
||||
pub fn RegOpenKeyExW(
|
||||
hkey: HKEY,
|
||||
lpsubkey: PCWSTR,
|
||||
uloptions: u32,
|
||||
samdesired: REG_SAM_FLAGS,
|
||||
phkresult: *mut HKEY,
|
||||
) -> WIN32_ERROR;
|
||||
}
|
||||
#[link(name = "advapi32")]
|
||||
extern "system" {
|
||||
pub fn RegQueryValueExW(
|
||||
hkey: HKEY,
|
||||
lpvaluename: PCWSTR,
|
||||
lpreserved: *const u32,
|
||||
lptype: *mut REG_VALUE_TYPE,
|
||||
lpdata: *mut u8,
|
||||
lpcbdata: *mut u32,
|
||||
) -> WIN32_ERROR;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn FreeLibrary(hlibmodule: HMODULE) -> BOOL;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn GetMachineTypeAttributes(
|
||||
machine: u16,
|
||||
machinetypeattributes: *mut MACHINE_ATTRIBUTES,
|
||||
) -> HRESULT;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn GetProcAddress(hmodule: HMODULE, lpprocname: PCSTR) -> FARPROC;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn LoadLibraryA(lplibfilename: PCSTR) -> HMODULE;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn OpenSemaphoreA(dwdesiredaccess: u32, binherithandle: BOOL, lpname: PCSTR) -> HANDLE;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn PeekNamedPipe(
|
||||
hnamedpipe: HANDLE,
|
||||
lpbuffer: *mut ::core::ffi::c_void,
|
||||
nbuffersize: u32,
|
||||
lpbytesread: *mut u32,
|
||||
lptotalbytesavail: *mut u32,
|
||||
lpbytesleftthismessage: *mut u32,
|
||||
) -> BOOL;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn ReleaseSemaphore(
|
||||
hsemaphore: HANDLE,
|
||||
lreleasecount: i32,
|
||||
lppreviouscount: *mut i32,
|
||||
) -> BOOL;
|
||||
}
|
||||
#[link(name = "kernel32")]
|
||||
extern "system" {
|
||||
pub fn WaitForSingleObject(hhandle: HANDLE, dwmilliseconds: u32) -> WAIT_EVENT;
|
||||
}
|
||||
#[link(name = "ole32")]
|
||||
extern "system" {
|
||||
pub fn CoCreateInstance(
|
||||
rclsid: *const GUID,
|
||||
punkouter: *mut ::core::ffi::c_void,
|
||||
dwclscontext: CLSCTX,
|
||||
riid: *const GUID,
|
||||
ppv: *mut *mut ::core::ffi::c_void,
|
||||
) -> HRESULT;
|
||||
}
|
||||
#[link(name = "ole32")]
|
||||
extern "system" {
|
||||
pub fn CoInitializeEx(pvreserved: *const ::core::ffi::c_void, dwcoinit: u32) -> HRESULT;
|
||||
}
|
||||
#[link(name = "oleaut32")]
|
||||
extern "system" {
|
||||
pub fn SysFreeString(bstrstring: BSTR);
|
||||
}
|
||||
#[link(name = "oleaut32")]
|
||||
extern "system" {
|
||||
pub fn SysStringLen(pbstr: BSTR) -> u32;
|
||||
}
|
||||
pub type ADVANCED_FEATURE_FLAGS = u16;
|
||||
pub type BOOL = i32;
|
||||
pub type BSTR = *const u16;
|
||||
pub type CLSCTX = u32;
|
||||
pub const CLSCTX_ALL: CLSCTX = 23u32;
|
||||
pub type COINIT = i32;
|
||||
pub const COINIT_MULTITHREADED: COINIT = 0i32;
|
||||
pub const ERROR_NO_MORE_ITEMS: WIN32_ERROR = 259u32;
|
||||
pub const ERROR_SUCCESS: WIN32_ERROR = 0u32;
|
||||
pub const FALSE: BOOL = 0i32;
|
||||
pub type FARPROC = ::core::option::Option<unsafe extern "system" fn() -> isize>;
|
||||
#[repr(C)]
|
||||
pub struct FILETIME {
|
||||
pub dwLowDateTime: u32,
|
||||
pub dwHighDateTime: u32,
|
||||
}
|
||||
impl ::core::marker::Copy for FILETIME {}
|
||||
impl ::core::clone::Clone for FILETIME {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct GUID {
|
||||
pub data1: u32,
|
||||
pub data2: u16,
|
||||
pub data3: u16,
|
||||
pub data4: [u8; 8],
|
||||
}
|
||||
impl ::core::marker::Copy for GUID {}
|
||||
impl ::core::clone::Clone for GUID {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl GUID {
|
||||
pub const fn from_u128(uuid: u128) -> Self {
|
||||
Self {
|
||||
data1: (uuid >> 96) as u32,
|
||||
data2: (uuid >> 80 & 0xffff) as u16,
|
||||
data3: (uuid >> 64 & 0xffff) as u16,
|
||||
data4: (uuid as u64).to_be_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type HANDLE = *mut ::core::ffi::c_void;
|
||||
pub type HKEY = *mut ::core::ffi::c_void;
|
||||
pub const HKEY_LOCAL_MACHINE: HKEY = -2147483646i32 as _;
|
||||
pub type HMODULE = *mut ::core::ffi::c_void;
|
||||
pub type HRESULT = i32;
|
||||
pub type IMAGE_FILE_MACHINE = u16;
|
||||
pub const IMAGE_FILE_MACHINE_AMD64: IMAGE_FILE_MACHINE = 34404u16;
|
||||
pub const KEY_READ: REG_SAM_FLAGS = 131097u32;
|
||||
pub const KEY_WOW64_32KEY: REG_SAM_FLAGS = 512u32;
|
||||
pub type MACHINE_ATTRIBUTES = i32;
|
||||
pub type PCSTR = *const u8;
|
||||
pub type PCWSTR = *const u16;
|
||||
pub type PWSTR = *mut u16;
|
||||
pub type REG_SAM_FLAGS = u32;
|
||||
pub const REG_SZ: REG_VALUE_TYPE = 1u32;
|
||||
pub type REG_VALUE_TYPE = u32;
|
||||
#[repr(C)]
|
||||
pub struct SAFEARRAY {
|
||||
pub cDims: u16,
|
||||
pub fFeatures: ADVANCED_FEATURE_FLAGS,
|
||||
pub cbElements: u32,
|
||||
pub cLocks: u32,
|
||||
pub pvData: *mut ::core::ffi::c_void,
|
||||
pub rgsabound: [SAFEARRAYBOUND; 1],
|
||||
}
|
||||
impl ::core::marker::Copy for SAFEARRAY {}
|
||||
impl ::core::clone::Clone for SAFEARRAY {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct SAFEARRAYBOUND {
|
||||
pub cElements: u32,
|
||||
pub lLbound: i32,
|
||||
}
|
||||
impl ::core::marker::Copy for SAFEARRAYBOUND {}
|
||||
impl ::core::clone::Clone for SAFEARRAYBOUND {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
pub const SEMAPHORE_MODIFY_STATE: SYNCHRONIZATION_ACCESS_RIGHTS = 2u32;
|
||||
pub type SYNCHRONIZATION_ACCESS_RIGHTS = u32;
|
||||
pub const S_FALSE: HRESULT = 0x1_u32 as _;
|
||||
pub const S_OK: HRESULT = 0x0_u32 as _;
|
||||
pub type THREAD_ACCESS_RIGHTS = u32;
|
||||
pub const THREAD_SYNCHRONIZE: THREAD_ACCESS_RIGHTS = 1048576u32;
|
||||
pub const UserEnabled: MACHINE_ATTRIBUTES = 1i32;
|
||||
pub const WAIT_ABANDONED: WAIT_EVENT = 128u32;
|
||||
pub type WAIT_EVENT = u32;
|
||||
pub const WAIT_FAILED: WAIT_EVENT = 4294967295u32;
|
||||
pub const WAIT_OBJECT_0: WAIT_EVENT = 0u32;
|
||||
pub const WAIT_TIMEOUT: WAIT_EVENT = 258u32;
|
||||
pub type WIN32_ERROR = u32;
|
|
@ -1,118 +0,0 @@
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
|
||||
mod support;
|
||||
use crate::support::Test;
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
ccache();
|
||||
distcc();
|
||||
ccache_spaces();
|
||||
ccache_env_flags();
|
||||
leading_spaces();
|
||||
extra_flags();
|
||||
path_to_ccache();
|
||||
more_spaces();
|
||||
}
|
||||
|
||||
fn ccache() {
|
||||
let test = Test::gnu();
|
||||
|
||||
env::set_var("CC", "ccache cc");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
}
|
||||
|
||||
fn ccache_spaces() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", "ccache cc");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
}
|
||||
|
||||
fn distcc() {
|
||||
let test = Test::gnu();
|
||||
test.shim("distcc");
|
||||
|
||||
env::set_var("CC", "distcc cc");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
}
|
||||
|
||||
fn ccache_env_flags() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", "ccache lol-this-is-not-a-compiler");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("lol-this-is-not-a-compiler"));
|
||||
assert_eq!(
|
||||
compiler.cc_env(),
|
||||
OsString::from("ccache lol-this-is-not-a-compiler")
|
||||
);
|
||||
assert!(
|
||||
compiler
|
||||
.cflags_env()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.contains("ccache")
|
||||
== false
|
||||
);
|
||||
assert!(
|
||||
compiler
|
||||
.cflags_env()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.contains(" lol-this-is-not-a-compiler")
|
||||
== false
|
||||
);
|
||||
|
||||
env::set_var("CC", "");
|
||||
}
|
||||
|
||||
fn leading_spaces() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", " test ");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("test"));
|
||||
|
||||
env::set_var("CC", "");
|
||||
}
|
||||
|
||||
fn extra_flags() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", "ccache cc -m32");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
}
|
||||
|
||||
fn path_to_ccache() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", "/path/to/ccache.exe cc -m32");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
assert_eq!(
|
||||
compiler.cc_env(),
|
||||
OsString::from("/path/to/ccache.exe cc -m32"),
|
||||
);
|
||||
}
|
||||
|
||||
fn more_spaces() {
|
||||
let test = Test::gnu();
|
||||
test.shim("ccache");
|
||||
|
||||
env::set_var("CC", "cc -m32");
|
||||
let compiler = test.gcc().file("foo.c").get_compiler();
|
||||
assert_eq!(compiler.path(), Path::new("cc"));
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
mod support;
|
||||
|
||||
use crate::support::Test;
|
||||
use std::env;
|
||||
|
||||
/// This test is in its own module because it modifies the environment and would affect other tests
|
||||
/// when run in parallel with them.
|
||||
#[test]
|
||||
fn gnu_no_warnings_if_cflags() {
|
||||
env::set_var("CFLAGS", "-arbitrary");
|
||||
let test = Test::gnu();
|
||||
test.gcc().file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra");
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
mod support;
|
||||
|
||||
use crate::support::Test;
|
||||
use std::env;
|
||||
|
||||
/// This test is in its own module because it modifies the environment and would affect other tests
|
||||
/// when run in parallel with them.
|
||||
#[test]
|
||||
fn gnu_no_warnings_if_cxxflags() {
|
||||
env::set_var("CXXFLAGS", "-arbitrary");
|
||||
let test = Test::gnu();
|
||||
test.gcc().file("foo.cpp").cpp(true).compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-Wall").must_not_have("-Wextra");
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use cc;
|
||||
use tempfile::{Builder, TempDir};
|
||||
|
||||
pub struct Test {
|
||||
pub td: TempDir,
|
||||
pub gcc: PathBuf,
|
||||
pub msvc: bool,
|
||||
}
|
||||
|
||||
pub struct Execution {
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
pub fn new() -> Test {
|
||||
// This is ugly: `sccache` needs to introspect the compiler it is
|
||||
// executing, as it adjusts its behavior depending on the
|
||||
// language/compiler. This crate's test driver uses mock compilers that
|
||||
// are obviously not supported by sccache, so the tests fail if
|
||||
// RUSTC_WRAPPER is set. rust doesn't build test dependencies with
|
||||
// the `test` feature enabled, so we can't conditionally disable the
|
||||
// usage of `sccache` if running in a test environment, at least not
|
||||
// without setting an environment variable here and testing for it
|
||||
// there. Explicitly deasserting RUSTC_WRAPPER here seems to be the
|
||||
// lesser of the two evils.
|
||||
env::remove_var("RUSTC_WRAPPER");
|
||||
|
||||
let mut gcc = PathBuf::from(env::current_exe().unwrap());
|
||||
gcc.pop();
|
||||
if gcc.ends_with("deps") {
|
||||
gcc.pop();
|
||||
}
|
||||
let td = Builder::new().prefix("gcc-test").tempdir_in(&gcc).unwrap();
|
||||
gcc.push(format!("gcc-shim{}", env::consts::EXE_SUFFIX));
|
||||
Test {
|
||||
td: td,
|
||||
gcc: gcc,
|
||||
msvc: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gnu() -> Test {
|
||||
let t = Test::new();
|
||||
t.shim("cc").shim("c++").shim("ar");
|
||||
t
|
||||
}
|
||||
|
||||
pub fn msvc() -> Test {
|
||||
let mut t = Test::new();
|
||||
t.shim("cl").shim("lib.exe");
|
||||
t.msvc = true;
|
||||
t
|
||||
}
|
||||
|
||||
pub fn shim(&self, name: &str) -> &Test {
|
||||
let name = if name.ends_with(env::consts::EXE_SUFFIX) {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{}{}", name, env::consts::EXE_SUFFIX)
|
||||
};
|
||||
link_or_copy(&self.gcc, self.td.path().join(name)).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gcc(&self) -> cc::Build {
|
||||
let mut cfg = cc::Build::new();
|
||||
let target = if self.msvc {
|
||||
"x86_64-pc-windows-msvc"
|
||||
} else {
|
||||
"x86_64-unknown-linux-gnu"
|
||||
};
|
||||
|
||||
cfg.target(target)
|
||||
.host(target)
|
||||
.opt_level(2)
|
||||
.debug(false)
|
||||
.out_dir(self.td.path())
|
||||
.__set_env("PATH", self.path())
|
||||
.__set_env("GCCTEST_OUT_DIR", self.td.path());
|
||||
if self.msvc {
|
||||
cfg.compiler(self.td.path().join("cl"));
|
||||
cfg.archiver(self.td.path().join("lib.exe"));
|
||||
}
|
||||
cfg
|
||||
}
|
||||
|
||||
fn path(&self) -> OsString {
|
||||
let mut path = env::split_paths(&env::var_os("PATH").unwrap()).collect::<Vec<_>>();
|
||||
path.insert(0, self.td.path().to_owned());
|
||||
env::join_paths(path).unwrap()
|
||||
}
|
||||
|
||||
pub fn cmd(&self, i: u32) -> Execution {
|
||||
let mut s = String::new();
|
||||
File::open(self.td.path().join(format!("out{}", i)))
|
||||
.unwrap()
|
||||
.read_to_string(&mut s)
|
||||
.unwrap();
|
||||
Execution {
|
||||
args: s.lines().map(|s| s.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Execution {
|
||||
pub fn must_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution {
|
||||
if !self.has(p.as_ref()) {
|
||||
panic!("didn't find {:?} in {:?}", p.as_ref(), self.args);
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn must_not_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution {
|
||||
if self.has(p.as_ref()) {
|
||||
panic!("found {:?}", p.as_ref());
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has(&self, p: &OsStr) -> bool {
|
||||
self.args.iter().any(|arg| OsStr::new(arg) == p)
|
||||
}
|
||||
|
||||
pub fn must_have_in_order(&self, before: &str, after: &str) -> &Execution {
|
||||
let before_position = self
|
||||
.args
|
||||
.iter()
|
||||
.rposition(|x| OsStr::new(x) == OsStr::new(before));
|
||||
let after_position = self
|
||||
.args
|
||||
.iter()
|
||||
.rposition(|x| OsStr::new(x) == OsStr::new(after));
|
||||
match (before_position, after_position) {
|
||||
(Some(b), Some(a)) if b < a => {}
|
||||
(b, a) => panic!(
|
||||
"{:?} (last position: {:?}) did not appear before {:?} (last position: {:?})",
|
||||
before, b, after, a
|
||||
),
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Hard link an executable or copy it if that fails.
|
||||
///
|
||||
/// We first try to hard link an executable to save space. If that fails (as on Windows with
|
||||
/// different mount points, issue #60), we copy.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
fs::hard_link(from, to).or_else(|_| fs::copy(from, to).map(|_| ()))
|
||||
}
|
||||
|
||||
/// Copy an executable.
|
||||
///
|
||||
/// On macOS, hard linking the executable leads to strange failures (issue #419), so we just copy.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
|
||||
fs::copy(from, to).map(|_| ())
|
||||
}
|
|
@ -1,461 +0,0 @@
|
|||
use crate::support::Test;
|
||||
|
||||
mod support;
|
||||
|
||||
// Some tests check that a flag is *not* present. These tests might fail if the flag is set in the
|
||||
// CFLAGS or CXXFLAGS environment variables. This function clears the CFLAGS and CXXFLAGS
|
||||
// variables to make sure that the tests can run correctly.
|
||||
fn reset_env() {
|
||||
std::env::set_var("CFLAGS", "");
|
||||
std::env::set_var("CXXFLAGS", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_smoke() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc().file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0)
|
||||
.must_have("-O2")
|
||||
.must_have("foo.c")
|
||||
.must_not_have("-gdwarf-4")
|
||||
.must_have("-c")
|
||||
.must_have("-ffunction-sections")
|
||||
.must_have("-fdata-sections");
|
||||
test.cmd(1).must_have(test.td.path().join("foo.o"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_opt_level_1() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc().opt_level(1).file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-O1").must_not_have("-O2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_opt_level_s() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc().opt_level_str("s").file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0)
|
||||
.must_have("-Os")
|
||||
.must_not_have("-O1")
|
||||
.must_not_have("-O2")
|
||||
.must_not_have("-O3")
|
||||
.must_not_have("-Oz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_debug() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().debug(true).file("foo.c").compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-4");
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.target("x86_64-apple-darwin")
|
||||
.debug(true)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_debug_fp_auto() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().debug(true).file("foo.c").compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-4");
|
||||
test.cmd(0).must_have("-fno-omit-frame-pointer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_debug_fp() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().debug(true).file("foo.c").compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-4");
|
||||
test.cmd(0).must_have("-fno-omit-frame-pointer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_debug_nofp() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.debug(true)
|
||||
.force_frame_pointer(false)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-4");
|
||||
test.cmd(0).must_not_have("-fno-omit-frame-pointer");
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.force_frame_pointer(false)
|
||||
.debug(true)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
test.cmd(0).must_have("-gdwarf-4");
|
||||
test.cmd(0).must_not_have("-fno-omit-frame-pointer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_warnings_into_errors() {
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.warnings_into_errors(true)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-Werror");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_warnings() {
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.warnings(true)
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-Wall").must_have("-Wextra");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_extra_warnings0() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.warnings(true)
|
||||
.extra_warnings(false)
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-Wall").must_not_have("-Wextra");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_extra_warnings1() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.warnings(false)
|
||||
.extra_warnings(true)
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-Wall").must_have("-Wextra");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_warnings_overridable() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.warnings(true)
|
||||
.flag("-Wno-missing-field-initializers")
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0)
|
||||
.must_have_in_order("-Wall", "-Wno-missing-field-initializers");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_x86_64() {
|
||||
for vendor in &["unknown-linux-gnu", "apple-darwin"] {
|
||||
let target = format!("x86_64-{}", vendor);
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.target(&target)
|
||||
.host(&target)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-fPIC").must_have("-m64");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_x86_64_no_pic() {
|
||||
reset_env();
|
||||
|
||||
for vendor in &["unknown-linux-gnu", "apple-darwin"] {
|
||||
let target = format!("x86_64-{}", vendor);
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.pic(false)
|
||||
.target(&target)
|
||||
.host(&target)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-fPIC");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_i686() {
|
||||
for vendor in &["unknown-linux-gnu", "apple-darwin"] {
|
||||
let target = format!("i686-{}", vendor);
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.target(&target)
|
||||
.host(&target)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-m32");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_i686_pic() {
|
||||
for vendor in &["unknown-linux-gnu", "apple-darwin"] {
|
||||
let target = format!("i686-{}", vendor);
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.pic(true)
|
||||
.target(&target)
|
||||
.host(&target)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-fPIC");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_x86_64_no_plt() {
|
||||
let target = "x86_64-unknown-linux-gnu";
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.pic(true)
|
||||
.use_plt(false)
|
||||
.target(&target)
|
||||
.host(&target)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
test.cmd(0).must_have("-fno-plt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_set_stdlib() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.cpp_set_stdlib(Some("foo"))
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-stdlib=foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_include() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().include("foo/bar").file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-I").must_have("foo/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_define() {
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.define("FOO", "bar")
|
||||
.define("BAR", None)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-DFOO=bar").must_have("-DBAR");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_compile_assembly() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().file("foo.S").compile("foo");
|
||||
test.cmd(0).must_have("foo.S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_shared() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.file("foo.c")
|
||||
.shared_flag(true)
|
||||
.static_flag(false)
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-shared").must_not_have("-static");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_flag_if_supported() {
|
||||
reset_env();
|
||||
|
||||
if cfg!(windows) {
|
||||
return;
|
||||
}
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.file("foo.c")
|
||||
.flag("-v")
|
||||
.flag_if_supported("-Wall")
|
||||
.flag_if_supported("-Wflag-does-not-exist")
|
||||
.flag_if_supported("-std=c++11")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0)
|
||||
.must_have("-v")
|
||||
.must_have("-Wall")
|
||||
.must_not_have("-Wflag-does-not-exist")
|
||||
.must_not_have("-std=c++11");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_flag_if_supported_cpp() {
|
||||
if cfg!(windows) {
|
||||
return;
|
||||
}
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.cpp(true)
|
||||
.file("foo.cpp")
|
||||
.flag_if_supported("-std=c++11")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-std=c++11");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_static() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.file("foo.c")
|
||||
.shared_flag(false)
|
||||
.static_flag(true)
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-static").must_not_have("-shared");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_no_dash_dash() {
|
||||
let test = Test::gnu();
|
||||
test.gcc().file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("--");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_smoke() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::msvc();
|
||||
test.gcc().file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0)
|
||||
.must_have("-O2")
|
||||
.must_have("foo.c")
|
||||
.must_not_have("-Z7")
|
||||
.must_have("-c")
|
||||
.must_have("-MD");
|
||||
test.cmd(1).must_have(test.td.path().join("foo.o"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_opt_level_0() {
|
||||
reset_env();
|
||||
|
||||
let test = Test::msvc();
|
||||
test.gcc().opt_level(0).file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("-O2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_debug() {
|
||||
let test = Test::msvc();
|
||||
test.gcc().debug(true).file("foo.c").compile("foo");
|
||||
test.cmd(0).must_have("-Z7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_include() {
|
||||
let test = Test::msvc();
|
||||
test.gcc().include("foo/bar").file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-I").must_have("foo/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_define() {
|
||||
let test = Test::msvc();
|
||||
test.gcc()
|
||||
.define("FOO", "bar")
|
||||
.define("BAR", None)
|
||||
.file("foo.c")
|
||||
.compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-DFOO=bar").must_have("-DBAR");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_static_crt() {
|
||||
let test = Test::msvc();
|
||||
test.gcc().static_crt(true).file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-MT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_no_static_crt() {
|
||||
let test = Test::msvc();
|
||||
test.gcc().static_crt(false).file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_have("-MD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msvc_no_dash_dash() {
|
||||
let test = Test::msvc();
|
||||
test.gcc().file("foo.c").compile("foo");
|
||||
|
||||
test.cmd(0).must_not_have("--");
|
||||
}
|
||||
|
||||
// Disable this test with the parallel feature because the execution
|
||||
// order is not deterministic.
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
#[test]
|
||||
fn asm_flags() {
|
||||
let test = Test::gnu();
|
||||
test.gcc()
|
||||
.file("foo.c")
|
||||
.file("x86_64.asm")
|
||||
.file("x86_64.S")
|
||||
.asm_flag("--abc")
|
||||
.compile("foo");
|
||||
test.cmd(0).must_not_have("--abc");
|
||||
test.cmd(1).must_have("--abc");
|
||||
test.cmd(2).must_have("--abc");
|
||||
}
|
Загрузка…
Ссылка в новой задаче