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

19
README.md

@ -97,6 +97,8 @@ and `HOST` variables.
## Optional features ## Optional features
### Parallel
Currently cc-rs supports parallel compilation (think `make -jN`) but this 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, feature is turned off by default. To enable cc-rs to compile C/C++ in parallel,
you can change your dependency to: you can change your dependency to:
@ -176,8 +178,17 @@ fn main() {
## License ## License
`cc-rs` is primarily distributed under the terms of both the MIT license and This project is licensed under either of
the Apache License (Version 2.0), with portions covered by various BSD-like
licenses. * 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 //! [`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 //! # Examples
//! //!
//! Use the `Build` struct to compile `src/foo.c`: //! Use the `Build` struct to compile `src/foo.c`:
@ -119,18 +135,21 @@ enum ErrorKind {
ToolNotFound, ToolNotFound,
} }
/// Represents an internal error that occurred, with an explaination. /// Represents an internal error that occurred, with an explanation.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Error { pub struct Error {
/// Describes the kind of error that occurred. /// Describes the kind of error that occurred.
kind: ErrorKind, kind: ErrorKind,
/// More explaination of error that occurred. /// More explanation of error that occurred.
message: String, message: String,
} }
impl Error { impl Error {
fn new(kind: ErrorKind, message: &str) -> 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)] #[derive(Clone, Debug)]
pub struct Tool { pub struct Tool {
path: PathBuf, path: PathBuf,
cc_wrapper_path: Option<PathBuf>,
cc_wrapper_args: Vec<OsString>,
args: Vec<OsString>, args: Vec<OsString>,
env: Vec<(OsString, OsString)>, env: Vec<(OsString, OsString)>,
family: ToolFamily family: ToolFamily,
} }
/// Represents the family of tools this tool belongs to. /// Represents the family of tools this tool belongs to.
@ -176,8 +197,7 @@ impl ToolFamily {
fn debug_flag(&self) -> &'static str { fn debug_flag(&self) -> &'static str {
match *self { match *self {
ToolFamily::Msvc => "/Z7", ToolFamily::Msvc => "/Z7",
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => "-g",
ToolFamily::Clang => "-g",
} }
} }
@ -185,8 +205,7 @@ impl ToolFamily {
fn include_flag(&self) -> &'static str { fn include_flag(&self) -> &'static str {
match *self { match *self {
ToolFamily::Msvc => "/I", ToolFamily::Msvc => "/I",
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => "-I",
ToolFamily::Clang => "-I",
} }
} }
@ -194,8 +213,7 @@ impl ToolFamily {
fn expand_flag(&self) -> &'static str { fn expand_flag(&self) -> &'static str {
match *self { match *self {
ToolFamily::Msvc => "/E", ToolFamily::Msvc => "/E",
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => "-E",
ToolFamily::Clang => "-E",
} }
} }
@ -206,8 +224,7 @@ impl ToolFamily {
match *self { match *self {
ToolFamily::Msvc => &MSVC_FLAGS, ToolFamily::Msvc => &MSVC_FLAGS,
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => &GNU_CLANG_FLAGS,
ToolFamily::Clang => &GNU_CLANG_FLAGS,
} }
} }
@ -215,8 +232,7 @@ impl ToolFamily {
fn warnings_to_errors_flag(&self) -> &'static str { fn warnings_to_errors_flag(&self) -> &'static str {
match *self { match *self {
ToolFamily::Msvc => "/WX", ToolFamily::Msvc => "/WX",
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => "-Werror",
ToolFamily::Clang => "-Werror"
} }
} }
@ -362,7 +378,10 @@ impl Build {
/// .compile("foo"); /// .compile("foo");
/// ``` /// ```
pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Build { 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 self
} }
@ -413,15 +432,22 @@ impl Build {
let target = self.get_target()?; let target = self.get_target()?;
let mut cfg = Build::new(); let mut cfg = Build::new();
cfg.flag(flag) cfg.flag(flag)
.target(&target) .target(&target)
.opt_level(0) .opt_level(0)
.host(&target) .host(&target)
.debug(false) .debug(false)
.cpp(self.cpp) .cpp(self.cpp);
.cuda(self.cuda); .cuda(self.cuda);
let compiler = cfg.try_get_compiler()?; let compiler = cfg.try_get_compiler()?;
let mut cmd = compiler.to_command(); let mut cmd = compiler.to_command();
command_add_output_file(&mut cmd, &obj, target.contains("msvc"), false); 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); cmd.arg(&src);
let output = cmd.output()?; let output = cmd.output()?;
@ -497,8 +523,10 @@ impl Build {
/// Add files which will be compiled /// Add files which will be compiled
pub fn files<P>(&mut self, p: P) -> &mut Build pub fn files<P>(&mut self, p: P) -> &mut Build
where P: IntoIterator, where
P::Item: AsRef<Path> { P: IntoIterator,
P::Item: AsRef<Path>,
{
for file in p.into_iter() { for file in p.into_iter() {
self.file(file); self.file(file);
} }
@ -604,7 +632,10 @@ impl Build {
/// .cpp_link_stdlib("stdc++") /// .cpp_link_stdlib("stdc++")
/// .compile("libfoo.so"); /// .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.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(|s| s.into()));
self self
} }
@ -642,7 +673,10 @@ impl Build {
/// .cpp_set_stdlib("c++") /// .cpp_set_stdlib("c++")
/// .compile("libfoo.a"); /// .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(); let cpp_set_stdlib = cpp_set_stdlib.into();
self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into()); self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into());
self.cpp_link_stdlib(cpp_set_stdlib); self.cpp_link_stdlib(cpp_set_stdlib);
@ -777,10 +811,13 @@ impl Build {
#[doc(hidden)] #[doc(hidden)]
pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build
where A: AsRef<OsStr>, where
B: AsRef<OsStr> 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 self
} }
@ -789,28 +826,35 @@ impl Build {
/// This will return a result instead of panicing; see compile() for the complete description. /// This will return a result instead of panicing; see compile() for the complete description.
pub fn try_compile(&self, output: &str) -> Result<(), Error> { pub fn try_compile(&self, output: &str) -> Result<(), Error> {
let (lib_name, gnu_lib_name) = if output.starts_with("lib") && output.ends_with(".a") { let (lib_name, gnu_lib_name) = if output.starts_with("lib") && output.ends_with(".a") {
(&output[3..output.len() - 2], output.to_owned()) (&output[3..output.len() - 2], output.to_owned())
} else { } else {
let mut gnu = String::with_capacity(5 + output.len()); let mut gnu = String::with_capacity(5 + output.len());
gnu.push_str("lib"); gnu.push_str("lib");
gnu.push_str(&output); gnu.push_str(&output);
gnu.push_str(".a"); gnu.push_str(".a");
(output, gnu) (output, gnu)
}; };
let dst = self.get_out_dir()?; let dst = self.get_out_dir()?;
let mut objects = Vec::new(); let mut objects = Vec::new();
for file in self.files.iter() { for file in self.files.iter() {
let obj = dst.join(file).with_extension("o"); let obj = dst.join(file).with_extension("o");
let obj = if !obj.starts_with(&dst) { 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 { } else {
obj obj
}; };
match obj.parent() { match obj.parent() {
Some(s) => fs::create_dir_all(s)?, 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)); objects.push(Object::new(file.to_path_buf(), obj));
@ -820,7 +864,8 @@ impl Build {
if self.get_target()?.contains("msvc") { if self.get_target()?.contains("msvc") {
let compiler = self.get_base_compiler()?; let compiler = self.get_base_compiler()?;
let atlmfc_lib = compiler.env() let atlmfc_lib = compiler
.env()
.iter() .iter()
.find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB")) .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB"))
.and_then(|&(_, ref lib_paths)| { .and_then(|&(_, ref lib_paths)| {
@ -831,7 +876,10 @@ impl Build {
}); });
if let Some(atlmfc_lib) = atlmfc_lib { 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()); let results: Mutex<Vec<Result<(), Error>>> = Mutex::new(Vec::new());
objs.par_iter().with_max_len(1) objs.par_iter().with_max_len(1).for_each(
.for_each(|obj| results.lock().unwrap().push(self.compile_object(obj))); |&(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. // Check for any errors and return the first one found.
for result in results.into_inner().unwrap().iter() { for result in results.into_inner().unwrap().iter() {
@ -912,12 +964,17 @@ impl Build {
for &(ref a, ref b) in self.env.iter() { for &(ref a, ref b) in self.env.iter() {
cmd.env(a, b); cmd.env(a, b);
} }
(cmd, (
compiler.path cmd,
.file_name() compiler
.ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))? .path
.to_string_lossy() .file_name()
.into_owned()) .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); command_add_output_file(&mut cmd, &obj.dst, msvc, is_asm);
cmd.arg(if msvc { "/c" } else { "-c" }); cmd.arg(if msvc { "/c" } else { "-c" });
@ -936,16 +993,21 @@ impl Build {
} }
cmd.arg(compiler.family.expand_flag()); cmd.arg(compiler.family.expand_flag());
assert!(self.files.len() <= 1, assert!(
"Expand may only be called for a single file"); self.files.len() <= 1,
"Expand may only be called for a single file"
);
for file in self.files.iter() { for file in self.files.iter() {
cmd.arg(file); cmd.arg(file);
} }
let name = compiler.path let name = compiler
.path
.file_name() .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() .to_string_lossy()
.into_owned(); .into_owned();
@ -1004,6 +1066,11 @@ impl Build {
let target = self.get_target()?; let target = self.get_target()?;
let mut cmd = self.get_base_compiler()?; 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 // Non-target flags
// If the flag is not conditioned on target variable, it belongs here :) // If the flag is not conditioned on target variable, it belongs here :)
@ -1018,14 +1085,14 @@ impl Build {
Some(true) => "/MT", Some(true) => "/MT",
Some(false) => "/MD", Some(false) => "/MD",
None => { None => {
let features = env::var("CARGO_CFG_TARGET_FEATURE") let features =
.unwrap_or(String::new()); env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new());
if features.contains("crt-static") { if features.contains("crt-static") {
"/MT" "/MT"
} else { } else {
"/MD" "/MD"
} }
}, }
}; };
cmd.args.push(crt_flag.into()); cmd.args.push(crt_flag.into());
@ -1037,8 +1104,7 @@ impl Build {
_ => {} _ => {}
} }
} }
ToolFamily::Gnu | ToolFamily::Gnu | ToolFamily::Clang => {
ToolFamily::Clang => {
// arm-linux-androideabi-gcc 4.8 shipped with Android NDK does // arm-linux-androideabi-gcc 4.8 shipped with Android NDK does
// not support '-Oz' // not support '-Oz'
if opt_level == "z" && cmd.family != ToolFamily::Clang { 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()); cmd.args.push(arg.into());
} }
@ -1083,6 +1149,8 @@ impl Build {
ToolFamily::Gnu => { ToolFamily::Gnu => {
if target.contains("i686") || target.contains("i586") { if target.contains("i686") || target.contains("i586") {
cmd.args.push("-m32".into()); 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") { } else if target.contains("x86_64") || target.contains("powerpc64") {
cmd.args.push("-m64".into()); cmd.args.push("-m64".into());
} }
@ -1177,15 +1245,18 @@ impl Build {
if self.cpp { if self.cpp {
match (self.cpp_set_stdlib.as_ref(), cmd.family) { match (self.cpp_set_stdlib.as_ref(), cmd.family) {
(None, _) => { } (None, _) => {}
(Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Gnu) |
(Some(stdlib), ToolFamily::Clang) => { (Some(stdlib), ToolFamily::Clang) => {
cmd.nvcc_wrap_arg(self.cuda); cmd.nvcc_wrap_arg(self.cuda);
cmd.args.push(format!("-stdlib=lib{}", stdlib).into()); cmd.args.push(format!("-stdlib=lib{}", stdlib).into());
} }
_ => { _ => {
println!("cargo:warning=cpp_set_stdlib is specified, but the {:?} compiler \ println!(
does not support this option, ignored", cmd.family); "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()); 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() { for flag in self.flags.iter() {
cmd.args.push(flag.into()); cmd.args.push(flag.into());
} }
@ -1207,7 +1284,11 @@ impl Build {
} }
for &(ref key, ref value) in self.definitions.iter() { 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 { if let Some(ref value) = *value {
cmd.args.push(format!("{}D{}={}", lead, key, value).into()); cmd.args.push(format!("{}D{}={}", lead, key, value).into());
} else { } else {
@ -1269,37 +1350,46 @@ impl Build {
if target.contains("msvc") { if target.contains("msvc") {
let mut cmd = match self.archiver { let mut cmd = match self.archiver {
Some(ref s) => self.cmd(s), 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:"); let mut out = OsString::from("/OUT:");
out.push(dst); out.push(dst);
run(cmd.arg(out) run(
.arg("/nologo") cmd.arg(out).arg("/nologo").args(objects).args(
.args(&objects) &self.objects,
.args(&self.objects), ),
"lib.exe")?; "lib.exe",
)?;
// The Rust compiler will look for libfoo.a and foo.lib, but the // 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 // MSVC linker will also be passed foo.lib, so be sure that both
// exist for now. // exist for now.
let lib_dst = dst.with_file_name(format!("{}.lib", lib_name)); let lib_dst = dst.with_file_name(format!("{}.lib", lib_name));
let _ = fs::remove_file(&lib_dst); let _ = fs::remove_file(&lib_dst);
match fs::hard_link(&dst, &lib_dst) match fs::hard_link(&dst, &lib_dst).or_else(|_| {
.or_else(|_| { // if hard-link fails, just copy (ignoring the number of bytes written)
// if hard-link fails, just copy (ignoring the number of bytes written) fs::copy(&dst, &lib_dst).map(|_| ())
fs::copy(&dst, &lib_dst).map(|_| ()) }) {
}) {
Ok(_) => (), 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 { } else {
let (mut ar, cmd) = self.get_ar()?; let (mut ar, cmd) = self.get_ar()?;
run(ar run(
.arg("crs") ar.arg("crs").arg(dst).args(objects).args(&self.objects),
.arg(dst) &cmd,
.args(&objects) )?;
.args(&self.objects),
&cmd)?;
} }
Ok(()) Ok(())
@ -1312,14 +1402,24 @@ impl Build {
} }
let target = self.get_target()?; 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 { let arch = match arch {
"arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7"), "arm" | "armv7" | "thumbv7" => ArchSpec::Device("armv7"),
"armv7s" | "thumbv7s" => ArchSpec::Device("armv7s"), "armv7s" | "thumbv7s" => ArchSpec::Device("armv7s"),
"arm64" | "aarch64" => ArchSpec::Device("arm64"), "arm64" | "aarch64" => ArchSpec::Device("arm64"),
"i386" | "i686" => ArchSpec::Simulator("-m32"), "i386" | "i686" => ArchSpec::Simulator("-m32"),
"x86_64" => ArchSpec::Simulator("-m64"), "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 { let sdk = match arch {
@ -1347,7 +1447,12 @@ impl Build {
let sdk_path = match String::from_utf8(sdk_path) { let sdk_path = match String::from_utf8(sdk_path) {
Ok(p) => p, 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()); cmd.args.push("-isysroot".into());
@ -1385,35 +1490,35 @@ impl Build {
"cc" "cc"
}; };
let tool_opt: Option<Tool> = self.env_tool(env) let tool_opt: Option<Tool> =
.map(|(tool, args)| { self.env_tool(env)
let mut t = Tool::new(PathBuf::from(tool)); .map(|(tool, cc, args)| {
for arg in args { let mut t = Tool::new(PathBuf::from(tool));
t.args.push(arg.into()); if let Some(cc) = cc {
} t.cc_wrapper_path = Some(PathBuf::from(cc));
t }
}) for arg in args {
.or_else(|| { t.cc_wrapper_args.push(arg.into());
if target.contains("emscripten") { }
let tool = if self.cpp { t
"em++" })
} else { .or_else(|| {
"emcc" 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 // Windows uses bat file so we have to be a bit more specific
if cfg!(windows) { if cfg!(windows) {
let mut t = Tool::new(PathBuf::from("cmd")); let mut t = Tool::new(PathBuf::from("cmd"));
t.args.push("/c".into()); t.args.push("/c".into());
t.args.push(format!("{}.bat", tool).into()); t.args.push(format!("{}.bat", tool).into());
Some(t) Some(t)
} else {
Some(Tool::new(PathBuf::from(tool)))
}
} else { } 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 { let tool = match tool_opt {
Some(t) => t, Some(t) => t,
@ -1454,6 +1559,7 @@ impl Build {
"powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"), "powerpc64-unknown-linux-gnu" => Some("powerpc-linux-gnu"),
"powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"), "powerpc64le-unknown-linux-gnu" => Some("powerpc64le-linux-gnu"),
"s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"), "s390x-unknown-linux-gnu" => Some("s390x-linux-gnu"),
"sparc64-unknown-linux-gnu" => Some("sparc64-linux-gnu"),
"sparc64-unknown-netbsd" => Some("sparc64--netbsd"), "sparc64-unknown-netbsd" => Some("sparc64--netbsd"),
"sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"), "sparcv9-sun-solaris" => Some("sparcv9-sun-solaris"),
"thumbv6m-none-eabi" => Some("arm-none-eabi"), "thumbv6m-none-eabi" => Some("arm-none-eabi"),
@ -1506,7 +1612,13 @@ impl Build {
match res { match res {
Some(res) => Ok(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() .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| { self.get_var(name).ok().map(|tool| {
let whitelist = ["ccache", "distcc", "sccache"]; let whitelist = ["ccache", "distcc", "sccache"];
for t in whitelist.iter() { for t in whitelist.iter() {
if tool.starts_with(t) && tool[t.len()..].starts_with(' ') { if tool.starts_with(t) && tool[t.len()..].starts_with(' ') {
return (t.to_string(), vec![tool[t.len()..].trim_left().to_string()]); 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 { } else {
Ok(Some("stdc++".to_string())) Ok(Some("stdc++".to_string()))
} }
}, }
} }
} }
fn get_ar(&self) -> Result<(Command, String), Error> { fn get_ar(&self) -> Result<(Command, String), Error> {
if let Some(ref p) = self.archiver { if let Some(ref p) = self.archiver {
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("ar"); 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") { 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") { let program = if self.get_target()?.contains("android") {
format!("{}-ar", self.get_target()?.replace("armv7", "arm")) format!("{}-ar", self.get_target()?.replace("armv7", "arm"))
@ -1566,7 +1683,7 @@ impl Build {
if cfg!(windows) { if cfg!(windows) {
let mut cmd = self.cmd("cmd"); let mut cmd = self.cmd("cmd");
cmd.arg("/c").arg("emar.bat"); cmd.arg("/c").arg("emar.bat");
return Ok((cmd, "emar.bat".to_string())) return Ok((cmd, "emar.bat".to_string()));
} }
"emar".to_string() "emar".to_string()
@ -1598,20 +1715,21 @@ impl Build {
} }
fn get_debug(&self) -> bool { fn get_debug(&self) -> bool {
self.debug.unwrap_or_else(|| { self.debug.unwrap_or_else(|| match self.getenv("DEBUG") {
match self.getenv("DEBUG") { Some(s) => s != "false",
Some(s) => s != "false", None => false,
None => false,
}
}) })
} }
fn get_out_dir(&self) -> Result<PathBuf, Error> { fn get_out_dir(&self) -> Result<PathBuf, Error> {
match self.out_dir.clone() { match self.out_dir.clone() {
Some(p) => Ok(p), Some(p) => Ok(p),
None => Ok(env::var_os("OUT_DIR") None => Ok(env::var_os("OUT_DIR").map(PathBuf::from).ok_or_else(|| {
.map(PathBuf::from) Error::new(
.ok_or_else(|| Error::new(ErrorKind::EnvVarNotFound, "Environment variable OUT_DIR not defined."))?), ErrorKind::EnvVarNotFound,
"Environment variable OUT_DIR not defined.",
)
})?),
} }
} }
@ -1624,7 +1742,13 @@ impl Build {
fn getenv_unwrap(&self, v: &str) -> Result<String, Error> { fn getenv_unwrap(&self, v: &str) -> Result<String, Error> {
match self.getenv(v) { match self.getenv(v) {
Some(s) => Ok(s), 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 { Tool {
path: path, path: path,
cc_wrapper_path: None,
cc_wrapper_args: Vec::new(),
args: Vec::new(), args: Vec::new(),
env: 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 /// command returned will already have the initial arguments and environment
/// variables configured. /// variables configured.
pub fn to_command(&self) -> Command { 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); cmd.args(&self.args);
for &(ref k, ref v) in self.env.iter() { for &(ref k, ref v) in self.env.iter() {
cmd.env(k, v); cmd.env(k, v);
@ -1709,13 +1843,73 @@ impl Tool {
pub fn env(&self) -> &[(OsString, OsString)] { pub fn env(&self) -> &[(OsString, OsString)] {
&self.env &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> { fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
let (mut child, print) = spawn(cmd, program)?; let (mut child, print) = spawn(cmd, program)?;
let status = match child.wait() { let status = match child.wait() {
Ok(s) => s, 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(); print.join().unwrap();
println!("{}", status); println!("{}", status);
@ -1723,7 +1917,15 @@ fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
if status.success() { if status.success() {
Ok(()) Ok(())
} else { } 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()); cmd.stdout(Stdio::piped());
let (mut child, print) = spawn(cmd, program)?; let (mut child, print) = spawn(cmd, program)?;
let mut stdout = vec![]; 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() { let status = match child.wait() {
Ok(s) => s, 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(); print.join().unwrap();
println!("{}", status); println!("{}", status);
@ -1742,7 +1958,15 @@ fn run_output(cmd: &mut Command, program: &str) -> Result<Vec<u8>, Error> {
if status.success() { if status.success() {
Ok(stdout) Ok(stdout)
} else { } 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() { match cmd.stderr(Stdio::piped()).spawn() {
Ok(mut child) => { Ok(mut child) => {
let stderr = BufReader::new(child.stderr.take().unwrap()); let stderr = BufReader::new(child.stderr.take().unwrap());
let print = thread::spawn(move || { let print = thread::spawn(move || for line in stderr.split(b'\n').filter_map(
for line in stderr.split(b'\n').filter_map(|l| l.ok()) { |l| l.ok(),
print!("cargo:warning="); )
std::io::stdout().write_all(&line).unwrap(); {
println!(""); print!("cargo:warning=");
} std::io::stdout().write_all(&line).unwrap();
println!("");
}); });
Ok((child, print)) Ok((child, print))
} }
@ -1772,9 +1997,23 @@ fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Er
} else { } 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 cc;
extern crate tempdir;
use std::env; use std::env;
use std::path::Path;
use std::ffi::OsString;
mod support; mod support;
use support::Test; use support::Test;
@ -11,39 +13,61 @@ fn main() {
ccache(); ccache();
distcc(); distcc();
ccache_spaces(); ccache_spaces();
ccache_env_flags();
} }
fn ccache() { fn ccache() {
let test = Test::gnu(); let test = Test::gnu();
test.shim("ccache");
env::set_var("CC", "ccache lol-this-is-not-a-compiler foo"); env::set_var("CC", "ccache cc");
test.gcc().file("foo.c").compile("libfoo.a"); let compiler = test.gcc().file("foo.c").get_compiler();
test.cmd(0) assert_eq!(compiler.path(), Path::new("cc"));
.must_have("lol-this-is-not-a-compiler foo")
.must_have("foo.c")
.must_not_have("ccache");
} }
fn ccache_spaces() { fn ccache_spaces() {
let test = Test::gnu(); let test = Test::gnu();
test.shim("ccache"); test.shim("ccache");
env::set_var("CC", "ccache lol-this-is-not-a-compiler foo"); env::set_var("CC", "ccache cc");
test.gcc().file("foo.c").compile("libfoo.a"); let compiler = test.gcc().file("foo.c").get_compiler();
test.cmd(0).must_have("lol-this-is-not-a-compiler foo"); assert_eq!(compiler.path(), Path::new("cc"));
} }
fn distcc() { fn distcc() {
let test = Test::gnu(); let test = Test::gnu();
test.shim("distcc"); test.shim("distcc");
env::set_var("CC", "distcc lol-this-is-not-a-compiler foo"); env::set_var("CC", "distcc cc");
test.gcc().file("foo.c").compile("libfoo.a"); 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) env::set_var("CC", "");
.must_have("lol-this-is-not-a-compiler foo")
.must_have("foo.c")
.must_not_have("distcc");
} }

10
tests/support/mod.rs

@ -109,4 +109,14 @@ impl Execution {
pub fn has(&self, p: &OsStr) -> bool { pub fn has(&self, p: &OsStr) -> bool {
self.args.iter().any(|arg| OsStr::new(arg) == p) 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(); let test = Test::gnu();
test.gcc() test.gcc()
.warnings(true) .warnings(true)
.flag("-Wno-missing-field-initializers")
.file("foo.c") .file("foo.c")
.compile("foo"); .compile("foo");
@ -84,6 +85,18 @@ fn gnu_warnings() {
.must_have("-Wextra"); .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] #[test]
fn gnu_x86_64() { fn gnu_x86_64() {
for vendor in &["unknown-linux-gnu", "apple-darwin"] { for vendor in &["unknown-linux-gnu", "apple-darwin"] {

Loading…
Cancel
Save