diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9097444b..cc5bc4ff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,15 +1,27 @@ +name: Release-plz + +permissions: + pull-requests: write + contents: write + on: push: - tags: - - '*' + branches: + - main jobs: - publish: + release-plz: + name: Release-plz runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Publish to crates.io - run: | - cargo publish - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: MarcoIeni/release-plz-action@v0.5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/regenerate-target-info.yml b/.github/workflows/regenerate-target-info.yml index a9334a57..9d30a4a1 100644 --- a/.github/workflows/regenerate-target-info.yml +++ b/.github/workflows/regenerate-target-info.yml @@ -21,8 +21,13 @@ jobs: - name: Install rust run: | rustup toolchain install stable nightly --no-self-update --profile minimal + + - name: Create lockfile + run: cargo update - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: 'true' - name: Regenerate target info run: cargo run -p gen-target-info diff --git a/.github/workflows/regenerate-windows-sys.yml b/.github/workflows/regenerate-windows-sys.yml new file mode 100644 index 00000000..8fabbe30 --- /dev/null +++ b/.github/workflows/regenerate-windows-sys.yml @@ -0,0 +1,51 @@ +name: Regenerate windows sys bindings + +on: + workflow_dispatch: # Allow running on-demand + schedule: + - cron: '0 3 * * 5' + +jobs: + regenerate: + name: Regenerate windows sys bindings & Open Pull Request if necessary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: true + + - name: Generate branch name + run: | + git checkout -b regenerate-windows-sys-${{ github.run_id }} + + - name: Create lockfile + run: cargo update + + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: 'true' + + - name: Regenerate windows sys bindings + run: cargo run -p gen-windows-sys-binding + + - name: Detect changes + id: changes + run: + # This output boolean tells us if the dependencies have actually changed + echo "count=$(git status --porcelain=v1 | wc -l)" >> $GITHUB_OUTPUT + + - name: Commit and push changes + # Only push if changes exist + if: steps.changes.outputs.count > 0 + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git commit -am "Regenerate windows sys bindings" + git push origin HEAD + + - name: Open pull request if needed + if: steps.changes.outputs.count > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create --base main --title "Regenerate windows sys bindings" --body "Automatically regenerated in CI" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..90ee15e2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.102](https://github.com/rust-lang/cc-rs/compare/cc-v1.0.101...cc-v1.0.102) - 2024-06-29 + +### Other +- Fix invalid wasi targets compatibility ([#1105](https://github.com/rust-lang/cc-rs/pull/1105)) +- Speedup regenerate-target-info and regenerate-windows-sys ([#1110](https://github.com/rust-lang/cc-rs/pull/1110)) + +## [1.0.101](https://github.com/rust-lang/cc-rs/compare/cc-v1.0.100...cc-v1.0.101) - 2024-06-25 + +### Other +- Use `Build::getenv` instead of `env::var*` in anywhere that makes sense ([#1103](https://github.com/rust-lang/cc-rs/pull/1103)) + +## [1.0.100](https://github.com/rust-lang/cc-rs/compare/cc-v1.0.99...cc-v1.0.100) - 2024-06-23 + +### Other +- Update publish.yml to use release-plz ([#1101](https://github.com/rust-lang/cc-rs/pull/1101)) +- Accpet `OsStr` instead of `str` for flags ([#1100](https://github.com/rust-lang/cc-rs/pull/1100)) +- Use `dep:` syntax to avoid implicit features. ([#1099](https://github.com/rust-lang/cc-rs/pull/1099)) +- Minor clippy fixes. ([#1098](https://github.com/rust-lang/cc-rs/pull/1098)) +- Fix WASI compilation for C++ ([#1083](https://github.com/rust-lang/cc-rs/pull/1083)) +- Regenerate windows sys bindings ([#1096](https://github.com/rust-lang/cc-rs/pull/1096)) +- Rename regenerate-windows-sys to regenerate-windows-sys.yml ([#1095](https://github.com/rust-lang/cc-rs/pull/1095)) +- Create regenerate-windows-sys.yml ([#1094](https://github.com/rust-lang/cc-rs/pull/1094)) +- Update windows-bindgen requirement from 0.56 to 0.57 ([#1091](https://github.com/rust-lang/cc-rs/pull/1091)) +- Eagerly close tempfile to fix [#1082](https://github.com/rust-lang/cc-rs/pull/1082) ([#1087](https://github.com/rust-lang/cc-rs/pull/1087)) +- Output msvc.exe in the output directory ([#1090](https://github.com/rust-lang/cc-rs/pull/1090)) +- Fix clippy warnings on Windows ([#1088](https://github.com/rust-lang/cc-rs/pull/1088)) +- Don't try to free DLL on drop ([#1089](https://github.com/rust-lang/cc-rs/pull/1089)) +- Fix panic safety issue in StderrForwarder ([#1079](https://github.com/rust-lang/cc-rs/pull/1079)) diff --git a/Cargo.toml b/Cargo.toml index 06852da5..23a3f2b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cc" -version = "1.0.98" +version = "1.0.102" authors = ["Alex Crichton "] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/cc-rs" @@ -29,7 +29,7 @@ once_cell = { version = "1.19", optional = true } libc = { version = "0.2.62", default-features = false, optional = true } [features] -parallel = ["libc", "jobserver", "once_cell"] +parallel = ["dep:libc", "dep:jobserver", "dep:once_cell"] [dev-dependencies] tempfile = "3" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..7f7ae210 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +disallowed-methods = [ + { path = "std::env::var_os", reason = "Please use Build::getenv" }, + { path = "std::env::var", reason = "Please use Build::getenv" }, +] diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 6fa3c1cf..4adb870c 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -1,3 +1,5 @@ +#![allow(clippy::disallowed_methods)] + use std::env; use std::fs; use std::path::{Path, PathBuf}; @@ -169,7 +171,7 @@ fn main() { #[track_caller] fn run_forked_capture_output(out: &Path, action: &str) { let program = env::current_exe().unwrap(); - let output = Command::new(&program).arg(action).output().unwrap(); + let output = Command::new(program).arg(action).output().unwrap(); assert!(output.status.success(), "output: {:#?}", output); // we've captured the output and now we write it to a dedicated directory in the // build output so our tests can access the output. diff --git a/dev-tools/cc-test/src/NMakefile b/dev-tools/cc-test/src/NMakefile index e310a4f3..579bd053 100644 --- a/dev-tools/cc-test/src/NMakefile +++ b/dev-tools/cc-test/src/NMakefile @@ -14,7 +14,7 @@ $(OUT_DIR)/msvc.o: src/msvc.c $(CC) $(EXTRA_CFLAGS) -c $(CFLAG_OUTPUT)$@ src/msvc.c -MD $(OUT_DIR)/msvc.exe: $(OUT_DIR)/msvc2.o - $(CC) $(EXTRA_CFLAGS) $(CFLAG_OUTPUT)$@ $(OUT_DIR)/msvc2.o + $(CC) $(EXTRA_CFLAGS) $(CFLAG_OUTPUT)$@ $(OUT_DIR)/msvc2.o -Fe:$(OUT_DIR)/msvc.exe $(OUT_DIR)/msvc2.o: src/msvc.c $(CC) $(EXTRA_CFLAGS) -c $(CFLAG_OUTPUT)$@ src/msvc.c -DMAIN -MD diff --git a/dev-tools/gen-target-info/src/main.rs b/dev-tools/gen-target-info/src/main.rs index 5f5cb6b5..0cde167e 100644 --- a/dev-tools/gen-target-info/src/main.rs +++ b/dev-tools/gen-target-info/src/main.rs @@ -12,7 +12,7 @@ fn generate_riscv_arch_mapping(f: &mut File, target_specs: &RustcTargetSpecs) { .iter() .filter_map(|(target, target_spec)| { let arch = target.split_once('-').unwrap().0; - (arch.contains("riscv") && arch != &target_spec.arch) + (arch.contains("riscv") && arch != target_spec.arch) .then_some((arch, &*target_spec.arch)) }) .collect::>(); diff --git a/dev-tools/gen-windows-sys-binding/Cargo.toml b/dev-tools/gen-windows-sys-binding/Cargo.toml index e5ce992e..69bdd3e7 100644 --- a/dev-tools/gen-windows-sys-binding/Cargo.toml +++ b/dev-tools/gen-windows-sys-binding/Cargo.toml @@ -5,5 +5,5 @@ edition = "2018" publish = false [dependencies] -windows-bindgen = "0.56" +windows-bindgen = "0.57" tempfile = "3" diff --git a/src/bin/gcc-shim.rs b/src/bin/gcc-shim.rs index f0d219f4..da7a8b35 100644 --- a/src/bin/gcc-shim.rs +++ b/src/bin/gcc-shim.rs @@ -2,6 +2,7 @@ //! It is not intended for users and is not published with the library code to crates.io. #![cfg_attr(test, allow(dead_code))] +#![allow(clippy::disallowed_methods)] use std::env; use std::fs::File; diff --git a/src/command_helpers.rs b/src/command_helpers.rs index fe919a52..4296d771 100644 --- a/src/command_helpers.rs +++ b/src/command_helpers.rs @@ -27,6 +27,7 @@ pub(crate) struct CargoOutput { impl CargoOutput { pub(crate) fn new() -> Self { + #[allow(clippy::disallowed_methods)] Self { metadata: true, warnings: true, @@ -90,7 +91,6 @@ impl StderrForwarder { } } - #[allow(clippy::uninit_vec)] fn forward_available(&mut self) -> bool { if let Some((stderr, buffer)) = self.inner.as_mut() { loop { @@ -104,7 +104,7 @@ impl StderrForwarder { 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, + Ok(0) => break false, #[cfg(unix)] Ok(0) => { // On Unix, depending on the implementation, we may sometimes get 0 in a @@ -120,7 +120,7 @@ impl StderrForwarder { write_warning(&buffer[..]); } self.inner = None; - return true; + break true; } #[cfg(unix)] Err(_) => { @@ -137,32 +137,21 @@ impl StderrForwarder { }; 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..]) { + // Safety: stderr.read only writes to the spare part of the buffer, it never reads from it + match stderr + .read(unsafe { &mut *(buffer.spare_capacity_mut() as *mut _ as *mut [u8]) }) + { Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { // No data currently, yield back. - buffer.truncate(old_data_end); - return false; + break 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; + continue; } - Ok(bytes_read) => { - buffer.truncate(old_data_end + bytes_read); + Ok(bytes_read) if bytes_read != 0 => { + // Safety: bytes_read bytes is written to spare part of the buffer + unsafe { buffer.set_len(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. @@ -173,6 +162,19 @@ impl StderrForwarder { } buffer.drain(..consumed); } + res => { + // End of stream: flush remaining data and bail. + if old_data_end > 0 { + write_warning(&buffer[..old_data_end]); + } + if let Err(err) = res { + write_warning( + format!("Failed to read from child stderr: {err}").as_bytes(), + ); + } + self.inner.take(); + break true; + } } } } else { @@ -397,7 +399,7 @@ pub(crate) struct CmdAddOutputFileArgs { pub(crate) fn command_add_output_file(cmd: &mut Command, dst: &Path, args: CmdAddOutputFileArgs) { if args.is_assembler_msvc - || (args.msvc && !args.clang && !args.gnu && !args.cuda && !(args.is_asm && args.is_arm)) + || !(!args.msvc || args.clang || args.gnu || args.cuda || (args.is_asm && args.is_arm)) { let mut s = OsString::from("-Fo"); s.push(dst); diff --git a/src/lib.rs b/src/lib.rs index cbaeb59c..7120b9c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,13 +19,10 @@ //! //! ```rust,no_run //! // build.rs -//! -//! fn main() { -//! cc::Build::new() -//! .file("foo.c") -//! .file("bar.c") -//! .compile("foo"); -//! } +//! 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 @@ -159,12 +156,10 @@ //! `Build`: //! //! ```rust,no_run -//! fn main() { -//! cc::Build::new() -//! .cpp(true) // Switch to C++ library compilation. -//! .file("foo.cpp") -//! .compile("foo"); -//! } +//! cc::Build::new() +//! .cpp(true) // Switch to C++ library compilation. +//! .file("foo.cpp") +//! .compile("foo"); //! ``` //! //! For C++ libraries, the `CXX` and `CXXFLAGS` environment variables are used instead of `CC` and `CFLAGS`. @@ -173,13 +168,11 @@ //! //! 1. by using the `cpp_link_stdlib` method on `Build`: //! ```rust,no_run -//! fn main() { -//! cc::Build::new() -//! .cpp(true) -//! .file("foo.cpp") -//! .cpp_link_stdlib("stdc++") // use libstdc++ -//! .compile("foo"); -//! } +//! cc::Build::new() +//! .cpp(true) +//! .file("foo.cpp") +//! .cpp_link_stdlib("stdc++") // use libstdc++ +//! .compile("foo"); //! ``` //! 2. by setting the `CXXSTDLIB` environment variable. //! @@ -193,31 +186,30 @@ //! on `Build`: //! //! ```rust,no_run -//! fn main() { -//! cc::Build::new() -//! // Switch to CUDA C++ library compilation using NVCC. -//! .cuda(true) -//! .cudart("static") -//! // Generate code for Maxwell (GTX 970, 980, 980 Ti, Titan X). -//! .flag("-gencode").flag("arch=compute_52,code=sm_52") -//! // Generate code for Maxwell (Jetson TX1). -//! .flag("-gencode").flag("arch=compute_53,code=sm_53") -//! // Generate code for Pascal (GTX 1070, 1080, 1080 Ti, Titan Xp). -//! .flag("-gencode").flag("arch=compute_61,code=sm_61") -//! // Generate code for Pascal (Tesla P100). -//! .flag("-gencode").flag("arch=compute_60,code=sm_60") -//! // Generate code for Pascal (Jetson TX2). -//! .flag("-gencode").flag("arch=compute_62,code=sm_62") -//! // Generate code in parallel -//! .flag("-t0") -//! .file("bar.cu") -//! .compile("bar"); -//! } +//! cc::Build::new() +//! // Switch to CUDA C++ library compilation using NVCC. +//! .cuda(true) +//! .cudart("static") +//! // Generate code for Maxwell (GTX 970, 980, 980 Ti, Titan X). +//! .flag("-gencode").flag("arch=compute_52,code=sm_52") +//! // Generate code for Maxwell (Jetson TX1). +//! .flag("-gencode").flag("arch=compute_53,code=sm_53") +//! // Generate code for Pascal (GTX 1070, 1080, 1080 Ti, Titan Xp). +//! .flag("-gencode").flag("arch=compute_61,code=sm_61") +//! // Generate code for Pascal (Tesla P100). +//! .flag("-gencode").flag("arch=compute_60,code=sm_60") +//! // Generate code for Pascal (Jetson TX2). +//! .flag("-gencode").flag("arch=compute_62,code=sm_62") +//! // Generate code in parallel +//! .flag("-t0") +//! .file("bar.cu") +//! .compile("bar"); //! ``` #![doc(html_root_url = "https://docs.rs/cc/1.0")] #![cfg_attr(test, deny(warnings))] #![deny(missing_docs)] +#![deny(clippy::disallowed_methods)] use std::borrow::Cow; use std::collections::HashMap; @@ -230,7 +222,7 @@ use std::path::{Component, Path, PathBuf}; #[cfg(feature = "parallel")] use std::process::Child; use std::process::Command; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; #[cfg(feature = "parallel")] mod parallel; @@ -249,12 +241,17 @@ use tool::ToolFamily; mod target_info; mod tempfile; +mod utilities; +use utilities::*; + #[derive(Debug, Eq, PartialEq, Hash)] struct CompilerFlag { compiler: Box, - flag: Box, + flag: Box, } +type Env = Option>; + /// A builder for compilation of a native library. /// /// A `Build` is the main type of the `cc` crate and is used to control all the @@ -265,11 +262,11 @@ pub struct Build { include_directories: Vec>, definitions: Vec<(Arc, Option>)>, objects: Vec>, - flags: Vec>, - flags_supported: Vec>, - known_flag_support_status_cache: Arc>>, - ar_flags: Vec>, - asm_flags: Vec>, + flags: Vec>, + flags_supported: Vec>, + known_flag_support_status_cache: Arc>>, + ar_flags: Vec>, + asm_flags: Vec>, no_default_flags: bool, files: Vec>, cpp: bool, @@ -277,6 +274,7 @@ pub struct Build { cpp_set_stdlib: Option>, cuda: bool, cudart: Option>, + ccbin: bool, std: Option>, target: Option>, host: Option>, @@ -289,7 +287,7 @@ pub struct Build { archiver: Option>, ranlib: Option>, cargo_output: CargoOutput, - link_lib_modifiers: Vec>, + link_lib_modifiers: Vec>, pic: Option, use_plt: Option, static_crt: Option, @@ -298,11 +296,11 @@ pub struct Build { warnings_into_errors: bool, warnings: Option, extra_warnings: Option, - env_cache: Arc>>>>, - apple_sdk_root_cache: Arc>>, - apple_versions_cache: Arc>>, + env_cache: Arc, Env>>>, + apple_sdk_root_cache: Arc, Arc>>>, + apple_versions_cache: Arc, Arc>>>, emit_rerun_if_env_changed: bool, - cached_compiler_family: Arc, ToolFamily>>>, + cached_compiler_family: Arc, ToolFamily>>>, } /// Represents the types of errors that may occur while using cc-rs. @@ -390,7 +388,7 @@ impl Build { objects: Vec::new(), flags: Vec::new(), flags_supported: Vec::new(), - known_flag_support_status_cache: Arc::new(Mutex::new(HashMap::new())), + known_flag_support_status_cache: Arc::new(RwLock::new(HashMap::new())), ar_flags: Vec::new(), asm_flags: Vec::new(), no_default_flags: false, @@ -402,6 +400,7 @@ impl Build { cpp_set_stdlib: None, cuda: false, cudart: None, + ccbin: true, std: None, target: None, host: None, @@ -421,9 +420,9 @@ impl Build { warnings: None, extra_warnings: None, warnings_into_errors: false, - env_cache: Arc::new(Mutex::new(HashMap::new())), - apple_sdk_root_cache: Arc::new(Mutex::new(HashMap::new())), - apple_versions_cache: Arc::new(Mutex::new(HashMap::new())), + env_cache: Arc::new(RwLock::new(HashMap::new())), + apple_sdk_root_cache: Arc::new(RwLock::new(HashMap::new())), + apple_versions_cache: Arc::new(RwLock::new(HashMap::new())), emit_rerun_if_env_changed: true, cached_compiler_family: Arc::default(), } @@ -511,8 +510,8 @@ impl Build { /// .flag("-ffunction-sections") /// .compile("foo"); /// ``` - pub fn flag(&mut self, flag: &str) -> &mut Build { - self.flags.push(flag.into()); + pub fn flag(&mut self, flag: impl AsRef) -> &mut Build { + self.flags.push(flag.as_ref().into()); self } @@ -545,8 +544,8 @@ impl Build { /// .ar_flag("/NODEFAULTLIB:libc.dll") /// .compile("foo"); /// ``` - pub fn ar_flag(&mut self, flag: &str) -> &mut Build { - self.ar_flags.push(flag.into()); + pub fn ar_flag(&mut self, flag: impl AsRef) -> &mut Build { + self.ar_flags.push(flag.as_ref().into()); self } @@ -564,8 +563,8 @@ impl Build { /// .file("src/bar.c") // The asm flag will not be applied here /// .compile("foo"); /// ``` - pub fn asm_flag(&mut self, flag: &str) -> &mut Build { - self.asm_flags.push(flag.into()); + pub fn asm_flag(&mut self, flag: impl AsRef) -> &mut Build { + self.asm_flags.push(flag.as_ref().into()); self } @@ -599,13 +598,17 @@ impl Build { /// Note: Once computed, the result of this call is stored in the /// `known_flag_support` field. If `is_flag_supported(flag)` /// is called again, the result will be read from the hash table. - pub fn is_flag_supported(&self, flag: &str) -> Result { - self.is_flag_supported_inner(flag, self.get_base_compiler()?.path(), &self.get_target()?) + pub fn is_flag_supported(&self, flag: impl AsRef) -> Result { + self.is_flag_supported_inner( + flag.as_ref(), + self.get_base_compiler()?.path(), + &self.get_target()?, + ) } fn is_flag_supported_inner( &self, - flag: &str, + flag: &OsStr, compiler_path: &Path, target: &str, ) -> Result { @@ -616,7 +619,7 @@ impl Build { if let Some(is_supported) = self .known_flag_support_status_cache - .lock() + .read() .unwrap() .get(&compiler_flag) .cloned() @@ -680,7 +683,7 @@ impl Build { let is_supported = output.status.success() && output.stderr.is_empty(); self.known_flag_support_status_cache - .lock() + .write() .unwrap() .insert(compiler_flag, is_supported); @@ -698,8 +701,8 @@ impl Build { /// .flag_if_supported("-Wunreachable-code") // only supported by clang /// .compile("foo"); /// ``` - pub fn flag_if_supported(&mut self, flag: &str) -> &mut Build { - self.flags_supported.push(flag.into()); + pub fn flag_if_supported(&mut self, flag: impl AsRef) -> &mut Build { + self.flags_supported.push(flag.as_ref().into()); self } @@ -728,7 +731,11 @@ impl Build { /// pub fn try_flags_from_environment(&mut self, environ_key: &str) -> Result<&mut Build, Error> { let flags = self.envflags(environ_key)?; - self.flags.extend(flags.into_iter().map(Into::into)); + self.flags.extend( + flags + .into_iter() + .map(|flag| Arc::from(OsString::from(flag).as_os_str())), + ); Ok(self) } @@ -850,6 +857,18 @@ impl Build { self } + /// Set CUDA host compiler. + /// + /// By default, a `-ccbin` flag will be passed to NVCC to specify the + /// underlying host compiler. The value of `-ccbin` is the same as the + /// chosen C++ compiler. This is not always desired, because NVCC might + /// not support that compiler. In this case, you can remove the `-ccbin` + /// flag so that NVCC will choose the host compiler by itself. + pub fn ccbin(&mut self, ccbin: bool) -> &mut Build { + self.ccbin = ccbin; + self + } + /// Specify the C or C++ language standard version. /// /// These values are common to modern versions of GCC, Clang and MSVC: @@ -979,7 +998,7 @@ impl Build { &mut self, cpp_link_stdlib: V, ) -> &mut Build { - self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(|s| s.into())); + self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(Arc::from)); self } @@ -1020,9 +1039,9 @@ impl Build { &mut self, cpp_set_stdlib: V, ) -> &mut Build { - let cpp_set_stdlib = cpp_set_stdlib.into(); - self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into()); - self.cpp_link_stdlib(cpp_set_stdlib); + let cpp_set_stdlib = cpp_set_stdlib.into().map(Arc::from); + self.cpp_set_stdlib.clone_from(&cpp_set_stdlib); + self.cpp_link_stdlib = Some(cpp_set_stdlib); self } @@ -1182,8 +1201,9 @@ impl Build { /// emitted for cargo if `cargo_metadata` is enabled. /// See /// for the list of modifiers accepted by rustc. - pub fn link_lib_modifier(&mut self, link_lib_modifier: &str) -> &mut Build { - self.link_lib_modifiers.push(link_lib_modifier.into()); + pub fn link_lib_modifier(&mut self, link_lib_modifier: impl AsRef) -> &mut Build { + self.link_lib_modifiers + .push(link_lib_modifier.as_ref().into()); self } @@ -1271,7 +1291,8 @@ impl Build { self.compile_objects(&objects)?; self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; - if self.get_target()?.contains("msvc") { + let target = self.get_target()?; + if target.contains("msvc") { let compiler = self.get_base_compiler()?; let atlmfc_lib = compiler .env() @@ -1296,10 +1317,13 @@ impl Build { self.cargo_output .print_metadata(&format_args!("cargo:rustc-link-lib=static={}", lib_name)); } else { - let m = self.link_lib_modifiers.join(","); self.cargo_output.print_metadata(&format_args!( "cargo:rustc-link-lib=static:{}={}", - m, lib_name + JoinOsStrs { + slice: &self.link_lib_modifiers, + delimiter: ',' + }, + lib_name )); } self.cargo_output.print_metadata(&format_args!( @@ -1311,16 +1335,26 @@ impl Build { if self.cpp { if let Some(stdlib) = self.get_cpp_link_stdlib()? { self.cargo_output - .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib)); + .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display())); + } + // Link c++ lib from WASI sysroot + if Build::is_wasi_target(target.as_ref()) { + if let Ok(wasi_sysroot) = self.wasi_sysroot() { + self.cargo_output.print_metadata(&format_args!( + "cargo:rustc-flags=-L {}/lib/{} -lstatic=c++ -lstatic=c++abi", + Path::new(&wasi_sysroot).display(), + target + )); + } } } let cudart = match &self.cudart { - Some(opt) => &*opt, // {none|shared|static} + Some(opt) => opt, // {none|shared|static} None => "none", }; if cudart != "none" { - if let Some(nvcc) = which(&self.get_compiler().path, None) { + if let Some(nvcc) = self.which(&self.get_compiler().path, None) { // Try to figure out the -L search path. If it fails, // it's on user to specify one by passing it through // RUSTFLAGS environment variable. @@ -1328,7 +1362,7 @@ impl Build { let mut libdir = nvcc; libdir.pop(); // remove 'nvcc' libdir.push(".."); - let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_arch = self.getenv_unwrap_str("CARGO_CFG_TARGET_ARCH")?; if cfg!(target_os = "linux") { libdir.push("targets"); libdir.push(target_arch.to_owned() + "-linux"); @@ -1703,7 +1737,7 @@ impl Build { .file_name() .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?; - Ok(run_output(&mut cmd, &name, &self.cargo_output)?) + run_output(&mut cmd, name, &self.cargo_output) } /// Run the compiler, returning the macro-expanded version of the input files. @@ -1849,7 +1883,7 @@ impl Build { None => { let features = self.getenv("CARGO_CFG_TARGET_FEATURE"); let features = features.as_deref().unwrap_or_default(); - if features.contains("crt-static") { + if features.to_string_lossy().contains("crt-static") { "-MT" } else { "-MD" @@ -1858,7 +1892,7 @@ impl Build { }; cmd.push_cc_arg(crt_flag.into()); - match &opt_level[..] { + match opt_level { // Msvc uses /O1 to enable all optimizations that minimize code size. "z" | "s" | "1" => cmd.push_opt_unless_duplicate("-O1".into()), // -O3 is a valid value for gcc and clang compilers, but not msvc. Cap to /O2. @@ -1912,6 +1946,17 @@ impl Build { cmd.push_cc_arg("-fno-plt".into()); } } + if Build::is_wasi_target(target) { + // WASI does not support exceptions yet. + // https://github.com/WebAssembly/exception-handling + cmd.push_cc_arg("-fno-exceptions".into()); + // Link clang sysroot + if let Ok(wasi_sysroot) = self.wasi_sysroot() { + cmd.push_cc_arg( + format!("--sysroot={}", Path::new(&wasi_sysroot).display()).into(), + ); + } + } } } @@ -1942,9 +1987,9 @@ impl Build { // Target flags match cmd.family { ToolFamily::Clang { .. } => { - if !cmd.has_internal_target_arg - && !(target.contains("android") - && android_clang_compiler_uses_target_arg_internally(&cmd.path)) + if !(cmd.has_internal_target_arg + || (target.contains("android") + && android_clang_compiler_uses_target_arg_internally(&cmd.path))) { let (arch, rest) = target.split_once('-').ok_or_else(|| { Error::new( @@ -2073,7 +2118,7 @@ impl Build { ); } } else if let Ok(index) = target_info::RISCV_ARCH_MAPPING - .binary_search_by_key(&arch, |(arch, _)| &arch) + .binary_search_by_key(&arch, |(arch, _)| arch) { cmd.args.push( format!( @@ -2372,11 +2417,7 @@ impl Build { fn has_flags(&self) -> bool { let flags_env_var_name = if self.cpp { "CXXFLAGS" } else { "CFLAGS" }; let flags_env_var_value = self.getenv_with_target_prefixes(flags_env_var_name); - if let Ok(_) = flags_env_var_value { - true - } else { - false - } + flags_env_var_value.is_ok() } fn msvc_macro_assembler(&self) -> Result<(Command, &'static str), Error> { @@ -2390,7 +2431,9 @@ impl Build { } else { "ml.exe" }; - let mut cmd = windows_registry::find(&target, tool).unwrap_or_else(|| self.cmd(tool)); + let mut cmd = self + .windows_registry_find(&target, tool) + .unwrap_or_else(|| self.cmd(tool)); cmd.arg("-nologo"); // undocumented, yet working with armasm[64] for directory in self.include_directories.iter() { cmd.arg("-I").arg(&**directory); @@ -2572,10 +2615,7 @@ impl Build { } else { AppleOs::Ios }; - let is_mac = match os { - AppleOs::MacOs => true, - _ => false, - }; + let is_mac = matches!(os, AppleOs::MacOs); let arch_str = target.split('-').nth(0).ok_or_else(|| { Error::new( @@ -2697,13 +2737,13 @@ impl Build { let sdk_path = self.apple_sdk_root(&sdk_details.sdk)?; cmd.args.push("-isysroot".into()); - cmd.args.push(sdk_path.clone()); + cmd.args.push(OsStr::new(&sdk_path).to_owned()); if let AppleArchSpec::Catalyst(_) = arch { // Mac Catalyst uses the macOS SDK, but to compile against and // link to iOS-specific frameworks, we should have the support // library stubs in the include and library search path. - let ios_support = PathBuf::from(sdk_path).join("System/iOSSupport"); + let ios_support = Path::new(&sdk_path).join("System/iOSSupport"); cmd.args.extend([ // Header search path @@ -2715,7 +2755,7 @@ impl Build { // Library search path { let mut s = OsString::from("-L"); - s.push(&ios_support.join("usr/lib")); + s.push(ios_support.join("usr/lib")); s }, // Framework linker search path @@ -2724,7 +2764,7 @@ impl Build { // `-iframework` implies it, but let's keep it in for // clarity. let mut s = OsString::from("-F"); - s.push(&ios_support.join("System/Library/Frameworks")); + s.push(ios_support.join("System/Library/Frameworks")); s }, ]); @@ -2773,7 +2813,7 @@ impl Build { traditional }; - let cl_exe = windows_registry::find_tool(target, "cl.exe"); + let cl_exe = self.windows_registry_find_tool(target, "cl.exe"); let tool_opt: Option = self .env_tool(env) @@ -2796,7 +2836,7 @@ impl Build { out_dir, ); if let Some(cc_wrapper) = wrapper { - t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); + t.cc_wrapper_path = Some(Path::new(&cc_wrapper).to_owned()); } for arg in args { t.cc_wrapper_args.push(arg.into()); @@ -2849,11 +2889,12 @@ impl Build { autodetect_android_compiler(target, &host, gnu, clang) } else if target.contains("cloudabi") { format!("{}-{}", target, traditional) - } else if target == "wasm32-wasi" - || target == "wasm32-unknown-wasi" - || target == "wasm32-unknown-unknown" - { - "clang".to_string() + } else if Build::is_wasi_target(target) { + if self.cpp { + "clang++".to_string() + } else { + "clang".to_string() + } } else if target.contains("vxworks") { if self.cpp { "wr-c++".to_string() @@ -2883,8 +2924,8 @@ impl Build { &self.cargo_output, out_dir, ); - if let Some(cc_wrapper) = Self::rustc_wrapper_fallback() { - t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); + if let Some(cc_wrapper) = self.rustc_wrapper_fallback() { + t.cc_wrapper_path = Some(Path::new(&cc_wrapper).to_owned()); } t } @@ -2907,9 +2948,11 @@ impl Build { &self.cargo_output, out_dir, ); - nvcc_tool - .args - .push(format!("-ccbin={}", tool.path.display()).into()); + if self.ccbin { + nvcc_tool + .args + .push(format!("-ccbin={}", tool.path.display()).into()); + } nvcc_tool.family = tool.family; nvcc_tool } else { @@ -2963,7 +3006,7 @@ impl Build { // of the box" experience. if let Some(cl_exe) = cl_exe { if tool.family == (ToolFamily::Msvc { clang_cl: true }) - && tool.env.len() == 0 + && tool.env.is_empty() && target.contains("msvc") { for (k, v) in cl_exe.env.iter() { @@ -2981,38 +3024,40 @@ impl Build { } /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER` - fn rustc_wrapper_fallback() -> Option { + fn rustc_wrapper_fallback(&self) -> Option> { // No explicit CC wrapper was detected, but check if RUSTC_WRAPPER // is defined and is a build accelerator that is compatible with // C/C++ compilers (e.g. sccache) - const VALID_WRAPPERS: &[&'static str] = &["sccache", "cachepot"]; + const VALID_WRAPPERS: &[&str] = &["sccache", "cachepot"]; - let rustc_wrapper = std::env::var_os("RUSTC_WRAPPER")?; + let rustc_wrapper = self.getenv("RUSTC_WRAPPER")?; let wrapper_path = Path::new(&rustc_wrapper); let wrapper_stem = wrapper_path.file_stem()?; if VALID_WRAPPERS.contains(&wrapper_stem.to_str()?) { - Some(rustc_wrapper.to_str()?.to_owned()) + Some(rustc_wrapper) } else { None } } /// Returns compiler path, optional modifier name from whitelist, and arguments vec - fn env_tool(&self, name: &str) -> Option<(PathBuf, Option, Vec)> { - let tool = match self.getenv_with_target_prefixes(name) { - Ok(tool) if !tool.trim().is_empty() => tool, - _ => return None, - }; + fn env_tool(&self, name: &str) -> Option<(PathBuf, Option>, Vec)> { + let tool = self.getenv_with_target_prefixes(name).ok()?; + let tool = tool.to_string_lossy(); let tool = tool.trim(); + if tool.is_empty() { + return None; + } + // If this is an exact path on the filesystem we don't want to do any // interpretation at all, just pass it on through. This'll hopefully get // us to support spaces-in-paths. if Path::new(tool).exists() { return Some(( PathBuf::from(tool), - Self::rustc_wrapper_fallback(), + self.rustc_wrapper_fallback(), Vec::new(), )); } @@ -3045,16 +3090,12 @@ impl Build { None => return None, }; - let file_stem = Path::new(maybe_wrapper) - .file_stem() - .unwrap() - .to_str() - .unwrap(); + let file_stem = Path::new(maybe_wrapper).file_stem()?.to_str()?; if known_wrappers.contains(&file_stem) { if let Some(compiler) = parts.next() { return Some(( compiler.into(), - Some(maybe_wrapper.to_string()), + Some(Arc::::from(OsStr::new(&maybe_wrapper))), parts.map(|s| s.to_string()).collect(), )); } @@ -3062,7 +3103,7 @@ impl Build { Some(( maybe_wrapper.into(), - Self::rustc_wrapper_fallback(), + self.rustc_wrapper_fallback(), parts.map(|s| s.to_string()).collect(), )) } @@ -3072,15 +3113,15 @@ impl Build { /// 2. Else if the `CXXSTDLIB` environment variable is set, uses its value. /// 3. Else the default is `libc++` for OS X and BSDs, `libc++_shared` for Android, /// `None` for MSVC and `libstdc++` for anything else. - fn get_cpp_link_stdlib(&self) -> Result, Error> { + fn get_cpp_link_stdlib(&self) -> Result>, Error> { match &self.cpp_link_stdlib { - Some(s) => Ok(s.as_ref().map(|s| (*s).to_string())), + Some(s) => Ok(s.as_deref().map(Path::new).map(Cow::Borrowed)), None => { if let Ok(stdlib) = self.getenv_with_target_prefixes("CXXSTDLIB") { if stdlib.is_empty() { Ok(None) } else { - Ok(Some(stdlib.to_string())) + Ok(Some(Cow::Owned(Path::new(&stdlib).to_owned()))) } } else { let target = self.get_target()?; @@ -3091,12 +3132,13 @@ impl Build { | target.contains("openbsd") | target.contains("aix") | target.contains("linux-ohos") + | target.contains("-wasi") { - Ok(Some("c++".to_string())) + Ok(Some(Cow::Borrowed(Path::new("c++")))) } else if target.contains("android") { - Ok(Some("c++_shared".to_string())) + Ok(Some(Cow::Borrowed(Path::new("c++_shared")))) } else { - Ok(Some("stdc++".to_string())) + Ok(Some(Cow::Borrowed(Path::new("stdc++")))) } } } @@ -3140,7 +3182,7 @@ impl Build { let (mut cmd, name) = self.get_base_archiver()?; let mut any_flags = false; if let Ok(flags) = self.envflags("ARFLAGS") { - any_flags = any_flags | !flags.is_empty(); + any_flags |= !flags.is_empty(); cmd.args(flags); } for flag in &self.ar_flags { @@ -3157,7 +3199,6 @@ impl Build { } self.get_base_archiver_variant("AR", "ar") - .map(|(cmd, archiver)| (cmd, archiver.into())) } /// Get the ranlib that's in use for this configuration. @@ -3211,7 +3252,7 @@ impl Build { let tool_opt: Option = self .env_tool(env) .map(|(tool, _wrapper, args)| { - name = tool.clone(); + name.clone_from(&tool); let mut cmd = self.cmd(tool); cmd.args(args); cmd @@ -3237,8 +3278,12 @@ impl Build { let compiler = self.get_base_compiler().ok()?; if compiler.is_like_clang() { name = format!("llvm-{}", tool).into(); - search_programs(&mut self.cmd(&compiler.path), &name, &self.cargo_output) - .map(|name| self.cmd(name)) + self.search_programs( + &mut self.cmd(&compiler.path), + &name, + &self.cargo_output, + ) + .map(|name| self.cmd(name)) } else { None } @@ -3272,18 +3317,18 @@ impl Build { // next to 'clang-cl' and use 'search_programs()' to locate // 'llvm-lib'. This is because 'clang-cl' doesn't support // the -print-search-dirs option. - if let Some(mut cmd) = which(&compiler.path, None) { + if let Some(mut cmd) = self.which(&compiler.path, None) { cmd.pop(); cmd.push("llvm-lib.exe"); - if let Some(llvm_lib) = which(&cmd, None) { - lib = llvm_lib.to_str().unwrap().to_owned(); + if let Some(llvm_lib) = self.which(&cmd, None) { + llvm_lib.to_str().unwrap().clone_into(&mut lib); } } } if lib.is_empty() { name = PathBuf::from("lib.exe"); - let mut cmd = match windows_registry::find(&target, "lib.exe") { + let mut cmd = match self.windows_registry_find(&target, "lib.exe") { Some(t) => t, None => self.cmd("lib.exe"), }; @@ -3338,135 +3383,144 @@ impl Build { Ok((tool, name)) } - fn prefix_for_target(&self, target: &str) -> Option { - // Put aside RUSTC_LINKER's prefix to be used as second choice, after CROSS_COMPILE - let linker_prefix = self - .getenv("RUSTC_LINKER") - .and_then(|var| var.strip_suffix("-gcc").map(str::to_string)); + fn prefix_for_target(&self, target: &str) -> Option> { // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" - let cc_env = self.getenv("CROSS_COMPILE"); - let cross_compile = cc_env.as_ref().map(|s| s.trim_end_matches('-').to_owned()); - cross_compile.or(linker_prefix).or(match &target[..] { - // Note: there is no `aarch64-pc-windows-gnu` target, only `-gnullvm` - "aarch64-pc-windows-gnullvm" => Some("aarch64-w64-mingw32"), - "aarch64-uwp-windows-gnu" => Some("aarch64-w64-mingw32"), - "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"), - "aarch64-unknown-linux-musl" => Some("aarch64-linux-musl"), - "aarch64-unknown-netbsd" => Some("aarch64--netbsd"), - "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), - "armv4t-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), - "armv5te-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), - "armv5te-unknown-linux-musleabi" => Some("arm-linux-gnueabi"), - "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi"), - "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), - "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"), - "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "arm-unknown-netbsd-eabi" => Some("arm--netbsdelf-eabi"), - "armv6-unknown-netbsd-eabihf" => Some("armv6--netbsdelf-eabihf"), - "armv7-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), - "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), - "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "armv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), - "armv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "thumbv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), - "thumbv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "thumbv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), - "thumbv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "armv7-unknown-netbsd-eabihf" => Some("armv7--netbsdelf-eabihf"), - "hexagon-unknown-linux-musl" => Some("hexagon-linux-musl"), - "i586-unknown-linux-musl" => Some("musl"), - "i686-pc-windows-gnu" => Some("i686-w64-mingw32"), - "i686-uwp-windows-gnu" => Some("i686-w64-mingw32"), - "i686-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ - "i686-linux-gnu", - "x86_64-linux-gnu", // transparently support gcc-multilib - ]), // explicit None if not found, so caller knows to fall back - "i686-unknown-linux-musl" => Some("musl"), - "i686-unknown-netbsd" => Some("i486--netbsdelf"), - "loongarch64-unknown-linux-gnu" => Some("loongarch64-linux-gnu"), - "mips-unknown-linux-gnu" => Some("mips-linux-gnu"), - "mips-unknown-linux-musl" => Some("mips-linux-musl"), - "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu"), - "mipsel-unknown-linux-musl" => Some("mipsel-linux-musl"), - "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64"), - "mips64el-unknown-linux-gnuabi64" => Some("mips64el-linux-gnuabi64"), - "mipsisa32r6-unknown-linux-gnu" => Some("mipsisa32r6-linux-gnu"), - "mipsisa32r6el-unknown-linux-gnu" => Some("mipsisa32r6el-linux-gnu"), - "mipsisa64r6-unknown-linux-gnuabi64" => Some("mipsisa64r6-linux-gnuabi64"), - "mipsisa64r6el-unknown-linux-gnuabi64" => Some("mipsisa64r6el-linux-gnuabi64"), - "powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu"), - "powerpc-unknown-linux-gnuspe" => Some("powerpc-linux-gnuspe"), - "powerpc-unknown-netbsd" => Some("powerpc--netbsd"), - "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"), - "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"), - "riscv32i-unknown-none-elf" => self.find_working_gnu_prefix(&[ - "riscv32-unknown-elf", - "riscv64-unknown-elf", - "riscv-none-embed", - ]), - "riscv32imac-esp-espidf" => Some("riscv32-esp-elf"), - "riscv32imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ - "riscv32-unknown-elf", - "riscv64-unknown-elf", - "riscv-none-embed", - ]), - "riscv32imac-unknown-xous-elf" => self.find_working_gnu_prefix(&[ - "riscv32-unknown-elf", - "riscv64-unknown-elf", - "riscv-none-embed", - ]), - "riscv32imc-esp-espidf" => Some("riscv32-esp-elf"), - "riscv32imc-unknown-none-elf" => self.find_working_gnu_prefix(&[ - "riscv32-unknown-elf", - "riscv64-unknown-elf", - "riscv-none-embed", - ]), - "riscv64gc-unknown-none-elf" => self.find_working_gnu_prefix(&[ - "riscv64-unknown-elf", - "riscv32-unknown-elf", - "riscv-none-embed", - ]), - "riscv64imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ - "riscv64-unknown-elf", - "riscv32-unknown-elf", - "riscv-none-embed", - ]), - "riscv64gc-unknown-linux-gnu" => Some("riscv64-linux-gnu"), - "riscv32gc-unknown-linux-gnu" => Some("riscv32-linux-gnu"), - "riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl"), - "riscv32gc-unknown-linux-musl" => Some("riscv32-linux-musl"), - "riscv64gc-unknown-netbsd" => Some("riscv64--netbsd"), - "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"), - "sparc-unknown-linux-gnu" => Some("sparc-linux-gnu"), - "sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu"), - "sparc64-unknown-netbsd" => Some("sparc64--netbsd"), - "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"), - "armv7a-none-eabi" => Some("arm-none-eabi"), - "armv7a-none-eabihf" => Some("arm-none-eabi"), - "armebv7r-none-eabi" => Some("arm-none-eabi"), - "armebv7r-none-eabihf" => Some("arm-none-eabi"), - "armv7r-none-eabi" => Some("arm-none-eabi"), - "armv7r-none-eabihf" => Some("arm-none-eabi"), - "armv8r-none-eabihf" => Some("arm-none-eabi"), - "thumbv6m-none-eabi" => Some("arm-none-eabi"), - "thumbv7em-none-eabi" => Some("arm-none-eabi"), - "thumbv7em-none-eabihf" => Some("arm-none-eabi"), - "thumbv7m-none-eabi" => Some("arm-none-eabi"), - "thumbv8m.base-none-eabi" => Some("arm-none-eabi"), - "thumbv8m.main-none-eabi" => Some("arm-none-eabi"), - "thumbv8m.main-none-eabihf" => Some("arm-none-eabi"), - "x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32"), - "x86_64-pc-windows-gnullvm" => Some("x86_64-w64-mingw32"), - "x86_64-uwp-windows-gnu" => Some("x86_64-w64-mingw32"), - "x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd"), - "x86_64-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ - "x86_64-linux-gnu", // rustfmt wrap - ]), // explicit None if not found, so caller knows to fall back - "x86_64-unknown-linux-musl" => Some("musl"), - "x86_64-unknown-netbsd" => Some("x86_64--netbsd"), - _ => None, - } - .map(|x| x.to_owned())) + self.getenv("CROSS_COMPILE") + .as_deref() + .map(|s| s.to_string_lossy().trim_end_matches('-').to_owned()) + .map(Cow::Owned) + .or_else(|| { + // Put aside RUSTC_LINKER's prefix to be used as second choice, after CROSS_COMPILE + self.getenv("RUSTC_LINKER").and_then(|var| { + var.to_string_lossy() + .strip_suffix("-gcc") + .map(str::to_string) + .map(Cow::Owned) + }) + }) + .or_else(|| { + match target { + // Note: there is no `aarch64-pc-windows-gnu` target, only `-gnullvm` + "aarch64-pc-windows-gnullvm" => Some("aarch64-w64-mingw32"), + "aarch64-uwp-windows-gnu" => Some("aarch64-w64-mingw32"), + "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"), + "aarch64-unknown-linux-musl" => Some("aarch64-linux-musl"), + "aarch64-unknown-netbsd" => Some("aarch64--netbsd"), + "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), + "armv4t-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), + "armv5te-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), + "armv5te-unknown-linux-musleabi" => Some("arm-linux-gnueabi"), + "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi"), + "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), + "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"), + "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), + "arm-unknown-netbsd-eabi" => Some("arm--netbsdelf-eabi"), + "armv6-unknown-netbsd-eabihf" => Some("armv6--netbsdelf-eabihf"), + "armv7-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), + "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), + "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), + "armv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), + "armv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), + "thumbv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), + "thumbv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), + "thumbv7neon-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), + "thumbv7neon-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), + "armv7-unknown-netbsd-eabihf" => Some("armv7--netbsdelf-eabihf"), + "hexagon-unknown-linux-musl" => Some("hexagon-linux-musl"), + "i586-unknown-linux-musl" => Some("musl"), + "i686-pc-windows-gnu" => Some("i686-w64-mingw32"), + "i686-uwp-windows-gnu" => Some("i686-w64-mingw32"), + "i686-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ + "i686-linux-gnu", + "x86_64-linux-gnu", // transparently support gcc-multilib + ]), // explicit None if not found, so caller knows to fall back + "i686-unknown-linux-musl" => Some("musl"), + "i686-unknown-netbsd" => Some("i486--netbsdelf"), + "loongarch64-unknown-linux-gnu" => Some("loongarch64-linux-gnu"), + "mips-unknown-linux-gnu" => Some("mips-linux-gnu"), + "mips-unknown-linux-musl" => Some("mips-linux-musl"), + "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu"), + "mipsel-unknown-linux-musl" => Some("mipsel-linux-musl"), + "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64"), + "mips64el-unknown-linux-gnuabi64" => Some("mips64el-linux-gnuabi64"), + "mipsisa32r6-unknown-linux-gnu" => Some("mipsisa32r6-linux-gnu"), + "mipsisa32r6el-unknown-linux-gnu" => Some("mipsisa32r6el-linux-gnu"), + "mipsisa64r6-unknown-linux-gnuabi64" => Some("mipsisa64r6-linux-gnuabi64"), + "mipsisa64r6el-unknown-linux-gnuabi64" => Some("mipsisa64r6el-linux-gnuabi64"), + "powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu"), + "powerpc-unknown-linux-gnuspe" => Some("powerpc-linux-gnuspe"), + "powerpc-unknown-netbsd" => Some("powerpc--netbsd"), + "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"), + "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"), + "riscv32i-unknown-none-elf" => self.find_working_gnu_prefix(&[ + "riscv32-unknown-elf", + "riscv64-unknown-elf", + "riscv-none-embed", + ]), + "riscv32imac-esp-espidf" => Some("riscv32-esp-elf"), + "riscv32imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ + "riscv32-unknown-elf", + "riscv64-unknown-elf", + "riscv-none-embed", + ]), + "riscv32imac-unknown-xous-elf" => self.find_working_gnu_prefix(&[ + "riscv32-unknown-elf", + "riscv64-unknown-elf", + "riscv-none-embed", + ]), + "riscv32imc-esp-espidf" => Some("riscv32-esp-elf"), + "riscv32imc-unknown-none-elf" => self.find_working_gnu_prefix(&[ + "riscv32-unknown-elf", + "riscv64-unknown-elf", + "riscv-none-embed", + ]), + "riscv64gc-unknown-none-elf" => self.find_working_gnu_prefix(&[ + "riscv64-unknown-elf", + "riscv32-unknown-elf", + "riscv-none-embed", + ]), + "riscv64imac-unknown-none-elf" => self.find_working_gnu_prefix(&[ + "riscv64-unknown-elf", + "riscv32-unknown-elf", + "riscv-none-embed", + ]), + "riscv64gc-unknown-linux-gnu" => Some("riscv64-linux-gnu"), + "riscv32gc-unknown-linux-gnu" => Some("riscv32-linux-gnu"), + "riscv64gc-unknown-linux-musl" => Some("riscv64-linux-musl"), + "riscv32gc-unknown-linux-musl" => Some("riscv32-linux-musl"), + "riscv64gc-unknown-netbsd" => Some("riscv64--netbsd"), + "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"), + "sparc-unknown-linux-gnu" => Some("sparc-linux-gnu"), + "sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu"), + "sparc64-unknown-netbsd" => Some("sparc64--netbsd"), + "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"), + "armv7a-none-eabi" => Some("arm-none-eabi"), + "armv7a-none-eabihf" => Some("arm-none-eabi"), + "armebv7r-none-eabi" => Some("arm-none-eabi"), + "armebv7r-none-eabihf" => Some("arm-none-eabi"), + "armv7r-none-eabi" => Some("arm-none-eabi"), + "armv7r-none-eabihf" => Some("arm-none-eabi"), + "armv8r-none-eabihf" => Some("arm-none-eabi"), + "thumbv6m-none-eabi" => Some("arm-none-eabi"), + "thumbv7em-none-eabi" => Some("arm-none-eabi"), + "thumbv7em-none-eabihf" => Some("arm-none-eabi"), + "thumbv7m-none-eabi" => Some("arm-none-eabi"), + "thumbv8m.base-none-eabi" => Some("arm-none-eabi"), + "thumbv8m.main-none-eabi" => Some("arm-none-eabi"), + "thumbv8m.main-none-eabihf" => Some("arm-none-eabi"), + "x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32"), + "x86_64-pc-windows-gnullvm" => Some("x86_64-w64-mingw32"), + "x86_64-uwp-windows-gnu" => Some("x86_64-w64-mingw32"), + "x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd"), + "x86_64-unknown-linux-gnu" => self.find_working_gnu_prefix(&[ + "x86_64-linux-gnu", // rustfmt wrap + ]), // explicit None if not found, so caller knows to fall back + "x86_64-unknown-linux-musl" => Some("musl"), + "x86_64-unknown-netbsd" => Some("x86_64--netbsd"), + _ => None, + } + .map(Cow::Borrowed) + }) } /// Some platforms have multiple, compatible, canonical prefixes. Look through @@ -3479,7 +3533,7 @@ impl Build { // Loop through PATH entries searching for each toolchain. This ensures that we // are more likely to discover the toolchain early on, because chances are good // that the desired toolchain is in one of the higher-priority paths. - env::var_os("PATH") + self.getenv("PATH") .as_ref() .and_then(|path_entries| { env::split_paths(path_entries).find_map(|path_entry| { @@ -3492,32 +3546,32 @@ impl Build { None }) }) - .map(|prefix| *prefix) + .copied() // If no toolchain was found, provide the first toolchain that was passed in. // This toolchain has been shown not to exist, however it will appear in the // error that is shown to the user which should make it easier to search for // where it should be obtained. - .or_else(|| prefixes.first().map(|prefix| *prefix)) + .or_else(|| prefixes.first().copied()) } - fn get_target(&self) -> Result, Error> { + fn get_target(&self) -> Result, Error> { match &self.target { - Some(t) => Ok(t.clone()), - None => self.getenv_unwrap("TARGET"), + Some(t) => Ok(Cow::Borrowed(t)), + None => self.getenv_unwrap_str("TARGET").map(Cow::Owned), } } - fn get_host(&self) -> Result, Error> { + fn get_host(&self) -> Result, Error> { match &self.host { - Some(h) => Ok(h.clone()), - None => self.getenv_unwrap("HOST"), + Some(h) => Ok(Cow::Borrowed(h)), + None => self.getenv_unwrap_str("HOST").map(Cow::Owned), } } - fn get_opt_level(&self) -> Result, Error> { + fn get_opt_level(&self) -> Result, Error> { match &self.opt_level { - Some(ol) => Ok(ol.clone()), - None => self.getenv_unwrap("OPT_LEVEL"), + Some(ol) => Ok(Cow::Borrowed(ol)), + None => self.getenv_unwrap_str("OPT_LEVEL").map(Cow::Owned), } } @@ -3554,7 +3608,9 @@ impl Build { fn get_out_dir(&self) -> Result, Error> { match &self.out_dir { Some(p) => Ok(Cow::Borrowed(&**p)), - None => env::var_os("OUT_DIR") + None => self + .getenv("OUT_DIR") + .as_deref() .map(PathBuf::from) .map(Cow::Owned) .ok_or_else(|| { @@ -3566,7 +3622,8 @@ impl Build { } } - fn getenv(&self, v: &str) -> Option> { + #[allow(clippy::disallowed_methods)] + fn getenv(&self, v: &str) -> Option> { // Returns true for environment variables cargo sets for build scripts: // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts // @@ -3581,22 +3638,24 @@ impl Build { _ => false, } } - let mut cache = self.env_cache.lock().unwrap(); - if let Some(val) = cache.get(v) { - return val.clone(); + if let Some(val) = self.env_cache.read().unwrap().get(v).cloned() { + return val; } if self.emit_rerun_if_env_changed && !provided_by_cargo(v) { self.cargo_output .print_metadata(&format_args!("cargo:rerun-if-env-changed={}", v)); } - let r = env::var(v).ok().map(Arc::from); - self.cargo_output - .print_metadata(&format_args!("{} = {:?}", v, r)); - cache.insert(v.to_string(), r.clone()); + let r = env::var_os(v).map(Arc::from); + self.cargo_output.print_metadata(&format_args!( + "{} = {}", + v, + OptionOsStrDisplay(r.as_deref()) + )); + self.env_cache.write().unwrap().insert(v.into(), r.clone()); r } - fn getenv_unwrap(&self, v: &str) -> Result, Error> { + fn getenv_unwrap(&self, v: &str) -> Result, Error> { match self.getenv(v) { Some(s) => Ok(s), None => Err(Error::new( @@ -3606,7 +3665,17 @@ impl Build { } } - fn getenv_with_target_prefixes(&self, var_base: &str) -> Result, Error> { + fn getenv_unwrap_str(&self, v: &str) -> Result { + let env = self.getenv_unwrap(v)?; + env.to_str().map(String::from).ok_or_else(|| { + Error::new( + ErrorKind::EnvVarNotFound, + format!("Environment variable {} is not valid utf-8.", v), + ) + }) + } + + fn getenv_with_target_prefixes(&self, var_base: &str) -> Result, Error> { let target = self.get_target()?; let host = self.get_host()?; let kind = if host == target { "HOST" } else { "TARGET" }; @@ -3629,8 +3698,9 @@ impl Build { fn envflags(&self, name: &str) -> Result, Error> { Ok(self .getenv_with_target_prefixes(name)? + .to_string_lossy() .split_ascii_whitespace() - .map(|slice| slice.to_string()) + .map(ToString::to_string) .collect()) } @@ -3646,54 +3716,39 @@ impl Build { Ok(()) } - fn apple_sdk_root(&self, sdk: &str) -> Result { + fn apple_sdk_root_inner(&self, sdk: &str) -> Result, Error> { // Code copied from rustc's compiler/rustc_codegen_ssa/src/back/link.rs. - if let Some(sdkroot) = env::var_os("SDKROOT") { - let p = PathBuf::from(sdkroot); - let sdkroot = p.to_string_lossy(); + if let Some(sdkroot) = self.getenv("SDKROOT") { + let p = Path::new(&sdkroot); + let does_sdkroot_contain = |strings: &[&str]| { + let sdkroot_str = p.to_string_lossy(); + strings.iter().any(|s| sdkroot_str.contains(s)) + }; match sdk { // Ignore `SDKROOT` if it's clearly set for the wrong platform. "appletvos" - if sdkroot.contains("TVSimulator.platform") - || sdkroot.contains("MacOSX.platform") => {} + if does_sdkroot_contain(&["TVSimulator.platform", "MacOSX.platform"]) => {} "appletvsimulator" - if sdkroot.contains("TVOS.platform") || sdkroot.contains("MacOSX.platform") => { - } + if does_sdkroot_contain(&["TVOS.platform", "MacOSX.platform"]) => {} "iphoneos" - if sdkroot.contains("iPhoneSimulator.platform") - || sdkroot.contains("MacOSX.platform") => {} + if does_sdkroot_contain(&["iPhoneSimulator.platform", "MacOSX.platform"]) => {} "iphonesimulator" - if sdkroot.contains("iPhoneOS.platform") - || sdkroot.contains("MacOSX.platform") => {} + if does_sdkroot_contain(&["iPhoneOS.platform", "MacOSX.platform"]) => {} "macosx10.15" - if sdkroot.contains("iPhoneOS.platform") - || sdkroot.contains("iPhoneSimulator.platform") => {} + if does_sdkroot_contain(&["iPhoneOS.platform", "iPhoneSimulator.platform"]) => { + } "watchos" - if sdkroot.contains("WatchSimulator.platform") - || sdkroot.contains("MacOSX.platform") => {} + if does_sdkroot_contain(&["WatchSimulator.platform", "MacOSX.platform"]) => {} "watchsimulator" - if sdkroot.contains("WatchOS.platform") - || sdkroot.contains("MacOSX.platform") => {} - "xros" - if sdkroot.contains("XRSimulator.platform") - || sdkroot.contains("MacOSX.platform") => {} - "xrsimulator" - if sdkroot.contains("XROS.platform") || sdkroot.contains("MacOSX.platform") => { - } + if does_sdkroot_contain(&["WatchOS.platform", "MacOSX.platform"]) => {} + "xros" if does_sdkroot_contain(&["XRSimulator.platform", "MacOSX.platform"]) => {} + "xrsimulator" if does_sdkroot_contain(&["XROS.platform", "MacOSX.platform"]) => {} // Ignore `SDKROOT` if it's not a valid path. _ if !p.is_absolute() || p == Path::new("/") || !p.exists() => {} - _ => return Ok(p.into()), + _ => return Ok(sdkroot), } } - let mut cache = self - .apple_sdk_root_cache - .lock() - .expect("apple_sdk_root_cache lock failed"); - if let Some(ret) = cache.get(sdk) { - return Ok(ret.clone()); - } - let sdk_path = run_output( self.cmd("xcrun") .arg("--show-sdk-path") @@ -3712,22 +3767,39 @@ impl Build { )); } }; - let ret: OsString = sdk_path.trim().into(); - cache.insert(sdk.into(), ret.clone()); - Ok(ret) + Ok(Arc::from(OsStr::new(sdk_path.trim()))) } - fn apple_deployment_version(&self, os: AppleOs, arch_str: Option<&str>, sdk: &str) -> String { - let default_deployment_from_sdk = || { - let mut cache = self - .apple_versions_cache - .lock() - .expect("apple_versions_cache lock failed"); + fn apple_sdk_root(&self, sdk: &str) -> Result, Error> { + if let Some(ret) = self + .apple_sdk_root_cache + .read() + .expect("apple_sdk_root_cache lock failed") + .get(sdk) + .cloned() + { + return Ok(ret); + } + let sdk_path = self.apple_sdk_root_inner(sdk)?; + self.apple_sdk_root_cache + .write() + .expect("apple_sdk_root_cache lock failed") + .insert(sdk.into(), sdk_path.clone()); + Ok(sdk_path) + } - if let Some(ret) = cache.get(sdk) { - return Some(ret.clone()); - } + fn apple_deployment_version(&self, os: AppleOs, arch_str: Option<&str>, sdk: &str) -> Arc { + if let Some(ret) = self + .apple_versions_cache + .read() + .expect("apple_versions_cache lock failed") + .get(sdk) + .cloned() + { + return ret; + } + let default_deployment_from_sdk = || -> Option> { let version = run_output( self.cmd("xcrun") .arg("--show-sdk-version") @@ -3738,27 +3810,27 @@ impl Build { ) .ok()?; - let version = std::str::from_utf8(&version).ok()?.trim().to_owned(); - - cache.insert(sdk.into(), version.clone()); - Some(version) + Some(Arc::from(std::str::from_utf8(&version).ok()?.trim())) }; - let deployment_from_env = |name: &str| { - // note this isn't hit in production codepaths, its mostly just for tests which don't + let deployment_from_env = |name: &str| -> Option> { + // note that self.env isn't hit in production codepaths, its mostly just for tests which don't // set the real env - if let Some((_, v)) = self.env.iter().find(|(k, _)| &**k == OsStr::new(name)) { - Some(v.to_str().unwrap().to_string()) - } else { - env::var(name).ok() - } + self.env + .iter() + .find(|(k, _)| &**k == OsStr::new(name)) + .map(|(_, v)| v) + .cloned() + .or_else(|| self.getenv(name))? + .to_str() + .map(Arc::from) }; // Determines if the acquired deployment target is too low to support modern C++ on some Apple platform. // // A long time ago they used libstdc++, but since macOS 10.9 and iOS 7 libc++ has been the library the SDKs provide to link against. // If a `cc`` config wants to use C++, we round up to these versions as the baseline. - let maybe_cpp_version_baseline = |deployment_target_ver: String| -> Option { + let maybe_cpp_version_baseline = |deployment_target_ver: Arc| -> Option> { if !self.cpp { return Some(deployment_target_ver); } @@ -3812,7 +3884,7 @@ impl Build { // // The ordering of env -> XCode SDK -> old rustc defaults is intentional for performance when using // an explicit target. - match os { + let version: Arc = match os { AppleOs::MacOs => deployment_from_env("MACOSX_DEPLOYMENT_TARGET") .and_then(maybe_cpp_version_baseline) .or_else(default_deployment_from_sdk) @@ -3820,8 +3892,8 @@ impl Build { if arch_str == Some("aarch64") { "11.0".into() } else { - let default = "10.7"; - maybe_cpp_version_baseline(default.into()).unwrap_or_else(|| default.into()) + let default: Arc = Arc::from("10.7"); + maybe_cpp_version_baseline(default.clone()).unwrap_or(default) } }), @@ -3841,8 +3913,38 @@ impl Build { AppleOs::VisionOS => deployment_from_env("XROS_DEPLOYMENT_TARGET") .or_else(default_deployment_from_sdk) .unwrap_or_else(|| "1.0".into()), + }; + + self.apple_versions_cache + .write() + .expect("apple_versions_cache lock failed") + .insert(sdk.into(), version.clone()); + + version + } + + fn wasi_sysroot(&self) -> Result, Error> { + if let Some(wasi_sysroot_path) = self.getenv("WASI_SYSROOT") { + Ok(wasi_sysroot_path) + } else { + Err(Error::new( + ErrorKind::EnvVarNotFound, + "Environment variable WASI_SYSROOT not defined. Download sysroot from github & setup environment variable WASI_SYSROOT targetting the folder.", + )) } } + fn is_wasi_target(target: &str) -> bool { + const TARGETS: [&'static str; 7] = [ + "wasm32-wasi", + "wasm32-wasip1", + "wasm32-wasip1-threads", + "wasm32-wasip2", + "wasm32-wasi-threads", + "wasm32-unknown-wasi", + "wasm32-unknown-unknown", + ]; + return TARGETS.contains(&target); + } fn cuda_file_count(&self) -> usize { self.files @@ -3850,6 +3952,71 @@ impl Build { .filter(|file| file.extension() == Some(OsStr::new("cu"))) .count() } + + fn which(&self, tool: &Path, path_entries: Option<&OsStr>) -> Option { + fn check_exe(mut exe: PathBuf) -> Option { + let exe_ext = std::env::consts::EXE_EXTENSION; + let check = + exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists()); + check.then_some(exe) + } + + // Loop through PATH entries searching for the |tool|. + let find_exe_in_path = |path_entries: &OsStr| -> Option { + env::split_paths(path_entries).find_map(|path_entry| check_exe(path_entry.join(tool))) + }; + + // If |tool| is not just one "word," assume it's an actual path... + if tool.components().count() > 1 { + check_exe(PathBuf::from(tool)) + } else { + path_entries + .and_then(find_exe_in_path) + .or_else(|| find_exe_in_path(&self.getenv("PATH")?)) + } + } + + /// search for |prog| on 'programs' path in '|cc| -print-search-dirs' output + fn search_programs( + &self, + cc: &mut Command, + prog: &Path, + cargo_output: &CargoOutput, + ) -> Option { + let search_dirs = run_output( + cc.arg("-print-search-dirs"), + "cc", + // this doesn't concern the compilation so we always want to show warnings. + cargo_output, + ) + .ok()?; + // clang driver appears to be forcing UTF-8 output even on Windows, + // hence from_utf8 is assumed to be usable in all cases. + let search_dirs = std::str::from_utf8(&search_dirs).ok()?; + for dirs in search_dirs.split(|c| c == '\r' || c == '\n') { + if let Some(path) = dirs.strip_prefix("programs: =") { + return self.which(prog, Some(OsStr::new(path))); + } + } + None + } + + fn windows_registry_find(&self, target: &str, tool: &str) -> Option { + self.windows_registry_find_tool(target, tool) + .map(|c| c.to_command()) + } + + fn windows_registry_find_tool(&self, target: &str, tool: &str) -> Option { + struct BuildEnvGetter<'s>(&'s Build); + + impl windows_registry::EnvGetter for BuildEnvGetter<'_> { + fn get_env(&self, name: &str) -> Option { + self.0.getenv(name).map(windows_registry::Env::Arced) + } + } + + windows_registry::find_tool_inner(target, tool, &BuildEnvGetter(self)) + } } impl Default for Build { @@ -3949,27 +4116,6 @@ fn android_clang_compiler_uses_target_arg_internally(clang_path: &Path) -> bool false } -#[test] -fn test_android_clang_compiler_uses_target_arg_internally() { - for version in 16..21 { - assert!(android_clang_compiler_uses_target_arg_internally( - &PathBuf::from(format!("armv7a-linux-androideabi{}-clang", version)) - )); - assert!(android_clang_compiler_uses_target_arg_internally( - &PathBuf::from(format!("armv7a-linux-androideabi{}-clang++", version)) - )); - } - assert!(!android_clang_compiler_uses_target_arg_internally( - &PathBuf::from("clang-i686-linux-android") - )); - assert!(!android_clang_compiler_uses_target_arg_internally( - &PathBuf::from("clang") - )); - assert!(!android_clang_compiler_uses_target_arg_internally( - &PathBuf::from("clang++") - )); -} - fn autodetect_android_compiler(target: &str, host: &str, gnu: &str, clang: &str) -> String { let new_clang_key = match target { "aarch64-linux-android" => Some("aarch64"), @@ -4038,50 +4184,6 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option< } } -fn which(tool: &Path, path_entries: Option) -> Option { - fn check_exe(exe: &mut PathBuf) -> bool { - let exe_ext = std::env::consts::EXE_EXTENSION; - exe.exists() || (!exe_ext.is_empty() && exe.set_extension(exe_ext) && exe.exists()) - } - - // If |tool| is not just one "word," assume it's an actual path... - if tool.components().count() > 1 { - let mut exe = PathBuf::from(tool); - return if check_exe(&mut exe) { Some(exe) } else { None }; - } - - // Loop through PATH entries searching for the |tool|. - let path_entries = path_entries.or(env::var_os("PATH"))?; - env::split_paths(&path_entries).find_map(|path_entry| { - let mut exe = path_entry.join(tool); - if check_exe(&mut exe) { - Some(exe) - } else { - None - } - }) -} - -// search for |prog| on 'programs' path in '|cc| -print-search-dirs' output -fn search_programs(cc: &mut Command, prog: &Path, cargo_output: &CargoOutput) -> Option { - let search_dirs = run_output( - cc.arg("-print-search-dirs"), - "cc", - // this doesn't concern the compilation so we always want to show warnings. - cargo_output, - ) - .ok()?; - // clang driver appears to be forcing UTF-8 output even on Windows, - // hence from_utf8 is assumed to be usable in all cases. - let search_dirs = std::str::from_utf8(&search_dirs).ok()?; - for dirs in search_dirs.split(|c| c == '\r' || c == '\n') { - if let Some(path) = dirs.strip_prefix("programs: =") { - return which(prog, Some(OsString::from(path))); - } - } - None -} - #[derive(Clone, Copy, PartialEq)] enum AsmFileExt { /// `.asm` files. On MSVC targets, we assume these should be passed to MASM @@ -4106,3 +4208,29 @@ impl AsmFileExt { None } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_android_clang_compiler_uses_target_arg_internally() { + for version in 16..21 { + assert!(android_clang_compiler_uses_target_arg_internally( + &PathBuf::from(format!("armv7a-linux-androideabi{}-clang", version)) + )); + assert!(android_clang_compiler_uses_target_arg_internally( + &PathBuf::from(format!("armv7a-linux-androideabi{}-clang++", version)) + )); + } + assert!(!android_clang_compiler_uses_target_arg_internally( + &PathBuf::from("clang-i686-linux-android") + )); + assert!(!android_clang_compiler_uses_target_arg_internally( + &PathBuf::from("clang") + )); + assert!(!android_clang_compiler_uses_target_arg_internally( + &PathBuf::from("clang++") + )); + } +} diff --git a/src/parallel/job_token.rs b/src/parallel/job_token.rs index 31dbfe83..da1ab737 100644 --- a/src/parallel/job_token.rs +++ b/src/parallel/job_token.rs @@ -186,7 +186,7 @@ mod inherited_jobserver { // Fallback to creating a help thread with blocking acquire let helper_thread = self .helper_thread - .get_or_try_init(|| HelperThread::new(&self.jobserver))?; + .get_or_try_init(|| HelperThread::new(self.jobserver))?; match helper_thread.rx.try_recv() { Ok(res) => { diff --git a/src/tempfile.rs b/src/tempfile.rs index ab0ac877..d9df2c22 100644 --- a/src/tempfile.rs +++ b/src/tempfile.rs @@ -69,8 +69,8 @@ impl NamedTempfile { &self.path } - pub(super) fn file(&self) -> &File { - self.file.as_ref().unwrap() + pub(super) fn take_file(&mut self) -> Option { + self.file.take() } } diff --git a/src/tool.rs b/src/tool.rs index ea0f36fa..72fc23b5 100644 --- a/src/tool.rs +++ b/src/tool.rs @@ -6,7 +6,7 @@ use std::{ io::Write, path::{Path, PathBuf}, process::{Command, Stdio}, - sync::Mutex, + sync::RwLock, }; use crate::{ @@ -40,7 +40,7 @@ pub struct Tool { impl Tool { pub(crate) fn new( path: PathBuf, - cached_compiler_family: &Mutex, ToolFamily>>, + cached_compiler_family: &RwLock, ToolFamily>>, cargo_output: &CargoOutput, out_dir: Option<&Path>, ) -> Self { @@ -57,7 +57,7 @@ impl Tool { pub(crate) fn with_clang_driver( path: PathBuf, clang_driver: Option<&str>, - cached_compiler_family: &Mutex, ToolFamily>>, + cached_compiler_family: &RwLock, ToolFamily>>, cargo_output: &CargoOutput, out_dir: Option<&Path>, ) -> Self { @@ -90,13 +90,13 @@ impl Tool { path: PathBuf, clang_driver: Option<&str>, cuda: bool, - cached_compiler_family: &Mutex, ToolFamily>>, + cached_compiler_family: &RwLock, ToolFamily>>, cargo_output: &CargoOutput, out_dir: Option<&Path>, ) -> Self { fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool { run_output( - Command::new(&path).arg("--version"), + Command::new(path).arg("--version"), path, // tool detection issues should always be shown as warnings cargo_output, @@ -122,7 +122,7 @@ impl Tool { .into(), })?; - let tmp = + let mut tmp = NamedTempfile::new(&out_dir, "detect_compiler_family.c").map_err(|err| Error { kind: ErrorKind::IOError, message: format!( @@ -132,8 +132,13 @@ impl Tool { ) .into(), })?; - tmp.file() - .write_all(include_bytes!("detect_compiler_family.c"))?; + let mut tmp_file = tmp.take_file().unwrap(); + tmp_file.write_all(include_bytes!("detect_compiler_family.c"))?; + // Close the file handle *now*, otherwise the compiler may fail to open it on Windows + // (#1082). The file stays on disk and its path remains valid until `tmp` is dropped. + tmp_file.flush()?; + tmp_file.sync_data()?; + drop(tmp_file); let stdout = run_output( Command::new(path).arg("-E").arg(tmp.path()), @@ -181,13 +186,13 @@ impl Tool { } } let detect_family = |path: &Path| -> Result { - if let Some(family) = cached_compiler_family.lock().unwrap().get(path) { + if let Some(family) = cached_compiler_family.read().unwrap().get(path) { return Ok(*family); } let family = detect_family_inner(path, cargo_output, out_dir)?; cached_compiler_family - .lock() + .write() .unwrap() .insert(path.into(), family); Ok(family) @@ -392,10 +397,7 @@ impl Tool { /// Whether the tool is MSVC-like. pub fn is_like_msvc(&self) -> bool { - match self.family { - ToolFamily::Msvc { .. } => true, - _ => false, - } + matches!(self.family, ToolFamily::Msvc { .. }) } } diff --git a/src/utilities.rs b/src/utilities.rs new file mode 100644 index 00000000..073e617b --- /dev/null +++ b/src/utilities.rs @@ -0,0 +1,45 @@ +use std::{ + ffi::OsStr, + fmt::{self, Write}, + path::Path, +}; + +pub(super) struct JoinOsStrs<'a, T> { + pub(super) slice: &'a [T], + pub(super) delimiter: char, +} + +impl fmt::Display for JoinOsStrs<'_, T> +where + T: AsRef, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let len = self.slice.len(); + for (index, os_str) in self.slice.iter().enumerate() { + // TODO: Use OsStr::display once it is stablised, + // Path and OsStr has the same `Display` impl + write!(f, "{}", Path::new(os_str).display())?; + if index + 1 < len { + f.write_char(self.delimiter)?; + } + } + Ok(()) + } +} + +pub(super) struct OptionOsStrDisplay(pub(super) Option); + +impl fmt::Display for OptionOsStrDisplay +where + T: AsRef, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Use OsStr::display once it is stablised + // Path and OsStr has the same `Display` impl + if let Some(os_str) = self.0.as_ref() { + write!(f, "Some({})", Path::new(os_str).display()) + } else { + f.write_str("None") + } + } +} diff --git a/src/windows/find_tools.rs b/src/windows/find_tools.rs index c4a92bde..a7be3c32 100644 --- a/src/windows/find_tools.rs +++ b/src/windows/find_tools.rs @@ -14,7 +14,14 @@ #![allow(clippy::upper_case_acronyms)] -use std::process::Command; +use std::{ + env, + ffi::{OsStr, OsString}, + ops::Deref, + path::PathBuf, + process::Command, + sync::Arc, +}; use crate::Tool; use crate::ToolFamily; @@ -36,6 +43,50 @@ impl<'a> From> for &'a str { } } +pub(crate) enum Env { + Owned(OsString), + Arced(Arc), +} + +impl AsRef for Env { + fn as_ref(&self) -> &OsStr { + self.deref() + } +} + +impl Deref for Env { + type Target = OsStr; + + fn deref(&self) -> &Self::Target { + match self { + Env::Owned(os_str) => os_str, + Env::Arced(os_str) => os_str, + } + } +} + +impl From for PathBuf { + fn from(env: Env) -> Self { + match env { + Env::Owned(os_str) => PathBuf::from(os_str), + Env::Arced(os_str) => PathBuf::from(os_str.deref()), + } + } +} + +pub(crate) trait EnvGetter { + fn get_env(&self, name: &'static str) -> Option; +} + +struct StdEnvGetter; + +impl EnvGetter for StdEnvGetter { + #[allow(clippy::disallowed_methods)] + fn get_env(&self, name: &'static str) -> Option { + env::var_os(name).map(Env::Owned) + } +} + /// Attempts to find a tool within an MSVC installation using the Windows /// registry as a point to search from. /// @@ -56,6 +107,14 @@ pub fn find(target: &str, tool: &str) -> Option { /// operation (finding a MSVC tool in a local install) but instead returns a /// `Tool` which may be introspected. pub fn find_tool(target: &str, tool: &str) -> Option { + find_tool_inner(target, tool, &StdEnvGetter) +} + +pub(crate) fn find_tool_inner( + target: &str, + tool: &str, + env_getter: &dyn EnvGetter, +) -> Option { // This logic is all tailored for MSVC, if we're not that then bail out // early. if !target.contains("msvc") { @@ -68,13 +127,13 @@ pub fn find_tool(target: &str, tool: &str) -> Option { // Looks like msbuild isn't located in the same location as other tools like // cl.exe and lib.exe. if tool.contains("msbuild") { - return impl_::find_msbuild(target); + return impl_::find_msbuild(target, env_getter); } // Looks like devenv isn't located in the same location as other tools like // cl.exe and lib.exe. if tool.contains("devenv") { - return impl_::find_devenv(target); + return impl_::find_devenv(target, env_getter); } // Ok, if we're here, now comes the fun part of the probing. Default shells @@ -84,10 +143,10 @@ pub fn find_tool(target: &str, tool: &str) -> Option { // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that // the tool is actually usable. - impl_::find_msvc_environment(tool, target) - .or_else(|| impl_::find_msvc_15plus(tool, target)) - .or_else(|| impl_::find_msvc_14(tool, target)) - .or_else(|| impl_::find_msvc_12(tool, target)) + impl_::find_msvc_environment(tool, target, env_getter) + .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter)) + .or_else(|| impl_::find_msvc_14(tool, target, env_getter)) + .or_else(|| impl_::find_msvc_12(tool, target, env_getter)) } /// A version of Visual Studio @@ -110,7 +169,12 @@ pub enum VsVers { /// /// This is used by the cmake crate to figure out the correct /// generator. +#[allow(clippy::disallowed_methods)] pub fn find_vs_version() -> Result { + fn has_msbuild_version(version: &str) -> bool { + impl_::has_msbuild_version(version, &StdEnvGetter) + } + match std::env::var("VisualStudioVersion") { Ok(version) => match &version[..] { "17.0" => Ok(VsVers::Vs17), @@ -131,25 +195,24 @@ pub fn find_vs_version() -> Result { _ => { // Check for the presence of a specific registry key // that indicates visual studio is installed. - if impl_::has_msbuild_version("17.0") { + if has_msbuild_version("17.0") { Ok(VsVers::Vs17) - } else if impl_::has_msbuild_version("16.0") { + } else if has_msbuild_version("16.0") { Ok(VsVers::Vs16) - } else if impl_::has_msbuild_version("15.0") { + } else if has_msbuild_version("15.0") { Ok(VsVers::Vs15) - } else if impl_::has_msbuild_version("14.0") { + } else if has_msbuild_version("14.0") { Ok(VsVers::Vs14) - } else if impl_::has_msbuild_version("12.0") { + } else if has_msbuild_version("12.0") { Ok(VsVers::Vs12) } else { - Err(format!( - "\n\n\ + Err("\n\n\ couldn't determine visual studio generator\n\ if VisualStudio is installed, however, consider \ running the appropriate vcvars script before building \ this crate\n\ " - )) + .to_string()) } } } @@ -163,7 +226,7 @@ mod impl_ { use crate::windows::setup_config::SetupConfiguration; use crate::windows::vs_instances::{VsInstances, VswhereInstance}; use crate::windows::windows_sys::{ - FreeLibrary, GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, + GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK, }; use std::convert::TryFrom; @@ -179,7 +242,7 @@ mod impl_ { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Once; - use super::{TargetArch, MSVC_FAMILY}; + use super::{EnvGetter, TargetArch, MSVC_FAMILY}; use crate::Tool; struct MsvcTool { @@ -211,12 +274,6 @@ mod impl_ { } } - impl Drop for LibraryHandle { - fn drop(&mut self) { - unsafe { FreeLibrary(self.0) }; - } - } - type GetMachineTypeAttributesFuncType = unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32; const _: () = { @@ -268,7 +325,7 @@ mod impl_ { } } - fn into_tool(self) -> Tool { + fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool { let MsvcTool { tool, libs, @@ -276,17 +333,17 @@ mod impl_ { include, } = self; let mut tool = Tool::with_family(tool, MSVC_FAMILY); - add_env(&mut tool, "LIB", libs); - add_env(&mut tool, "PATH", path); - add_env(&mut tool, "INCLUDE", include); + add_env(&mut tool, "LIB", libs, env_getter); + add_env(&mut tool, "PATH", path, env_getter); + add_env(&mut tool, "INCLUDE", include, env_getter); tool } } /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the /// given target's arch. Returns `None` if the variable does not exist. - fn is_vscmd_target(target: TargetArch<'_>) -> Option { - let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?; + fn is_vscmd_target(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH")?; // Convert the Rust target arch to its VS arch equivalent. let arch = match target.into() { "x86_64" => "x64", @@ -296,25 +353,28 @@ mod impl_ { // An unrecognized arch. _ => return Some(false), }; - Some(vscmd_arch == arch) + Some(vscmd_arch.as_ref() == arch) } /// Attempt to find the tool using environment variables set by vcvars. - pub(super) fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option { + pub(super) fn find_msvc_environment( + tool: &str, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { // Early return if the environment doesn't contain a VC install. - if env::var_os("VCINSTALLDIR").is_none() { - return None; - } - let vs_install_dir = env::var_os("VSINSTALLDIR")?.into(); + env_getter.get_env("VCINSTALLDIR")?; + let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR")?.into(); // If the vscmd target differs from the requested target then // attempt to get the tool using the VS install directory. - if is_vscmd_target(target) == Some(false) { + if is_vscmd_target(target, env_getter) == Some(false) { // We will only get here with versions 15+. - tool_from_vs15plus_instance(tool, target, &vs_install_dir) + tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter) } else { // Fallback to simply using the current environment. - env::var_os("PATH") + env_getter + .get_env("PATH") .and_then(|path| { env::split_paths(&path) .map(|p| p.join(tool)) @@ -324,25 +384,26 @@ mod impl_ { } } - fn find_msbuild_vs17(target: TargetArch<'_>) -> Option { - find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17") + fn find_msbuild_vs17(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17", env_getter) } #[allow(bare_trait_objects)] fn vs16plus_instances( target: TargetArch<'_>, version: &'static str, + env_getter: &dyn EnvGetter, ) -> Box> { - let instances = if let Some(instances) = vs15plus_instances(target) { + let instances = if let Some(instances) = vs15plus_instances(target, env_getter) { instances } else { return Box::new(iter::empty()); }; Box::new(instances.into_iter().filter_map(move |instance| { let installation_name = instance.installation_name()?; - if installation_name.starts_with(&format!("VisualStudio/{}.", version)) { - Some(instance.installation_path()?) - } else if installation_name.starts_with(&format!("VisualStudioPreview/{}.", version)) { + if installation_name.starts_with(&format!("VisualStudio/{}.", version)) + || installation_name.starts_with(&format!("VisualStudioPreview/{}.", version)) + { Some(instance.installation_path()?) } else { None @@ -354,8 +415,9 @@ mod impl_ { tool: &str, target: TargetArch<'_>, version: &'static str, + env_getter: &dyn EnvGetter, ) -> Option { - vs16plus_instances(target, version) + vs16plus_instances(target, version, env_getter) .filter_map(|path| { let path = path.join(tool); if !path.is_file() { @@ -373,8 +435,8 @@ mod impl_ { .next() } - fn find_msbuild_vs16(target: TargetArch<'_>) -> Option { - find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16") + fn find_msbuild_vs16(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16", env_getter) } // In MSVC 15 (2017) MS once again changed the scheme for locating @@ -389,8 +451,12 @@ mod impl_ { // // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. // Hence, as the last resort we try to use vswhere.exe to list available instances. - fn vs15plus_instances(target: TargetArch<'_>) -> Option { - vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target)) + fn vs15plus_instances( + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { + vs15plus_instances_using_com() + .or_else(|| vs15plus_instances_using_vswhere(target, env_getter)) } fn vs15plus_instances_using_com() -> Option { @@ -402,11 +468,15 @@ mod impl_ { Some(VsInstances::ComBased(enum_setup_instances)) } - fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option { - let program_files_path: PathBuf = env::var("ProgramFiles(x86)") - .or_else(|_| env::var("ProgramFiles")) - .ok()? - .into(); + fn vs15plus_instances_using_vswhere( + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { + let program_files_path = env_getter + .get_env("ProgramFiles(x86)") + .or_else(|| env_getter.get_env("ProgramFiles"))?; + + let program_files_path = Path::new(program_files_path.as_ref()); let vswhere_path = program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe"); @@ -423,7 +493,7 @@ mod impl_ { }; let vswhere_output = Command::new(vswhere_path) - .args(&[ + .args([ "-latest", "-products", "*", @@ -452,13 +522,17 @@ mod impl_ { .collect() } - pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option { - let iter = vs15plus_instances(target)?; + pub(super) fn find_msvc_15plus( + tool: &str, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { + let iter = vs15plus_instances(target, env_getter)?; iter.into_iter() .filter_map(|instance| { let version = parse_version(&instance.installation_version()?)?; let instance_path = instance.installation_path()?; - let tool = tool_from_vs15plus_instance(tool, target, &instance_path)?; + let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?; Some((version, tool)) }) .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version)) @@ -472,8 +546,12 @@ mod impl_ { // we keep the registry method as a fallback option. // // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 - fn find_tool_in_vs15_path(tool: &str, target: TargetArch<'_>) -> Option { - let mut path = match vs15plus_instances(target) { + fn find_tool_in_vs15_path( + tool: &str, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { + let mut path = match vs15plus_instances(target, env_getter) { Some(instances) => instances .into_iter() .filter_map(|instance| instance.installation_path()) @@ -506,10 +584,11 @@ mod impl_ { fn tool_from_vs15plus_instance( tool: &str, target: TargetArch<'_>, - instance_path: &PathBuf, + instance_path: &Path, + env_getter: &dyn EnvGetter, ) -> Option { 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, env_getter)?; let tool_path = bin_path.join(tool); if !tool_path.exists() { return None; @@ -529,14 +608,15 @@ mod impl_ { tool.include.push(atl_include_path); } - add_sdks(&mut tool, target)?; + add_sdks(&mut tool, target, env_getter)?; - Some(tool.into_tool()) + Some(tool.into_tool(env_getter)) } fn vs15plus_vc_paths( target: TargetArch<'_>, instance_path: &Path, + env_getter: &dyn EnvGetter, ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option, PathBuf)> { let version = vs15plus_vc_read_version(instance_path)?; @@ -575,7 +655,7 @@ mod impl_ { // architecture, because it contains dlls like mspdb140.dll compiled for // the host architecture. let host_dylib_path = host_path.join(host.to_lowercase()); - let lib_fragment = if use_spectre_mitigated_libs() { + let lib_fragment = if use_spectre_mitigated_libs(env_getter) { r"lib\spectre" } else { "lib" @@ -630,8 +710,11 @@ mod impl_ { Some(version) } - fn use_spectre_mitigated_libs() -> bool { - env::var("VSCMD_ARG_VCVARS_SPECTRE").as_deref() == Ok("spectre") + fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool { + env_getter + .get_env("VSCMD_ARG_VCVARS_SPECTRE") + .map(|env| env.as_ref() == "spectre") + .unwrap_or_default() } fn atl_paths(target: TargetArch<'_>, path: &Path) -> Option<(PathBuf, PathBuf)> { @@ -646,14 +729,22 @@ mod impl_ { // For MSVC 14 we need to find the Universal CRT as well as either // the Windows 10 SDK or Windows 8.1 SDK. - pub(super) fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option { + pub(super) fn find_msvc_14( + tool: &str, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { let vcdir = get_vc_dir("14.0")?; let mut tool = get_tool(tool, &vcdir, target)?; - add_sdks(&mut tool, target)?; - Some(tool.into_tool()) + add_sdks(&mut tool, target, env_getter)?; + Some(tool.into_tool(env_getter)) } - fn add_sdks(tool: &mut MsvcTool, target: TargetArch<'_>) -> Option<()> { + fn add_sdks( + tool: &mut MsvcTool, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option<()> { let sub = lib_subdir(target)?; let (ucrt, ucrt_version) = get_ucrt_dir()?; @@ -673,7 +764,7 @@ mod impl_ { let ucrt_lib = ucrt.join("lib").join(&ucrt_version); tool.libs.push(ucrt_lib.join("ucrt").join(sub)); - if let Some((sdk, version)) = get_sdk10_dir() { + if let Some((sdk, version)) = get_sdk10_dir(env_getter) { tool.path.push(sdk.join("bin").join(host)); let sdk_lib = sdk.join("lib").join(&version); tool.libs.push(sdk_lib.join("um").join(sub)); @@ -696,7 +787,11 @@ mod impl_ { } // For MSVC 12 we need to find the Windows 8.1 SDK. - pub(super) fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option { + pub(super) fn find_msvc_12( + tool: &str, + target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { let vcdir = get_vc_dir("12.0")?; let mut tool = get_tool(tool, &vcdir, target)?; let sub = lib_subdir(target)?; @@ -708,11 +803,17 @@ mod impl_ { tool.include.push(sdk_include.join("shared")); tool.include.push(sdk_include.join("um")); tool.include.push(sdk_include.join("winrt")); - Some(tool.into_tool()) + Some(tool.into_tool(env_getter)) } - fn add_env(tool: &mut Tool, env: &str, paths: Vec) { - let prev = env::var_os(env).unwrap_or(OsString::new()); + fn add_env( + tool: &mut Tool, + env: &'static str, + paths: Vec, + env_getter: &dyn EnvGetter, + ) { + let prev = env_getter.get_env(env); + let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default(); let prev = env::split_paths(&prev); let new = paths.into_iter().chain(prev); tool.env @@ -797,10 +898,18 @@ mod impl_ { // Before doing that, we check the "WindowsSdkDir" and "WindowsSDKVersion" // environment variables set by vcvars to use the environment sdk version // if one is already configured. - fn get_sdk10_dir() -> Option<(PathBuf, String)> { - if let (Ok(root), Ok(version)) = (env::var("WindowsSdkDir"), env::var("WindowsSDKVersion")) - { - return Some((root.into(), version.trim_end_matches('\\').to_string())); + fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> { + if let (Some(root), Some(version)) = ( + env_getter.get_env("WindowsSdkDir"), + env_getter + .get_env("WindowsSDKVersion") + .as_ref() + .and_then(|version| version.as_ref().to_str()), + ) { + return Some(( + PathBuf::from(root), + version.trim_end_matches('\\').to_string(), + )); } let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0"; @@ -815,8 +924,7 @@ mod impl_ { let dir = dirs .into_iter() .rev() - .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file()) - .next()?; + .find(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())?; let version = dir.components().last().unwrap(); let version = version.as_os_str().to_str().unwrap().to_string(); Some((root.into(), version)) @@ -928,7 +1036,7 @@ mod impl_ { for subkey in key.iter().filter_map(|k| k.ok()) { let val = subkey .to_str() - .and_then(|s| s.trim_start_matches("v").replace('.', "").parse().ok()); + .and_then(|s| s.trim_start_matches('v').replace('.', "").parse().ok()); let val = match val { Some(s) => s, None => continue, @@ -943,22 +1051,23 @@ mod impl_ { max_key } - pub(super) fn has_msbuild_version(version: &str) -> bool { + #[inline(always)] + pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool { match version { "17.0" => { - find_msbuild_vs17(TargetArch("x86_64")).is_some() - || find_msbuild_vs17(TargetArch("i686")).is_some() - || find_msbuild_vs17(TargetArch("aarch64")).is_some() + find_msbuild_vs17(TargetArch("x86_64"), env_getter).is_some() + || find_msbuild_vs17(TargetArch("i686"), env_getter).is_some() + || find_msbuild_vs17(TargetArch("aarch64"), env_getter).is_some() } "16.0" => { - find_msbuild_vs16(TargetArch("x86_64")).is_some() - || find_msbuild_vs16(TargetArch("i686")).is_some() - || find_msbuild_vs16(TargetArch("aarch64")).is_some() + find_msbuild_vs16(TargetArch("x86_64"), env_getter).is_some() + || find_msbuild_vs16(TargetArch("i686"), env_getter).is_some() + || find_msbuild_vs16(TargetArch("aarch64"), env_getter).is_some() } "15.0" => { - find_msbuild_vs15(TargetArch("x86_64")).is_some() - || find_msbuild_vs15(TargetArch("i686")).is_some() - || find_msbuild_vs15(TargetArch("aarch64")).is_some() + find_msbuild_vs15(TargetArch("x86_64"), env_getter).is_some() + || find_msbuild_vs15(TargetArch("i686"), env_getter).is_some() + || find_msbuild_vs15(TargetArch("aarch64"), env_getter).is_some() } "12.0" | "14.0" => LOCAL_MACHINE .open(&OsString::from(format!( @@ -970,30 +1079,30 @@ mod impl_ { } } - pub(super) fn find_devenv(target: TargetArch<'_>) -> Option { - find_devenv_vs15(target) + pub(super) fn find_devenv(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + find_devenv_vs15(target, env_getter) } - fn find_devenv_vs15(target: TargetArch<'_>) -> Option { - find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target) + fn find_devenv_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target, env_getter) } // see http://stackoverflow.com/questions/328017/path-to-msbuild - pub(super) fn find_msbuild(target: TargetArch<'_>) -> Option { + pub(super) fn find_msbuild(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { // VS 15 (2017) changed how to locate msbuild - if let Some(r) = find_msbuild_vs17(target) { + if let Some(r) = find_msbuild_vs17(target, env_getter) { Some(r) - } else if let Some(r) = find_msbuild_vs16(target) { + } else if let Some(r) = find_msbuild_vs16(target, env_getter) { return Some(r); - } else if let Some(r) = find_msbuild_vs15(target) { + } else if let Some(r) = find_msbuild_vs15(target, env_getter) { return Some(r); } else { find_old_msbuild(target) } } - fn find_msbuild_vs15(target: TargetArch<'_>) -> Option { - find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target) + fn find_msbuild_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option { + find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter) } fn find_old_msbuild(target: TargetArch<'_>) -> Option { @@ -1019,60 +1128,88 @@ mod impl_ { /// Non-Windows Implementation. #[cfg(not(windows))] mod impl_ { - use std::{env, ffi::OsString}; + use std::{env, ffi::OsStr}; - use super::{TargetArch, MSVC_FAMILY}; + use super::{EnvGetter, 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 { + #[inline(always)] + pub(super) fn find_msbuild(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option { 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 { + #[inline(always)] + pub(super) fn find_devenv(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option { None } /// Attempt to find the tool using environment variables set by vcvars. - pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option { + pub(super) fn find_msvc_environment( + tool: &str, + _target: TargetArch<'_>, + env_getter: &dyn EnvGetter, + ) -> Option { // 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 vc_install_dir = env_getter.get_env("VCINSTALLDIR")?; + let vs_install_dir = env_getter.get_env("VSINSTALLDIR")?; - let get_tool = |install_dir: OsString| { - env::split_paths(&install_dir) + let get_tool = |install_dir: &OsStr| { + env::split_paths(install_dir) .map(|p| p.join(tool)) .find(|p| p.exists()) - .map(|path| Tool::with_family(path.into(), MSVC_FAMILY)) + .map(|path| Tool::with_family(path, MSVC_FAMILY)) }; // Take the path of tool for the vc install directory. - get_tool(vc_install_dir) + get_tool(vc_install_dir.as_ref()) // Take the path of tool for the vs install directory. - .or_else(|| get_tool(vs_install_dir)) + .or_else(|| get_tool(vs_install_dir.as_ref())) // Take the path of tool for the current path environment. - .or_else(|| env::var_os("PATH").and_then(|path| get_tool(path))) + .or_else(|| { + env_getter + .get_env("PATH") + .as_ref() + .map(|path| path.as_ref()) + .and_then(get_tool) + }) } - pub(super) fn find_msvc_15plus(_tool: &str, _target: TargetArch<'_>) -> Option { + #[inline(always)] + pub(super) fn find_msvc_15plus( + _tool: &str, + _target: TargetArch<'_>, + _: &dyn EnvGetter, + ) -> Option { 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 { + #[inline(always)] + pub(super) fn find_msvc_14( + _tool: &str, + _target: TargetArch<'_>, + _: &dyn EnvGetter, + ) -> Option { None } // For MSVC 12 we need to find the Windows 8.1 SDK. - pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option { + #[inline(always)] + pub(super) fn find_msvc_12( + _tool: &str, + _target: TargetArch<'_>, + _: &dyn EnvGetter, + ) -> Option { None } - pub(super) fn has_msbuild_version(version: &str) -> bool { + #[inline(always)] + pub(super) fn has_msbuild_version(version: &str, _: &dyn EnvGetter) -> bool { match version { "17.0" => false, "16.0" => false, diff --git a/src/windows/vs_instances.rs b/src/windows/vs_instances.rs index e863dada..3e6eeed9 100644 --- a/src/windows/vs_instances.rs +++ b/src/windows/vs_instances.rs @@ -73,7 +73,7 @@ impl TryFrom<&Vec> for VswhereInstance { fn try_from(output: &Vec) -> Result { let map: HashMap<_, _> = output .lines() - .filter_map(Result::ok) + .map_while(Result::ok) .filter_map(|s| { let mut splitn = s.splitn(2, ": "); Some((splitn.next()?.to_owned(), splitn.next()?.to_owned())) diff --git a/src/windows/windows_sys.rs b/src/windows/windows_sys.rs index 56b54424..80d23ebf 100644 --- a/src/windows/windows_sys.rs +++ b/src/windows/windows_sys.rs @@ -6,7 +6,7 @@ // cd generate-windows-sys/ // cargo run // ``` -// Bindings generated by `windows-bindgen` 0.55.0 +// Bindings generated by `windows-bindgen` 0.57.0 #![allow( non_snake_case, @@ -133,31 +133,21 @@ pub const ERROR_SUCCESS: WIN32_ERROR = 0u32; pub const FALSE: BOOL = 0i32; pub type FARPROC = Option isize>; #[repr(C)] +#[derive(Clone, Copy)] pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } -impl Copy for FILETIME {} -impl Clone for FILETIME { - fn clone(&self) -> Self { - *self - } -} pub const FILE_ATTRIBUTE_TEMPORARY: FILE_FLAGS_AND_ATTRIBUTES = 256u32; pub type FILE_FLAGS_AND_ATTRIBUTES = u32; #[repr(C)] +#[derive(Clone, Copy)] pub struct GUID { pub data1: u32, pub data2: u16, pub data3: u16, pub data4: [u8; 8], } -impl Copy for GUID {} -impl Clone for GUID { - fn clone(&self) -> Self { - *self - } -} impl GUID { pub const fn from_u128(uuid: u128) -> Self { Self { @@ -185,6 +175,7 @@ pub type REG_SAM_FLAGS = u32; pub const REG_SZ: REG_VALUE_TYPE = 1u32; pub type REG_VALUE_TYPE = u32; #[repr(C)] +#[derive(Clone, Copy)] pub struct SAFEARRAY { pub cDims: u16, pub fFeatures: ADVANCED_FEATURE_FLAGS, @@ -193,23 +184,12 @@ pub struct SAFEARRAY { pub pvData: *mut core::ffi::c_void, pub rgsabound: [SAFEARRAYBOUND; 1], } -impl Copy for SAFEARRAY {} -impl Clone for SAFEARRAY { - fn clone(&self) -> Self { - *self - } -} #[repr(C)] +#[derive(Clone, Copy)] pub struct SAFEARRAYBOUND { pub cElements: u32, pub lLbound: i32, } -impl Copy for SAFEARRAYBOUND {} -impl 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 _; diff --git a/tests/cc_env.rs b/tests/cc_env.rs index 43eb689f..ced77ca9 100644 --- a/tests/cc_env.rs +++ b/tests/cc_env.rs @@ -55,22 +55,16 @@ fn ccache_env_flags() { 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 - ); + assert!(!compiler + .cflags_env() + .into_string() + .unwrap() + .contains("ccache")); + assert!(!compiler + .cflags_env() + .into_string() + .unwrap() + .contains(" lol-this-is-not-a-compiler")); env::set_var("CC", ""); } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 5b9f3b5c..5e87001e 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +#![allow(clippy::disallowed_methods)] use std::env; use std::ffi::{OsStr, OsString}; @@ -7,7 +8,6 @@ use std::io; use std::io::prelude::*; use std::path::{Path, PathBuf}; -use cc; use tempfile::{Builder, TempDir}; pub struct Test { @@ -86,12 +86,10 @@ impl Test { let mut cfg = cc::Build::new(); let target = if self.msvc { "x86_64-pc-windows-msvc" + } else if cfg!(target_os = "macos") { + "x86_64-apple-darwin" } else { - if cfg!(target_os = "macos") { - "x86_64-apple-darwin" - } else { - "x86_64-unknown-linux-gnu" - } + "x86_64-unknown-linux-gnu" }; cfg.target(target) diff --git a/tests/test.rs b/tests/test.rs index 8fc8da64..4c070889 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,3 +1,5 @@ +#![allow(clippy::disallowed_methods)] + use crate::support::Test; mod support; @@ -575,8 +577,8 @@ fn clang_apple_tvos() { let test = Test::clang(); test.gcc() .__set_env("TVOS_DEPLOYMENT_TARGET", "9.0") - .target(&target) - .host(&target) + .target(target) + .host(target) .file("foo.c") .compile("foo"); @@ -628,8 +630,8 @@ fn clang_apple_tvsimulator() { let test = Test::clang(); test.gcc() .__set_env("TVOS_DEPLOYMENT_TARGET", "9.0") - .target(&target) - .host(&target) + .target(target) + .host(target) .file("foo.c") .compile("foo");