Repository: rust-lang/cc-rs
Pull request #1105 (2024-06-24 18:46:03)

Fix invalid wasi targets compatibility
Thanks for your help following [Fix WASI compilation for C++](https://github.com/rust-lang/cc-rs/pull/1083), I have tested it on my side from main and spotted some issues with incompatible targets, took some time to clean this aswell and ensure every targets are clearly defined to avoid potential issues with no-exceptions flags


Patch:
diff --git a/.github/workflows/regenerate-target-info.yml b/.github/workflows/regenerate-target-info.yml
index a9334a5..9d30a4a 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
index df6695c..8fabbe3 100644
--- a/.github/workflows/regenerate-windows-sys.yml
+++ b/.github/workflows/regenerate-windows-sys.yml
@@ -18,7 +18,13 @@ jobs:
         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
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1eb9dc..4d952de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [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
diff --git a/Cargo.toml b/Cargo.toml
index 1741214..51c29dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cc"
-version = "1.0.100"
+version = "1.0.101"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/rust-lang/cc-rs"
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..7f7ae21
--- /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/src/bin/gcc-shim.rs b/src/bin/gcc-shim.rs
index f0d219f..da7a8b3 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 e9bd27a..4296d77 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,
diff --git a/src/lib.rs b/src/lib.rs
index dbeb446..042ee79 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -209,6 +209,7 @@
 #![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;
@@ -221,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, RwLock};
+use std::sync::{Arc, RwLock};
 
 #[cfg(feature = "parallel")]
 mod parallel;
@@ -249,6 +250,8 @@ struct CompilerFlag {
     flag: Box<OsStr>,
 }
 
+type Env = Option<Arc<OsStr>>;
+
 /// 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
@@ -261,7 +264,7 @@ pub struct Build {
     objects: Vec<Arc<Path>>,
     flags: Vec<Arc<OsStr>>,
     flags_supported: Vec<Arc<OsStr>>,
-    known_flag_support_status_cache: Arc<Mutex<HashMap<CompilerFlag, bool>>>,
+    known_flag_support_status_cache: Arc<RwLock<HashMap<CompilerFlag, bool>>>,
     ar_flags: Vec<Arc<OsStr>>,
     asm_flags: Vec<Arc<OsStr>>,
     no_default_flags: bool,
@@ -293,11 +296,11 @@ pub struct Build {
     warnings_into_errors: bool,
     warnings: Option<bool>,
     extra_warnings: Option<bool>,
-    env_cache: Arc<RwLock<HashMap<Box<str>, Option<Arc<OsStr>>>>>,
-    apple_sdk_root_cache: Arc<Mutex<HashMap<String, OsString>>>,
-    apple_versions_cache: Arc<Mutex<HashMap<String, String>>>,
+    env_cache: Arc<RwLock<HashMap<Box<str>, Env>>>,
+    apple_sdk_root_cache: Arc<RwLock<HashMap<Box<str>, Arc<OsStr>>>>,
+    apple_versions_cache: Arc<RwLock<HashMap<Box<str>, Arc<str>>>>,
     emit_rerun_if_env_changed: bool,
-    cached_compiler_family: Arc<Mutex<HashMap<Box<Path>, ToolFamily>>>,
+    cached_compiler_family: Arc<RwLock<HashMap<Box<Path>, ToolFamily>>>,
 }
 
 /// Represents the types of errors that may occur while using cc-rs.
@@ -385,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,
@@ -418,8 +421,8 @@ impl Build {
             extra_warnings: None,
             warnings_into_errors: false,
             env_cache: Arc::new(RwLock::new(HashMap::new())),
-            apple_sdk_root_cache: Arc::new(Mutex::new(HashMap::new())),
-            apple_versions_cache: Arc::new(Mutex::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(),
         }
@@ -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);
 
@@ -1037,7 +1040,7 @@ impl Build {
         cpp_set_stdlib: V,
     ) -> &mut Build {
         let cpp_set_stdlib = cpp_set_stdlib.into().map(Arc::from);
-        self.cpp_set_stdlib = cpp_set_stdlib.clone();
+        self.cpp_set_stdlib.clone_from(&cpp_set_stdlib);
         self.cpp_link_stdlib = Some(cpp_set_stdlib);
         self
     }
@@ -1288,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()
@@ -1334,11 +1338,12 @@ impl Build {
                     .print_metadata(&format_args!("cargo:rustc-link-lib={}", stdlib.display()));
             }
             // Link c++ lib from WASI sysroot
-            if self.get_target()?.contains("wasi") {
+            if Build::is_wasi_target(target.as_ref()) {
                 let wasi_sysroot = self.wasi_sysroot()?;
                 self.cargo_output.print_metadata(&format_args!(
-                    "cargo:rustc-flags=-L {}/lib/wasm32-wasi -lstatic=c++ -lstatic=c++abi",
-                    Path::new(&wasi_sysroot).display()
+                    "cargo:rustc-flags=-L {}/lib/{} -lstatic=c++ -lstatic=c++abi",
+                    Path::new(&wasi_sysroot).display(),
+                    target
                 ));
             }
         }
@@ -1348,7 +1353,7 @@ impl Build {
             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.
@@ -1356,7 +1361,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");
@@ -1940,7 +1945,7 @@ impl Build {
                         cmd.push_cc_arg("-fno-plt".into());
                     }
                 }
-                if target == "wasm32-wasip1" {
+                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());
@@ -2432,7 +2437,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);
@@ -2736,13 +2743,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
@@ -2812,7 +2819,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<Tool> = self
             .env_tool(env)
@@ -2835,7 +2842,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());
@@ -2888,10 +2895,7 @@ 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"
-                {
+                } else if Build::is_wasi_target(target) {
                     if self.cpp {
                         "clang++".to_string()
                     } else {
@@ -2926,8 +2930,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
             }
@@ -3026,25 +3030,25 @@ impl Build {
     }
 
     /// Returns a fallback `cc_compiler_wrapper` by introspecting `RUSTC_WRAPPER`
-    fn rustc_wrapper_fallback() -> Option<String> {
+    fn rustc_wrapper_fallback(&self) -> Option<Arc<OsStr>> {
         // 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: &[&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<String>, Vec<String>)> {
+    fn env_tool(&self, name: &str) -> Option<(PathBuf, Option<Arc<OsStr>>, Vec<String>)> {
         let tool = self.getenv_with_target_prefixes(name).ok()?;
         let tool = tool.to_string_lossy();
         let tool = tool.trim();
@@ -3059,7 +3063,7 @@ impl Build {
         if Path::new(tool).exists() {
             return Some((
                 PathBuf::from(tool),
-                Self::rustc_wrapper_fallback(),
+                self.rustc_wrapper_fallback(),
                 Vec::new(),
             ));
         }
@@ -3092,16 +3096,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::<OsStr>::from(OsStr::new(&maybe_wrapper))),
                     parts.map(|s| s.to_string()).collect(),
                 ));
             }
@@ -3109,7 +3109,7 @@ impl Build {
 
         Some((
             maybe_wrapper.into(),
-            Self::rustc_wrapper_fallback(),
+            self.rustc_wrapper_fallback(),
             parts.map(|s| s.to_string()).collect(),
         ))
     }
@@ -3284,8 +3284,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
                     }
@@ -3319,10 +3323,10 @@ 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) {
+                            if let Some(llvm_lib) = self.which(&cmd, None) {
                                 llvm_lib.to_str().unwrap().clone_into(&mut lib);
                             }
                         }
@@ -3330,7 +3334,7 @@ impl Build {
 
                     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"),
                         };
@@ -3386,142 +3390,143 @@ impl Build {
     }
 
     fn prefix_for_target(&self, target: &str) -> Option<Cow<'static, str>> {
-        // 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.to_string_lossy()
-                .strip_suffix("-gcc")
-                .map(str::to_string)
-                .map(Cow::Owned)
-        });
         // CROSS_COMPILE is of the form: "arm-linux-gnueabi-"
-        let cc_env = self.getenv("CROSS_COMPILE");
-        let cross_compile = cc_env
+        self.getenv("CROSS_COMPILE")
             .as_deref()
             .map(|s| s.to_string_lossy().trim_end_matches('-').to_owned())
-            .map(Cow::Owned);
-        cross_compile.or(linker_prefix).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)
-        })
+            .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
@@ -3534,7 +3539,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| {
@@ -3555,16 +3560,6 @@ impl Build {
             .or_else(|| prefixes.first().copied())
     }
 
-    fn getenv_unwrap_str(&self, v: &str) -> Result<String, Error> {
-        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 get_target(&self) -> Result<Cow<'_, str>, Error> {
         match &self.target {
             Some(t) => Ok(Cow::Borrowed(t)),
@@ -3619,7 +3614,9 @@ impl Build {
     fn get_out_dir(&self) -> Result<Cow<'_, Path>, 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(|| {
@@ -3631,6 +3628,7 @@ impl Build {
         }
     }
 
+    #[allow(clippy::disallowed_methods)]
     fn getenv(&self, v: &str) -> Option<Arc<OsStr>> {
         // 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
@@ -3673,6 +3671,16 @@ impl Build {
         }
     }
 
+    fn getenv_unwrap_str(&self, v: &str) -> Result<String, Error> {
+        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<Arc<OsStr>, Error> {
         let target = self.get_target()?;
         let host = self.get_host()?;
@@ -3698,7 +3706,7 @@ impl Build {
             .getenv_with_target_prefixes(name)?
             .to_string_lossy()
             .split_ascii_whitespace()
-            .map(|slice| slice.to_string())
+            .map(ToString::to_string)
             .collect())
     }
 
@@ -3714,54 +3722,39 @@ impl Build {
         Ok(())
     }
 
-    fn apple_sdk_root(&self, sdk: &str) -> Result<OsString, Error> {
+    fn apple_sdk_root_inner(&self, sdk: &str) -> Result<Arc<OsStr>, 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")
@@ -3780,22 +3773,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<Arc<OsStr>, 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<str> {
+        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<Arc<str>> {
             let version = run_output(
                 self.cmd("xcrun")
                     .arg("--show-sdk-version")
@@ -3806,27 +3816,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<Arc<str>> {
+            // 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<String> {
+        let maybe_cpp_version_baseline = |deployment_target_ver: Arc<str>| -> Option<Arc<str>> {
             if !self.cpp {
                 return Some(deployment_target_ver);
             }
@@ -3880,7 +3890,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<str> = match os {
             AppleOs::MacOs => deployment_from_env("MACOSX_DEPLOYMENT_TARGET")
                 .and_then(maybe_cpp_version_baseline)
                 .or_else(default_deployment_from_sdk)
@@ -3888,8 +3898,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<str> = Arc::from("10.7");
+                        maybe_cpp_version_baseline(default.clone()).unwrap_or(default)
                     }
                 }),
 
@@ -3909,7 +3919,14 @@ 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<Arc<OsStr>, Error> {
@@ -3922,6 +3939,16 @@ impl Build {
             ))
         }
     }
+    fn is_wasi_target(target: &str) -> bool {
+        const TARGETS: [&'static str; 5] = [
+            "wasm32-wasi",
+            "wasm32-wasip1",
+            "wasm32-wasip1-threads",
+            "wasm32-wasip2",
+            "wasm32-wasi-threads",
+        ];
+        return TARGETS.contains(&target);
+    }
 
     fn cuda_file_count(&self) -> usize {
         self.files
@@ -3929,6 +3956,71 @@ impl Build {
             .filter(|file| file.extension() == Some(OsStr::new("cu")))
             .count()
     }
+
+    fn which(&self, tool: &Path, path_entries: Option<&OsStr>) -> Option<PathBuf> {
+        fn check_exe(mut exe: PathBuf) -> Option<PathBuf> {
+            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<PathBuf> {
+            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<PathBuf> {
+        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<Command> {
+        self.windows_registry_find_tool(target, tool)
+            .map(|c| c.to_command())
+    }
+
+    fn windows_registry_find_tool(&self, target: &str, tool: &str) -> Option<Tool> {
+        struct BuildEnvGetter<'s>(&'s Build);
+
+        impl windows_registry::EnvGetter for BuildEnvGetter<'_> {
+            fn get_env(&self, name: &str) -> Option<windows_registry::Env> {
+                self.0.getenv(name).map(windows_registry::Env::Arced)
+            }
+        }
+
+        windows_registry::find_tool_inner(target, tool, &BuildEnvGetter(self))
+    }
 }
 
 impl Default for Build {
@@ -4028,27 +4120,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"),
@@ -4117,50 +4188,6 @@ fn map_darwin_target_from_rust_to_compiler_architecture(target: &str) -> Option<
     }
 }
 
-fn which(tool: &Path, path_entries: Option<OsString>) -> Option<PathBuf> {
-    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<PathBuf> {
-    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
@@ -4185,3 +4212,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/tool.rs b/src/tool.rs
index f0f5b95..72fc23b 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<HashMap<Box<Path>, ToolFamily>>,
+        cached_compiler_family: &RwLock<HashMap<Box<Path>, 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<HashMap<Box<Path>, ToolFamily>>,
+        cached_compiler_family: &RwLock<HashMap<Box<Path>, ToolFamily>>,
         cargo_output: &CargoOutput,
         out_dir: Option<&Path>,
     ) -> Self {
@@ -90,7 +90,7 @@ impl Tool {
         path: PathBuf,
         clang_driver: Option<&str>,
         cuda: bool,
-        cached_compiler_family: &Mutex<HashMap<Box<Path>, ToolFamily>>,
+        cached_compiler_family: &RwLock<HashMap<Box<Path>, ToolFamily>>,
         cargo_output: &CargoOutput,
         out_dir: Option<&Path>,
     ) -> Self {
@@ -186,13 +186,13 @@ impl Tool {
             }
         }
         let detect_family = |path: &Path| -> Result<ToolFamily, Error> {
-            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)
diff --git a/src/utilities.rs b/src/utilities.rs
index d09ffff..073e617 100644
--- a/src/utilities.rs
+++ b/src/utilities.rs
@@ -15,7 +15,7 @@ where
 {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let len = self.slice.len();
-        for (index, os_str) in self.slice.into_iter().enumerate() {
+        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())?;
diff --git a/src/windows/find_tools.rs b/src/windows/find_tools.rs
index 015b01d..a7be3c3 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<TargetArch<'a>> for &'a str {
     }
 }
 
+pub(crate) enum Env {
+    Owned(OsString),
+    Arced(Arc<OsStr>),
+}
+
+impl AsRef<OsStr> 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<Env> 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<Env>;
+}
+
+struct StdEnvGetter;
+
+impl EnvGetter for StdEnvGetter {
+    #[allow(clippy::disallowed_methods)]
+    fn get_env(&self, name: &'static str) -> Option<Env> {
+        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<Command> {
 /// 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<Tool> {
+    find_tool_inner(target, tool, &StdEnvGetter)
+}
+
+pub(crate) fn find_tool_inner(
+    target: &str,
+    tool: &str,
+    env_getter: &dyn EnvGetter,
+) -> Option<Tool> {
     // 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<Tool> {
     // 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<Tool> {
     // 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<VsVers, String> {
+    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,15 +195,15 @@ pub fn find_vs_version() -> Result<VsVers, String> {
         _ => {
             // 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("\n\n\
@@ -178,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 {
@@ -261,7 +325,7 @@ mod impl_ {
             }
         }
 
-        fn into_tool(self) -> Tool {
+        fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool {
             let MsvcTool {
                 tool,
                 libs,
@@ -269,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<bool> {
-        let vscmd_arch = env::var("VSCMD_ARG_TGT_ARCH").ok()?;
+    fn is_vscmd_target(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<bool> {
+        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",
@@ -289,23 +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<Tool> {
+    pub(super) fn find_msvc_environment(
+        tool: &str,
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
         // Early return if the environment doesn't contain a VC install.
-        env::var_os("VCINSTALLDIR")?;
-        let vs_install_dir: PathBuf = 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))
@@ -315,16 +384,17 @@ mod impl_ {
         }
     }
 
-    fn find_msbuild_vs17(target: TargetArch<'_>) -> Option<Tool> {
-        find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "17")
+    fn find_msbuild_vs17(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
+        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<Iterator<Item = PathBuf>> {
-        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());
@@ -345,8 +415,9 @@ mod impl_ {
         tool: &str,
         target: TargetArch<'_>,
         version: &'static str,
+        env_getter: &dyn EnvGetter,
     ) -> Option<Tool> {
-        vs16plus_instances(target, version)
+        vs16plus_instances(target, version, env_getter)
             .filter_map(|path| {
                 let path = path.join(tool);
                 if !path.is_file() {
@@ -364,8 +435,8 @@ mod impl_ {
             .next()
     }
 
-    fn find_msbuild_vs16(target: TargetArch<'_>) -> Option<Tool> {
-        find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe", target, "16")
+    fn find_msbuild_vs16(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
+        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
@@ -380,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<VsInstances> {
-        vs15plus_instances_using_com().or_else(|| vs15plus_instances_using_vswhere(target))
+    fn vs15plus_instances(
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<VsInstances> {
+        vs15plus_instances_using_com()
+            .or_else(|| vs15plus_instances_using_vswhere(target, env_getter))
     }
 
     fn vs15plus_instances_using_com() -> Option<VsInstances> {
@@ -393,11 +468,15 @@ mod impl_ {
         Some(VsInstances::ComBased(enum_setup_instances))
     }
 
-    fn vs15plus_instances_using_vswhere(target: TargetArch<'_>) -> Option<VsInstances> {
-        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<VsInstances> {
+        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");
@@ -443,13 +522,17 @@ mod impl_ {
             .collect()
     }
 
-    pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
-        let iter = vs15plus_instances(target)?;
+    pub(super) fn find_msvc_15plus(
+        tool: &str,
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
+        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))
@@ -463,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<Tool> {
-        let mut path = match vs15plus_instances(target) {
+    fn find_tool_in_vs15_path(
+        tool: &str,
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
+        let mut path = match vs15plus_instances(target, env_getter) {
             Some(instances) => instances
                 .into_iter()
                 .filter_map(|instance| instance.installation_path())
@@ -498,9 +585,10 @@ mod impl_ {
         tool: &str,
         target: TargetArch<'_>,
         instance_path: &Path,
+        env_getter: &dyn EnvGetter,
     ) -> Option<Tool> {
         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;
@@ -520,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>, PathBuf)> {
         let version = vs15plus_vc_read_version(instance_path)?;
 
@@ -566,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"
@@ -621,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)> {
@@ -637,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<Tool> {
+    pub(super) fn find_msvc_14(
+        tool: &str,
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
         let vcdir = get_vc_dir("14.0")?;
         let mut tool = get_tool(tool, &vcdir, target)?;
-        add_sdks(&mut tool, target)?;
-        Some(tool.into_tool())
+        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()?;
 
@@ -664,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));
@@ -687,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<Tool> {
+    pub(super) fn find_msvc_12(
+        tool: &str,
+        target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
         let vcdir = get_vc_dir("12.0")?;
         let mut tool = get_tool(tool, &vcdir, target)?;
         let sub = lib_subdir(target)?;
@@ -699,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<PathBuf>) {
-        let prev = env::var_os(env).unwrap_or_default();
+    fn add_env(
+        tool: &mut Tool,
+        env: &'static str,
+        paths: Vec<PathBuf>,
+        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
@@ -788,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";
@@ -933,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!(
@@ -960,30 +1079,30 @@ mod impl_ {
         }
     }
 
-    pub(super) fn find_devenv(target: TargetArch<'_>) -> Option<Tool> {
-        find_devenv_vs15(target)
+    pub(super) fn find_devenv(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
+        find_devenv_vs15(target, env_getter)
     }
 
-    fn find_devenv_vs15(target: TargetArch<'_>) -> Option<Tool> {
-        find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target)
+    fn find_devenv_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
+        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<Tool> {
+    pub(super) fn find_msbuild(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
         // 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<Tool> {
-        find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target)
+    fn find_msbuild_vs15(target: TargetArch<'_>, env_getter: &dyn EnvGetter) -> Option<Tool> {
+        find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target, env_getter)
     }
 
     fn find_old_msbuild(target: TargetArch<'_>) -> Option<Tool> {
@@ -1009,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<Tool> {
+    #[inline(always)]
+    pub(super) fn find_msbuild(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option<Tool> {
         None
     }
 
     // Finding devenv.exe tool under unix system is not currently supported.
     // Maybe can check it using an environment variable looks like `DEVENV_BIN`.
-    pub(super) fn find_devenv(_target: TargetArch<'_>) -> Option<Tool> {
+    #[inline(always)]
+    pub(super) fn find_devenv(_target: TargetArch<'_>, _: &dyn EnvGetter) -> Option<Tool> {
         None
     }
 
     /// Attempt to find the tool using environment variables set by vcvars.
-    pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msvc_environment(
+        tool: &str,
+        _target: TargetArch<'_>,
+        env_getter: &dyn EnvGetter,
+    ) -> Option<Tool> {
         // Early return if the environment doesn't contain a VC install.
-        let vc_install_dir = env::var_os("VCINSTALLDIR")?;
-        let vs_install_dir = env::var_os("VSINSTALLDIR")?;
+        let 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, 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(get_tool))
+            .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<Tool> {
+    #[inline(always)]
+    pub(super) fn find_msvc_15plus(
+        _tool: &str,
+        _target: TargetArch<'_>,
+        _: &dyn EnvGetter,
+    ) -> Option<Tool> {
         None
     }
 
     // For MSVC 14 we need to find the Universal CRT as well as either
     // the Windows 10 SDK or Windows 8.1 SDK.
-    pub(super) fn find_msvc_14(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+    #[inline(always)]
+    pub(super) fn find_msvc_14(
+        _tool: &str,
+        _target: TargetArch<'_>,
+        _: &dyn EnvGetter,
+    ) -> Option<Tool> {
         None
     }
 
     // For MSVC 12 we need to find the Windows 8.1 SDK.
-    pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+    #[inline(always)]
+    pub(super) fn find_msvc_12(
+        _tool: &str,
+        _target: TargetArch<'_>,
+        _: &dyn EnvGetter,
+    ) -> Option<Tool> {
         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,


Test patch:
diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs
index d8fc08c..4adb870 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};
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
index 4bdef02..5e87001 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};
diff --git a/tests/test.rs b/tests/test.rs
index 8fc8da6..4c07088 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");