diff --git a/src/commands.rs b/src/commands.rs index 0defb8c3e..6543f4b08 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -680,13 +680,14 @@ pub fn run_command(cmd: Command) -> Result { let runtime = Runtime::new()?; let jobserver = unsafe { Client::new() }; let creator = ProcessCommandCreator::new(&jobserver); + let args: Vec<_> = env::args_os().collect(); let env: Vec<_> = env::vars_os().collect(); let out_file = File::create(out)?; let cwd = env::current_dir().expect("A current working dir should exist"); let pool = runtime.handle().clone(); runtime.block_on(async move { - compiler::get_compiler_info(creator, &executable, &cwd, &env, &pool, None) + compiler::get_compiler_info(creator, &executable, &cwd, &args, &env, &pool, None) .await .map(|compiler| compiler.0.get_toolchain_packager()) .and_then(|packager| packager.write_pkg(out_file)) diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index d099ef339..507ba3000 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -31,7 +31,7 @@ use crate::mock_command::{exit_status, CommandChild, CommandCreatorSync, RunComm use crate::util::{fmt_duration_as_secs, ref_env, run_input_output}; use filetime::FileTime; use std::borrow::Cow; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fmt; #[cfg(feature = "dist-client")] use std::fs; @@ -860,11 +860,24 @@ pub async fn write_temp_file( .context("failed to write temporary file") } +/// Returns true if the given path looks like a program known to have +/// a rustc compatible interface. +fn is_rustc_like>(p: P) -> bool { + matches!( + p.as_ref() + .file_stem() + .map(|s| s.to_string_lossy().to_lowercase()) + .as_deref(), + Some("rustc") | Some("clippy-driver") + ) +} + /// If `executable` is a known compiler, return `Some(Box)`. async fn detect_compiler( creator: T, executable: &Path, cwd: &Path, + args: &[OsString], env: &[(OsString, OsString)], pool: &tokio::runtime::Handle, dist_archive: Option, @@ -875,109 +888,127 @@ where trace!("detect_compiler: {}", executable.display()); // First, see if this looks like rustc. - let filename = match executable.file_stem() { - None => bail!("could not determine compiler kind"), - Some(f) => f, + + let rustc_executable = if is_rustc_like(executable) { + Some(executable.to_path_buf()) + } else if env.iter().any(|(k, _)| k == OsStr::new("CARGO")) { + // If not, detect the scenario where cargo is configured to wrap rustc with something other than sccache. + // This happens when sccache is used as a `RUSTC_WRAPPER` and another tool is used as a + // `RUSTC_WORKSPACE_WRAPPER`. In that case rustc will be the first argument rather than the command. + // + // The check for the `CARGO` env acts as a guardrail against false positives. + // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads + args.iter().next().and_then(|arg1| { + if is_rustc_like(arg1) { + Some(PathBuf::from(arg1)) + } else { + None + } + }) + } else { + None }; - let filename = filename.to_string_lossy().to_lowercase(); - let rustc_vv = if filename == "rustc" || filename == "clippy-driver" { + let creator1 = creator.clone(); + let pool = pool.clone(); + let cwd = cwd.to_owned(); + if let Some(rustc_executable) = rustc_executable { // Sanity check that it's really rustc. - let executable = executable.to_path_buf(); let mut child = creator.clone().new_command_sync(executable); + // We're wrapping rustc if the executable doesn't match the detected rustc_executable. In this case the wrapper + // expects rustc as the first argument. + if rustc_executable != executable { + child.arg(&rustc_executable); + } + child.env_clear().envs(ref_env(env)).args(&["-vV"]); - run_input_output(child, None).await.map(|output| { + let rustc_vv = run_input_output(child, None).await.map(|output| { if let Ok(stdout) = String::from_utf8(output.stdout.clone()) { if stdout.starts_with("rustc ") { - return Some(Ok(stdout)); + return Ok(stdout); } } - Some(Err(ProcessError(output))) - })? - } else { - None - }; + Err(ProcessError(output)) + })?; - let creator1 = creator.clone(); - let executable = executable.to_owned(); - let executable2 = executable.clone(); - let pool = pool.clone(); - let cwd = cwd.to_owned(); - match rustc_vv { - Some(Ok(rustc_verbose_version)) => { - debug!("Found rustc"); - - let proxy = RustupProxy::find_proxy_executable::( - &executable2, - "rustup", - creator.clone(), - env, - ); - use futures::TryFutureExt; - let res = proxy.and_then(move |proxy| async move { - match proxy { - Ok(Some(proxy)) => { - trace!("Found rustup proxy executable"); - // take the pathbuf for rustc as resolved by the proxy - match proxy.resolve_proxied_executable(creator1, cwd, env).await { - Ok((resolved_path, _time)) => { - trace!("Resolved path with rustup proxy {:?}", &resolved_path); - Ok((Some(proxy), resolved_path)) - } - Err(e) => { - trace!("Could not resolve compiler with rustup proxy: {}", e); - Ok((None, executable)) + match rustc_vv { + Ok(rustc_verbose_version) => { + debug!("Found rustc"); + + let rustc_executable2 = rustc_executable.clone(); + + let proxy = RustupProxy::find_proxy_executable::( + &rustc_executable2, + "rustup", + creator.clone(), + env, + ); + use futures::TryFutureExt; + let res = proxy.and_then(move |proxy| async move { + match proxy { + Ok(Some(proxy)) => { + trace!("Found rustup proxy executable"); + // take the pathbuf for rustc as resolved by the proxy + match proxy.resolve_proxied_executable(creator1, cwd, env).await { + Ok((resolved_path, _time)) => { + trace!("Resolved path with rustup proxy {:?}", &resolved_path); + Ok((Some(proxy), resolved_path)) + } + Err(e) => { + trace!("Could not resolve compiler with rustup proxy: {}", e); + Ok((None, rustc_executable)) + } } } + Ok(None) => { + trace!("Did not find rustup"); + Ok((None, rustc_executable)) + } + Err(e) => { + trace!("Did not find rustup due to {}, compiling without proxy", e); + Ok((None, rustc_executable)) + } } - Ok(None) => { - trace!("Did not find rustup"); - Ok((None, executable)) - } - Err(e) => { - trace!("Did not find rustup due to {}, compiling without proxy", e); - Ok((None, executable)) - } - } - }); + }); - let (proxy, resolved_rustc) = res + let (proxy, resolved_rustc) = res + .await + .map(|(proxy, resolved_compiler_executable)| { + ( + proxy + .map(Box::new) + .map(|x: Box| x as Box>), + resolved_compiler_executable, + ) + }) + .unwrap_or_else(|_e| { + trace!("Compiling rust without proxy"); + (None, rustc_executable2) + }); + + Rust::new( + creator, + resolved_rustc, + env, + &rustc_verbose_version, + dist_archive, + pool, + ) .await - .map(|(proxy, resolved_compiler_executable)| { + .map(|c| { ( - proxy - .map(Box::new) - .map(|x: Box| x as Box>), - resolved_compiler_executable, + Box::new(c) as Box>, + proxy as Option>>, ) }) - .unwrap_or_else(|_e| { - trace!("Compiling rust without proxy"); - (None, executable2) - }); - - Rust::new( - creator, - resolved_rustc, - env, - &rustc_verbose_version, - dist_archive, - pool, - ) - .await - .map(|c| { - ( - Box::new(c) as Box>, - proxy as Option>>, - ) - }) - } - Some(Err(e)) => Err(e).context("Failed to launch subprocess for compiler determination"), - None => { - let cc = detect_c_compiler(creator, executable, env.to_vec(), pool).await; - cc.map(|c| (c, None)) + } + Err(e) => Err(e).context("Failed to launch subprocess for compiler determination"), } + } else { + let executable = executable.to_owned(); + let cc = detect_c_compiler(creator, executable, env.to_vec(), pool).await; + cc.map(|c| (c, None)) } } @@ -1160,6 +1191,7 @@ pub async fn get_compiler_info( creator: T, executable: &Path, cwd: &Path, + args: &[OsString], env: &[(OsString, OsString)], pool: &tokio::runtime::Handle, dist_archive: Option, @@ -1168,7 +1200,7 @@ where T: CommandCreatorSync, { let pool = pool.clone(); - detect_compiler(creator, executable, cwd, env, &pool, dist_archive).await + detect_compiler(creator, executable, cwd, args, env, &pool, dist_archive).await } #[cfg(test)] @@ -1192,7 +1224,7 @@ mod test { let runtime = single_threaded_runtime(); let pool = runtime.handle(); next_command(&creator, Ok(MockChild::new(exit_status(0), "\n\ngcc", ""))); - let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1206,7 +1238,7 @@ mod test { let runtime = single_threaded_runtime(); let pool = runtime.handle(); next_command(&creator, Ok(MockChild::new(exit_status(0), "clang\n", ""))); - let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1234,7 +1266,7 @@ mod test { &creator, Ok(MockChild::new(exit_status(0), &stdout, &String::new())), ); - let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1248,7 +1280,7 @@ mod test { let runtime = single_threaded_runtime(); let pool = runtime.handle(); next_command(&creator, Ok(MockChild::new(exit_status(0), "nvcc\n", ""))); - let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1261,13 +1293,41 @@ mod test { // Windows uses bin, everything else uses lib. Just create both. fs::create_dir(f.tempdir.path().join("lib")).unwrap(); fs::create_dir(f.tempdir.path().join("bin")).unwrap(); - let rustc = f.mk_bin("rustc").unwrap(); + let rustc = f.mk_bin("rustc.exe").unwrap(); let creator = new_creator(); let runtime = single_threaded_runtime(); let pool = runtime.handle(); + populate_rustc_command_mock(&creator, &f); + let c = detect_compiler(creator, &rustc, f.tempdir.path(), &[], &[], pool, None) + .wait() + .unwrap() + .0; + assert_eq!(CompilerKind::Rust, c.kind()); + } + + #[test] + fn test_is_rustc_like() { + assert!(is_rustc_like("rustc")); + assert!(is_rustc_like("rustc.exe")); + assert!(is_rustc_like("/path/to/rustc.exe")); + assert!(is_rustc_like("/path/to/rustc")); + assert!(is_rustc_like("/PATH/TO/RUSTC.EXE")); + assert!(is_rustc_like("/Path/To/RustC.Exe")); + assert!(is_rustc_like("/path/to/clippy-driver")); + assert!(is_rustc_like("/path/to/clippy-driver.exe")); + assert!(is_rustc_like("/PATH/TO/CLIPPY-DRIVER.EXE")); + assert!(is_rustc_like("/Path/To/Clippy-Driver.Exe")); + assert!(!is_rustc_like("rust")); + assert!(!is_rustc_like("RUST")); + } + + fn populate_rustc_command_mock( + creator: &Arc>, + f: &TestFixture, + ) { // rustc --vV next_command( - &creator, + creator, Ok(MockChild::new( exit_status(0), "\ @@ -1283,14 +1343,66 @@ LLVM version: 6.0", ); // rustc --print=sysroot let sysroot = f.tempdir.path().to_str().unwrap(); - next_command(&creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); - next_command(&creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); - next_command(&creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); - let c = detect_compiler(creator, &rustc, f.tempdir.path(), &[], pool, None) - .wait() - .unwrap() - .0; + next_command(creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); + next_command(creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); + next_command(creator, Ok(MockChild::new(exit_status(0), &sysroot, ""))); + } + + #[test] + fn test_detect_compiler_kind_rustc_workspace_wrapper() { + let f = TestFixture::new(); + // Windows uses bin, everything else uses lib. Just create both. + fs::create_dir(f.tempdir.path().join("lib")).unwrap(); + fs::create_dir(f.tempdir.path().join("bin")).unwrap(); + let rustc = f.mk_bin("rustc-workspace-wrapper").unwrap(); + let creator = new_creator(); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); + populate_rustc_command_mock(&creator, &f); + let c = detect_compiler( + creator, + &rustc, + f.tempdir.path(), + // Specifying an extension tests the ignoring + &[OsString::from("rustc.exe")], + &[(OsString::from("CARGO"), OsString::from("CARGO"))], + pool, + None, + ) + .wait() + .unwrap() + .0; assert_eq!(CompilerKind::Rust, c.kind()); + + // Test we don't detect rustc if the first arg is not rustc + let creator = new_creator(); + populate_rustc_command_mock(&creator, &f); + assert!(detect_compiler( + creator, + &rustc, + f.tempdir.path(), + &[OsString::from("not-rustc")], + &[(OsString::from("CARGO"), OsString::from("CARGO"))], + pool, + None, + ) + .wait() + .is_err()); + + // Test we don't detect rustc if the CARGO env is not defined + let creator = new_creator(); + populate_rustc_command_mock(&creator, &f); + assert!(detect_compiler( + creator, + &rustc, + f.tempdir.path(), + &[OsString::from("rustc")], + &[], + pool, + None, + ) + .wait() + .is_err()); } #[test] @@ -1300,7 +1412,7 @@ LLVM version: 6.0", let runtime = single_threaded_runtime(); let pool = runtime.handle(); next_command(&creator, Ok(MockChild::new(exit_status(0), "\ndiab\n", ""))); - let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = detect_compiler(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1322,6 +1434,7 @@ LLVM version: 6.0", "/foo/bar".as_ref(), f.tempdir.path(), &[], + &[], pool, None ) @@ -1341,6 +1454,7 @@ LLVM version: 6.0", "/foo/bar".as_ref(), f.tempdir.path(), &[], + &[], pool, None ) @@ -1367,6 +1481,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], pool, None, ) @@ -1399,7 +1514,7 @@ LLVM version: 6.0", let f = TestFixture::new(); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); - let c = get_compiler_info(creator, &f.bins[0], f.tempdir.path(), &[], pool, None) + let c = get_compiler_info(creator, &f.bins[0], f.tempdir.path(), &[], &[], pool, None) .wait() .unwrap() .0; @@ -1423,6 +1538,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) @@ -1533,6 +1649,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) @@ -1639,6 +1756,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) @@ -1716,6 +1834,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) @@ -1833,6 +1952,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) @@ -1905,6 +2025,7 @@ LLVM version: 6.0", &f.bins[0], f.tempdir.path(), &[], + &[], &pool, None, ) diff --git a/src/server.rs b/src/server.rs index 4eb8713d3..28add920c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -880,7 +880,9 @@ where let env_vars = compile.env_vars; let me = self.clone(); - let info = self.compiler_info(exe.into(), cwd.clone(), &env_vars).await; + let info = self + .compiler_info(exe.into(), cwd.clone(), &cmd, &env_vars) + .await; Ok(me.check_compiler(info, cmd, cwd, env_vars).await) } @@ -890,6 +892,7 @@ where &self, path: PathBuf, cwd: PathBuf, + args: &[OsString], env: &[(OsString, OsString)], ) -> Result>> { trace!("compiler_info"); @@ -981,6 +984,7 @@ where me.creator.clone(), &path1, &cwd, + args, env.as_slice(), &me.rt, dist_info.clone().map(|(p, _)| p),