Repository: rust-lang/cc-rs
Pull request #978 (2024-02-25 12:48:51)

CI: Add automatic publish on tag creation
Requires a new secret `CARGO_REGISTRY_TOKEN` to be added to github action secrets of this repository.

It would only need permission to publsh new versions of cc on crates.io , using scoped token.

Amanieu: cc @Mark-Simulacrum 

Patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..9097444
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,15 @@
+on:
+  push:
+    tags:        
+      - '*'
+
+jobs:
+  publish:
+    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 }}
diff --git a/Cargo.toml b/Cargo.toml
index a5aaee9..005a522 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cc"
-version = "1.0.88"
+version = "1.0.90"
 authors = ["Alex Crichton <alex@alexcrichton.com>"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/rust-lang/cc-rs"
@@ -19,13 +19,16 @@ exclude = ["/.github", "tests", "src/bin"]
 edition = "2018"
 rust-version = "1.53"
 
+[dependencies]
+jobserver = { version = "0.1.20", default-features = false, optional = true }
+
 [target.'cfg(unix)'.dependencies]
 # Don't turn on the feature "std" for this, see https://github.com/rust-lang/cargo/issues/4866
 # which is still an issue with `resolver = "1"`.
 libc = { version = "0.2.62", default-features = false, optional = true }
 
 [features]
-parallel = ["libc"]
+parallel = ["libc", "jobserver"]
 
 [dev-dependencies]
 tempfile = "3"
diff --git a/dev-tools/gen-windows-sys-binding/Cargo.toml b/dev-tools/gen-windows-sys-binding/Cargo.toml
index 9166030..f8995e1 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.53"
+windows-bindgen = "0.55"
 tempfile = "3"
diff --git a/src/command_helpers.rs b/src/command_helpers.rs
index 919d276..10dafeb 100644
--- a/src/command_helpers.rs
+++ b/src/command_helpers.rs
@@ -215,7 +215,7 @@ fn write_warning(line: &[u8]) {
 
 fn wait_on_child(
     cmd: &Command,
-    program: &str,
+    program: &Path,
     child: &mut Child,
     cargo_output: &CargoOutput,
 ) -> Result<(), Error> {
@@ -227,8 +227,10 @@ fn wait_on_child(
             return Err(Error::new(
                 ErrorKind::ToolExecError,
                 format!(
-                    "Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
-                    cmd, program, e
+                    "Failed to wait on spawned child process, command {:?} with args {}: {}.",
+                    cmd,
+                    program.display(),
+                    e
                 ),
             ));
         }
@@ -242,8 +244,10 @@ fn wait_on_child(
         Err(Error::new(
             ErrorKind::ToolExecError,
             format!(
-                "Command {:?} with args {:?} did not execute successfully (status code {}).",
-                cmd, program, status
+                "Command {:?} with args {} did not execute successfully (status code {}).",
+                cmd,
+                program.display(),
+                status
             ),
         ))
     }
@@ -299,18 +303,22 @@ pub(crate) fn objects_from_files(files: &[Arc<Path>], dst: &Path) -> Result<Vec<
 
 pub(crate) fn run(
     cmd: &mut Command,
-    program: &str,
+    program: impl AsRef<Path>,
     cargo_output: &CargoOutput,
 ) -> Result<(), Error> {
+    let program = program.as_ref();
+
     let mut child = spawn(cmd, program, cargo_output)?;
     wait_on_child(cmd, program, &mut child, cargo_output)
 }
 
 pub(crate) fn run_output(
     cmd: &mut Command,
-    program: &str,
+    program: impl AsRef<Path>,
     cargo_output: &CargoOutput,
 ) -> Result<Vec<u8>, Error> {
+    let program = program.as_ref();
+
     cmd.stdout(Stdio::piped());
 
     let mut child = spawn(cmd, program, cargo_output)?;
@@ -330,7 +338,7 @@ pub(crate) fn run_output(
 
 pub(crate) fn spawn(
     cmd: &mut Command,
-    program: &str,
+    program: &Path,
     cargo_output: &CargoOutput,
 ) -> Result<Child, Error> {
     struct ResetStderr<'cmd>(&'cmd mut Command);
@@ -358,14 +366,20 @@ for help)"
             };
             Err(Error::new(
                 ErrorKind::ToolNotFound,
-                format!("Failed to find tool. Is `{}` installed?{}", program, extra),
+                format!(
+                    "Failed to find tool. Is `{}` installed?{}",
+                    program.display(),
+                    extra
+                ),
             ))
         }
         Err(e) => Err(Error::new(
             ErrorKind::ToolExecError,
             format!(
-                "Command {:?} with args {:?} failed to start: {:?}",
-                cmd.0, program, e
+                "Command {:?} with args {} failed to start: {:?}",
+                cmd.0,
+                program.display(),
+                e
             ),
         )),
     }
@@ -393,7 +407,7 @@ pub(crate) fn command_add_output_file(
 #[cfg(feature = "parallel")]
 pub(crate) fn try_wait_on_child(
     cmd: &Command,
-    program: &str,
+    program: &Path,
     child: &mut Child,
     stdout: &mut dyn io::Write,
     stderr_forwarder: &mut StderrForwarder,
@@ -412,8 +426,10 @@ pub(crate) fn try_wait_on_child(
                 Err(Error::new(
                     ErrorKind::ToolExecError,
                     format!(
-                        "Command {:?} with args {:?} did not execute successfully (status code {}).",
-                            cmd, program, status
+                        "Command {:?} with args {} did not execute successfully (status code {}).",
+                        cmd,
+                        program.display(),
+                        status
                     ),
                 ))
             }
@@ -424,8 +440,10 @@ pub(crate) fn try_wait_on_child(
             Err(Error::new(
                 ErrorKind::ToolExecError,
                 format!(
-                    "Failed to wait on spawned child process, command {:?} with args {:?}: {}.",
-                    cmd, program, e
+                    "Failed to wait on spawned child process, command {:?} with args {}: {}.",
+                    cmd,
+                    program.display(),
+                    e
                 ),
             ))
         }
diff --git a/src/lib.rs b/src/lib.rs
index f2b3474..8ef4830 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -312,6 +312,9 @@ enum ErrorKind {
     ToolNotFound,
     /// One of the function arguments failed validation.
     InvalidArgument,
+    #[cfg(feature = "parallel")]
+    /// jobserver helpthread failure
+    JobserverHelpThreadError,
 }
 
 /// Represents an internal error that occurred, with an explanation.
@@ -758,6 +761,11 @@ impl Build {
         self
     }
 
+    /// Get the files which will be compiled
+    pub fn get_files(&self) -> impl Iterator<Item = &Path> {
+        self.files.iter().map(AsRef::as_ref)
+    }
+
     /// Set C++ support.
     ///
     /// The other `cpp_*` options will only become active if this is set to
@@ -1431,7 +1439,7 @@ impl Build {
 
         let pendings = Cell::new(Vec::<(
             Command,
-            String,
+            Cow<'static, Path>,
             KillOnDrop,
             parallel::job_token::JobToken,
         )>::new());
@@ -1505,13 +1513,7 @@ impl Build {
         let spawn_future = async {
             for obj in objs {
                 let (mut cmd, program) = self.create_compile_object_cmd(obj)?;
-                let token = loop {
-                    if let Some(token) = tokens.try_acquire()? {
-                        break token;
-                    } else {
-                        YieldOnce::default().await
-                    }
-                };
+                let token = tokens.acquire().await?;
                 let mut child = spawn(&mut cmd, &program, &self.cargo_output)?;
                 let mut stderr_forwarder = StderrForwarder::new(&mut child);
                 stderr_forwarder.set_non_blocking()?;
@@ -1561,7 +1563,10 @@ impl Build {
         Ok(())
     }
 
-    fn create_compile_object_cmd(&self, obj: &Object) -> Result<(Command, String), Error> {
+    fn create_compile_object_cmd(
+        &self,
+        obj: &Object,
+    ) -> Result<(Command, Cow<'static, Path>), Error> {
         let asm_ext = AsmFileExt::from_path(&obj.src);
         let is_asm = asm_ext.is_some();
         let target = self.get_target()?;
@@ -1572,7 +1577,8 @@ impl Build {
 
         let is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm);
         let (mut cmd, name) = if is_assembler_msvc {
-            self.msvc_macro_assembler()?
+            let (cmd, name) = self.msvc_macro_assembler()?;
+            (cmd, Cow::Borrowed(Path::new(name)))
         } else {
             let mut cmd = compiler.to_command();
             for (a, b) in self.env.iter() {
@@ -1583,9 +1589,9 @@ impl Build {
                 compiler
                     .path
                     .file_name()
-                    .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?
-                    .to_string_lossy()
-                    .into_owned(),
+                    .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))
+                    .map(PathBuf::from)
+                    .map(Cow::Owned)?,
             )
         };
         let is_arm = target.contains("aarch64") || target.contains("arm");
@@ -1651,9 +1657,7 @@ impl Build {
         let name = compiler
             .path
             .file_name()
-            .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?
-            .to_string_lossy()
-            .into_owned();
+            .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?;
 
         Ok(run_output(&mut cmd, &name, &self.cargo_output)?)
     }
@@ -1892,7 +1896,10 @@ impl Build {
         // Target flags
         match cmd.family {
             ToolFamily::Clang => {
-                if !(target.contains("android") && cmd.has_internal_target_arg) {
+                if !cmd.has_internal_target_arg
+                    && !(target.contains("android")
+                        && android_clang_compiler_uses_target_arg_internally(&cmd.path))
+                {
                     if target.contains("darwin") {
                         if let Some(arch) =
                             map_darwin_target_from_rust_to_compiler_architecture(target)
@@ -1980,10 +1987,35 @@ impl Build {
                         cmd.args.push(
                             format!("--target={}", target.replace("riscv64gc", "riscv64")).into(),
                         );
+                    } else if target.starts_with("riscv64imac-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv64imac", "riscv64")).into(),
+                        );
                     } else if target.starts_with("riscv32gc-") {
                         cmd.args.push(
                             format!("--target={}", target.replace("riscv32gc", "riscv32")).into(),
                         );
+                    } else if target.starts_with("riscv32i-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv32i", "riscv32")).into(),
+                        )
+                    } else if target.starts_with("riscv32im-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv32im", "riscv32")).into(),
+                        )
+                    } else if target.starts_with("riscv32imc-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv32imc", "riscv32")).into(),
+                        )
+                    } else if target.starts_with("riscv32imac-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv32imac", "riscv32")).into(),
+                        )
+                    } else if target.starts_with("riscv32imafc-") {
+                        cmd.args.push(
+                            format!("--target={}", target.replace("riscv32imafc", "riscv32"))
+                                .into(),
+                        )
                     } else if target.contains("uefi") {
                         if target.contains("x86_64") {
                             cmd.args.push("--target=x86_64-unknown-windows-gnu".into());
@@ -2291,7 +2323,7 @@ impl Build {
         }
     }
 
-    fn msvc_macro_assembler(&self) -> Result<(Command, String), Error> {
+    fn msvc_macro_assembler(&self) -> Result<(Command, &'static str), Error> {
         let target = self.get_target()?;
         let tool = if target.contains("x86_64") {
             "ml64.exe"
@@ -2344,7 +2376,7 @@ impl Build {
             cmd.arg("-safeseh");
         }
 
-        Ok((cmd, tool.to_string()))
+        Ok((cmd, tool))
     }
 
     fn assemble(&self, lib_name: &str, dst: &Path, objs: &[Object]) -> Result<(), Error> {
@@ -2996,7 +3028,7 @@ impl Build {
         }
     }
 
-    fn get_ar(&self) -> Result<(Command, String, bool), Error> {
+    fn get_ar(&self) -> Result<(Command, PathBuf, bool), Error> {
         self.try_get_archiver_and_flags()
     }
 
@@ -3029,7 +3061,7 @@ impl Build {
         Ok(self.try_get_archiver_and_flags()?.0)
     }
 
-    fn try_get_archiver_and_flags(&self) -> Result<(Command, String, bool), Error> {
+    fn try_get_archiver_and_flags(&self) -> Result<(Command, PathBuf, bool), Error> {
         let (mut cmd, name) = self.get_base_archiver()?;
         let mut any_flags = false;
         if let Ok(flags) = self.envflags("ARFLAGS") {
@@ -3043,12 +3075,14 @@ impl Build {
         Ok((cmd, name, any_flags))
     }
 
-    fn get_base_archiver(&self) -> Result<(Command, String), Error> {
+    fn get_base_archiver(&self) -> Result<(Command, PathBuf), Error> {
         if let Some(ref a) = self.archiver {
-            return Ok((self.cmd(&**a), a.to_string_lossy().into_owned()));
+            let archiver = &**a;
+            return Ok((self.cmd(archiver), archiver.into()));
         }
 
         self.get_base_archiver_variant("AR", "ar")
+            .map(|(cmd, archiver)| (cmd, archiver.into()))
     }
 
     /// Get the ranlib that's in use for this configuration.
@@ -3138,7 +3172,11 @@ impl Build {
             Some(t) => t,
             None => {
                 if target.contains("android") {
-                    name = format!("{}-{}", target.replace("armv7", "arm"), tool);
+                    name = format!("llvm-{}", tool);
+                    match Command::new(&name).arg("--version").status() {
+                        Ok(status) if status.success() => (),
+                        _ => name = format!("{}-{}", target.replace("armv7", "arm"), tool),
+                    }
                     self.cmd(&name)
                 } else if target.contains("msvc") {
                     // NOTE: There isn't really a ranlib on msvc, so arguably we should return
diff --git a/src/parallel/job_token.rs b/src/parallel/job_token.rs
new file mode 100644
index 0000000..4fec982
--- /dev/null
+++ b/src/parallel/job_token.rs
@@ -0,0 +1,255 @@
+use std::{marker::PhantomData, mem::MaybeUninit, sync::Once};
+
+use crate::Error;
+
+pub(crate) struct JobToken(PhantomData<()>);
+
+impl JobToken {
+    fn new() -> Self {
+        Self(PhantomData)
+    }
+}
+
+impl Drop for JobToken {
+    fn drop(&mut self) {
+        match JobTokenServer::new() {
+            JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(),
+            JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(),
+        }
+    }
+}
+
+enum JobTokenServer {
+    Inherited(inherited_jobserver::JobServer),
+    InProcess(inprocess_jobserver::JobServer),
+}
+
+impl JobTokenServer {
+    /// This function returns a static reference to the jobserver because
+    ///  - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might
+    ///    be closed by other jobserver users in the process) and better do it
+    ///    at the start of the program.
+    ///  - in case a jobserver cannot be created from env (e.g. it's not
+    ///    present), we will create a global in-process only jobserver
+    ///    that has to be static so that it will be shared by all cc
+    ///    compilation.
+    fn new() -> &'static Self {
+        static INIT: Once = Once::new();
+        static mut JOBSERVER: MaybeUninit<JobTokenServer> = MaybeUninit::uninit();
+
+        unsafe {
+            INIT.call_once(|| {
+                let server = inherited_jobserver::JobServer::from_env()
+                    .map(Self::Inherited)
+                    .unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new()));
+                JOBSERVER = MaybeUninit::new(server);
+            });
+            // TODO: Poor man's assume_init_ref, as that'd require a MSRV of 1.55.
+            &*JOBSERVER.as_ptr()
+        }
+    }
+}
+
+pub(crate) enum ActiveJobTokenServer {
+    Inherited(inherited_jobserver::ActiveJobServer<'static>),
+    InProcess(&'static inprocess_jobserver::JobServer),
+}
+
+impl ActiveJobTokenServer {
+    pub(crate) fn new() -> Result<Self, Error> {
+        match JobTokenServer::new() {
+            JobTokenServer::Inherited(inherited_jobserver) => {
+                inherited_jobserver.enter_active().map(Self::Inherited)
+            }
+            JobTokenServer::InProcess(inprocess_jobserver) => {
+                Ok(Self::InProcess(inprocess_jobserver))
+            }
+        }
+    }
+
+    pub(crate) async fn acquire(&self) -> Result<JobToken, Error> {
+        match &self {
+            Self::Inherited(jobserver) => jobserver.acquire().await,
+            Self::InProcess(jobserver) => Ok(jobserver.acquire().await),
+        }
+    }
+}
+
+mod inherited_jobserver {
+    use super::JobToken;
+
+    use crate::{parallel::async_executor::YieldOnce, Error, ErrorKind};
+
+    use std::{
+        io, mem,
+        sync::{mpsc, Mutex, MutexGuard, PoisonError},
+    };
+
+    pub(super) struct JobServer {
+        /// Implicit token for this process which is obtained and will be
+        /// released in parent. Since JobTokens only give back what they got,
+        /// there should be at most one global implicit token in the wild.
+        ///
+        /// Since Rust does not execute any `Drop` for global variables,
+        /// we can't just put it back to jobserver and then re-acquire it at
+        /// the end of the process.
+        ///
+        /// Use `Mutex` to avoid race between acquire and release.
+        /// If an `AtomicBool` is used, then it's possible for:
+        ///  - `release_token_raw`: Tries to set `global_implicit_token` to true, but it is already
+        ///    set  to `true`, continue to release it to jobserver
+        ///  - `acquire` takes the global implicit token, set `global_implicit_token` to false
+        ///  - `release_token_raw` now writes the token back into the jobserver, while
+        ///    `global_implicit_token` is `false`
+        ///
+        /// If the program exits here, then cc effectively increases parallelism by one, which is
+        /// incorrect, hence we use a `Mutex` here.
+        global_implicit_token: Mutex<bool>,
+        inner: jobserver::Client,
+    }
+
+    impl JobServer {
+        pub(super) unsafe fn from_env() -> Option<Self> {
+            jobserver::Client::from_env().map(|inner| Self {
+                inner,
+                global_implicit_token: Mutex::new(true),
+            })
+        }
+
+        fn get_global_implicit_token(&self) -> MutexGuard<'_, bool> {
+            self.global_implicit_token
+                .lock()
+                .unwrap_or_else(PoisonError::into_inner)
+        }
+
+        /// All tokens except for the global implicit token will be put back into the jobserver
+        /// immediately and they cannot be cached, since Rust does not call `Drop::drop` on
+        /// global variables.
+        pub(super) fn release_token_raw(&self) {
+            let mut global_implicit_token = self.get_global_implicit_token();
+
+            if *global_implicit_token {
+                // There's already a global implicit token, so this token must
+                // be released back into jobserver.
+                //
+                // `release_raw` should not block
+                let _ = self.inner.release_raw();
+            } else {
+                *global_implicit_token = true;
+            }
+        }
+
+        pub(super) fn enter_active(&self) -> Result<ActiveJobServer<'_>, Error> {
+            ActiveJobServer::new(self)
+        }
+    }
+
+    pub(crate) struct ActiveJobServer<'a> {
+        jobserver: &'a JobServer,
+        helper_thread: jobserver::HelperThread,
+        /// When rx is dropped, all the token stored within it will be dropped.
+        rx: mpsc::Receiver<io::Result<jobserver::Acquired>>,
+    }
+
+    impl<'a> ActiveJobServer<'a> {
+        fn new(jobserver: &'a JobServer) -> Result<Self, Error> {
+            let (tx, rx) = mpsc::channel();
+
+            Ok(Self {
+                rx,
+                helper_thread: jobserver.inner.clone().into_helper_thread(move |res| {
+                    let _ = tx.send(res);
+                })?,
+                jobserver,
+            })
+        }
+
+        pub(super) async fn acquire(&self) -> Result<JobToken, Error> {
+            let mut has_requested_token = false;
+
+            loop {
+                // Fast path
+                if mem::replace(&mut *self.jobserver.get_global_implicit_token(), false) {
+                    break Ok(JobToken::new());
+                }
+
+                // Cold path, no global implicit token, obtain one
+                match self.rx.try_recv() {
+                    Ok(res) => {
+                        let acquired = res?;
+                        acquired.drop_without_releasing();
+                        break Ok(JobToken::new());
+                    }
+                    Err(mpsc::TryRecvError::Disconnected) => {
+                        break Err(Error::new(
+                            ErrorKind::JobserverHelpThreadError,
+                            "jobserver help thread has returned before ActiveJobServer is dropped",
+                        ))
+                    }
+                    Err(mpsc::TryRecvError::Empty) => {
+                        if !has_requested_token {
+                            self.helper_thread.request_token();
+                            has_requested_token = true;
+                        }
+                        YieldOnce::default().await
+                    }
+                }
+            }
+        }
+    }
+}
+
+mod inprocess_jobserver {
+    use super::JobToken;
+
+    use crate::parallel::async_executor::YieldOnce;
+
+    use std::{
+        env::var,
+        sync::atomic::{
+            AtomicU32,
+            Ordering::{AcqRel, Acquire},
+        },
+    };
+
+    pub(crate) struct JobServer(AtomicU32);
+
+    impl JobServer {
+        pub(super) fn new() -> Self {
+            // Use `NUM_JOBS` if set (it's configured by Cargo) and otherwise
+            // just fall back to a semi-reasonable number.
+            //
+            // Note that we could use `num_cpus` here but it's an extra
+            // dependency that will almost never be used, so
+            // it's generally not too worth it.
+            let mut parallelism = 4;
+            // TODO: Use std::thread::available_parallelism as an upper bound
+            // when MSRV is bumped.
+            if let Ok(amt) = var("NUM_JOBS") {
+                if let Ok(amt) = amt.parse() {
+                    parallelism = amt;
+                }
+            }
+
+            Self(AtomicU32::new(parallelism))
+        }
+
+        pub(super) async fn acquire(&self) -> JobToken {
+            loop {
+                let res = self
+                    .0
+                    .fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1));
+
+                if res.is_ok() {
+                    break JobToken::new();
+                }
+
+                YieldOnce::default().await
+            }
+        }
+
+        pub(super) fn release_token_raw(&self) {
+            self.0.fetch_add(1, AcqRel);
+        }
+    }
+}
diff --git a/src/parallel/job_token/mod.rs b/src/parallel/job_token/mod.rs
deleted file mode 100644
index a04d762..0000000
--- a/src/parallel/job_token/mod.rs
+++ /dev/null
@@ -1,257 +0,0 @@
-use std::{mem::MaybeUninit, sync::Once};
-
-use crate::Error;
-
-#[cfg(unix)]
-#[path = "unix.rs"]
-mod sys;
-
-#[cfg(windows)]
-#[path = "windows.rs"]
-mod sys;
-
-pub(crate) struct JobToken();
-
-impl Drop for JobToken {
-    fn drop(&mut self) {
-        match JobTokenServer::new() {
-            JobTokenServer::Inherited(jobserver) => jobserver.release_token_raw(),
-            JobTokenServer::InProcess(jobserver) => jobserver.release_token_raw(),
-        }
-    }
-}
-
-enum JobTokenServer {
-    Inherited(inherited_jobserver::JobServer),
-    InProcess(inprocess_jobserver::JobServer),
-}
-
-impl JobTokenServer {
-    /// This function returns a static reference to the jobserver because
-    ///  - creating a jobserver from env is a bit fd-unsafe (e.g. the fd might
-    ///    be closed by other jobserver users in the process) and better do it
-    ///    at the start of the program.
-    ///  - in case a jobserver cannot be created from env (e.g. it's not
-    ///    present), we will create a global in-process only jobserver
-    ///    that has to be static so that it will be shared by all cc
-    ///    compilation.
-    fn new() -> &'static Self {
-        static INIT: Once = Once::new();
-        static mut JOBSERVER: MaybeUninit<JobTokenServer> = MaybeUninit::uninit();
-
-        unsafe {
-            INIT.call_once(|| {
-                let server = inherited_jobserver::JobServer::from_env()
-                    .map(Self::Inherited)
-                    .unwrap_or_else(|| Self::InProcess(inprocess_jobserver::JobServer::new()));
-                JOBSERVER = MaybeUninit::new(server);
-            });
-            // TODO: Poor man's assume_init_ref, as that'd require a MSRV of 1.55.
-            &*JOBSERVER.as_ptr()
-        }
-    }
-}
-
-pub(crate) struct ActiveJobTokenServer(&'static JobTokenServer);
-
-impl ActiveJobTokenServer {
-    pub(crate) fn new() -> Result<Self, Error> {
-        let jobserver = JobTokenServer::new();
-
-        #[cfg(unix)]
-        if let JobTokenServer::Inherited(inherited_jobserver) = &jobserver {
-            inherited_jobserver.enter_active()?;
-        }
-
-        Ok(Self(jobserver))
-    }
-
-    pub(crate) fn try_acquire(&self) -> Result<Option<JobToken>, Error> {
-        match &self.0 {
-            JobTokenServer::Inherited(jobserver) => jobserver.try_acquire(),
-            JobTokenServer::InProcess(jobserver) => Ok(jobserver.try_acquire()),
-        }
-    }
-}
-
-impl Drop for ActiveJobTokenServer {
-    fn drop(&mut self) {
-        #[cfg(unix)]
-        if let JobTokenServer::Inherited(inherited_jobserver) = &self.0 {
-            inherited_jobserver.exit_active();
-        }
-    }
-}
-
-mod inherited_jobserver {
-    use super::{sys, Error, JobToken};
-
-    use std::{
-        env::var_os,
-        sync::atomic::{
-            AtomicBool,
-            Ordering::{AcqRel, Acquire},
-        },
-    };
-
-    #[cfg(unix)]
-    use std::sync::{Mutex, MutexGuard, PoisonError};
-
-    pub(crate) struct JobServer {
-        /// Implicit token for this process which is obtained and will be
-        /// released in parent. Since JobTokens only give back what they got,
-        /// there should be at most one global implicit token in the wild.
-        ///
-        /// Since Rust does not execute any `Drop` for global variables,
-        /// we can't just put it back to jobserver and then re-acquire it at
-        /// the end of the process.
-        global_implicit_token: AtomicBool,
-        inner: sys::JobServerClient,
-        /// number of active clients is required to know when it is safe to clear non-blocking
-        /// flags
-        #[cfg(unix)]
-        active_clients_cnt: Mutex<usize>,
-    }
-
-    impl JobServer {
-        pub(super) unsafe fn from_env() -> Option<Self> {
-            let var = var_os("CARGO_MAKEFLAGS")
-                .or_else(|| var_os("MAKEFLAGS"))
-                .or_else(|| var_os("MFLAGS"))?;
-
-            #[cfg(unix)]
-            let var = std::os::unix::ffi::OsStrExt::as_bytes(var.as_os_str());
-            #[cfg(not(unix))]
-            let var = var.to_str()?.as_bytes();
-
-            let makeflags = var.split(u8::is_ascii_whitespace);
-
-            // `--jobserver-auth=` is the only documented makeflags.
-            // `--jobserver-fds=` is actually an internal only makeflags, so we should
-            // always prefer `--jobserver-auth=`.
-            //
-            // Also, according to doc of makeflags, if there are multiple `--jobserver-auth=`
-            // the last one is used
-            if let Some(flag) = makeflags
-                .clone()
-                .filter_map(|s| s.strip_prefix(b"--jobserver-auth="))
-                .last()
-            {
-                sys::JobServerClient::open(flag)
-            } else {
-                sys::JobServerClient::open(
-                    makeflags
-                        .filter_map(|s| s.strip_prefix(b"--jobserver-fds="))
-                        .last()?,
-                )
-            }
-            .map(|inner| Self {
-                inner,
-                global_implicit_token: AtomicBool::new(true),
-                #[cfg(unix)]
-                active_clients_cnt: Mutex::new(0),
-            })
-        }
-
-        #[cfg(unix)]
-        fn get_locked_active_cnt(&self) -> MutexGuard<'_, usize> {
-            self.active_clients_cnt
-                .lock()
-                .unwrap_or_else(PoisonError::into_inner)
-        }
-
-        #[cfg(unix)]
-        pub(super) fn enter_active(&self) -> Result<(), Error> {
-            let mut active_cnt = self.get_locked_active_cnt();
-            if *active_cnt == 0 {
-                self.inner.prepare_for_acquires()?;
-            }
-
-            *active_cnt += 1;
-
-            Ok(())
-        }
-
-        #[cfg(unix)]
-        pub(super) fn exit_active(&self) {
-            let mut active_cnt = self.get_locked_active_cnt();
-            *active_cnt -= 1;
-
-            if *active_cnt == 0 {
-                self.inner.done_acquires();
-            }
-        }
-
-        pub(super) fn try_acquire(&self) -> Result<Option<JobToken>, Error> {
-            if !self.global_implicit_token.swap(false, AcqRel) {
-                // Cold path, no global implicit token, obtain one
-                if self.inner.try_acquire()?.is_none() {
-                    return Ok(None);
-                }
-            }
-            Ok(Some(JobToken()))
-        }
-
-        pub(super) fn release_token_raw(&self) {
-            // All tokens will be put back into the jobserver immediately
-            // and they cannot be cached, since Rust does not call `Drop::drop`
-            // on global variables.
-            if self
-                .global_implicit_token
-                .compare_exchange(false, true, AcqRel, Acquire)
-                .is_err()
-            {
-                // There's already a global implicit token, so this token must
-                // be released back into jobserver
-                let _ = self.inner.release();
-            }
-        }
-    }
-}
-
-mod inprocess_jobserver {
-    use super::JobToken;
-
-    use std::{
-        env::var,
-        sync::atomic::{
-            AtomicU32,
-            Ordering::{AcqRel, Acquire},
-        },
-    };
-
-    pub(crate) struct JobServer(AtomicU32);
-
-    impl JobServer {
-        pub(super) fn new() -> Self {
-            // Use `NUM_JOBS` if set (it's configured by Cargo) and otherwise
-            // just fall back to a semi-reasonable number.
-            //
-            // Note that we could use `num_cpus` here but it's an extra
-            // dependency that will almost never be used, so
-            // it's generally not too worth it.
-            let mut parallelism = 4;
-            // TODO: Use std::thread::available_parallelism as an upper bound
-            // when MSRV is bumped.
-            if let Ok(amt) = var("NUM_JOBS") {
-                if let Ok(amt) = amt.parse() {
-                    parallelism = amt;
-                }
-            }
-
-            Self(AtomicU32::new(parallelism))
-        }
-
-        pub(super) fn try_acquire(&self) -> Option<JobToken> {
-            let res = self
-                .0
-                .fetch_update(AcqRel, Acquire, |tokens| tokens.checked_sub(1));
-
-            res.ok().map(|_| JobToken())
-        }
-
-        pub(super) fn release_token_raw(&self) {
-            self.0.fetch_add(1, AcqRel);
-        }
-    }
-}
diff --git a/src/parallel/job_token/unix.rs b/src/parallel/job_token/unix.rs
deleted file mode 100644
index 7f8e1b8..0000000
--- a/src/parallel/job_token/unix.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-use std::{
-    ffi::OsStr,
-    fs::{self, File},
-    io::{self, Read, Write},
-    mem::ManuallyDrop,
-    os::{raw::c_int, unix::prelude::*},
-    path::Path,
-};
-
-use crate::parallel::stderr::{set_blocking, set_non_blocking};
-
-pub(super) struct JobServerClient {
-    read: File,
-    write: Option<File>,
-}
-
-impl JobServerClient {
-    pub(super) unsafe fn open(var: &[u8]) -> Option<Self> {
-        if let Some(fifo) = var.strip_prefix(b"fifo:") {
-            Self::from_fifo(Path::new(OsStr::from_bytes(fifo)))
-        } else {
-            Self::from_pipe(OsStr::from_bytes(var).to_str()?)
-        }
-    }
-
-    /// `--jobserver-auth=fifo:PATH`
-    fn from_fifo(path: &Path) -> Option<Self> {
-        let file = fs::OpenOptions::new()
-            .read(true)
-            .write(true)
-            .open(path)
-            .ok()?;
-
-        if is_pipe(&file)? {
-            // File in Rust is always closed-on-exec as long as it's opened by
-            // `File::open` or `fs::OpenOptions::open`.
-            set_non_blocking(&file).ok()?;
-
-            Some(Self {
-                read: file,
-                write: None,
-            })
-        } else {
-            None
-        }
-    }
-
-    /// `--jobserver-auth=fd-for-R,fd-for-W`
-    unsafe fn from_pipe(s: &str) -> Option<Self> {
-        let (read, write) = s.split_once(',')?;
-
-        let read = read.parse().ok()?;
-        let write = write.parse().ok()?;
-
-        let read = ManuallyDrop::new(File::from_raw_fd(read));
-        let write = ManuallyDrop::new(File::from_raw_fd(write));
-
-        // Ok so we've got two integers that look like file descriptors, but
-        // for extra sanity checking let's see if they actually look like
-        // instances of a pipe before we return the client.
-        //
-        // If we're called from `make` *without* the leading + on our rule
-        // then we'll have `MAKEFLAGS` env vars but won't actually have
-        // access to the file descriptors.
-        match (
-            is_pipe(&read),
-            is_pipe(&write),
-            get_access_mode(&read),
-            get_access_mode(&write),
-        ) {
-            (
-                Some(true),
-                Some(true),
-                Some(libc::O_RDONLY) | Some(libc::O_RDWR),
-                Some(libc::O_WRONLY) | Some(libc::O_RDWR),
-            ) => {
-                // Optimization: Try converting it to a fifo by using /dev/fd
-                if let Some(jobserver) =
-                    Self::from_fifo(Path::new(&format!("/dev/fd/{}", read.as_raw_fd())))
-                {
-                    return Some(jobserver);
-                }
-
-                let read = read.try_clone().ok()?;
-                let write = write.try_clone().ok()?;
-
-                Some(Self {
-                    read,
-                    write: Some(write),
-                })
-            }
-            _ => None,
-        }
-    }
-
-    pub(super) fn prepare_for_acquires(&self) -> Result<(), crate::Error> {
-        if let Some(write) = self.write.as_ref() {
-            set_non_blocking(&self.read)?;
-            set_non_blocking(write)?;
-        }
-
-        Ok(())
-    }
-
-    pub(super) fn done_acquires(&self) {
-        if let Some(write) = self.write.as_ref() {
-            let _ = set_blocking(&self.read);
-            let _ = set_blocking(write);
-        }
-    }
-
-    /// Must call `prepare_for_acquire` before using it.
-    pub(super) fn try_acquire(&self) -> io::Result<Option<()>> {
-        let mut fds = [libc::pollfd {
-            fd: self.read.as_raw_fd(),
-            events: libc::POLLIN,
-            revents: 0,
-        }];
-
-        let ret = cvt(unsafe { libc::poll(fds.as_mut_ptr(), 1, 0) })?;
-        if ret == 1 {
-            let mut buf = [0];
-            match (&self.read).read(&mut buf) {
-                Ok(1) => Ok(Some(())),
-                Ok(_) => Ok(None), // 0, eof
-                Err(e)
-                    if e.kind() == io::ErrorKind::Interrupted
-                        || e.kind() == io::ErrorKind::WouldBlock =>
-                {
-                    Ok(None)
-                }
-                Err(e) => Err(e),
-            }
-        } else {
-            Ok(None)
-        }
-    }
-
-    pub(super) fn release(&self) -> io::Result<()> {
-        // For write to block, this would mean that pipe is full.
-        // If all every release are pair with an acquire, then this cannot
-        // happen.
-        //
-        // If it does happen, it is likely a bug in the program using this
-        // crate or some other programs that use the same jobserver have a
-        // bug in their code.
-        //
-        // If that turns out to not be the case we'll get an error anyway!
-        let mut write = self.write.as_ref().unwrap_or(&self.read);
-        match write.write(&[b'+'])? {
-            1 => Ok(()),
-            _ => Err(io::Error::from(io::ErrorKind::UnexpectedEof)),
-        }
-    }
-}
-
-fn cvt(t: c_int) -> io::Result<c_int> {
-    if t == -1 {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(t)
-    }
-}
-
-fn is_pipe(file: &File) -> Option<bool> {
-    Some(file.metadata().ok()?.file_type().is_fifo())
-}
-
-fn get_access_mode(file: &File) -> Option<c_int> {
-    let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) };
-    if ret == -1 {
-        return None;
-    }
-
-    Some(ret & libc::O_ACCMODE)
-}
diff --git a/src/parallel/job_token/windows.rs b/src/parallel/job_token/windows.rs
deleted file mode 100644
index 434fe16..0000000
--- a/src/parallel/job_token/windows.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-use std::{ffi::CString, io, ptr, str};
-
-use crate::windows::windows_sys::{
-    OpenSemaphoreA, ReleaseSemaphore, WaitForSingleObject, FALSE, HANDLE, SEMAPHORE_MODIFY_STATE,
-    THREAD_SYNCHRONIZE, WAIT_ABANDONED, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
-};
-
-pub(super) struct JobServerClient {
-    sem: HANDLE,
-}
-
-unsafe impl Sync for JobServerClient {}
-unsafe impl Send for JobServerClient {}
-
-impl JobServerClient {
-    pub(super) unsafe fn open(var: &[u8]) -> Option<Self> {
-        let var = str::from_utf8(var).ok()?;
-        if !var.is_ascii() {
-            // `OpenSemaphoreA` only accepts ASCII, not utf-8.
-            //
-            // Upstream implementation jobserver and jobslot also uses the
-            // same function and they works without problem, so there's no
-            // motivation to support utf-8 here using `OpenSemaphoreW`
-            // which only makes the code harder to maintain by making it more
-            // different than upstream.
-            return None;
-        }
-
-        let name = CString::new(var).ok()?;
-
-        let sem = OpenSemaphoreA(
-            THREAD_SYNCHRONIZE | SEMAPHORE_MODIFY_STATE,
-            FALSE,
-            name.as_bytes().as_ptr(),
-        );
-        if sem != ptr::null_mut() {
-            Some(Self { sem })
-        } else {
-            None
-        }
-    }
-
-    pub(super) fn try_acquire(&self) -> io::Result<Option<()>> {
-        match unsafe { WaitForSingleObject(self.sem, 0) } {
-            WAIT_OBJECT_0 => Ok(Some(())),
-            WAIT_TIMEOUT => Ok(None),
-            WAIT_FAILED => Err(io::Error::last_os_error()),
-            // We believe this should be impossible for a semaphore, but still
-            // check the error code just in case it happens.
-            WAIT_ABANDONED => Err(io::Error::new(
-                io::ErrorKind::Other,
-                "Wait on jobserver semaphore returned WAIT_ABANDONED",
-            )),
-            _ => unreachable!("Unexpected return value from WaitForSingleObject"),
-        }
-    }
-
-    pub(super) fn release(&self) -> io::Result<()> {
-        // SAFETY: ReleaseSemaphore will write to prev_count is it is Some
-        // and release semaphore self.sem by 1.
-        let r = unsafe { ReleaseSemaphore(self.sem, 1, ptr::null_mut()) };
-        if r != 0 {
-            Ok(())
-        } else {
-            Err(io::Error::last_os_error())
-        }
-    }
-}
diff --git a/src/parallel/stderr.rs b/src/parallel/stderr.rs
index 2b85772..47fa085 100644
--- a/src/parallel/stderr.rs
+++ b/src/parallel/stderr.rs
@@ -39,16 +39,6 @@ fn set_flags(fd: std::os::unix::io::RawFd, flags: std::os::raw::c_int) -> Result
     }
 }
 
-#[cfg(unix)]
-pub fn set_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> {
-    // On Unix, switch the pipe to non-blocking mode.
-    // On Windows, we have a different way to be non-blocking.
-    let fd = pipe.as_raw_fd();
-
-    let flags = get_flags(fd)?;
-    set_flags(fd, flags & (!libc::O_NONBLOCK))
-}
-
 #[cfg(unix)]
 pub fn set_non_blocking(pipe: &impl std::os::unix::io::AsRawFd) -> Result<(), Error> {
     // On Unix, switch the pipe to non-blocking mode.
diff --git a/src/tool.rs b/src/tool.rs
index 39131ea..bb7de51 100644
--- a/src/tool.rs
+++ b/src/tool.rs
@@ -53,7 +53,6 @@ impl Tool {
         )
     }
 
-    #[cfg(windows)]
     /// Explicitly set the `ToolFamily`, skipping name-based detection.
     pub(crate) fn with_family(path: PathBuf, family: ToolFamily) -> Self {
         Self {
@@ -82,7 +81,7 @@ impl Tool {
 
             let stdout = match run_output(
                 &mut cmd,
-                &path.to_string_lossy(),
+                path,
                 // tool detection issues should always be shown as warnings
                 cargo_output,
             )
diff --git a/src/windows/find_tools.rs b/src/windows/find_tools.rs
index 9c65115..6f02446 100644
--- a/src/windows/find_tools.rs
+++ b/src/windows/find_tools.rs
@@ -8,20 +8,34 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-//! A helper module to probe the Windows Registry when looking for
-//! windows-specific tools.
+//! A helper module to looking for windows-specific tools:
+//! 1. On Windows host, probe the Windows Registry if needed;
+//! 2. On non-Windows host, check specified environment variables.
 
 #![allow(clippy::upper_case_acronyms)]
 
 use std::process::Command;
 
 use crate::Tool;
-#[cfg(windows)]
 use crate::ToolFamily;
 
-#[cfg(windows)]
 const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
 
+#[derive(Copy, Clone)]
+struct TargetArch<'a>(pub &'a str);
+
+impl PartialEq<&str> for TargetArch<'_> {
+    fn eq(&self, other: &&str) -> bool {
+        self.0 == *other
+    }
+}
+
+impl<'a> From<TargetArch<'a>> for &'a str {
+    fn from(target: TargetArch<'a>) -> Self {
+        target.0
+    }
+}
+
 /// Attempts to find a tool within an MSVC installation using the Windows
 /// registry as a point to search from.
 ///
@@ -41,13 +55,6 @@ pub fn find(target: &str, tool: &str) -> Option<Command> {
 /// Similar to the `find` function above, this function will attempt the same
 /// operation (finding a MSVC tool in a local install) but instead returns a
 /// `Tool` which may be introspected.
-#[cfg(not(windows))]
-pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
-    None
-}
-
-/// Documented above.
-#[cfg(windows)]
 pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
     // This logic is all tailored for MSVC, if we're not that then bail out
     // early.
@@ -56,15 +63,16 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
     }
 
     // Split the target to get the arch.
-    let target = impl_::TargetArch(target.split_once('-')?.0);
+    let target = TargetArch(target.split_once('-')?.0);
 
     // Looks like msbuild isn't located in the same location as other tools like
-    // cl.exe and lib.exe. To handle this we probe for it manually with
-    // dedicated registry keys.
+    // cl.exe and lib.exe.
     if tool.contains("msbuild") {
         return impl_::find_msbuild(target);
     }
 
+    // Looks like devenv isn't located in the same location as other tools like
+    // cl.exe and lib.exe.
     if tool.contains("devenv") {
         return impl_::find_devenv(target);
     }
@@ -80,7 +88,6 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
         .or_else(|| impl_::find_msvc_15plus(tool, target))
         .or_else(|| impl_::find_msvc_14(tool, target))
         .or_else(|| impl_::find_msvc_12(tool, target))
-        .or_else(|| impl_::find_msvc_11(tool, target))
 }
 
 /// A version of Visual Studio
@@ -103,17 +110,8 @@ pub enum VsVers {
 ///
 /// This is used by the cmake crate to figure out the correct
 /// generator.
-#[cfg(not(windows))]
-pub fn find_vs_version() -> Result<VsVers, String> {
-    Err("not windows".to_string())
-}
-
-/// Documented above
-#[cfg(windows)]
 pub fn find_vs_version() -> Result<VsVers, String> {
-    use std::env;
-
-    match env::var("VisualStudioVersion") {
+    match std::env::var("VisualStudioVersion") {
         Ok(version) => match &version[..] {
             "17.0" => Ok(VsVers::Vs17),
             "16.0" => Ok(VsVers::Vs16),
@@ -157,6 +155,7 @@ pub fn find_vs_version() -> Result<VsVers, String> {
     }
 }
 
+/// Windows Implementation.
 #[cfg(windows)]
 mod impl_ {
     use crate::windows::com;
@@ -180,24 +179,9 @@ mod impl_ {
     use std::sync::atomic::{AtomicBool, Ordering};
     use std::sync::Once;
 
-    use super::MSVC_FAMILY;
+    use super::{TargetArch, MSVC_FAMILY};
     use crate::Tool;
 
-    #[derive(Copy, Clone)]
-    pub struct TargetArch<'a>(pub &'a str);
-
-    impl PartialEq<&str> for TargetArch<'_> {
-        fn eq(&self, other: &&str) -> bool {
-            self.0 == *other
-        }
-    }
-
-    impl<'a> From<TargetArch<'a>> for &'a str {
-        fn from(target: TargetArch<'a>) -> Self {
-            target.0
-        }
-    }
-
     struct MsvcTool {
         tool: PathBuf,
         libs: Vec<PathBuf>,
@@ -312,7 +296,7 @@ mod impl_ {
     }
 
     /// Attempt to find the tool using environment variables set by vcvars.
-    pub fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msvc_environment(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
         // Early return if the environment doesn't contain a VC install.
         if env::var_os("VCINSTALLDIR").is_none() {
             return None;
@@ -464,7 +448,7 @@ mod impl_ {
             .collect()
     }
 
-    pub fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msvc_15plus(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
         let iter = vs15plus_instances(target)?;
         iter.into_iter()
             .filter_map(|instance| {
@@ -649,7 +633,7 @@ mod impl_ {
 
     // For MSVC 14 we need to find the Universal CRT as well as either
     // the Windows 10 SDK or Windows 8.1 SDK.
-    pub fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msvc_14(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
         let vcdir = get_vc_dir("14.0")?;
         let mut tool = get_tool(tool, &vcdir, target)?;
         add_sdks(&mut tool, target)?;
@@ -699,7 +683,7 @@ mod impl_ {
     }
 
     // For MSVC 12 we need to find the Windows 8.1 SDK.
-    pub fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msvc_12(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
         let vcdir = get_vc_dir("12.0")?;
         let mut tool = get_tool(tool, &vcdir, target)?;
         let sub = lib_subdir(target)?;
@@ -714,22 +698,6 @@ mod impl_ {
         Some(tool.into_tool())
     }
 
-    // For MSVC 11 we need to find the Windows 8 SDK.
-    pub fn find_msvc_11(tool: &str, target: TargetArch<'_>) -> Option<Tool> {
-        let vcdir = get_vc_dir("11.0")?;
-        let mut tool = get_tool(tool, &vcdir, target)?;
-        let sub = lib_subdir(target)?;
-        let sdk8 = get_sdk8_dir()?;
-        tool.path.push(sdk8.join("bin").join(sub));
-        let sdk_lib = sdk8.join("lib").join("win8");
-        tool.libs.push(sdk_lib.join("um").join(sub));
-        let sdk_include = sdk8.join("include");
-        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())
-    }
-
     fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
         let prev = env::var_os(env).unwrap_or(OsString::new());
         let prev = env::split_paths(&prev);
@@ -852,13 +820,6 @@ mod impl_ {
         Some(root.into())
     }
 
-    fn get_sdk8_dir() -> Option<PathBuf> {
-        let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
-        let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
-        let root = key.query_str("InstallationFolder").ok()?;
-        Some(root.into())
-    }
-
     const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
     const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
     const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
@@ -969,7 +930,7 @@ mod impl_ {
         max_key
     }
 
-    pub fn has_msbuild_version(version: &str) -> bool {
+    pub(super) fn has_msbuild_version(version: &str) -> bool {
         match version {
             "17.0" => {
                 find_msbuild_vs17(TargetArch("x86_64")).is_some()
@@ -996,7 +957,7 @@ mod impl_ {
         }
     }
 
-    pub fn find_devenv(target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_devenv(target: TargetArch<'_>) -> Option<Tool> {
         find_devenv_vs15(target)
     }
 
@@ -1005,7 +966,7 @@ mod impl_ {
     }
 
     // see http://stackoverflow.com/questions/328017/path-to-msbuild
-    pub fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> {
+    pub(super) fn find_msbuild(target: TargetArch<'_>) -> Option<Tool> {
         // VS 15 (2017) changed how to locate msbuild
         if let Some(r) = find_msbuild_vs17(target) {
             Some(r)
@@ -1041,3 +1002,70 @@ mod impl_ {
             })
     }
 }
+
+/// Non-Windows Implementation.
+#[cfg(not(windows))]
+mod impl_ {
+    use std::{env, ffi::OsString};
+
+    use super::{TargetArch, MSVC_FAMILY};
+    use crate::Tool;
+
+    /// Finding msbuild.exe tool under unix system is not currently supported.
+    /// Maybe can check it using an environment variable looks like `MSBUILD_BIN`.
+    pub(super) fn find_msbuild(_target: TargetArch<'_>) -> Option<Tool> {
+        None
+    }
+
+    // Finding devenv.exe tool under unix system is not currently supported.
+    // Maybe can check it using an environment variable looks like `DEVENV_BIN`.
+    pub(super) fn find_devenv(_target: TargetArch<'_>) -> Option<Tool> {
+        None
+    }
+
+    /// Attempt to find the tool using environment variables set by vcvars.
+    pub(super) fn find_msvc_environment(tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+        // Early return if the environment doesn't contain a VC install.
+        let vc_install_dir = env::var_os("VCINSTALLDIR")?;
+        let vs_install_dir = env::var_os("VSINSTALLDIR")?;
+
+        let get_tool = |install_dir: OsString| {
+            env::split_paths(&install_dir)
+                .map(|p| p.join(tool))
+                .find(|p| p.exists())
+                .map(|path| Tool::with_family(path.into(), MSVC_FAMILY))
+        };
+
+        // Take the path of tool for the vc install directory.
+        get_tool(vc_install_dir)
+            // Take the path of tool for the vs install directory.
+            .or_else(|| get_tool(vs_install_dir))
+            // Take the path of tool for the current path environment.
+            .or_else(|| env::var_os("PATH").and_then(|path| get_tool(path)))
+    }
+
+    pub(super) fn find_msvc_15plus(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+        None
+    }
+
+    // For MSVC 14 we need to find the Universal CRT as well as either
+    // the Windows 10 SDK or Windows 8.1 SDK.
+    pub(super) fn find_msvc_14(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+        None
+    }
+
+    // For MSVC 12 we need to find the Windows 8.1 SDK.
+    pub(super) fn find_msvc_12(_tool: &str, _target: TargetArch<'_>) -> Option<Tool> {
+        None
+    }
+
+    pub(super) fn has_msbuild_version(version: &str) -> bool {
+        match version {
+            "17.0" => false,
+            "16.0" => false,
+            "15.0" => false,
+            "12.0" | "14.0" => false,
+            _ => false,
+        }
+    }
+}


Test patch:
diff --git a/tests/test.rs b/tests/test.rs
index f8c950d..f48e05d 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -617,3 +617,33 @@ fn compile_intermediates() {
     assert!(intermediates[1].display().to_string().contains("x86_64"));
     assert!(intermediates[2].display().to_string().contains("x86_64"));
 }
+
+#[test]
+fn clang_android() {
+    let target = "arm-linux-androideabi";
+
+    // On Windows, we don't use the Android NDK shims for Clang, so verify that
+    // we use "clang" and set the target correctly.
+    #[cfg(windows)]
+    {
+        let test = Test::new();
+        test.shim("clang").shim("llvm-ar");
+        test.gcc()
+            .target(target)
+            .host("x86_64-pc-windows-msvc")
+            .file("foo.c")
+            .compile("foo");
+        test.cmd(0).must_have("--target=arm-linux-androideabi");
+    }
+
+    // On non-Windows, we do use the shims, so make sure that we use the shim
+    // and don't set the target.
+    #[cfg(not(windows))]
+    {
+        let test = Test::new();
+        test.shim("arm-linux-androideabi-clang")
+            .shim("arm-linux-androideabi-ar");
+        test.gcc().target(target).file("foo.c").compile("foo");
+        test.cmd(0).must_not_have("--target=arm-linux-androideabi");
+    }
+}