Browse Source

Merge remote-tracking branch 'origin/master' into peterhj-cuda-devel

Conflicts:
	src/lib.rs
cl-test
Peter Jin 7 years ago
parent
commit
bca8996006
  1. 7
      Cargo.toml
  2. 19
      README.md
  3. 525
      src/lib.rs
  4. 58
      tests/cc_env.rs
  5. 10
      tests/support/mod.rs
  6. 13
      tests/test.rs

7
Cargo.toml

@ -1,10 +1,11 @@
[package]
name = "cc"
version = "1.0.0"
version = "1.0.3"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
license = "MIT/Apache-2.0"
repository = "https://github.com/alexcrichton/cc-rs"
homepage = "https://github.com/alexcrichton/cc-rs"
documentation = "https://docs.rs/cc"
description = """
A build-time dependency for Cargo build scripts to assist in invoking the native
@ -16,8 +17,8 @@ readme = "README.md"
categories = ["development-tools"]
[badges]
travis-ci = { repository = "alexcrichton/gcc-rs" }
appveyor = { repository = "alexcrichton/gcc-rs" }
travis-ci = { repository = "alexcrichton/cc-rs" }
appveyor = { repository = "alexcrichton/cc-rs" }
[dependencies]
rayon = { version = "0.8", optional = true }

19
README.md

@ -97,6 +97,8 @@ and `HOST` variables.
## Optional features
### Parallel
Currently cc-rs supports parallel compilation (think `make -jN`) but this
feature is turned off by default. To enable cc-rs to compile C/C++ in parallel,
you can change your dependency to:
@ -176,8 +178,17 @@ fn main() {
## License
`cc-rs` is primarily distributed under the terms of both the MIT license and
the Apache License (Version 2.0), with portions covered by various BSD-like
licenses.
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
See LICENSE-APACHE, and LICENSE-MIT for details.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

525
src/lib.rs

@ -21,6 +21,22 @@
//!
//! [`Build`]: struct.Build.html
//!
//! # Parallelism
//!
//! To parallelize computation, enable the `parallel` feature for the crate.
//!
//! ```toml
//! [build-dependencies]
//! cc = { version = "1.0", features = ["parallel"] }
//! ```
//! To specify the max number of concurrent compilation jobs, set the `NUM_JOBS`
//! environment variable to the desired amount.
//!
//! Cargo will also set this environment variable when executed with the `-jN` flag.
//!
//! If `NUM_JOBS` is not set, the `RAYON_NUM_THREADS` environment variable can
//! also specify the build paralellism.
//!
//! # Examples
//!
//! Use the `Build` struct to compile `src/foo.c`:
@ -119,18 +135,21 @@ enum ErrorKind {
ToolNotFound,
}
/// Represents an internal error that occurred, with an explaination.
/// Represents an internal error that occurred, with an explanation.
#[derive(Clone, Debug)]
pub struct Error {
/// Describes the kind of error that occurred.
kind: ErrorKind,
/// More explaination of error that occurred.
/// More explanation of error that occurred.
message: String,
}
impl Error {
fn new(kind: ErrorKind, message: &str) -> Error {
Error { kind: kind, message: message.to_owned() }
Error {
kind: kind,
message: message.to_owned(),
}
}
}
@ -150,9 +169,11 @@ impl From<io::Error> for Error {
#[derive(Clone, Debug)]
pub struct Tool {
path: PathBuf,
cc_wrapper_path: Option<PathBuf>,
cc_wrapper_args: Vec<OsString>,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
family: ToolFamily
family: ToolFamily,
}
/// Represents the family of tools this tool belongs to.
@ -176,8 +197,7 @@ impl ToolFamily {
fn debug_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc => "/Z7",
ToolFamily::Gnu |
ToolFamily::Clang => "-g",
ToolFamily::Gnu | ToolFamily::Clang => "-g",
}
}
@ -185,8 +205,7 @@ impl ToolFamily {
fn include_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc => "/I",
ToolFamily::Gnu |
ToolFamily::Clang => "-I",
ToolFamily::Gnu | ToolFamily::Clang => "-I",
}
}
@ -194,8 +213,7 @@ impl ToolFamily {
fn expand_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc => "/E",
ToolFamily::Gnu |
ToolFamily::Clang => "-E",
ToolFamily::Gnu | ToolFamily::Clang => "-E",
}
}
@ -206,8 +224,7 @@ impl ToolFamily {
match *self {
ToolFamily::Msvc => &MSVC_FLAGS,
ToolFamily::Gnu |
ToolFamily::Clang => &GNU_CLANG_FLAGS,
ToolFamily::Gnu | ToolFamily::Clang => &GNU_CLANG_FLAGS,
}
}
@ -215,8 +232,7 @@ impl ToolFamily {
fn warnings_to_errors_flag(&self) -> &'static str {
match *self {
ToolFamily::Msvc => "/WX",
ToolFamily::Gnu |
ToolFamily::Clang => "-Werror"
ToolFamily::Gnu | ToolFamily::Clang => "-Werror",
}
}
@ -362,7 +378,10 @@ impl Build {
/// .compile("foo");
/// ```
pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build {
self.definitions.push((var.to_string(), val.into().map(|s| s.to_string())));
self.definitions.push((
var.to_string(),
val.into().map(|s| s.to_string()),
));
self
}
@ -413,15 +432,22 @@ impl Build {
let target = self.get_target()?;
let mut cfg = Build::new();
cfg.flag(flag)
.target(&target)
.opt_level(0)
.host(&target)
.debug(false)
.cpp(self.cpp)
.cuda(self.cuda);
.target(&target)
.opt_level(0)
.host(&target)
.debug(false)
.cpp(self.cpp);
.cuda(self.cuda);
let compiler = cfg.try_get_compiler()?;
let mut cmd = compiler.to_command();
command_add_output_file(&mut cmd, &obj, target.contains("msvc"), false);
// We need to explicitly tell msvc not to link and create an exe
// in the root directory of the crate
if target.contains("msvc") {
cmd.arg("/c");
}
cmd.arg(&src);
let output = cmd.output()?;
@ -497,8 +523,10 @@ impl Build {
/// Add files which will be compiled
pub fn files<P>(&mut self, p: P) -> &mut Build
where P: IntoIterator,
P::Item: AsRef<Path> {
where
P: IntoIterator,
P::Item: AsRef<Path>,
{
for file in p.into_iter() {
self.file(file);
}
@ -604,7 +632,10 @@ impl Build {
/// .cpp_link_stdlib("stdc++")
/// .compile("libfoo.so");
/// ```
pub fn cpp_link_stdlib<'a, V: Into<Option<&'a str>>>(&mut self, cpp_link_stdlib: V) -> &mut Build {
pub fn cpp_link_stdlib<'a, V: Into<Option<&'a str>>>(
&mut self,
cpp_link_stdlib: V,
) -> &mut Build {
self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(|s| s.into()));
self
}
@ -642,7 +673,10 @@ impl Build {
/// .cpp_set_stdlib("c++")
/// .compile("libfoo.a");
/// ```
pub fn cpp_set_stdlib<'a, V: Into<Option<&'a str>>>(&mut self, cpp_set_stdlib: V) -> &mut Build {
pub fn cpp_set_stdlib<'a, V: Into<Option<&'a str>>>(
&mut self,
cpp_set_stdlib: V,
) -> &mut Build {
let cpp_set_stdlib = cpp_set_stdlib.into();
self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into());
self.cpp_link_stdlib(cpp_set_stdlib);
@ -777,10 +811,13 @@ impl Build {
#[doc(hidden)]
pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build
where A: AsRef<OsStr>,
B: AsRef<OsStr>
where
A: AsRef<OsStr>,
B: AsRef<OsStr>,
{
self.env.push((a.as_ref().to_owned(), b.as_ref().to_owned()));
self.env.push(
(a.as_ref().to_owned(), b.as_ref().to_owned()),
);
self
}
@ -789,28 +826,35 @@ impl Build {
/// This will return a result instead of panicing; see compile() for the complete description.
pub fn try_compile(&self, output: &str) -> Result<(), Error> {
let (lib_name, gnu_lib_name) = if output.starts_with("lib") && output.ends_with(".a") {
(&output[3..output.len() - 2], output.to_owned())
} else {
let mut gnu = String::with_capacity(5 + output.len());
gnu.push_str("lib");
gnu.push_str(&output);
gnu.push_str(".a");
(output, gnu)
};
(&output[3..output.len() - 2], output.to_owned())
} else {
let mut gnu = String::with_capacity(5 + output.len());
gnu.push_str("lib");
gnu.push_str(&output);
gnu.push_str(".a");
(output, gnu)
};
let dst = self.get_out_dir()?;
let mut objects = Vec::new();
for file in self.files.iter() {
let obj = dst.join(file).with_extension("o");
let obj = if !obj.starts_with(&dst) {
dst.join(obj.file_name().ok_or_else(|| Error::new(ErrorKind::IOError, "Getting object file details failed."))?)
dst.join(obj.file_name().ok_or_else(|| {
Error::new(ErrorKind::IOError, "Getting object file details failed.")
})?)
} else {
obj
};
match obj.parent() {
Some(s) => fs::create_dir_all(s)?,
None => return Err(Error::new(ErrorKind::IOError, "Getting object file details failed.")),
None => {
return Err(Error::new(
ErrorKind::IOError,
"Getting object file details failed.",
))
}
};
objects.push(Object::new(file.to_path_buf(), obj));
@ -820,7 +864,8 @@ impl Build {
if self.get_target()?.contains("msvc") {
let compiler = self.get_base_compiler()?;
let atlmfc_lib = compiler.env()
let atlmfc_lib = compiler
.env()
.iter()
.find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB"))
.and_then(|&(_, ref lib_paths)| {
@ -831,7 +876,10 @@ impl Build {
});
if let Some(atlmfc_lib) = atlmfc_lib {
self.print(&format!("cargo:rustc-link-search=native={}", atlmfc_lib.display()));
self.print(&format!(
"cargo:rustc-link-search=native={}",
atlmfc_lib.display()
));
}
}
@ -880,8 +928,12 @@ impl Build {
let results: Mutex<Vec<Result<(), Error>>> = Mutex::new(Vec::new());
objs.par_iter().with_max_len(1)
.for_each(|obj| results.lock().unwrap().push(self.compile_object(obj)));
objs.par_iter().with_max_len(1).for_each(
|&(ref src, ref dst)| {
let res = self.compile_object(src, dst);
results.lock().unwrap().push(res)
},
);
// Check for any errors and return the first one found.
for result in results.into_inner().unwrap().iter() {
@ -912,12 +964,17 @@ impl Build {
for &(ref a, ref b) in self.env.iter() {
cmd.env(a, b);
}
(cmd,
compiler.path
.file_name()
.ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?
.to_string_lossy()
.into_owned())
(
cmd,
compiler
.path
.file_name()
.ok_or_else(|| {
Error::new(ErrorKind::IOError, "Failed to get compiler path.")
})?
.to_string_lossy()
.into_owned(),
)
};
command_add_output_file(&mut cmd, &obj.dst, msvc, is_asm);
cmd.arg(if msvc { "/c" } else { "-c" });
@ -936,16 +993,21 @@ impl Build {
}
cmd.arg(compiler.family.expand_flag());
assert!(self.files.len() <= 1,
"Expand may only be called for a single file");
assert!(
self.files.len() <= 1,
"Expand may only be called for a single file"
);
for file in self.files.iter() {
cmd.arg(file);
}
let name = compiler.path
let name = compiler
.path
.file_name()
.ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))?
.ok_or_else(|| {
Error::new(ErrorKind::IOError, "Failed to get compiler path.")
})?
.to_string_lossy()
.into_owned();
@ -1004,6 +1066,11 @@ impl Build {
let target = self.get_target()?;
let mut cmd = self.get_base_compiler()?;
let nvcc = cmd.path
.file_name()
.and_then(|p| p.to_str())
.map(|p| p.contains("nvcc"))
.unwrap_or(false);
// Non-target flags
// If the flag is not conditioned on target variable, it belongs here :)
@ -1018,14 +1085,14 @@ impl Build {
Some(true) => "/MT",
Some(false) => "/MD",
None => {
let features = env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap_or(String::new());
let features =
env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new());
if features.contains("crt-static") {
"/MT"
} else {
"/MD"
}
},
}
};
cmd.args.push(crt_flag.into());
@ -1037,8 +1104,7 @@ impl Build {
_ => {}
}
}
ToolFamily::Gnu |
ToolFamily::Clang => {
ToolFamily::Gnu | ToolFamily::Clang => {
// arm-linux-androideabi-gcc 4.8 shipped with Android NDK does
// not support '-Oz'
if opt_level == "z" && cmd.family != ToolFamily::Clang {
@ -1057,7 +1123,7 @@ impl Build {
}
}
}
for arg in self.envflags(if self.cpp {"CXXFLAGS"} else {"CFLAGS"}) {
for arg in self.envflags(if self.cpp { "CXXFLAGS" } else { "CFLAGS" }) {
cmd.args.push(arg.into());
}
@ -1083,6 +1149,8 @@ impl Build {
ToolFamily::Gnu => {
if target.contains("i686") || target.contains("i586") {
cmd.args.push("-m32".into());
} else if target == "x86_64-unknown-linux-gnux32" {
cmd.args.push("-mx32".into());
} else if target.contains("x86_64") || target.contains("powerpc64") {
cmd.args.push("-m64".into());
}
@ -1177,15 +1245,18 @@ impl Build {
if self.cpp {
match (self.cpp_set_stdlib.as_ref(), cmd.family) {
(None, _) => { }
(None, _) => {}
(Some(stdlib), ToolFamily::Gnu) |
(Some(stdlib), ToolFamily::Clang) => {
cmd.nvcc_wrap_arg(self.cuda);
cmd.args.push(format!("-stdlib=lib{}", stdlib).into());
}
_ => {
println!("cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \
does not support this option, ignored", cmd.family);
println!(
"cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \
does not support this option, ignored",
cmd.family
);
}
}
}
@ -1195,6 +1266,12 @@ impl Build {
cmd.args.push(directory.into());
}
if self.warnings {
for flag in cmd.family.warnings_flags().iter() {
cmd.args.push(flag.into());
}
}
for flag in self.flags.iter() {
cmd.args.push(flag.into());
}
@ -1207,7 +1284,11 @@ impl Build {
}
for &(ref key, ref value) in self.definitions.iter() {
let lead = if let ToolFamily::Msvc = cmd.family {"/"} else {"-"};
let lead = if let ToolFamily::Msvc = cmd.family {
"/"
} else {
"-"
};
if let Some(ref value) = *value {
cmd.args.push(format!("{}D{}={}", lead, key, value).into());
} else {
@ -1269,37 +1350,46 @@ impl Build {
if target.contains("msvc") {
let mut cmd = match self.archiver {
Some(ref s) => self.cmd(s),
None => windows_registry::find(&target, "lib.exe").unwrap_or_else(|| self.cmd("lib.exe")),
None => {
windows_registry::find(&target, "lib.exe").unwrap_or_else(
|| {
self.cmd("lib.exe")
},
)
}
};
let mut out = OsString::from("/OUT:");
out.push(dst);
run(cmd.arg(out)
.arg("/nologo")
.args(&objects)
.args(&self.objects),
"lib.exe")?;
run(
cmd.arg(out).arg("/nologo").args(objects).args(
&self.objects,
),
"lib.exe",
)?;
// The Rust compiler will look for libfoo.a and foo.lib, but the
// MSVC linker will also be passed foo.lib, so be sure that both
// exist for now.
let lib_dst = dst.with_file_name(format!("{}.lib", lib_name));
let _ = fs::remove_file(&lib_dst);
match fs::hard_link(&dst, &lib_dst)
.or_else(|_| {
// if hard-link fails, just copy (ignoring the number of bytes written)
fs::copy(&dst, &lib_dst).map(|_| ())
}) {
match fs::hard_link(&dst, &lib_dst).or_else(|_| {
// if hard-link fails, just copy (ignoring the number of bytes written)
fs::copy(&dst, &lib_dst).map(|_| ())
}) {
Ok(_) => (),
Err(_) => return Err(Error::new(ErrorKind::IOError, "Could not copy or create a hard-link to the generated lib file.")),
Err(_) => {
return Err(Error::new(
ErrorKind::IOError,
"Could not copy or create a hard-link to the generated lib file.",
))
}
};
} else {
let (mut ar, cmd) = self.get_ar()?;
run(ar
.arg("crs")
.arg(dst)
.args(&objects)
.args(&self.objects),
&cmd)?;
run(
ar.arg("crs").arg(dst).args(objects).args(&self.objects),
&cmd,
)?;
}
Ok(())
@ -1312,14 +1402,24 @@ impl Build {
}
let target = self.get_target()?;
let arch = target.split('-').nth(0).ok_or_else(|| Error::new(ErrorKind::ArchitectureInvalid, "Unknown architecture for iOS target."))?;
let arch = target.split('-').nth(0).ok_or_else(|| {
Error::new(
ErrorKind::ArchitectureInvalid,
"Unknown architecture for iOS target.",
)
})?;
let arch = match arch {
"arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7"),
"armv7s" | "thumbv7s" => ArchSpec::Device("armv7s"),
"arm64" | "aarch64" => ArchSpec::Device("arm64"),
"i386" | "i686" => ArchSpec::Simulator("-m32"),
"x86_64" => ArchSpec::Simulator("-m64"),
_ => return Err(Error::new(ErrorKind::ArchitectureInvalid, "Unknown architecture for iOS target.")),
_ => {
return Err(Error::new(
ErrorKind::ArchitectureInvalid,
"Unknown architecture for iOS target.",
))
}
};
let sdk = match arch {
@ -1347,7 +1447,12 @@ impl Build {
let sdk_path = match String::from_utf8(sdk_path) {
Ok(p) => p,
Err(_) => return Err(Error::new(ErrorKind::IOError, "Unable to determine iOS SDK path.")),
Err(_) => {
return Err(Error::new(
ErrorKind::IOError,
"Unable to determine iOS SDK path.",
))
}
};
cmd.args.push("-isysroot".into());
@ -1385,35 +1490,35 @@ impl Build {
"cc"
};
let tool_opt: Option<Tool> = self.env_tool(env)
.map(|(tool, args)| {
let mut t = Tool::new(PathBuf::from(tool));
for arg in args {
t.args.push(arg.into());
}
t
})
.or_else(|| {
if target.contains("emscripten") {
let tool = if self.cpp {
"em++"
} else {
"emcc"
};
// Windows uses bat file so we have to be a bit more specific
if cfg!(windows) {
let mut t = Tool::new(PathBuf::from("cmd"));
t.args.push("/c".into());
t.args.push(format!("{}.bat", tool).into());
Some(t)
let tool_opt: Option<Tool> =
self.env_tool(env)
.map(|(tool, cc, args)| {
let mut t = Tool::new(PathBuf::from(tool));
if let Some(cc) = cc {
t.cc_wrapper_path = Some(PathBuf::from(cc));
}
for arg in args {
t.cc_wrapper_args.push(arg.into());
}
t
})
.or_else(|| {
if target.contains("emscripten") {
let tool = if self.cpp { "em++" } else { "emcc" };
// Windows uses bat file so we have to be a bit more specific
if cfg!(windows) {
let mut t = Tool::new(PathBuf::from("cmd"));
t.args.push("/c".into());
t.args.push(format!("{}.bat", tool).into());
Some(t)
} else {
Some(Tool::new(PathBuf::from(tool)))
}
} else {
Some(Tool::new(PathBuf::from(tool)))
None
}
} else {
None
}
})
.or_else(|| windows_registry::find_tool(&target, "cl.exe"));
})
.or_else(|| windows_registry::find_tool(&target, "cl.exe"));
let tool = match tool_opt {
Some(t) => t,
@ -1454,6 +1559,7 @@ impl Build {
"powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"),
"powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"),
"s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"),
"sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu"),
"sparc64-unknown-netbsd" => Some("sparc64--netbsd"),
"sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"),
"thumbv6m-none-eabi" => Some("arm-none-eabi"),
@ -1506,7 +1612,13 @@ impl Build {
match res {
Some(res) => Ok(res),
None => Err(Error::new(ErrorKind::EnvVarNotFound, &format!("Could not find environment variable {}.", var_base))),
None => Err(Error::new(
ErrorKind::EnvVarNotFound,
&format!(
"Could not find environment variable {}.",
var_base
),
)),
}
}
@ -1519,15 +1631,20 @@ impl Build {
.collect()
}
fn env_tool(&self, name: &str) -> Option<(String, Vec<String>)> {
/// Returns compiler path, optional modifier name from whitelist, and arguments vec
fn env_tool(&self, name: &str) -> Option<(String, Option<String>, Vec<String>)> {
self.get_var(name).ok().map(|tool| {
let whitelist = ["ccache", "distcc", "sccache"];
for t in whitelist.iter() {
if tool.starts_with(t) && tool[t.len()..].starts_with(' ') {
return (t.to_string(), vec![tool[t.len()..].trim_left().to_string()]);
if tool.starts_with(t) && tool[t.len()..].starts_with(' ') {
let args = tool.split_whitespace().collect::<Vec<_>>();
return (args[1].to_string(), Some(t.to_string()), args[2..].iter().map(|s| s.to_string()).collect());
}
}
(tool, Vec::new())
(tool, None, Vec::new())
})
}
@ -1547,17 +1664,17 @@ impl Build {
} else {
Ok(Some("stdc++".to_string()))
}
},
}
}
}
fn get_ar(&self) -> Result<(Command, String), Error> {
if let Some(ref p) = self.archiver {
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("ar");
return Ok((self.cmd(p), name.to_string()))
return Ok((self.cmd(p), name.to_string()));
}
if let Ok(p) = self.get_var("AR") {
return Ok((self.cmd(&p), p))
return Ok((self.cmd(&p), p));
}
let program = if self.get_target()?.contains("android") {
format!("{}-ar", self.get_target()?.replace("armv7", "arm"))
@ -1566,7 +1683,7 @@ impl Build {
if cfg!(windows) {
let mut cmd = self.cmd("cmd");
cmd.arg("/c").arg("emar.bat");
return Ok((cmd, "emar.bat".to_string()))
return Ok((cmd, "emar.bat".to_string()));
}
"emar".to_string()
@ -1598,20 +1715,21 @@ impl Build {
}
fn get_debug(&self) -> bool {
self.debug.unwrap_or_else(|| {
match self.getenv("DEBUG") {
Some(s) => s != "false",
None => false,
}
self.debug.unwrap_or_else(|| match self.getenv("DEBUG") {
Some(s) => s != "false",
None => false,
})
}
fn get_out_dir(&self) -> Result<PathBuf, Error> {
match self.out_dir.clone() {
Some(p) => Ok(p),
None => Ok(env::var_os("OUT_DIR")
.map(PathBuf::from)
.ok_or_else(|| Error::new(ErrorKind::EnvVarNotFound, "Environment variable OUT_DIR not defined."))?),
None => Ok(env::var_os("OUT_DIR").map(PathBuf::from).ok_or_else(|| {
Error::new(
ErrorKind::EnvVarNotFound,
"Environment variable OUT_DIR not defined.",
)
})?),
}
}
@ -1624,7 +1742,13 @@ impl Build {
fn getenv_unwrap(&self, v: &str) -> Result<String, Error> {
match self.getenv(v) {
Some(s) => Ok(s),
None => Err(Error::new(ErrorKind::EnvVarNotFound, &format!("Environment variable {} not defined.", v.to_string()))),
None => Err(Error::new(
ErrorKind::EnvVarNotFound,
&format!(
"Environment variable {} not defined.",
v.to_string()
),
)),
}
}
@ -1657,9 +1781,11 @@ impl Tool {
};
Tool {
path: path,
cc_wrapper_path: None,
cc_wrapper_args: Vec::new(),
args: Vec::new(),
env: Vec::new(),
family: family
family: family,
}
}
@ -1680,7 +1806,15 @@ impl Tool {
/// command returned will already have the initial arguments and environment
/// variables configured.
pub fn to_command(&self) -> Command {
let mut cmd = Command::new(&self.path);
let mut cmd = match self.cc_wrapper_path {
Some(ref cc_wrapper_path) => {
let mut cmd = Command::new(&cc_wrapper_path);
cmd.arg(&self.path);
cmd.args(&self.cc_wrapper_args);
cmd
},
None => Command::new(&self.path)
};
cmd.args(&self.args);
for &(ref k, ref v) in self.env.iter() {
cmd.env(k, v);
@ -1709,13 +1843,73 @@ impl Tool {
pub fn env(&self) -> &[(OsString, OsString)] {
&self.env
}
/// Returns the compiler command in format of CC environment variable.
/// Or empty string if CC env was not present
///
/// This is typically used by configure script
pub fn cc_env(&self) -> OsString {
match self.cc_wrapper_path {
Some(ref cc_wrapper_path) => {
let mut cc_env = cc_wrapper_path.as_os_str().to_owned();
cc_env.push(" ");
cc_env.push(self.path.to_path_buf().into_os_string());
for arg in self.cc_wrapper_args.iter() {
cc_env.push(" ");
cc_env.push(arg);
}
cc_env
},
None => {
OsString::from("")
}
}
}
/// Returns the compiler flags in format of CFLAGS environment variable.
/// Important here - this will not be CFLAGS from env, its internal gcc's flags to use as CFLAGS
/// This is typically used by configure script
pub fn cflags_env(&self) -> OsString {
let mut flags = OsString::new();
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
flags.push(" ");
}
flags.push(arg);
}
flags
}
/// Whether the tool is GNU Compiler Collection-like.
pub fn is_like_gnu(&self) -> bool {
self.family == ToolFamily::Gnu
}
/// Whether the tool is Clang-like.
pub fn is_like_clang(&self) -> bool {
self.family == ToolFamily::Clang
}
/// Whether the tool is MSVC-like.
pub fn is_like_msvc(&self) -> bool {
self.family == ToolFamily::Msvc
}
}
fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
let (mut child, print) = spawn(cmd, program)?;
let status = match child.wait() {
Ok(s) => s,
Err(_) => return Err(Error::new(ErrorKind::ToolExecError, &format!("Failed to wait on spawned child process, command {:?} with args {:?}.", cmd, program))),
Err(_) => {
return Err(Error::new(
ErrorKind::ToolExecError,
&format!(
"Failed to wait on spawned child process, command {:?} with args {:?}.",
cmd,
program
),
))
}
};
print.join().unwrap();
println!("{}", status);
@ -1723,7 +1917,15 @@ fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
if status.success() {
Ok(())
} else {
Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} did not execute successfully (status code {}).", cmd, program, status)))
Err(Error::new(
ErrorKind::ToolExecError,
&format!(
"Command {:?} with args {:?} did not execute successfully (status code {}).",
cmd,
program,
status
),
))
}
}
@ -1731,10 +1933,24 @@ fn run_output(cmd: &mut Command, program: &str) -> Result<Vec<u8>, Error> {
cmd.stdout(Stdio::piped());
let (mut child, print) = spawn(cmd, program)?;
let mut stdout = vec![];
child.stdout.take().unwrap().read_to_end(&mut stdout).unwrap();
child
.stdout
.take()
.unwrap()
.read_to_end(&mut stdout)
.unwrap();
let status = match child.wait() {
Ok(s) => s,
Err(_) => return Err(Error::new(ErrorKind::ToolExecError, &format!("Failed to wait on spawned child process, command {:?} with args {:?}.", cmd, program))),
Err(_) => {
return Err(Error::new(
ErrorKind::ToolExecError,
&format!(
"Failed to wait on spawned child process, command {:?} with args {:?}.",
cmd,
program
),
))
}
};
print.join().unwrap();
println!("{}", status);
@ -1742,7 +1958,15 @@ fn run_output(cmd: &mut Command, program: &str) -> Result<Vec<u8>, Error> {
if status.success() {
Ok(stdout)
} else {
Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} did not execute successfully (status code {}).", cmd, program, status)))
Err(Error::new(
ErrorKind::ToolExecError,
&format!(
"Command {:?} with args {:?} did not execute successfully (status code {}).",
cmd,
program,
status
),
))
}
}
@ -1756,12 +1980,13 @@ fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Er
match cmd.stderr(Stdio::piped()).spawn() {
Ok(mut child) => {
let stderr = BufReader::new(child.stderr.take().unwrap());
let print = thread::spawn(move || {
for line in stderr.split(b'\n').filter_map(|l| l.ok()) {
print!("cargo:warning=");
std::io::stdout().write_all(&line).unwrap();
println!("");
}
let print = thread::spawn(move || for line in stderr.split(b'\n').filter_map(
|l| l.ok(),
)
{
print!("cargo:warning=");
std::io::stdout().write_all(&line).unwrap();
println!("");
});
Ok((child, print))
}
@ -1772,9 +1997,23 @@ fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Er
} else {
""
};
Err(Error::new(ErrorKind::ToolNotFound, &format!("Failed to find tool. Is `{}` installed?{}", program, extra)))
Err(Error::new(
ErrorKind::ToolNotFound,
&format!(
"Failed to find tool. Is `{}` installed?{}",
program,
extra
),
))
}
Err(_) => Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} failed to start.", cmd, program))),
Err(_) => Err(Error::new(
ErrorKind::ToolExecError,
&format!(
"Command {:?} with args {:?} failed to start.",
cmd,
program
),
)),
}
}

58
tests/cc_env.rs

@ -1,7 +1,9 @@
extern crate tempdir;
extern crate cc;
extern crate tempdir;
use std::env;
use std::path::Path;
use std::ffi::OsString;
mod support;
use support::Test;
@ -11,39 +13,61 @@ fn main() {
ccache();
distcc();
ccache_spaces();
ccache_env_flags();
}
fn ccache() {
let test = Test::gnu();
test.shim("ccache");
env::set_var("CC", "ccache lol-this-is-not-a-compiler foo");
test.gcc().file("foo.c").compile("libfoo.a");
env::set_var("CC", "ccache cc");
let compiler = test.gcc().file("foo.c").get_compiler();
test.cmd(0)
.must_have("lol-this-is-not-a-compiler foo")
.must_have("foo.c")
.must_not_have("ccache");
assert_eq!(compiler.path(), Path::new("cc"));
}
fn ccache_spaces() {
let test = Test::gnu();
test.shim("ccache");
env::set_var("CC", "ccache lol-this-is-not-a-compiler foo");
test.gcc().file("foo.c").compile("libfoo.a");
test.cmd(0).must_have("lol-this-is-not-a-compiler foo");
env::set_var("CC", "ccache cc");
let compiler = test.gcc().file("foo.c").get_compiler();
assert_eq!(compiler.path(), Path::new("cc"));
}
fn distcc() {
let test = Test::gnu();
test.shim("distcc");
env::set_var("CC", "distcc lol-this-is-not-a-compiler foo");
test.gcc().file("foo.c").compile("libfoo.a");
env::set_var("CC", "distcc cc");
let compiler = test.gcc().file("foo.c").get_compiler();
assert_eq!(compiler.path(), Path::new("cc"));
}
fn ccache_env_flags() {
let test = Test::gnu();
test.shim("ccache");
env::set_var("CC", "ccache lol-this-is-not-a-compiler");
let compiler = test.gcc().file("foo.c").get_compiler();
assert_eq!(compiler.path(), Path::new("lol-this-is-not-a-compiler"));
assert_eq!(
compiler.cc_env(),
OsString::from("ccache lol-this-is-not-a-compiler")
);
assert!(
compiler
.cflags_env()
.into_string()
.unwrap()
.contains("ccache") == false
);
assert!(
compiler
.cflags_env()
.into_string()
.unwrap()
.contains(" lol-this-is-not-a-compiler") == false
);
test.cmd(0)
.must_have("lol-this-is-not-a-compiler foo")
.must_have("foo.c")
.must_not_have("distcc");
env::set_var("CC", "");
}

10
tests/support/mod.rs

@ -109,4 +109,14 @@ impl Execution {
pub fn has(&self, p: &OsStr) -> bool {
self.args.iter().any(|arg| OsStr::new(arg) == p)
}
pub fn must_have_in_order(&self, before: &str, after: &str) -> &Execution {
let before_position = self.args.iter().rposition(|x| OsStr::new(x) == OsStr::new(before));
let after_position = self.args.iter().rposition(|x| OsStr::new(x) == OsStr::new(after));
match (before_position, after_position) {
(Some(b), Some(a)) if b < a => {},
(b, a) => { panic!("{:?} (last position: {:?}) did not appear before {:?} (last position: {:?})", before, b, after, a) },
};
self
}
}

13
tests/test.rs

@ -77,6 +77,7 @@ fn gnu_warnings() {
let test = Test::gnu();
test.gcc()
.warnings(true)
.flag("-Wno-missing-field-initializers")
.file("foo.c")
.compile("foo");
@ -84,6 +85,18 @@ fn gnu_warnings() {
.must_have("-Wextra");
}
#[test]
fn gnu_warnings_overridable() {
let test = Test::gnu();
test.gcc()
.warnings(true)
.flag("-Wno-missing-field-initializers")
.file("foo.c")
.compile("foo");
test.cmd(0).must_have_in_order("-Wall", "-Wno-missing-field-initializers");
}
#[test]
fn gnu_x86_64() {
for vendor in &["unknown-linux-gnu", "apple-darwin"] {

Loading…
Cancel
Save