Detect MSVC -showIncludes prefix (not actually wired up to anything yet)

This commit is contained in:
Ted Mielczarek 2016-06-21 07:42:22 -04:00
Родитель 0f4c6b888e
Коммит 86aa35b191
5 изменённых файлов: 125 добавлений и 11 удалений

2
Cargo.lock сгенерированный
Просмотреть файл

@ -5,6 +5,7 @@ dependencies = [
"clap 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -14,6 +15,7 @@ dependencies = [
"rusoto 0.13.1 (git+https://github.com/rusoto/rusoto?branch=feature_mutex_credentials)",
"sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
]

Просмотреть файл

@ -15,6 +15,8 @@ protobuf = "1.0.18"
retry = "0.4.0"
sha1 = "0.1.1"
tempdir = "0.3.4"
winapi = "0.2"
kernel32-sys = "0.2.2"
zip = { version = "0.1", default-features = false }
[dependencies.rusoto]

Просмотреть файл

@ -22,6 +22,7 @@ use compiler::{
gcc,
msvc,
};
use compiler::msvc::maybe_str;
use filetime::FileTime;
use log::LogLevel::Trace;
use mock_command::{
@ -49,14 +50,17 @@ use std::thread;
use tempdir::TempDir;
/// Supported compilers.
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone)]
pub enum CompilerKind {
/// GCC
Gcc,
/// clang
Clang,
/// Microsoft Visual C++
Msvc,
Msvc {
/// The prefix used in the output of `-showIncludes`.
includes_prefix: Vec<u8>,
},
}
impl CompilerKind {
@ -66,7 +70,7 @@ impl CompilerKind {
// accept different sets of arguments.
&CompilerKind::Gcc => gcc::parse_arguments(arguments, gcc::argument_takes_value),
&CompilerKind::Clang => gcc::parse_arguments(arguments, clang::argument_takes_value),
&CompilerKind::Msvc => msvc::parse_arguments(arguments),
&CompilerKind::Msvc { .. } => msvc::parse_arguments(arguments),
}
}
@ -76,7 +80,7 @@ impl CompilerKind {
// GCC and clang use the same preprocessor invocation.
gcc::preprocess(creator, compiler, parsed_args, cwd)
},
&CompilerKind::Msvc => msvc::preprocess(creator, compiler, parsed_args, cwd),
&CompilerKind::Msvc { .. } => msvc::preprocess(creator, compiler, parsed_args, cwd),
}
}
@ -84,7 +88,7 @@ impl CompilerKind {
match self {
&CompilerKind::Gcc => gcc::compile(creator, compiler, preprocessor_output, parsed_args, cwd),
&CompilerKind::Clang => clang::compile(creator, compiler, preprocessor_output, parsed_args, cwd),
&CompilerKind::Msvc => msvc::compile(creator, compiler, preprocessor_output, parsed_args, cwd),
&CompilerKind::Msvc { .. } => msvc::compile(creator, compiler, preprocessor_output, parsed_args, cwd),
}
}
}
@ -332,7 +336,11 @@ gcc
return Ok(Some(CompilerKind::Clang));
} else if line == "msvc" {
trace!("Found MSVC");
return Ok(Some(CompilerKind::Msvc));
let prefix = try!(msvc::detect_showincludes_prefix(&mut creator, &executable));
trace!("showIncludes prefix: '{}'", maybe_str(&prefix));
return Ok(Some(CompilerKind::Msvc {
includes_prefix: prefix,
}));
}
}
Ok(None)
@ -427,8 +435,19 @@ mod test {
#[test]
fn test_detect_compiler_kind_msvc() {
let creator = new_creator();
let f = TestFixture::new();
let srcfile = f.touch("stdio.h").unwrap();
let mut s = srcfile.to_str().unwrap();
if s.starts_with("\\\\?\\") {
s = &s[4..];
}
let prefix = "blah: ";
let stdout = format!("{}{}\r\n", prefix, s);
// Compiler detection output
next_command(&creator, Ok(MockChild::new(exit_status(0), "foo\nmsvc\nbar", "")));
assert_eq!(Some(CompilerKind::Msvc), detect_compiler_kind(creator.clone(), "/foo/bar"));
// showincludes prefix detection output
next_command(&creator, Ok(MockChild::new(exit_status(0), &stdout, &String::new())));
assert_eq!(Some(CompilerKind::Msvc { includes_prefix: prefix.as_bytes().iter().map(|&b| b).collect() }), detect_compiler_kind(creator.clone(), "/foo/bar"));
}
#[test]

Просмотреть файл

@ -25,7 +25,9 @@ use mock_command::{
CommandCreatorSync,
RunCommand,
};
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{
self,
@ -34,9 +36,76 @@ use std::io::{
Write,
};
use std::path::Path;
use std::process;
use std::process::{self,Stdio};
use std::str;
use tempdir::TempDir;
#[cfg(windows)]
fn file_exists(filename: &[u8]) -> bool {
use kernel32;
use std::ffi::CString;
use winapi::fileapi::INVALID_FILE_ATTRIBUTES;
CString::new(filename).map(|c| {
unsafe { kernel32::GetFileAttributesA(c.as_ptr()) != INVALID_FILE_ATTRIBUTES }
})
.unwrap_or(false)
}
#[cfg(not(windows))]
fn file_exists(filename: &[u8]) -> bool {
use std::os::unix::ffi::OsStrExt;
Path::new(OsStr::from_bytes(filename)).exists()
}
pub fn maybe_str(bytes: &[u8]) -> Cow<str> {
str::from_utf8(bytes)
.map(|s| Cow::Borrowed(s))
.unwrap_or(Cow::Owned(format!("{:?}", bytes)))
}
/// Detect the prefix included in the output of MSVC's -showIncludes output.
pub fn detect_showincludes_prefix<T : CommandCreatorSync, U: AsRef<OsStr>>(mut creator: &mut T, exe: U) -> io::Result<Vec<u8>> {
let tempdir = try!(TempDir::new("sccache"));
let input = tempdir.path().join("test.c");
{
try!(File::create(&input)
.and_then(|mut f| f.write_all(b"#include <stdio.h>\n")))
}
let mut cmd = creator.new_command_sync(&exe);
cmd.args(&["-nologo", "-showIncludes", "-c", "-Fonul"])
.arg(&input)
// The MSDN docs say the -showIncludes output goes to stderr,
// but that's just not true.
.stdout(Stdio::piped())
.stderr(Stdio::null());
if log_enabled!(Trace) {
trace!("detect_showincludes_prefix: {:?}", cmd);
}
let output = try!(run_input_output(cmd, None));
if output.status.success() {
let process::Output { stdout, .. } = output;
for line in stdout.split(|&b| b == b'\n') {
if line.ends_with(b"stdio.h\r") {
for (i, c) in line.iter().enumerate().rev() {
if *c == b' ' {
let len = line.len();
// See if the rest of this line is a full pathname.
if file_exists(&line[i+1..len-1]) {
// Everything from the beginning of the line
// to this index is the prefix.
return Ok(line[..i+1].iter().map(|&b| b).collect());
}
}
}
}
}
}
return Err(Error::new(ErrorKind::Other, "Failed to detect showIncludes prefix"));
}
pub fn parse_arguments(arguments: &[String]) -> CompilerArguments {
let mut output_arg = None;
let mut input_arg = None;
@ -219,11 +288,31 @@ pub fn compile<T : CommandCreatorSync>(mut creator: T, compiler: &Compiler, prep
#[cfg(test)]
mod test {
use super::*;
use env_logger;
use std::collections::HashMap;
use mock_command::*;
use ::compiler::*;
use test::utils::*;
#[test]
fn test_detect_showincludes_prefix() {
match env_logger::init() {
Ok(_) => {},
Err(_) => {},
}
let mut creator = new_creator();
let f = TestFixture::new();
let srcfile = f.touch("stdio.h").unwrap();
let mut s = srcfile.to_str().unwrap();
if s.starts_with("\\\\?\\") {
s = &s[4..];
}
let stdout = format!("blah: {}\r\n", s);
let stderr = String::from("some\r\nstderr\r\n");
next_command(&creator, Ok(MockChild::new(exit_status(0), &stdout, &stderr)));
assert_eq!(&b"blah: "[..], AsRef::<[u8]>::as_ref(&detect_showincludes_prefix(&mut creator, "cl.exe").unwrap()));
}
#[test]
fn test_parse_arguments_simple() {
match parse_arguments(&stringvec!["-c", "foo.c", "-Fofoo.obj"]) {
@ -346,7 +435,7 @@ mod test {
common_args: vec!(),
};
let compiler = Compiler::new(f.bins[0].to_str().unwrap(),
CompilerKind::Msvc).unwrap();
CompilerKind::Msvc { includes_prefix: vec!() }).unwrap();
// Compiler invocation.
next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
let (cacheable, _) = compile(creator.clone(), &compiler, vec!(), &parsed_args, f.tempdir.path().to_str().unwrap()).unwrap();
@ -369,7 +458,7 @@ mod test {
common_args: vec!(),
};
let compiler = Compiler::new(f.bins[0].to_str().unwrap(),
CompilerKind::Msvc).unwrap();
CompilerKind::Msvc { includes_prefix: vec!() }).unwrap();
// Compiler invocation.
next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
let (cacheable, _) = compile(creator.clone(), &compiler, vec!(), &parsed_args, f.tempdir.path().to_str().unwrap()).unwrap();
@ -390,7 +479,7 @@ mod test {
common_args: vec!(),
};
let compiler = Compiler::new(f.bins[0].to_str().unwrap(),
CompilerKind::Msvc).unwrap();
CompilerKind::Msvc { includes_prefix: vec!() }).unwrap();
// First compiler invocation fails.
next_command(&creator, Ok(MockChild::new(exit_status(1), "", "")));
// Second compiler invocation succeeds.

Просмотреть файл

@ -15,6 +15,7 @@
extern crate clap;
extern crate env_logger;
extern crate filetime;
extern crate kernel32;
#[macro_use] extern crate log;
extern crate libc;
extern crate mio;
@ -24,6 +25,7 @@ extern crate retry;
extern crate rusoto;
extern crate sha1;
extern crate tempdir;
extern crate winapi;
extern crate zip;
// To get macros in scope, this has to be first.