Bug 1882202 - Upgrade the cc crate to 1.0.89. r=emilio,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D202771
This commit is contained in:
Mike Hommey 2024-03-05 09:03:57 +00:00
parent 99133f08ff
commit b0cd1791f4
28 changed files with 3532 additions and 2637 deletions

5
Cargo.lock generated
View File

@ -646,11 +646,12 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.78" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc",
] ]
[[package]] [[package]]

View File

@ -4754,6 +4754,12 @@ user-id = 6741 # Alice Ryhl (Darksonn)
start = "2021-01-11" start = "2021-01-11"
end = "2024-05-05" 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]] [[trusted.clap]]
criteria = "safe-to-deploy" criteria = "safe-to-deploy"
user-id = 6743 # Ed Page (epage) user-id = 6743 # Ed Page (epage)

View File

@ -71,6 +71,13 @@ user-id = 6741
user-login = "Darksonn" user-login = "Darksonn"
user-name = "Alice Ryhl" user-name = "Alice Ryhl"
[[publisher.cc]]
version = "1.0.89"
when = "2024-03-04"
user-id = 2915
user-login = "Amanieu"
user-name = "Amanieu d'Antras"
[[publisher.cexpr]] [[publisher.cexpr]]
version = "0.6.0" version = "0.6.0"
when = "2021-10-11" when = "2021-10-11"

View File

@ -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":"1288f536f4ddf6bcdc664a91a070aad2ebd7c6edc32ce24e8d6bc04c2cd64d49","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"f1ddbede208a5b78333a25dac0a7598e678e9b601a7d99a791069bddaf180dfe","src/command_helpers.rs":"3ef95bdcd79a43406fdab275d8a8f45ba787876399b54df34068955ec0109e69","src/lib.rs":"91efa8f9242266752658edd66ee607ce30635f4c30710508a99eb62e7b3c54da","src/parallel/async_executor.rs":"4ce24435fff6b6555b43fee042c16bd65d4150d0346567f246b9190d85b45983","src/parallel/job_token.rs":"0676c3177b5be9d7ede483bf4bd45c5ca0f5511073e4d1c9f181a0bc83db05dc","src/parallel/mod.rs":"aaffed5ad3dc0d28641533ab0d6f522bf34a059d4b1a239dc4d217cb5d58e232","src/parallel/stderr.rs":"a2d18ba3f2e04deb9047ece9ab7ca5452d9a76b515afbe20a76307e31597f34b","src/tool.rs":"172cfcbecd7c6a363ea841a48a10a75b0a01e83b83c0691107c601598b68dedf","src/windows/com.rs":"be1564756c9f3ef1398eafeed7b54ba610caba28e8f6258d28a997737ebf9535","src/windows/find_tools.rs":"9234fe7ab27b0259c6fa9fb47826e7d1a3d1d2c7c4042ef7153ab90ccb9a3412","src/windows/mod.rs":"42f1ad7fee35a17686b003e6aa520d3d1940d47d2f531d626e9ae0c48ba49005","src/windows/registry.rs":"c521b72c825e8095843e73482ffa810ed066ad8bb9f86e6db0c5c143c171aba1","src/windows/setup_config.rs":"754439cbab492afd44c9755abcbec1a41c9b2c358131cee2df13c0e996dbbec8","src/windows/vs_instances.rs":"76e3cee74b5fd38ddaf533bba11fe401667c50dda5f9d064099840893eaa7587","src/windows/winapi.rs":"250d51c1826d1a2329e9889dd9f058cfce253dbf2a678b076147c6cdb5db046c","src/windows/windows_sys.rs":"f6b90b87f23e446284bde86749b53858c0d37b8a43515ed8d0e90b1ac8cf7771"},"package":"a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"}

110
third_party/rust/cc/Cargo.lock generated vendored
View File

@ -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"

View File

@ -11,10 +11,15 @@
[package] [package]
edition = "2018" edition = "2018"
rust-version = "1.53"
name = "cc" name = "cc"
version = "1.0.78" version = "1.0.89"
authors = ["Alex Crichton <alex@alexcrichton.com>"] authors = ["Alex Crichton <alex@alexcrichton.com>"]
exclude = ["/.github"] exclude = [
"/.github",
"tests",
"src/bin",
]
description = """ description = """
A build-time dependency for Cargo build scripts to assist in invoking the native 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 C compiler to compile native C code into a static archive to be linked into Rust
@ -29,11 +34,20 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/cc-rs" repository = "https://github.com/rust-lang/cc-rs"
[dependencies.jobserver] [dependencies.jobserver]
version = "0.1.16" version = "0.1.20"
optional = true optional = true
default-features = false
[dev-dependencies.tempfile] [dev-dependencies.tempfile]
version = "3" version = "3"
[features] [features]
parallel = ["jobserver"] parallel = [
"libc",
"jobserver",
]
[target."cfg(unix)".dependencies.libc]
version = "0.2.62"
optional = true
default-features = false

View File

@ -1,209 +1,13 @@
# cc-rs # 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) Refer to the [documentation](https://docs.rs/cc) for detailed usage instructions.
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");
}
```
## License ## License

View File

@ -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()
));
}

View File

@ -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
),
))
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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
};
}
}

View File

@ -0,0 +1,255 @@
use std::{marker::PhantomData, mem::MaybeUninit, sync::Once};
use crate::Error;
pub(crate) struct JobToken(PhantomData<()>);
impl JobToken {
fn new() -> Self {
Self(PhantomData)
}
}
impl Drop for JobToken {
fn drop(&mut self) {
match JobTokenServer::new() {
JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(),
JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(),
}
}
}
enum JobTokenServer {
Inherited(inherited_jobserver::JobServer),
InProcess(inprocess_jobserver::JobServer),
}
impl JobTokenServer {
/// This function returns a static reference to the jobserver because
/// - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might
/// be closed by other jobserver users in the process) and better do it
/// at the start of the program.
/// - in case a jobserver cannot be created from env (e.g. it's not
/// present), we will create a global in-process only jobserver
/// that has to be static so that it will be shared by all cc
/// compilation.
fn new() -> &'static Self {
static INIT: Once = Once::new();
static mut JOBSERVER: MaybeUninit<JobTokenServer> = MaybeUninit::uninit();
unsafe {
INIT.call_once(|| {
let server = inherited_jobserver::JobServer::from_env()
.map(Self::Inherited)
.unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new()));
JOBSERVER = MaybeUninit::new(server);
});
// TODO: Poor man's assume_init_ref, as that'd require a MSRV of 1.55.
&*JOBSERVER.as_ptr()
}
}
}
pub(crate) enum ActiveJobTokenServer {
Inherited(inherited_jobserver::ActiveJobServer<'static>),
InProcess(&'static inprocess_jobserver::JobServer),
}
impl ActiveJobTokenServer {
pub(crate) fn new() -> Result<Self, Error> {
match JobTokenServer::new() {
JobTokenServer::Inherited(inherited_jobserver) => {
inherited_jobserver.enter_active().map(Self::Inherited)
}
JobTokenServer::InProcess(inprocess_jobserver) => {
Ok(Self::InProcess(inprocess_jobserver))
}
}
}
pub(crate) async fn acquire(&self) -> Result<JobToken, Error> {
match &self {
Self::Inherited(jobserver) => jobserver.acquire().await,
Self::InProcess(jobserver) => Ok(jobserver.acquire().await),
}
}
}
mod inherited_jobserver {
use super::JobToken;
use crate::{parallel::async_executor::YieldOnce, Error, ErrorKind};
use std::{
io, mem,
sync::{mpsc, Mutex, MutexGuard, PoisonError},
};
pub(super) struct JobServer {
/// Implicit token for this process which is obtained and will be
/// released in parent. Since JobTokens only give back what they got,
/// there should be at most one global implicit token in the wild.
///
/// Since Rust does not execute any `Drop` for global variables,
/// we can't just put it back to jobserver and then re-acquire it at
/// the end of the process.
///
/// Use `Mutex` to avoid race between acquire and release.
/// If an `AtomicBool` is used, then it's possible for:
/// - `release_token_raw`: Tries to set `global_implicit_token` to true, but it is already
/// set to `true`, continue to release it to jobserver
/// - `acquire` takes the global implicit token, set `global_implicit_token` to false
/// - `release_token_raw` now writes the token back into the jobserver, while
/// `global_implicit_token` is `false`
///
/// If the program exits here, then cc effectively increases parallelism by one, which is
/// incorrect, hence we use a `Mutex` here.
global_implicit_token: Mutex<bool>,
inner: jobserver::Client,
}
impl JobServer {
pub(super) unsafe fn from_env() -> Option<Self> {
jobserver::Client::from_env().map(|inner| Self {
inner,
global_implicit_token: Mutex::new(true),
})
}
fn get_global_implicit_token(&self) -> MutexGuard<'_, bool> {
self.global_implicit_token
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
/// All tokens except for the global implicit token will be put back into the jobserver
/// immediately and they cannot be cached, since Rust does not call `Drop::drop` on
/// global variables.
pub(super) fn release_token_raw(&self) {
let mut global_implicit_token = self.get_global_implicit_token();
if *global_implicit_token {
// There's already a global implicit token, so this token must
// be released back into jobserver.
//
// `release_raw` should not block
let _ = self.inner.release_raw();
} else {
*global_implicit_token = true;
}
}
pub(super) fn enter_active(&self) -> Result<ActiveJobServer<'_>, Error> {
ActiveJobServer::new(self)
}
}
pub(crate) struct ActiveJobServer<'a> {
jobserver: &'a JobServer,
helper_thread: jobserver::HelperThread,
/// When rx is dropped, all the token stored within it will be dropped.
rx: mpsc::Receiver<io::Result<jobserver::Acquired>>,
}
impl<'a> ActiveJobServer<'a> {
fn new(jobserver: &'a JobServer) -> Result<Self, Error> {
let (tx, rx) = mpsc::channel();
Ok(Self {
rx,
helper_thread: jobserver.inner.clone().into_helper_thread(move |res| {
let _ = tx.send(res);
})?,
jobserver,
})
}
pub(super) async fn acquire(&self) -> Result<JobToken, Error> {
let mut has_requested_token = false;
loop {
// Fast path
if mem::replace(&mut *self.jobserver.get_global_implicit_token(), false) {
break Ok(JobToken::new());
}
// Cold path, no global implicit token, obtain one
match self.rx.try_recv() {
Ok(res) => {
let acquired = res?;
acquired.drop_without_releasing();
break Ok(JobToken::new());
}
Err(mpsc::TryRecvError::Disconnected) => {
break Err(Error::new(
ErrorKind::JobserverHelpThreadError,
"jobserver help thread has returned before ActiveJobServer is dropped",
))
}
Err(mpsc::TryRecvError::Empty) => {
if !has_requested_token {
self.helper_thread.request_token();
has_requested_token = true;
}
YieldOnce::default().await
}
}
}
}
}
}
mod inprocess_jobserver {
use super::JobToken;
use crate::parallel::async_executor::YieldOnce;
use std::{
env::var,
sync::atomic::{
AtomicU32,
Ordering::{AcqRel, Acquire},
},
};
pub(crate) struct JobServer(AtomicU32);
impl JobServer {
pub(super) fn new() -> Self {
// Use `NUM_JOBS` if set (it's configured by Cargo) and otherwise
// just fall back to a semi-reasonable number.
//
// Note that we could use `num_cpus` here but it's an extra
// dependency that will almost never be used, so
// it's generally not too worth it.
let mut parallelism = 4;
// TODO: Use std::thread::available_parallelism as an upper bound
// when MSRV is bumped.
if let Ok(amt) = var("NUM_JOBS") {
if let Ok(amt) = amt.parse() {
parallelism = amt;
}
}
Self(AtomicU32::new(parallelism))
}
pub(super) async fn acquire(&self) -> JobToken {
loop {
let res = self
.0
.fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1));
if res.is_ok() {
break JobToken::new();
}
YieldOnce::default().await
}
}
pub(super) fn release_token_raw(&self) {
self.0.fetch_add(1, AcqRel);
}
}
}

20
third_party/rust/cc/src/parallel/mod.rs vendored Normal file
View File

@ -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);
}
}
}

View File

@ -0,0 +1,90 @@
/// Helpers functions for [ChildStderr].
use std::{convert::TryInto, process::ChildStderr};
use crate::{Error, ErrorKind};
#[cfg(all(not(unix), not(windows)))]
compile_error!("Only unix and windows support non-blocking pipes! For other OSes, disable the parallel feature.");
#[cfg(unix)]
fn get_flags(fd: std::os::unix::io::RawFd) -> Result<i32, Error> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
if flags == -1 {
Err(Error::new(
ErrorKind::IOError,
format!(
"Failed to get flags for pipe {}: {}",
fd,
std::io::Error::last_os_error()
),
))
} else {
Ok(flags)
}
}
#[cfg(unix)]
fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result<(), Error> {
if unsafe { libc::fcntl(fd, libc::F_SETFL, flags) } == -1 {
Err(Error::new(
ErrorKind::IOError,
format!(
"Failed to set flags for pipe {}: {}",
fd,
std::io::Error::last_os_error()
),
))
} else {
Ok(())
}
}
#[cfg(unix)]
pub fn set_non_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> {
// On Unix, switch the pipe to non-blocking mode.
// On Windows, we have a different way to be non-blocking.
let fd = pipe.as_raw_fd();
let flags = get_flags(fd)?;
set_flags(fd, flags | libc::O_NONBLOCK)
}
pub fn bytes_available(stderr: &mut ChildStderr) -> Result<usize, Error> {
let mut bytes_available = 0;
#[cfg(windows)]
{
use crate::windows::windows_sys::PeekNamedPipe;
use std::os::windows::io::AsRawHandle;
use std::ptr::null_mut;
if unsafe {
PeekNamedPipe(
stderr.as_raw_handle(),
null_mut(),
0,
null_mut(),
&mut bytes_available,
null_mut(),
)
} == 0
{
return Err(Error::new(
ErrorKind::IOError,
format!(
"PeekNamedPipe failed with {}",
std::io::Error::last_os_error()
),
));
}
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
if unsafe { libc::ioctl(stderr.as_raw_fd(), libc::FIONREAD, &mut bytes_available) } != 0 {
return Err(Error::new(
ErrorKind::IOError,
format!("ioctl failed with {}", std::io::Error::last_os_error()),
));
}
}
Ok(bytes_available.try_into().unwrap())
}

399
third_party/rust/cc/src/tool.rs vendored Normal file
View File

@ -0,0 +1,399 @@
use std::{
collections::HashMap,
ffi::OsString,
path::{Path, PathBuf},
process::Command,
sync::Mutex,
};
use crate::command_helpers::{run_output, CargoOutput};
/// Configuration used to represent an invocation of a C compiler.
///
/// This can be used to figure out what compiler is in use, what the arguments
/// to it are, and what the environment variables look like for the compiler.
/// This can be used to further configure other build systems (e.g. forward
/// along CC and/or CFLAGS) or the `to_command` method can be used to run the
/// compiler itself.
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub struct Tool {
pub(crate) path: PathBuf,
pub(crate) cc_wrapper_path: Option<PathBuf>,
pub(crate) cc_wrapper_args: Vec<OsString>,
pub(crate) args: Vec<OsString>,
pub(crate) env: Vec<(OsString, OsString)>,
pub(crate) family: ToolFamily,
pub(crate) cuda: bool,
pub(crate) removed_args: Vec<OsString>,
pub(crate) has_internal_target_arg: bool,
}
impl Tool {
pub(crate) fn new(
path: PathBuf,
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
cargo_output: &CargoOutput,
) -> Self {
Self::with_features(path, None, false, cached_compiler_family, cargo_output)
}
pub(crate) fn with_clang_driver(
path: PathBuf,
clang_driver: Option<&str>,
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
cargo_output: &CargoOutput,
) -> Self {
Self::with_features(
path,
clang_driver,
false,
cached_compiler_family,
cargo_output,
)
}
/// Explicitly set the `ToolFamily`, skipping name-based detection.
pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self {
Self {
path,
cc_wrapper_path: None,
cc_wrapper_args: Vec::new(),
args: Vec::new(),
env: Vec::new(),
family,
cuda: false,
removed_args: Vec::new(),
has_internal_target_arg: false,
}
}
pub(crate) fn with_features(
path: PathBuf,
clang_driver: Option<&str>,
cuda: bool,
cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
cargo_output: &CargoOutput,
) -> Self {
fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily {
let mut cmd = Command::new(path);
cmd.arg("--version");
let stdout = match run_output(
&mut cmd,
&path.to_string_lossy(),
// tool detection issues should always be shown as warnings
cargo_output,
)
.ok()
.and_then(|o| String::from_utf8(o).ok())
{
Some(s) => s,
None => {
// --version failed. fallback to gnu
cargo_output.print_warning(&format_args!("Failed to run: {:?}", cmd));
return ToolFamily::Gnu;
}
};
if stdout.contains("clang") {
ToolFamily::Clang
} else if stdout.contains("GCC") {
ToolFamily::Gnu
} else {
// --version doesn't include clang for GCC
cargo_output.print_warning(&format_args!(
"Compiler version doesn't include clang or GCC: {:?}",
cmd
));
ToolFamily::Gnu
}
}
let detect_family = |path: &Path| -> ToolFamily {
if let Some(family) = cached_compiler_family.lock().unwrap().get(path) {
return *family;
}
let family = detect_family_inner(path, cargo_output);
cached_compiler_family
.lock()
.unwrap()
.insert(path.into(), family);
family
};
// Try to detect family of the tool from its name, falling back to Gnu.
let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) {
if fname.contains("clang-cl") {
ToolFamily::Msvc { clang_cl: true }
} else if fname.ends_with("cl") || fname == "cl.exe" {
ToolFamily::Msvc { clang_cl: false }
} else if fname.contains("clang") {
match clang_driver {
Some("cl") => ToolFamily::Msvc { clang_cl: true },
_ => ToolFamily::Clang,
}
} else {
detect_family(&path)
}
} else {
detect_family(&path)
};
Tool {
path,
cc_wrapper_path: None,
cc_wrapper_args: Vec::new(),
args: Vec::new(),
env: Vec::new(),
family,
cuda,
removed_args: Vec::new(),
has_internal_target_arg: false,
}
}
/// Add an argument to be stripped from the final command arguments.
pub(crate) fn remove_arg(&mut self, flag: OsString) {
self.removed_args.push(flag);
}
/// Push an "exotic" flag to the end of the compiler's arguments list.
///
/// Nvidia compiler accepts only the most common compiler flags like `-D`,
/// `-I`, `-c`, etc. Options meant specifically for the underlying
/// host C++ compiler have to be prefixed with `-Xcompiler`.
/// [Another possible future application for this function is passing
/// clang-specific flags to clang-cl, which otherwise accepts only
/// MSVC-specific options.]
pub(crate) fn push_cc_arg(&mut self, flag: OsString) {
if self.cuda {
self.args.push("-Xcompiler".into());
}
self.args.push(flag);
}
/// Checks if an argument or flag has already been specified or conflicts.
///
/// Currently only checks optimization flags.
pub(crate) fn is_duplicate_opt_arg(&self, flag: &OsString) -> bool {
let flag = flag.to_str().unwrap();
let mut chars = flag.chars();
// Only duplicate check compiler flags
if self.is_like_msvc() {
if chars.next() != Some('/') {
return false;
}
} else if self.is_like_gnu() || self.is_like_clang() {
if chars.next() != Some('-') {
return false;
}
}
// Check for existing optimization flags (-O, /O)
if chars.next() == Some('O') {
return self
.args()
.iter()
.any(|a| a.to_str().unwrap_or("").chars().nth(1) == Some('O'));
}
// TODO Check for existing -m..., -m...=..., /arch:... flags
false
}
/// Don't push optimization arg if it conflicts with existing args.
pub(crate) fn push_opt_unless_duplicate(&mut self, flag: OsString) {
if self.is_duplicate_opt_arg(&flag) {
println!("Info: Ignoring duplicate arg {:?}", &flag);
} else {
self.push_cc_arg(flag);
}
}
/// Converts this compiler into a `Command` that's ready to be run.
///
/// This is useful for when the compiler needs to be executed and the
/// command returned will already have the initial arguments and environment
/// variables configured.
pub fn to_command(&self) -> Command {
let mut cmd = match self.cc_wrapper_path {
Some(ref cc_wrapper_path) => {
let mut cmd = Command::new(cc_wrapper_path);
cmd.arg(&self.path);
cmd
}
None => Command::new(&self.path),
};
cmd.args(&self.cc_wrapper_args);
let value = self
.args
.iter()
.filter(|a| !self.removed_args.contains(a))
.collect::<Vec<_>>();
cmd.args(&value);
for (k, v) in self.env.iter() {
cmd.env(k, v);
}
cmd
}
/// Returns the path for this compiler.
///
/// Note that this may not be a path to a file on the filesystem, e.g. "cc",
/// but rather something which will be resolved when a process is spawned.
pub fn path(&self) -> &Path {
&self.path
}
/// Returns the default set of arguments to the compiler needed to produce
/// executables for the target this compiler generates.
pub fn args(&self) -> &[OsString] {
&self.args
}
/// Returns the set of environment variables needed for this compiler to
/// operate.
///
/// This is typically only used for MSVC compilers currently.
pub fn env(&self) -> &[(OsString, OsString)] {
&self.env
}
/// Returns the compiler command in format of CC environment variable.
/// Or empty string if CC env was not present
///
/// This is typically used by configure script
pub fn cc_env(&self) -> OsString {
match self.cc_wrapper_path {
Some(ref cc_wrapper_path) => {
let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
cc_env.push(" ");
cc_env.push(self.path.to_path_buf().into_os_string());
for arg in self.cc_wrapper_args.iter() {
cc_env.push(" ");
cc_env.push(arg);
}
cc_env
}
None => OsString::from(""),
}
}
/// Returns the compiler flags in format of CFLAGS environment variable.
/// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS
/// This is typically used by configure script
pub fn cflags_env(&self) -> OsString {
let mut flags = OsString::new();
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
flags.push(" ");
}
flags.push(arg);
}
flags
}
/// Whether the tool is GNU Compiler Collection-like.
pub fn is_like_gnu(&self) -> bool {
self.family == ToolFamily::Gnu
}
/// Whether the tool is Clang-like.
pub fn is_like_clang(&self) -> bool {
self.family == ToolFamily::Clang
}
/// Whether the tool is AppleClang under .xctoolchain
#[cfg(target_vendor = "apple")]
pub(crate) fn is_xctoolchain_clang(&self) -> bool {
let path = self.path.to_string_lossy();
path.contains(".xctoolchain/")
}
#[cfg(not(target_vendor = "apple"))]
pub(crate) fn is_xctoolchain_clang(&self) -> bool {
false
}
/// Whether the tool is MSVC-like.
pub fn is_like_msvc(&self) -> bool {
match self.family {
ToolFamily::Msvc { .. } => true,
_ => false,
}
}
}
/// Represents the family of tools this tool belongs to.
///
/// Each family of tools differs in how and what arguments they accept.
///
/// Detection of a family is done on best-effort basis and may not accurately reflect the tool.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ToolFamily {
/// Tool is GNU Compiler Collection-like.
Gnu,
/// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags
/// and its cross-compilation approach is different.
Clang,
/// Tool is the MSVC cl.exe.
Msvc { clang_cl: bool },
}
impl ToolFamily {
/// What the flag to request debug info for this family of tools look like
pub(crate) fn add_debug_flags(&self, cmd: &mut Tool, dwarf_version: Option<u32>) {
match *self {
ToolFamily::Msvc { .. } => {
cmd.push_cc_arg("-Z7".into());
}
ToolFamily::Gnu | ToolFamily::Clang => {
cmd.push_cc_arg(
dwarf_version
.map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v))
.into(),
);
}
}
}
/// What the flag to force frame pointers.
pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) {
match *self {
ToolFamily::Gnu | ToolFamily::Clang => {
cmd.push_cc_arg("-fno-omit-frame-pointer".into());
}
_ => (),
}
}
/// What the flags to enable all warnings
pub(crate) fn warnings_flags(&self) -> &'static str {
match *self {
ToolFamily::Msvc { .. } => "-W4",
ToolFamily::Gnu | ToolFamily::Clang => "-Wall",
}
}
/// What the flags to enable extra warnings
pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> {
match *self {
ToolFamily::Msvc { .. } => None,
ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"),
}
}
/// What the flag to turn warning into errors
pub(crate) fn warnings_to_errors_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc { .. } => "-WX",
ToolFamily::Gnu | ToolFamily::Clang => "-Werror",
}
}
pub(crate) fn verbose_stderr(&self) -> bool {
*self == ToolFamily::Clang
}
}

View File

@ -7,27 +7,31 @@
#![allow(unused)] #![allow(unused)]
use crate::winapi::CoInitializeEx; use crate::windows::{
use crate::winapi::IUnknown; winapi::{IUnknown, Interface},
use crate::winapi::Interface; windows_sys::{
use crate::winapi::BSTR; CoInitializeEx, SysFreeString, SysStringLen, BSTR, COINIT_MULTITHREADED, HRESULT, S_FALSE,
use crate::winapi::COINIT_MULTITHREADED; S_OK,
use crate::winapi::{SysFreeString, SysStringLen}; },
use crate::winapi::{HRESULT, S_FALSE, S_OK}; };
use std::ffi::{OsStr, OsString}; use std::{
use std::mem::forget; convert::TryInto,
use std::ops::Deref; ffi::{OsStr, OsString},
use std::os::windows::ffi::{OsStrExt, OsStringExt}; mem::ManuallyDrop,
use std::ptr::null_mut; ops::Deref,
use std::slice::from_raw_parts; os::windows::ffi::{OsStrExt, OsStringExt},
ptr::{null, null_mut},
slice::from_raw_parts,
};
pub fn initialize() -> Result<(), HRESULT> { 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 { if err != S_OK && err != S_FALSE {
// S_FALSE just means COM is already initialized // S_FALSE just means COM is already initialized
return Err(err); Err(err)
} else {
Ok(())
} }
Ok(())
} }
pub struct ComPtr<T>(*mut T) pub struct ComPtr<T>(*mut T)
@ -55,15 +59,13 @@ where
/// Extracts the raw pointer. /// Extracts the raw pointer.
/// You are now responsible for releasing it yourself. /// You are now responsible for releasing it yourself.
pub fn into_raw(self) -> *mut T { pub fn into_raw(self) -> *mut T {
let p = self.0; ManuallyDrop::new(self).0
forget(self);
p
} }
/// For internal use only. /// For internal use only.
fn as_unknown(&self) -> &IUnknown { fn as_unknown(&self) -> &IUnknown {
unsafe { &*(self.0 as *mut IUnknown) } unsafe { &*(self.0 as *mut IUnknown) }
} }
/// Performs QueryInterface fun. /// Performs `QueryInterface` fun.
pub fn cast<U>(&self) -> Result<ComPtr<U>, i32> pub fn cast<U>(&self) -> Result<ComPtr<U>, i32>
where where
U: Interface, U: Interface,

View File

@ -8,18 +8,34 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
//! A helper module to probe the Windows Registry when looking for //! A helper module to looking for windows-specific tools:
//! windows-specific tools. //! 1. On Windows host, probe the Windows Registry if needed;
//! 2. On non-Windows host, check specified environment variables.
#![allow(clippy::upper_case_acronyms)]
use std::process::Command; use std::process::Command;
use crate::Tool; use crate::Tool;
#[cfg(windows)]
use crate::ToolFamily; use crate::ToolFamily;
#[cfg(windows)]
const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
#[derive(Copy, Clone)]
struct TargetArch<'a>(pub &'a str);
impl PartialEq<&str> for TargetArch<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl<'a> From<TargetArch<'a>> for &'a str {
fn from(target: TargetArch<'a>) -> Self {
target.0
}
}
/// Attempts to find a tool within an MSVC installation using the Windows /// Attempts to find a tool within an MSVC installation using the Windows
/// registry as a point to search from. /// registry as a point to search from.
/// ///
@ -39,13 +55,6 @@ pub fn find(target: &str, tool: &str) -> Option<Command> {
/// Similar to the `find` function above, this function will attempt the same /// Similar to the `find` function above, this function will attempt the same
/// operation (finding a MSVC tool in a local install) but instead returns a /// operation (finding a MSVC tool in a local install) but instead returns a
/// `Tool` which may be introspected. /// `Tool` which may be introspected.
#[cfg(not(windows))]
pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
None
}
/// Documented above.
#[cfg(windows)]
pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
// This logic is all tailored for MSVC, if we're not that then bail out // This logic is all tailored for MSVC, if we're not that then bail out
// early. // early.
@ -53,13 +62,17 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
return None; return None;
} }
// Split the target to get the arch.
let target = TargetArch(target.split_once('-')?.0);
// Looks like msbuild isn't located in the same location as other tools like // 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 // cl.exe and lib.exe.
// dedicated registry keys.
if tool.contains("msbuild") { if tool.contains("msbuild") {
return impl_::find_msbuild(target); return impl_::find_msbuild(target);
} }
// Looks like devenv isn't located in the same location as other tools like
// cl.exe and lib.exe.
if tool.contains("devenv") { if tool.contains("devenv") {
return impl_::find_devenv(target); return impl_::find_devenv(target);
} }
@ -71,15 +84,16 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
// environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
// the tool is actually usable. // 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_15plus(tool, target))
.or_else(|| impl_::find_msvc_14(tool, target)) .or_else(|| impl_::find_msvc_14(tool, target))
.or_else(|| impl_::find_msvc_12(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 /// A version of Visual Studio
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum VsVers { pub enum VsVers {
/// Visual Studio 12 (2013) /// Visual Studio 12 (2013)
Vs12, Vs12,
@ -91,30 +105,14 @@ pub enum VsVers {
Vs16, Vs16,
/// Visual Studio 17 (2022) /// Visual Studio 17 (2022)
Vs17, 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 /// Find the most recent installed version of Visual Studio
/// ///
/// This is used by the cmake crate to figure out the correct /// This is used by the cmake crate to figure out the correct
/// generator. /// generator.
#[cfg(not(windows))]
pub fn find_vs_version() -> Result<VsVers, String> { pub fn find_vs_version() -> Result<VsVers, String> {
Err(format!("not windows")) match std::env::var("VisualStudioVersion") {
}
/// Documented above
#[cfg(windows)]
pub fn find_vs_version() -> Result<VsVers, String> {
use std::env;
match env::var("VisualStudioVersion") {
Ok(version) => match &version[..] { Ok(version) => match &version[..] {
"17.0" => Ok(VsVers::Vs17), "17.0" => Ok(VsVers::Vs17),
"16.0" => Ok(VsVers::Vs16), "16.0" => Ok(VsVers::Vs16),
@ -158,12 +156,17 @@ pub fn find_vs_version() -> Result<VsVers, String> {
} }
} }
/// Windows Implementation.
#[cfg(windows)] #[cfg(windows)]
mod impl_ { mod impl_ {
use crate::com; use crate::windows::com;
use crate::registry::{RegistryKey, LOCAL_MACHINE}; use crate::windows::registry::{RegistryKey, LOCAL_MACHINE};
use crate::setup_config::SetupConfiguration; use crate::windows::setup_config::SetupConfiguration;
use crate::vs_instances::{VsInstances, VswhereInstance}; 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::convert::TryFrom;
use std::env; use std::env;
use std::ffi::OsString; use std::ffi::OsString;
@ -174,8 +177,10 @@ mod impl_ {
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;
use super::MSVC_FAMILY; use super::{TargetArch, MSVC_FAMILY};
use crate::Tool; use crate::Tool;
struct MsvcTool { struct MsvcTool {
@ -185,10 +190,75 @@ mod impl_ {
include: Vec<PathBuf>, 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 { impl MsvcTool {
fn new(tool: PathBuf) -> MsvcTool { fn new(tool: PathBuf) -> MsvcTool {
MsvcTool { MsvcTool {
tool: tool, tool,
libs: Vec::new(), libs: Vec::new(),
path: Vec::new(), path: Vec::new(),
include: Vec::new(), include: Vec::new(),
@ -202,7 +272,7 @@ mod impl_ {
path, path,
include, include,
} = self; } = 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, "LIB", libs);
add_env(&mut tool, "PATH", path); add_env(&mut tool, "PATH", path);
add_env(&mut tool, "INCLUDE", include); add_env(&mut tool, "INCLUDE", include);
@ -212,15 +282,14 @@ mod impl_ {
/// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the /// 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. /// given target's arch. Returns `None` if the variable does not exist.
#[cfg(windows)] fn is_vscmd_target(target: TargetArch<'_>) -> Option<bool> {
fn is_vscmd_target(target: &str) -> Option<bool> {
let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?; let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?;
// Convert the Rust target arch to its VS arch equivalent. // Convert the Rust target arch to its VS arch equivalent.
let arch = match target.split("-").next() { let arch = match target.into() {
Some("x86_64") => "x64", "x86_64" => "x64",
Some("aarch64") => "arm64", "aarch64" | "arm64ec" => "arm64",
Some("i686") | Some("i586") => "x86", "i686" | "i586" => "x86",
Some("thumbv7a") => "arm", "thumbv7a" => "arm",
// An unrecognized arch. // An unrecognized arch.
_ => return Some(false), _ => return Some(false),
}; };
@ -228,7 +297,7 @@ mod impl_ {
} }
/// Attempt to find the tool using environment variables set by vcvars. /// Attempt to find the tool using environment variables set by vcvars.
pub fn find_msvc_environment(tool: &str, target: &str) -> Option<Tool> { pub(super) fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
// Early return if the environment doesn't contain a VC install. // Early return if the environment doesn't contain a VC install.
if env::var_os("VCINSTALLDIR").is_none() { if env::var_os("VCINSTALLDIR").is_none() {
return None; return None;
@ -248,16 +317,19 @@ mod impl_ {
.map(|p| p.join(tool)) .map(|p| p.join(tool))
.find(|p| p.exists()) .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") find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17")
} }
#[allow(bare_trait_objects)] #[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) { let instances = if let Some(instances) = vs15plus_instances(target) {
instances instances
} else { } else {
@ -275,7 +347,11 @@ mod impl_ {
})) }))
} }
fn find_tool_in_vs16plus_path(tool: &str, target: &str, version: &'static str) -> Option<Tool> { fn find_tool_in_vs16plus_path(
tool: &str,
target: TargetArch<'_>,
version: &'static str,
) -> Option<Tool> {
vs16plus_instances(target, version) vs16plus_instances(target, version)
.filter_map(|path| { .filter_map(|path| {
let path = path.join(tool); let path = path.join(tool);
@ -283,10 +359,10 @@ mod impl_ {
return None; return None;
} }
let mut tool = Tool::with_family(path, MSVC_FAMILY); 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.env.push(("Platform".into(), "X64".into()));
} }
if target.contains("aarch64") { if target == "aarch64" || target == "arm64ec" {
tool.env.push(("Platform".into(), "ARM64".into())); tool.env.push(("Platform".into(), "ARM64".into()));
} }
Some(tool) Some(tool)
@ -294,7 +370,7 @@ mod impl_ {
.next() .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") find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16")
} }
@ -310,7 +386,7 @@ mod impl_ {
// //
// However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. // 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. // 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)) vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target))
} }
@ -323,7 +399,7 @@ mod impl_ {
Some(VsInstances::ComBased(enum_setup_instances)) 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)") let program_files_path: PathBuf = env::var("ProgramFiles(x86)")
.or_else(|_| env::var("ProgramFiles")) .or_else(|_| env::var("ProgramFiles"))
.ok()? .ok()?
@ -336,11 +412,10 @@ mod impl_ {
return None; return None;
} }
let arch = target.split('-').next().unwrap(); let tools_arch = match target.into() {
let tools_arch = match arch {
"i586" | "i686" | "x86_64" => Some("x86.x64"), "i586" | "i686" | "x86_64" => Some("x86.x64"),
"arm" | "thumbv7a" => Some("ARM"), "arm" | "thumbv7a" => Some("ARM"),
"aarch64" => Some("ARM64"), "aarch64" | "arm64ec" => Some("ARM64"),
_ => None, _ => None,
}; };
@ -374,7 +449,7 @@ mod impl_ {
.collect() .collect()
} }
pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> { pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
let iter = vs15plus_instances(target)?; let iter = vs15plus_instances(target)?;
iter.into_iter() iter.into_iter()
.filter_map(|instance| { .filter_map(|instance| {
@ -394,13 +469,13 @@ mod impl_ {
// we keep the registry method as a fallback option. // we keep the registry method as a fallback option.
// //
// [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 // [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) { let mut path = match vs15plus_instances(target) {
Some(instances) => instances Some(instances) => instances
.into_iter() .into_iter()
.filter_map(|instance| instance.installation_path()) .filter_map(|instance| instance.installation_path())
.map(|path| path.join(tool)) .map(|path| path.join(tool))
.find(|ref path| path.is_file()), .find(|path| path.is_file()),
None => None, None => None,
}; };
@ -416,10 +491,9 @@ mod impl_ {
path.map(|path| { path.map(|path| {
let mut tool = Tool::with_family(path, MSVC_FAMILY); 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.env.push(("Platform".into(), "X64".into()));
} } else if target == "aarch64" {
if target.contains("aarch64") {
tool.env.push(("Platform".into(), "ARM64".into())); tool.env.push(("Platform".into(), "ARM64".into()));
} }
tool tool
@ -428,10 +502,10 @@ mod impl_ {
fn tool_from_vs15plus_instance( fn tool_from_vs15plus_instance(
tool: &str, tool: &str,
target: &str, target: TargetArch<'_>,
instance_path: &PathBuf, instance_path: &PathBuf,
) -> Option<Tool> { ) -> 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)?; vs15plus_vc_paths(target, instance_path)?;
let tool_path = bin_path.join(tool); let tool_path = bin_path.join(tool);
if !tool_path.exists() { if !tool_path.exists() {
@ -441,6 +515,9 @@ mod impl_ {
let mut tool = MsvcTool::new(tool_path); let mut tool = MsvcTool::new(tool_path);
tool.path.push(bin_path.clone()); tool.path.push(bin_path.clone());
tool.path.push(host_dylib_path); 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.libs.push(lib_path);
tool.include.push(include_path); tool.include.push(include_path);
@ -455,45 +532,97 @@ mod impl_ {
} }
fn vs15plus_vc_paths( fn vs15plus_vc_paths(
target: &str, target: TargetArch<'_>,
instance_path: &PathBuf, instance_path: &Path,
) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, PathBuf)> { ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> {
let version_path = let version = vs15plus_vc_read_version(instance_path)?;
instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
let mut version_file = File::open(version_path).ok()?; let hosts = match host_arch() {
let mut version = String::new(); X86 => &["X86"],
version_file.read_to_string(&mut version).ok()?; X86_64 => &["X64"],
let version = version.trim(); // Starting with VS 17.4, there is a natively hosted compiler on ARM64:
let host = match host_arch() { // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/
X86 => "X86", // On older versions of VS, we use x64 if running under emulation is supported,
X86_64 => "X64", // otherwise use x86.
// There is no natively hosted compiler on ARM64. AARCH64 => {
// Instead, use the x86 toolchain under emulation (there is no x64 emulation). if is_amd64_emulation_supported() {
AARCH64 => "X86", &["ARM64", "X64", "X86"][..]
} else {
&["ARM64", "X86"]
}
}
_ => return None, _ => return None,
}; };
let target = lib_subdir(target)?; let target = lib_subdir(target)?;
// The directory layout here is MSVC/bin/Host$host/$target/ // The directory layout here is MSVC/bin/Host$host/$target/
let path = instance_path.join(r"VC\Tools\MSVC").join(version); 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 // This is the path to the toolchain for a particular target, running
// on a given host // on a given host
let bin_path = path let bin_path = host_path.join(target);
.join("bin")
.join(&format!("Host{}", host))
.join(&target);
// But! we also need PATH to contain the target directory for the host // But! we also need PATH to contain the target directory for the host
// architecture, because it contains dlls like mspdb140.dll compiled for // architecture, because it contains dlls like mspdb140.dll compiled for
// the host architecture. // the host architecture.
let host_dylib_path = path let host_dylib_path = host_path.join(host.to_lowercase());
.join("bin") let lib_path = path.join("lib").join(target);
.join(&format!("Host{}", host)) let alt_lib_path = (target == "arm64ec").then(|| path.join("lib").join("arm64ec"));
.join(&host.to_lowercase());
let lib_path = path.join("lib").join(&target);
let include_path = path.join("include"); 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 atl_path = path.join("atlmfc");
let sub = lib_subdir(target)?; let sub = lib_subdir(target)?;
if atl_path.exists() { if atl_path.exists() {
@ -505,14 +634,14 @@ mod impl_ {
// For MSVC 14 we need to find the Universal CRT as well as either // For MSVC 14 we need to find the Universal CRT as well as either
// the Windows 10 SDK or Windows 8.1 SDK. // the Windows 10 SDK or Windows 8.1 SDK.
pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { pub(super) fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
let vcdir = get_vc_dir("14.0")?; let vcdir = get_vc_dir("14.0")?;
let mut tool = get_tool(tool, &vcdir, target)?; let mut tool = get_tool(tool, &vcdir, target)?;
add_sdks(&mut tool, target)?; add_sdks(&mut tool, target)?;
Some(tool.into_tool()) 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 sub = lib_subdir(target)?;
let (ucrt, ucrt_version) = get_ucrt_dir()?; let (ucrt, ucrt_version) = get_ucrt_dir()?;
@ -555,7 +684,7 @@ mod impl_ {
} }
// For MSVC 12 we need to find the Windows 8.1 SDK. // For MSVC 12 we need to find the Windows 8.1 SDK.
pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { pub(super) fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
let vcdir = get_vc_dir("12.0")?; let vcdir = get_vc_dir("12.0")?;
let mut tool = get_tool(tool, &vcdir, target)?; let mut tool = get_tool(tool, &vcdir, target)?;
let sub = lib_subdir(target)?; let sub = lib_subdir(target)?;
@ -571,7 +700,7 @@ mod impl_ {
} }
// For MSVC 11 we need to find the Windows 8 SDK. // For MSVC 11 we need to find the Windows 8 SDK.
pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { pub(super) fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
let vcdir = get_vc_dir("11.0")?; let vcdir = get_vc_dir("11.0")?;
let mut tool = get_tool(tool, &vcdir, target)?; let mut tool = get_tool(tool, &vcdir, target)?;
let sub = lib_subdir(target)?; let sub = lib_subdir(target)?;
@ -596,7 +725,7 @@ mod impl_ {
// Given a possible MSVC installation directory, we look for the linker and // Given a possible MSVC installation directory, we look for the linker and
// then add the MSVC library path. // 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) bin_subdir(target)
.into_iter() .into_iter()
.map(|(sub, host)| { .map(|(sub, host)| {
@ -605,7 +734,7 @@ mod impl_ {
path.join("bin").join(host), path.join("bin").join(host),
) )
}) })
.filter(|&(ref path, _)| path.is_file()) .filter(|(path, _)| path.is_file())
.map(|(path, host)| { .map(|(path, host)| {
let mut tool = MsvcTool::new(path); let mut tool = MsvcTool::new(path);
tool.path.push(host); tool.path.push(host);
@ -734,9 +863,8 @@ mod impl_ {
// linkers that can target the architecture we desire. The 64-bit host // 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 // linker is preferred, and hence first, due to 64-bit allowing it more
// address space to work with and potentially being faster. // address space to work with and potentially being faster.
fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> { fn bin_subdir(target: TargetArch<'_>) -> Vec<(&'static str, &'static str)> {
let arch = target.split('-').next().unwrap(); match (target.into(), host_arch()) {
match (arch, host_arch()) {
("i586", X86) | ("i686", X86) => vec![("", "")], ("i586", X86) | ("i686", X86) => vec![("", "")],
("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")], ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
("x86_64", X86) => vec![("x86_amd64", "")], ("x86_64", X86) => vec![("x86_amd64", "")],
@ -747,21 +875,19 @@ mod impl_ {
} }
} }
fn lib_subdir(target: &str) -> Option<&'static str> { fn lib_subdir(target: TargetArch<'_>) -> Option<&'static str> {
let arch = target.split('-').next().unwrap(); match target.into() {
match arch {
"i586" | "i686" => Some("x86"), "i586" | "i686" => Some("x86"),
"x86_64" => Some("x64"), "x86_64" => Some("x64"),
"arm" | "thumbv7a" => Some("arm"), "arm" | "thumbv7a" => Some("arm"),
"aarch64" => Some("arm64"), "aarch64" | "arm64ec" => Some("arm64"),
_ => None, _ => None,
} }
} }
// MSVC's x86 libraries are not in a subfolder // MSVC's x86 libraries are not in a subfolder
fn vc_lib_subdir(target: &str) -> Option<&'static str> { fn vc_lib_subdir(target: TargetArch<'_>) -> Option<&'static str> {
let arch = target.split('-').next().unwrap(); match target.into() {
match arch {
"i586" | "i686" => Some(""), "i586" | "i686" => Some(""),
"x86_64" => Some("amd64"), "x86_64" => Some("amd64"),
"arm" | "thumbv7a" => Some("arm"), "arm" | "thumbv7a" => Some("arm"),
@ -813,7 +939,7 @@ mod impl_ {
for subkey in key.iter().filter_map(|k| k.ok()) { for subkey in key.iter().filter_map(|k| k.ok()) {
let val = subkey let val = subkey
.to_str() .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 { let val = match val {
Some(s) => s, Some(s) => s,
None => continue, None => continue,
@ -828,22 +954,22 @@ mod impl_ {
max_key max_key
} }
pub fn has_msbuild_version(version: &str) -> bool { pub(super) fn has_msbuild_version(version: &str) -> bool {
match version { match version {
"17.0" => { "17.0" => {
find_msbuild_vs17("x86_64-pc-windows-msvc").is_some() find_msbuild_vs17(TargetArch("x86_64")).is_some()
|| find_msbuild_vs17("i686-pc-windows-msvc").is_some() || find_msbuild_vs17(TargetArch("i686")).is_some()
|| find_msbuild_vs17("aarch64-pc-windows-msvc").is_some() || find_msbuild_vs17(TargetArch("aarch64")).is_some()
} }
"16.0" => { "16.0" => {
find_msbuild_vs16("x86_64-pc-windows-msvc").is_some() find_msbuild_vs16(TargetArch("x86_64")).is_some()
|| find_msbuild_vs16("i686-pc-windows-msvc").is_some() || find_msbuild_vs16(TargetArch("i686")).is_some()
|| find_msbuild_vs16("aarch64-pc-windows-msvc").is_some() || find_msbuild_vs16(TargetArch("aarch64")).is_some()
} }
"15.0" => { "15.0" => {
find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() find_msbuild_vs15(TargetArch("x86_64")).is_some()
|| find_msbuild_vs15("i686-pc-windows-msvc").is_some() || find_msbuild_vs15(TargetArch("i686")).is_some()
|| find_msbuild_vs15("aarch64-pc-windows-msvc").is_some() || find_msbuild_vs15(TargetArch("aarch64")).is_some()
} }
"12.0" | "14.0" => LOCAL_MACHINE "12.0" | "14.0" => LOCAL_MACHINE
.open(&OsString::from(format!( .open(&OsString::from(format!(
@ -855,18 +981,20 @@ mod impl_ {
} }
} }
pub fn find_devenv(target: &str) -> Option<Tool> { pub(super) fn find_devenv(target: TargetArch<'_>) -> Option<Tool> {
find_devenv_vs15(&target) 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) find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target)
} }
// see http://stackoverflow.com/questions/328017/path-to-msbuild // see http://stackoverflow.com/questions/328017/path-to-msbuild
pub fn find_msbuild(target: &str) -> Option<Tool> { pub(super) fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> {
// VS 15 (2017) changed how to locate msbuild // 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); return Some(r);
} else if let Some(r) = find_msbuild_vs15(target) { } else if let Some(r) = find_msbuild_vs15(target) {
return Some(r); return Some(r);
@ -875,11 +1003,11 @@ mod impl_ {
} }
} }
fn find_msbuild_vs15(target: &str) -> Option<Tool> { fn find_msbuild_vs15(target: TargetArch<'_>) -> Option<Tool> {
find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) 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"; let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
LOCAL_MACHINE LOCAL_MACHINE
.open(key.as_ref()) .open(key.as_ref())
@ -891,10 +1019,82 @@ mod impl_ {
let mut path = PathBuf::from(path); let mut path = PathBuf::from(path);
path.push("MSBuild.exe"); path.push("MSBuild.exe");
let mut tool = Tool::with_family(path, MSVC_FAMILY); 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.env.push(("Platform".into(), "X64".into()));
} }
tool tool
}) })
} }
} }
/// Non-Windows Implementation.
#[cfg(not(windows))]
mod impl_ {
use std::{env, ffi::OsString};
use super::{TargetArch, MSVC_FAMILY};
use crate::Tool;
/// Finding msbuild.exe tool under unix system is not currently supported.
/// Maybe can check it using an environment variable looks like `MSBUILD_BIN`.
pub(super) fn find_msbuild(_target: TargetArch<'_>) -> Option<Tool> {
None
}
// Finding devenv.exe tool under unix system is not currently supported.
// Maybe can check it using an environment variable looks like `DEVENV_BIN`.
pub(super) fn find_devenv(_target: TargetArch<'_>) -> Option<Tool> {
None
}
/// Attempt to find the tool using environment variables set by vcvars.
pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
// Early return if the environment doesn't contain a VC install.
let vc_install_dir = env::var_os("VCINSTALLDIR")?;
let vs_install_dir = env::var_os("VSINSTALLDIR")?;
let get_tool = |install_dir: OsString| {
env::split_paths(&install_dir)
.map(|p| p.join(tool))
.find(|p| p.exists())
.map(|path| Tool::with_family(path.into(), MSVC_FAMILY))
};
// Take the path of tool for the vc install directory.
get_tool(vc_install_dir)
// Take the path of tool for the vs install directory.
.or_else(|| get_tool(vs_install_dir))
// Take the path of tool for the current path environment.
.or_else(|| env::var_os("PATH").and_then(|path| get_tool(path)))
}
pub(super) fn find_msvc_15plus(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
None
}
// For MSVC 14 we need to find the Universal CRT as well as either
// the Windows 10 SDK or Windows 8.1 SDK.
pub(super) fn find_msvc_14(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
None
}
// For MSVC 12 we need to find the Windows 8.1 SDK.
pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
None
}
// For MSVC 11 we need to find the Windows 8 SDK.
pub(super) fn find_msvc_11(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
None
}
pub(super) fn has_msbuild_version(version: &str) -> bool {
match version {
"17.0" => false,
"16.0" => false,
"15.0" => false,
"12.0" | "14.0" => false,
_ => false,
}
}
}

20
third_party/rust/cc/src/windows/mod.rs vendored Normal file
View File

@ -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;

View File

@ -8,63 +8,23 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use std::ffi::{OsStr, OsString}; use crate::windows::windows_sys::{
use std::io; RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, RegQueryValueExW, ERROR_NO_MORE_ITEMS,
use std::ops::RangeFrom; ERROR_SUCCESS, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, REG_SZ,
use std::os::raw; };
use std::os::windows::prelude::*; use std::{
ffi::{OsStr, OsString},
io,
ops::RangeFrom,
os::windows::prelude::*,
ptr::null_mut,
};
/// Must never be `HKEY_PERFORMANCE_DATA`. /// Must never be `HKEY_PERFORMANCE_DATA`.
pub(crate) struct RegistryKey(Repr); pub(crate) struct RegistryKey(Repr);
type HKEY = *mut u8; #[allow(clippy::upper_case_acronyms)]
type DWORD = u32; 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); struct OwnedKey(HKEY);
@ -97,7 +57,7 @@ impl RegistryKey {
/// Open a sub-key of `self`. /// Open a sub-key of `self`.
pub fn open(&self, key: &OsStr) -> io::Result<RegistryKey> { pub fn open(&self, key: &OsStr) -> io::Result<RegistryKey> {
let key = key.encode_wide().chain(Some(0)).collect::<Vec<_>>(); let key = key.encode_wide().chain(Some(0)).collect::<Vec<_>>();
let mut ret = 0 as *mut _; let mut ret = null_mut();
let err = unsafe { let err = unsafe {
RegOpenKeyExW( RegOpenKeyExW(
self.raw(), self.raw(),
@ -107,7 +67,7 @@ impl RegistryKey {
&mut ret, &mut ret,
) )
}; };
if err == ERROR_SUCCESS as LONG { if err == ERROR_SUCCESS {
Ok(RegistryKey(Repr::Owned(OwnedKey(ret)))) Ok(RegistryKey(Repr::Owned(OwnedKey(ret))))
} else { } else {
Err(io::Error::from_raw_os_error(err as i32)) Err(io::Error::from_raw_os_error(err as i32))
@ -130,12 +90,12 @@ impl RegistryKey {
let err = RegQueryValueExW( let err = RegQueryValueExW(
self.raw(), self.raw(),
name.as_ptr(), name.as_ptr(),
0 as *mut _, null_mut(),
&mut kind, &mut kind,
0 as *mut _, null_mut(),
&mut len, &mut len,
); );
if err != ERROR_SUCCESS as LONG { if err != ERROR_SUCCESS {
return Err(io::Error::from_raw_os_error(err as i32)); return Err(io::Error::from_raw_os_error(err as i32));
} }
if kind != REG_SZ { if kind != REG_SZ {
@ -156,8 +116,8 @@ impl RegistryKey {
let err = RegQueryValueExW( let err = RegQueryValueExW(
self.raw(), self.raw(),
name.as_ptr(), name.as_ptr(),
0 as *mut _, null_mut(),
0 as *mut _, null_mut(),
v.as_mut_ptr() as *mut _, v.as_mut_ptr() as *mut _,
&mut len, &mut len,
); );
@ -165,7 +125,7 @@ impl RegistryKey {
// grew between the first and second call to `RegQueryValueExW`), // grew between the first and second call to `RegQueryValueExW`),
// both because it's extremely unlikely, and this is a bit more // both because it's extremely unlikely, and this is a bit more
// defensive more defensive against weird types of registry keys. // 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)); return Err(io::Error::from_raw_os_error(err as i32));
} }
// The length is allowed to change, but should still be even, as // 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 { if !v.is_empty() && v[v.len() - 1] == 0 {
v.pop(); v.pop();
} }
return Ok(OsString::from_wide(&v)); Ok(OsString::from_wide(&v))
} }
} }
} }
@ -213,14 +173,14 @@ impl<'a> Iterator for Iter<'a> {
i, i,
v.as_mut_ptr(), v.as_mut_ptr(),
&mut len, &mut len,
0 as *mut _, null_mut(),
0 as *mut _, null_mut(),
0 as *mut _, null_mut(),
0 as *mut _, null_mut(),
); );
if ret == ERROR_NO_MORE_ITEMS as LONG { if ret == ERROR_NO_MORE_ITEMS {
None None
} else if ret != ERROR_SUCCESS as LONG { } else if ret != ERROR_SUCCESS {
Some(Err(io::Error::from_raw_os_error(ret as i32))) Some(Err(io::Error::from_raw_os_error(ret as i32)))
} else { } else {
v.set_len(len as usize); v.set_len(len as usize);

View File

@ -8,19 +8,19 @@
#![allow(bad_style)] #![allow(bad_style)]
#![allow(unused)] #![allow(unused)]
use crate::winapi::Interface; use crate::windows::{
use crate::winapi::BSTR; com::{BStr, ComPtr},
use crate::winapi::LPCOLESTR; winapi::{
use crate::winapi::LPSAFEARRAY; IUnknown, IUnknownVtbl, Interface, LCID, LPCOLESTR, LPCWSTR, LPFILETIME, LPSAFEARRAY,
use crate::winapi::S_FALSE; PULONGLONG, ULONG,
use crate::winapi::{CoCreateInstance, CLSCTX_ALL}; },
use crate::winapi::{IUnknown, IUnknownVtbl}; windows_sys::{CoCreateInstance, BSTR, CLSCTX_ALL, HRESULT, S_FALSE},
use crate::winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG}; };
use crate::winapi::{LPFILETIME, ULONG};
use std::ffi::OsString;
use std::ptr::null_mut;
use crate::com::{BStr, ComPtr}; use std::{
ffi::OsString,
ptr::{null, null_mut},
};
// Bindings to the Setup.Configuration stuff // Bindings to the Setup.Configuration stuff
pub type InstanceState = u32; pub type InstanceState = u32;
@ -212,7 +212,7 @@ impl SetupInstance {
SetupInstance(ComPtr::from_raw(obj)) SetupInstance(ComPtr::from_raw(obj))
} }
pub fn instance_id(&self) -> Result<OsString, i32> { 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 err = unsafe { self.0.GetInstanceId(&mut s) };
let bstr = unsafe { BStr::from_raw(s) }; let bstr = unsafe { BStr::from_raw(s) };
if err < 0 { if err < 0 {
@ -221,7 +221,7 @@ impl SetupInstance {
Ok(bstr.to_osstring()) Ok(bstr.to_osstring())
} }
pub fn installation_name(&self) -> Result<OsString, i32> { 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 err = unsafe { self.0.GetInstallationName(&mut s) };
let bstr = unsafe { BStr::from_raw(s) }; let bstr = unsafe { BStr::from_raw(s) };
if err < 0 { if err < 0 {
@ -230,7 +230,7 @@ impl SetupInstance {
Ok(bstr.to_osstring()) Ok(bstr.to_osstring())
} }
pub fn installation_path(&self) -> Result<OsString, i32> { 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 err = unsafe { self.0.GetInstallationPath(&mut s) };
let bstr = unsafe { BStr::from_raw(s) }; let bstr = unsafe { BStr::from_raw(s) };
if err < 0 { if err < 0 {
@ -239,7 +239,7 @@ impl SetupInstance {
Ok(bstr.to_osstring()) Ok(bstr.to_osstring())
} }
pub fn installation_version(&self) -> Result<OsString, i32> { 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 err = unsafe { self.0.GetInstallationVersion(&mut s) };
let bstr = unsafe { BStr::from_raw(s) }; let bstr = unsafe { BStr::from_raw(s) };
if err < 0 { if err < 0 {
@ -248,7 +248,7 @@ impl SetupInstance {
Ok(bstr.to_osstring()) Ok(bstr.to_osstring())
} }
pub fn product_path(&self) -> Result<OsString, i32> { pub fn product_path(&self) -> Result<OsString, i32> {
let mut s = null_mut(); let mut s = null();
let this = self.0.cast::<ISetupInstance2>()?; let this = self.0.cast::<ISetupInstance2>()?;
let err = unsafe { this.GetProductPath(&mut s) }; let err = unsafe { this.GetProductPath(&mut s) };
let bstr = unsafe { BStr::from_raw(s) }; let bstr = unsafe { BStr::from_raw(s) };

View File

@ -4,7 +4,7 @@ use std::convert::TryFrom;
use std::io::BufRead; use std::io::BufRead;
use std::path::PathBuf; use std::path::PathBuf;
use crate::setup_config::{EnumSetupInstances, SetupInstance}; use crate::windows::setup_config::{EnumSetupInstances, SetupInstance};
pub enum VsInstance { pub enum VsInstance {
Com(SetupInstance), Com(SetupInstance),

View File

@ -5,26 +5,19 @@
// All files in the project carrying such notice may not be copied, modified, or distributed // All files in the project carrying such notice may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
#![allow(bad_style)] #![allow(bad_style, clippy::upper_case_acronyms)]
use std::os::raw; use std::os::raw;
pub type wchar_t = u16; pub type wchar_t = u16;
pub type UINT = raw::c_uint; pub use crate::windows::windows_sys::{FILETIME, GUID, HRESULT, SAFEARRAY};
pub type LPUNKNOWN = *mut IUnknown;
pub type REFIID = *const IID; pub type REFIID = *const IID;
pub type IID = GUID; 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 ULONG = raw::c_ulong;
pub type LONG = raw::c_long;
pub type DWORD = u32; pub type DWORD = u32;
pub type LPVOID = *mut raw::c_void;
pub type HRESULT = raw::c_long;
pub type LPFILETIME = *mut FILETIME; pub type LPFILETIME = *mut FILETIME;
pub type BSTR = *mut OLECHAR;
pub type OLECHAR = WCHAR; pub type OLECHAR = WCHAR;
pub type WCHAR = wchar_t; pub type WCHAR = wchar_t;
pub type LPCOLESTR = *const OLECHAR; pub type LPCOLESTR = *const OLECHAR;
@ -33,75 +26,10 @@ pub type LPCWSTR = *const WCHAR;
pub type PULONGLONG = *mut ULONGLONG; pub type PULONGLONG = *mut ULONGLONG;
pub type ULONGLONG = u64; 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 { pub trait Interface {
fn uuidof() -> GUID; 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; pub type LPSAFEARRAY = *mut SAFEARRAY;
macro_rules! DEFINE_GUID { macro_rules! DEFINE_GUID {
@ -109,11 +37,11 @@ macro_rules! DEFINE_GUID {
$name:ident, $l:expr, $w1:expr, $w2:expr, $name:ident, $l:expr, $w1:expr, $w2:expr,
$b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8: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 { pub const $name: $crate::windows::winapi::GUID = $crate::windows::winapi::GUID {
Data1: $l, data1: $l,
Data2: $w1, data2: $w1,
Data3: $w2, data3: $w2,
Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
}; };
}; };
} }
@ -193,14 +121,14 @@ macro_rules! RIDL {
$l:expr, $w1:expr, $w2:expr, $l:expr, $w1:expr, $w2:expr,
$b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8: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] #[inline]
fn uuidof() -> $crate::winapi::GUID { fn uuidof() -> $crate::windows::winapi::GUID {
$crate::winapi::GUID { $crate::windows::winapi::GUID {
Data1: $l, data1: $l,
Data2: $w1, data2: $w1,
Data3: $w2, data3: $w2,
Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8],
} }
} }
} }

View File

@ -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;

View File

@ -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"));
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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(|_| ())
}

View File

@ -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");
}