diff options
Diffstat (limited to 'gcc/src/lib.rs')
-rw-r--r-- | gcc/src/lib.rs | 891 |
1 files changed, 682 insertions, 209 deletions
diff --git a/gcc/src/lib.rs b/gcc/src/lib.rs index 7d19e13..4c5e724 100644 --- a/gcc/src/lib.rs +++ b/gcc/src/lib.rs @@ -10,35 +10,30 @@ //! //! The purpose of this crate is to provide the utility functions necessary to //! compile C code into a static archive which is then linked into a Rust crate. -//! The top-level `compile_library` function serves as a convenience and more -//! advanced configuration is available through the `Config` builder. +//! Configuration is available through the `Build` struct. //! //! This crate will automatically detect situations such as cross compilation or //! other environment variables set by Cargo and will build code appropriately. //! -//! # Examples -//! -//! Use the default configuration: +//! The crate is not limited to C code, it can accept any source code that can +//! be passed to a C or C++ compiler. As such, assembly files with extensions +//! `.s` (gcc/clang) and `.asm` (MSVC) can also be compiled. //! -//! ```no_run -//! extern crate gcc; +//! [`Build`]: struct.Build.html //! -//! fn main() { -//! gcc::compile_library("libfoo.a", &["src/foo.c"]); -//! } -//! ``` +//! # Examples //! -//! Use more advanced configuration: +//! Use the `Build` struct to compile `src/foo.c`: //! //! ```no_run //! extern crate gcc; //! //! fn main() { -//! gcc::Config::new() -//! .file("src/foo.c") -//! .define("FOO", Some("bar")) -//! .include("src") -//! .compile("libfoo.a"); +//! gcc::Build::new() +//! .file("src/foo.c") +//! .define("FOO", Some("bar")) +//! .include("src") +//! .compile("foo"); //! } //! ``` @@ -57,6 +52,13 @@ use std::process::{Command, Stdio, Child}; use std::io::{self, BufReader, BufRead, Read, Write}; use std::thread::{self, JoinHandle}; +#[doc(hidden)] +#[deprecated(since="0.3.51", note="gcc::Config has been renamed to gcc::Build")] +pub type Config = Build; + +#[cfg(feature = "parallel")] +use std::sync::Mutex; + // These modules are all glue to support reading the MSVC version from // the registry and from COM interfaces #[cfg(windows)] @@ -72,11 +74,13 @@ mod setup_config; pub mod windows_registry; /// Extra configuration to pass to gcc. -pub struct Config { +#[derive(Clone, Debug)] +pub struct Build { include_directories: Vec<PathBuf>, definitions: Vec<(String, Option<String>)>, objects: Vec<PathBuf>, flags: Vec<String>, + flags_supported: Vec<String>, files: Vec<PathBuf>, cpp: bool, cpp_link_stdlib: Option<Option<String>>, @@ -92,6 +96,46 @@ pub struct Config { cargo_metadata: bool, pic: Option<bool>, static_crt: Option<bool>, + shared_flag: Option<bool>, + static_flag: Option<bool>, + warnings_into_errors: bool, + warnings: bool, +} + +/// Represents the types of errors that may occur while using gcc-rs. +#[derive(Clone, Debug)] +enum ErrorKind { + /// Error occurred while performing I/O. + IOError, + /// Invalid architecture supplied. + ArchitectureInvalid, + /// Environment variable not found, with the var in question as extra info. + EnvVarNotFound, + /// Error occurred while using external tools (ie: invocation of compiler). + ToolExecError, + /// Error occurred due to missing external tools. + ToolNotFound, +} + +/// Represents an internal error that occurred, with an explaination. +#[derive(Clone, Debug)] +pub struct Error { + /// Describes the kind of error that occurred. + kind: ErrorKind, + /// More explaination of error that occurred. + message: String, +} + +impl Error { + fn new(kind: ErrorKind, message: &str) -> Error { + Error { kind: kind, message: message.to_owned() } + } +} + +impl From<io::Error> for Error { + fn from(e: io::Error) -> Error { + Error::new(ErrorKind::IOError, &format!("{}", e)) + } } /// Configuration used to represent an invocation of a C compiler. @@ -101,6 +145,7 @@ pub struct Config { /// This can be used to further configure other build systems (e.g. forward /// along CC and/or CFLAGS) or the `to_command` method can be used to run the /// compiler itself. +#[derive(Clone, Debug)] pub struct Tool { path: PathBuf, args: Vec<OsString>, @@ -113,7 +158,7 @@ pub struct Tool { /// Each family of tools differs in how and what arguments they accept. /// /// Detection of a family is done on best-effort basis and may not accurately reflect the tool. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] enum ToolFamily { /// Tool is GNU Compiler Collection-like. Gnu, @@ -151,6 +196,27 @@ impl ToolFamily { ToolFamily::Clang => "-E", } } + + /// What the flags to enable all warnings + fn warnings_flags(&self) -> &'static [&'static str] { + static MSVC_FLAGS: &'static [&'static str] = &["/W4"]; + static GNU_CLANG_FLAGS: &'static [&'static str] = &["-Wall", "-Wextra"]; + + match *self { + ToolFamily::Msvc => &MSVC_FLAGS, + ToolFamily::Gnu | + ToolFamily::Clang => &GNU_CLANG_FLAGS, + } + } + + /// What the flag to turn warning into errors + fn warnings_to_errors_flag(&self) -> &'static str { + match *self { + ToolFamily::Msvc => "/WX", + ToolFamily::Gnu | + ToolFamily::Clang => "-Werror" + } + } } /// Compile a library from the given set of input C files. @@ -164,27 +230,34 @@ impl ToolFamily { /// # Example /// /// ```no_run -/// gcc::compile_library("libfoo.a", &["foo.c", "bar.c"]); +/// gcc::compile_library("foo", &["foo.c", "bar.c"]); /// ``` +#[deprecated] +#[doc(hidden)] pub fn compile_library(output: &str, files: &[&str]) { - let mut c = Config::new(); + let mut c = Build::new(); for f in files.iter() { c.file(*f); } c.compile(output); } -impl Config { +impl Build { /// Construct a new instance of a blank set of configuration. /// - /// This builder is finished with the `compile` function. - pub fn new() -> Config { - Config { + /// This builder is finished with the [`compile`] function. + /// + /// [`compile`]: struct.Build.html#method.compile + pub fn new() -> Build { + Build { include_directories: Vec::new(), definitions: Vec::new(), objects: Vec::new(), flags: Vec::new(), + flags_supported: Vec::new(), files: Vec::new(), + shared_flag: None, + static_flag: None, cpp: false, cpp_link_stdlib: None, cpp_set_stdlib: None, @@ -199,48 +272,229 @@ impl Config { cargo_metadata: true, pic: None, static_crt: None, + warnings: true, + warnings_into_errors: false, } } /// Add a directory to the `-I` or include path for headers - pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Config { + /// + /// # Example + /// + /// ```no_run + /// use std::path::Path; + /// + /// let library_path = Path::new("/path/to/library"); + /// + /// gcc::Build::new() + /// .file("src/foo.c") + /// .include(library_path) + /// .include("src") + /// .compile("foo"); + /// ``` + pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Build { self.include_directories.push(dir.as_ref().to_path_buf()); self } /// Specify a `-D` variable with an optional value. - pub fn define(&mut self, var: &str, val: Option<&str>) -> &mut Config { - self.definitions.push((var.to_string(), val.map(|s| s.to_string()))); + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .define("FOO", "BAR") + /// .define("BAZ", None) + /// .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 } /// Add an arbitrary object file to link in - pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Config { + pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Build { self.objects.push(obj.as_ref().to_path_buf()); self } /// Add an arbitrary flag to the invocation of the compiler - pub fn flag(&mut self, flag: &str) -> &mut Config { + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .flag("-ffunction-sections") + /// .compile("foo"); + /// ``` + pub fn flag(&mut self, flag: &str) -> &mut Build { self.flags.push(flag.to_string()); self } + fn ensure_check_file(&self) -> Result<PathBuf, Error> { + let out_dir = self.get_out_dir()?; + let src = if self.cpp { + out_dir.join("flag_check.cpp") + } else { + out_dir.join("flag_check.c") + }; + + if !src.exists() { + let mut f = fs::File::create(&src)?; + write!(f, "int main(void) {{ return 0; }}")?; + } + + Ok(src) + } + + fn is_flag_supported(&self, flag: &str) -> Result<bool, Error> { + let out_dir = self.get_out_dir()?; + let src = self.ensure_check_file()?; + let obj = out_dir.join("flag_check"); + 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); + let compiler = cfg.try_get_compiler()?; + let mut cmd = compiler.to_command(); + command_add_output_file(&mut cmd, &obj, target.contains("msvc"), false); + cmd.arg(&src); + + let output = cmd.output()?; + Ok(output.stderr.is_empty()) + } + + /// Add an arbitrary flag to the invocation of the compiler if it supports it + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .flag_if_supported("-Wlogical-op") // only supported by GCC + /// .flag_if_supported("-Wunreachable-code") // only supported by clang + /// .compile("foo"); + /// ``` + pub fn flag_if_supported(&mut self, flag: &str) -> &mut Build { + self.flags_supported.push(flag.to_string()); + self + } + + /// Set the `-shared` flag. + /// + /// When enabled, the compiler will produce a shared object which can + /// then be linked with other objects to form an executable. + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .shared_flag(true) + /// .compile("libfoo.so"); + /// ``` + + pub fn shared_flag(&mut self, shared_flag: bool) -> &mut Build { + self.shared_flag = Some(shared_flag); + self + } + + /// Set the `-static` flag. + /// + /// When enabled on systems that support dynamic linking, this prevents + /// linking with the shared libraries. + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .shared_flag(true) + /// .static_flag(true) + /// .compile("foo"); + /// ``` + pub fn static_flag(&mut self, static_flag: bool) -> &mut Build { + self.static_flag = Some(static_flag); + self + } + /// Add a file which will be compiled - pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Config { + pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Build { self.files.push(p.as_ref().to_path_buf()); self } + /// Add files which will be compiled + pub fn files<P>(&mut self, p: P) -> &mut Build + where P: IntoIterator, + P::Item: AsRef<Path> { + for file in p.into_iter() { + self.file(file); + } + self + } + /// Set C++ support. /// /// The other `cpp_*` options will only become active if this is set to /// `true`. - pub fn cpp(&mut self, cpp: bool) -> &mut Config { + pub fn cpp(&mut self, cpp: bool) -> &mut Build { self.cpp = cpp; self } + /// Set warnings into errors flag. + /// + /// Disabled by default. + /// + /// Warning: turning warnings into errors only make sense + /// if you are a developer of the crate using gcc-rs. + /// Some warnings only appear on some architecture or + /// specific version of the compiler. Any user of this crate, + /// or any other crate depending on it, could fail during + /// compile time. + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .warnings_into_errors(true) + /// .compile("libfoo.a"); + /// ``` + pub fn warnings_into_errors(&mut self, warnings_into_errors: bool) -> &mut Build { + self.warnings_into_errors = warnings_into_errors; + self + } + + /// Set warnings flags. + /// + /// Adds some flags: + /// - "/Wall" for MSVC. + /// - "-Wall", "-Wextra" for GNU and Clang. + /// + /// Enabled by default. + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .warnings(false) + /// .compile("libfoo.a"); + /// ``` + pub fn warnings(&mut self, warnings: bool) -> &mut Build { + self.warnings = warnings; + self + } + /// Set the standard library to link against when compiling with C++ /// support. /// @@ -252,8 +506,22 @@ impl Config { /// otherwise cargo will link against the specified library. /// /// The given library name must not contain the `lib` prefix. - pub fn cpp_link_stdlib(&mut self, cpp_link_stdlib: Option<&str>) -> &mut Config { - self.cpp_link_stdlib = Some(cpp_link_stdlib.map(|s| s.into())); + /// + /// Common values: + /// - `stdc++` for GNU + /// - `c++` for Clang + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .shared_flag(true) + /// .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 { + self.cpp_link_stdlib = Some(cpp_link_stdlib.into().map(|s| s.into())); self } @@ -277,7 +545,21 @@ impl Config { /// be used, otherwise `-stdlib` is added to the compile invocation. /// /// The given library name must not contain the `lib` prefix. - pub fn cpp_set_stdlib(&mut self, cpp_set_stdlib: Option<&str>) -> &mut Config { + /// + /// Common values: + /// - `stdc++` for GNU + /// - `c++` for Clang + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .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 { + 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); self @@ -287,7 +569,16 @@ impl Config { /// /// This option is automatically scraped from the `TARGET` environment /// variable by build scripts, so it's not required to call this function. - pub fn target(&mut self, target: &str) -> &mut Config { + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .target("aarch64-linux-android") + /// .compile("foo"); + /// ``` + pub fn target(&mut self, target: &str) -> &mut Build { self.target = Some(target.to_string()); self } @@ -296,7 +587,16 @@ impl Config { /// /// This option is automatically scraped from the `HOST` environment /// variable by build scripts, so it's not required to call this function. - pub fn host(&mut self, host: &str) -> &mut Config { + /// + /// # Example + /// + /// ```no_run + /// gcc::Build::new() + /// .file("src/foo.c") + /// .host("arm-linux-gnueabihf") + /// .compile("foo"); + /// ``` + pub fn host(&mut self, host: &str) -> &mut Build { self.host = Some(host.to_string()); self } @@ -305,7 +605,7 @@ impl Config { /// /// This option is automatically scraped from the `OPT_LEVEL` environment /// variable by build scripts, so it's not required to call this function. - pub fn opt_level(&mut self, opt_level: u32) -> &mut Config { + pub fn opt_level(&mut self, opt_level: u32) -> &mut Build { self.opt_level = Some(opt_level.to_string()); self } @@ -314,7 +614,7 @@ impl Config { /// /// This option is automatically scraped from the `OPT_LEVEL` environment /// variable by build scripts, so it's not required to call this function. - pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Config { + pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Build { self.opt_level = Some(opt_level.to_string()); self } @@ -325,7 +625,7 @@ impl Config { /// This option is automatically scraped from the `PROFILE` environment /// variable by build scripts (only enabled when the profile is "debug"), so /// it's not required to call this function. - pub fn debug(&mut self, debug: bool) -> &mut Config { + pub fn debug(&mut self, debug: bool) -> &mut Build { self.debug = Some(debug); self } @@ -335,7 +635,7 @@ impl Config { /// /// This option is automatically scraped from the `OUT_DIR` environment /// variable by build scripts, so it's not required to call this function. - pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Config { + pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Build { self.out_dir = Some(out_dir.as_ref().to_owned()); self } @@ -345,7 +645,7 @@ impl Config { /// This option is automatically determined from the target platform or a /// number of environment variables, so it's not required to call this /// function. - pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Config { + pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Build { self.compiler = Some(compiler.as_ref().to_owned()); self } @@ -355,13 +655,21 @@ impl Config { /// This option is automatically determined from the target platform or a /// number of environment variables, so it's not required to call this /// function. - pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Config { + pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Build { self.archiver = Some(archiver.as_ref().to_owned()); self } /// Define whether metadata should be emitted for cargo allowing it to /// automatically link the binary. Defaults to `true`. - pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config { + /// + /// The emitted metadata is: + /// + /// - `rustc-link-lib=static=`*compiled lib* + /// - `rustc-link-search=native=`*target folder* + /// - When target is MSVC, the ATL-MFC libs are added via `rustc-link-search=native=` + /// - When C++ is enabled, the C++ stdlib is added via `rustc-link-lib` + /// + pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Build { self.cargo_metadata = cargo_metadata; self } @@ -370,7 +678,7 @@ impl Config { /// /// This option defaults to `false` for `windows-gnu` targets and /// to `true` for all other targets. - pub fn pic(&mut self, pic: bool) -> &mut Config { + pub fn pic(&mut self, pic: bool) -> &mut Build { self.pic = Some(pic); self } @@ -378,14 +686,13 @@ impl Config { /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. /// /// This option defaults to `false`, and affect only msvc targets. - pub fn static_crt(&mut self, static_crt: bool) -> &mut Config { + pub fn static_crt(&mut self, static_crt: bool) -> &mut Build { self.static_crt = Some(static_crt); self } - #[doc(hidden)] - pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Config + pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Build where A: AsRef<OsStr>, B: AsRef<OsStr> { @@ -395,31 +702,42 @@ impl Config { /// Run the compiler, generating the file `output` /// - /// The name `output` must begin with `lib` and end with `.a` - pub fn compile(&self, output: &str) { - assert!(output.starts_with("lib")); - assert!(output.ends_with(".a")); - let lib_name = &output[3..output.len() - 2]; - let dst = self.get_out_dir(); + /// 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) + }; + let dst = self.get_out_dir()?; let mut objects = Vec::new(); let mut src_dst = 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().unwrap()) + dst.join(obj.file_name().ok_or_else(|| Error::new(ErrorKind::IOError, "Getting object file details failed."))?) } else { obj }; - fs::create_dir_all(&obj.parent().unwrap()).unwrap(); + + match obj.parent() { + Some(s) => fs::create_dir_all(s)?, + None => return Err(Error::new(ErrorKind::IOError, "Getting object file details failed.")), + }; + src_dst.push((file.to_path_buf(), obj.clone())); objects.push(obj); } - self.compile_objects(&src_dst); - self.assemble(lib_name, &dst.join(output), &objects); + self.compile_objects(&src_dst)?; + self.assemble(lib_name, &dst.join(gnu_lib_name), &objects)?; - if self.get_target().contains("msvc") { - let compiler = self.get_base_compiler(); + if self.get_target()?.contains("msvc") { + let compiler = self.get_base_compiler()?; let atlmfc_lib = compiler.env() .iter() .find(|&&(ref var, _)| var.as_os_str() == OsStr::new("LIB")) @@ -435,20 +753,39 @@ impl Config { } } - self.print(&format!("cargo:rustc-link-lib=static={}", - &output[3..output.len() - 2])); + self.print(&format!("cargo:rustc-link-lib=static={}", lib_name)); self.print(&format!("cargo:rustc-link-search=native={}", dst.display())); // Add specific C++ libraries, if enabled. if self.cpp { - if let Some(stdlib) = self.get_cpp_link_stdlib() { + if let Some(stdlib) = self.get_cpp_link_stdlib()? { self.print(&format!("cargo:rustc-link-lib={}", stdlib)); } } + + Ok(()) + } + + /// Run the compiler, generating the file `output` + /// + /// The name `output` should be the name of the library. For backwards compatibility, + /// the `output` may start with `lib` and end with `.a`. The Rust compilier will create + /// the assembly with the lib prefix and .a extension. MSVC will create a file without prefix, + /// ending with `.lib`. + /// + /// # Panics + /// + /// Panics if `output` is not formatted correctly or if one of the underlying + /// compiler commands fails. It can also panic if it fails reading file names + /// or creating directories. + pub fn compile(&self, output: &str) { + if let Err(e) = self.try_compile(output) { + fail(&e.message); + } } #[cfg(feature = "parallel")] - fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) { + fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) -> Result<(), Error> { use self::rayon::prelude::*; let mut cfg = rayon::Configuration::new(); @@ -459,24 +796,36 @@ impl Config { } drop(rayon::initialize(cfg)); + let results: Mutex<Vec<Result<(), Error>>> = Mutex::new(Vec::new()); + objs.par_iter().with_max_len(1) - .for_each(|&(ref src, ref dst)| self.compile_object(src, dst)); + .for_each(|&(ref src, ref dst)| results.lock().unwrap().push(self.compile_object(src, dst))); + + // Check for any errors and return the first one found. + for result in results.into_inner().unwrap().iter() { + if result.is_err() { + return result.clone(); + } + } + + Ok(()) } #[cfg(not(feature = "parallel"))] - fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) { + fn compile_objects(&self, objs: &[(PathBuf, PathBuf)]) -> Result<(), Error> { for &(ref src, ref dst) in objs { - self.compile_object(src, dst); + self.compile_object(src, dst)?; } + Ok(()) } - fn compile_object(&self, file: &Path, dst: &Path) { + fn compile_object(&self, file: &Path, dst: &Path) -> Result<(), Error> { let is_asm = file.extension().and_then(|s| s.to_str()) == Some("asm"); - let msvc = self.get_target().contains("msvc"); + let msvc = self.get_target()?.contains("msvc"); let (mut cmd, name) = if msvc && is_asm { - self.msvc_macro_assembler() + self.msvc_macro_assembler()? } else { - let compiler = self.get_compiler(); + let compiler = self.try_get_compiler()?; let mut cmd = compiler.to_command(); for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); @@ -484,46 +833,62 @@ impl Config { (cmd, compiler.path .file_name() - .unwrap() + .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))? .to_string_lossy() .into_owned()) }; - if msvc && is_asm { - cmd.arg("/Fo").arg(dst); - } else if msvc { - let mut s = OsString::from("/Fo"); - s.push(&dst); - cmd.arg(s); - } else { - cmd.arg("-o").arg(&dst); - } + command_add_output_file(&mut cmd, dst, msvc, is_asm); cmd.arg(if msvc { "/c" } else { "-c" }); cmd.arg(file); - run(&mut cmd, &name); + run(&mut cmd, &name)?; + Ok(()) } - /// Run the compiler, returning the macro-expanded version of the input files. - /// - /// This is only relevant for C and C++ files. - pub fn expand(&self) -> Vec<u8> { - let compiler = self.get_compiler(); + /// This will return a result instead of panicing; see expand() for the complete description. + pub fn try_expand(&self) -> Result<Vec<u8>, Error> { + let compiler = self.try_get_compiler()?; let mut cmd = compiler.to_command(); for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); } cmd.arg(compiler.family.expand_flag()); + + 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 .file_name() - .unwrap() + .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get compiler path."))? .to_string_lossy() .into_owned(); - run_output(&mut cmd, &name) + Ok(run_output(&mut cmd, &name)?) + } + + /// Run the compiler, returning the macro-expanded version of the input files. + /// + /// This is only relevant for C and C++ files. + /// + /// # Panics + /// Panics if more than one file is present in the config, or if compiler + /// path has an invalid file name. + /// + /// # Example + /// ```no_run + /// let out = gcc::Build::new() + /// .file("src/foo.c") + /// .expand(); + /// ``` + pub fn expand(&self) -> Vec<u8> { + match self.try_expand() { + Err(e) => fail(&e.message), + Ok(v) => v, + } } /// Get the compiler that's in use for this configuration. @@ -540,11 +905,25 @@ impl Config { /// conventions for this path, e.g. looking at the explicitly set compiler, /// environment variables (a number of which are inspected here), and then /// falling back to the default configuration. + /// + /// # Panics + /// + /// Panics if an error occurred while determining the architecture. pub fn get_compiler(&self) -> Tool { - let opt_level = self.get_opt_level(); - let target = self.get_target(); + match self.try_get_compiler() { + Ok(tool) => tool, + Err(e) => fail(&e.message), + } + } - let mut cmd = self.get_base_compiler(); + /// Get the compiler that's in use for this configuration. + /// + /// This will return a result instead of panicing; see get_compiler() for the complete description. + pub fn try_get_compiler(&self) -> Result<Tool, Error> { + let opt_level = self.get_opt_level()?; + 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); @@ -580,7 +959,14 @@ impl Config { } ToolFamily::Gnu | ToolFamily::Clang => { - cmd.args.push(format!("-O{}", opt_level).into()); + // arm-linux-androideabi-gcc 4.8 shipped with Android NDK does + // not support '-Oz' + if opt_level == "z" && cmd.family != ToolFamily::Clang { + cmd.args.push("-Os".into()); + } else { + cmd.args.push(format!("-O{}", opt_level).into()); + } + if !nvcc { cmd.args.push("-ffunction-sections".into()); cmd.args.push("-fdata-sections".into()); @@ -618,7 +1004,7 @@ impl Config { cmd.args.push("-m64".into()); } - if target.contains("musl") { + if self.static_flag.is_none() && target.contains("musl") { cmd.args.push("-static".into()); } @@ -695,7 +1081,14 @@ impl Config { if target.contains("-ios") { // FIXME: potential bug. iOS is always compiled with Clang, but Gcc compiler may be // detected instead. - self.ios_flags(&mut cmd); + self.ios_flags(&mut cmd)?; + } + + if self.static_flag.unwrap_or(false) { + cmd.args.push("-static".into()); + } + if self.shared_flag.unwrap_or(false) { + cmd.args.push("-shared".into()); } if self.cpp { @@ -721,6 +1114,12 @@ impl Config { cmd.args.push(flag.into()); } + for flag in self.flags_supported.iter() { + if self.is_flag_supported(flag).unwrap_or(false) { + cmd.args.push(flag.into()); + } + } + for &(ref key, ref value) in self.definitions.iter() { let lead = if let ToolFamily::Msvc = cmd.family {"/"} else {"-"}; if let Some(ref value) = *value { @@ -729,11 +1128,22 @@ impl Config { cmd.args.push(format!("{}D{}", lead, key).into()); } } - cmd + + if self.warnings { + for flag in cmd.family.warnings_flags().iter() { + cmd.args.push(flag.into()); + } + } + + if self.warnings_into_errors { + cmd.args.push(cmd.family.warnings_to_errors_flag().into()); + } + + Ok(cmd) } - fn msvc_macro_assembler(&self) -> (Command, String) { - let target = self.get_target(); + fn msvc_macro_assembler(&self) -> Result<(Command, String), Error> { + let target = self.get_target()?; let tool = if target.contains("x86_64") { "ml64.exe" } else { @@ -758,15 +1168,15 @@ impl Config { cmd.arg(flag); } - (cmd, tool.to_string()) + Ok((cmd, tool.to_string())) } - fn assemble(&self, lib_name: &str, dst: &Path, objects: &[PathBuf]) { + fn assemble(&self, lib_name: &str, dst: &Path, objects: &[PathBuf]) -> Result<(), Error> { // Delete the destination if it exists as the `ar` tool at least on Unix // appends to it, which we don't want. let _ = fs::remove_file(&dst); - let target = self.get_target(); + let target = self.get_target()?; if target.contains("msvc") { let mut cmd = match self.archiver { Some(ref s) => self.cmd(s), @@ -778,46 +1188,52 @@ impl Config { .arg("/nologo") .args(objects) .args(&self.objects), - "lib.exe"); + "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); - fs::hard_link(&dst, &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(|_| ()) - }) - .expect("Copying from {:?} to {:?} failed.");; + }) { + Ok(_) => (), + Err(_) => return Err(Error::new(ErrorKind::IOError, "Could not copy or create a hard-link to the generated lib file.")), + }; } else { - let ar = self.get_ar(); - let cmd = ar.file_name().unwrap().to_string_lossy(); + let ar = self.get_ar()?; + let cmd = ar.file_name() + .ok_or_else(|| Error::new(ErrorKind::IOError, "Failed to get archiver (ar) path."))? + .to_string_lossy(); run(self.cmd(&ar) .arg("crs") .arg(dst) .args(objects) .args(&self.objects), - &cmd); + &cmd)?; } + + Ok(()) } - fn ios_flags(&self, cmd: &mut Tool) { + fn ios_flags(&self, cmd: &mut Tool) -> Result<(), Error> { enum ArchSpec { Device(&'static str), Simulator(&'static str), } - let target = self.get_target(); - let arch = target.split('-').nth(0).unwrap(); + 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 = 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"), - _ => fail("Unknown arch for iOS target"), + _ => return Err(Error::new(ErrorKind::ArchitectureInvalid, "Unknown architecture for iOS target.")), }; let sdk = match arch { @@ -840,14 +1256,18 @@ impl Config { .arg("--sdk") .arg(sdk) .stderr(Stdio::inherit()) - .output() - .unwrap() + .output()? .stdout; - let sdk_path = String::from_utf8(sdk_path).unwrap(); + 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.")), + }; cmd.args.push("-isysroot".into()); cmd.args.push(sdk_path.trim().into()); + + Ok(()) } fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command { @@ -858,12 +1278,12 @@ impl Config { cmd } - fn get_base_compiler(&self) -> Tool { + fn get_base_compiler(&self) -> Result<Tool, Error> { if let Some(ref c) = self.compiler { - return Tool::new(c.clone()); + return Ok(Tool::new(c.clone())); } - let host = self.get_host(); - let target = self.get_target(); + let host = self.get_host()?; + let target = self.get_target()?; let (env, msvc, gnu) = if self.cpp { ("CXX", "cl.exe", "g++") } else { @@ -879,7 +1299,7 @@ impl Config { "cc" }; - self.env_tool(env) + let tool_opt: Option<Tool> = self.env_tool(env) .map(|(tool, args)| { let mut t = Tool::new(PathBuf::from(tool)); for arg in args { @@ -889,28 +1309,29 @@ impl Config { }) .or_else(|| { if target.contains("emscripten") { - //Windows uses bat file so we have to be a bit more specific let tool = if self.cpp { - if cfg!(windows) { - "em++.bat" - } else { - "em++" - } + "em++" } else { - if cfg!(windows) { - "emcc.bat" - } else { - "emcc" - } + "emcc" }; - - Some(Tool::new(PathBuf::from(tool))) + // 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 { None } }) - .or_else(|| windows_registry::find_tool(&target, "cl.exe")) - .unwrap_or_else(|| { + .or_else(|| windows_registry::find_tool(&target, "cl.exe")); + + let tool = match tool_opt { + Some(t) => t, + None => { let compiler = if host.contains("windows") && target.contains("windows") { if target.contains("msvc") { msvc.to_string() @@ -919,7 +1340,7 @@ impl Config { } } else if target.contains("android") { format!("{}-{}", target.replace("armv7", "arm"), gnu) - } else if self.get_host() != target { + } else if self.get_host()? != target { // CROSS_COMPILE is of the form: "arm-linux-gnueabi-" let cc_env = self.getenv("CROSS_COMPILE"); let cross_compile = cc_env.as_ref().map(|s| s.trim_right_matches('-')); @@ -930,14 +1351,14 @@ impl Config { "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"), "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "arm-unknown-netbsdelf-eabi" => Some("arm--netbsdelf-eabi"), - "armv6-unknown-netbsdelf-eabihf" => Some("armv6--netbsdelf-eabihf"), + "arm-unknown-netbsd-eabi" => Some("arm--netbsdelf-eabi"), + "armv6-unknown-netbsd-eabihf" => Some("armv6--netbsdelf-eabihf"), "armv7-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), "armv7-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), - "armv7-unknown-netbsdelf-eabihf" => Some("armv7--netbsdelf-eabihf"), + "armv7-unknown-netbsd-eabihf" => Some("armv7--netbsdelf-eabihf"), "i686-pc-windows-gnu" => Some("i686-w64-mingw32"), "i686-unknown-linux-musl" => Some("musl"), - "i686-unknown-netbsdelf" => Some("i486--netbsdelf"), + "i686-unknown-netbsd" => Some("i486--netbsdelf"), "mips-unknown-linux-gnu" => Some("mips-linux-gnu"), "mipsel-unknown-linux-gnu" => Some("mipsel-linux-gnu"), "mips64-unknown-linux-gnuabi64" => Some("mips64-linux-gnuabi64"), @@ -967,12 +1388,15 @@ impl Config { default.to_string() }; Tool::new(PathBuf::from(compiler)) - }) + } + }; + + Ok(tool) } - fn get_var(&self, var_base: &str) -> Result<String, String> { - let target = self.get_target(); - let host = self.get_host(); + fn get_var(&self, var_base: &str) -> Result<String, Error> { + let target = self.get_target()?; + let host = self.get_host()?; let kind = if host == target { "HOST" } else { "TARGET" }; let target_u = target.replace("-", "_"); let res = self.getenv(&format!("{}_{}", var_base, target)) @@ -982,7 +1406,7 @@ impl Config { match res { Some(res) => Ok(res), - None => Err("could not get environment variable".to_string()), + None => Err(Error::new(ErrorKind::EnvVarNotFound, &format!("Could not find environment variable {}.", var_base))), } } @@ -997,7 +1421,7 @@ impl Config { fn env_tool(&self, name: &str) -> Option<(String, Vec<String>)> { self.get_var(name).ok().map(|tool| { - let whitelist = ["ccache", "distcc"]; + 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()]); @@ -1009,61 +1433,85 @@ impl Config { /// Returns the default C++ standard library for the current target: `libc++` /// for OS X and `libstdc++` for anything else. - fn get_cpp_link_stdlib(&self) -> Option<String> { - self.cpp_link_stdlib.clone().unwrap_or_else(|| { - let target = self.get_target(); - if target.contains("msvc") { - None - } else if target.contains("darwin") { - Some("c++".to_string()) - } else if target.contains("freebsd") { - Some("c++".to_string()) - } else { - Some("stdc++".to_string()) - } - }) + fn get_cpp_link_stdlib(&self) -> Result<Option<String>, Error> { + match self.cpp_link_stdlib.clone() { + Some(s) => Ok(s), + None => { + let target = self.get_target()?; + if target.contains("msvc") { + Ok(None) + } else if target.contains("darwin") { + Ok(Some("c++".to_string())) + } else if target.contains("freebsd") { + Ok(Some("c++".to_string())) + } else { + Ok(Some("stdc++".to_string())) + } + }, + } } - fn get_ar(&self) -> PathBuf { - self.archiver + fn get_ar(&self) -> Result<PathBuf, Error> { + match self.archiver .clone() - .or_else(|| self.get_var("AR").map(PathBuf::from).ok()) - .unwrap_or_else(|| { - if self.get_target().contains("android") { - PathBuf::from(format!("{}-ar", self.get_target().replace("armv7", "arm"))) - } else if self.get_target().contains("emscripten") { - //Windows use bat files so we have to be a bit more specific - let tool = if cfg!(windows) { - "emar.bat" - } else { - "emar" - }; + .or_else(|| self.get_var("AR").map(PathBuf::from).ok()) { + Some(p) => Ok(p), + None => { + if self.get_target()?.contains("android") { + Ok(PathBuf::from(format!("{}-ar", self.get_target()?.replace("armv7", "arm")))) + } else if self.get_target()?.contains("emscripten") { + //Windows use bat files so we have to be a bit more specific + let tool = if cfg!(windows) { + "emar.bat" + } else { + "emar" + }; - PathBuf::from(tool) - } else { - PathBuf::from("ar") + Ok(PathBuf::from(tool)) + } else { + Ok(PathBuf::from("ar")) + } } - }) + } } - fn get_target(&self) -> String { - self.target.clone().unwrap_or_else(|| self.getenv_unwrap("TARGET")) + fn get_target(&self) -> Result<String, Error> { + match self.target.clone() { + Some(t) => Ok(t), + None => Ok(self.getenv_unwrap("TARGET")?), + } } - fn get_host(&self) -> String { - self.host.clone().unwrap_or_else(|| self.getenv_unwrap("HOST")) + fn get_host(&self) -> Result<String, Error> { + match self.host.clone() { + Some(h) => Ok(h), + None => Ok(self.getenv_unwrap("HOST")?), + } } - fn get_opt_level(&self) -> String { - self.opt_level.as_ref().cloned().unwrap_or_else(|| self.getenv_unwrap("OPT_LEVEL")) + fn get_opt_level(&self) -> Result<String, Error> { + match self.opt_level.as_ref().cloned() { + Some(ol) => Ok(ol), + None => Ok(self.getenv_unwrap("OPT_LEVEL")?), + } } fn get_debug(&self) -> bool { - self.debug.unwrap_or_else(|| self.getenv_unwrap("PROFILE") == "debug") + self.debug.unwrap_or_else(|| { + match self.getenv("DEBUG") { + Some(s) => s != "false", + None => false, + } + }) } - fn get_out_dir(&self) -> PathBuf { - self.out_dir.clone().unwrap_or_else(|| env::var_os("OUT_DIR").map(PathBuf::from).unwrap()) + 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."))?), + } } fn getenv(&self, v: &str) -> Option<String> { @@ -1072,10 +1520,10 @@ impl Config { r } - fn getenv_unwrap(&self, v: &str) -> String { + fn getenv_unwrap(&self, v: &str) -> Result<String, Error> { match self.getenv(v) { - Some(s) => s, - None => fail(&format!("environment variable `{}` not defined", v)), + Some(s) => Ok(s), + None => Err(Error::new(ErrorKind::EnvVarNotFound, &format!("Environment variable {} not defined.", v.to_string()))), } } @@ -1086,6 +1534,12 @@ impl Config { } } +impl Default for Build { + fn default() -> Build { + Build::new() + } +} + impl Tool { fn new(path: PathBuf) -> Tool { // Try to detect family of the tool from its name, falling back to Gnu. @@ -1145,31 +1599,42 @@ impl Tool { } } -fn run(cmd: &mut Command, program: &str) { - let (mut child, print) = spawn(cmd, program); - let status = child.wait().expect("failed to wait on child process"); +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))), + }; print.join().unwrap(); println!("{}", status); - if !status.success() { - fail(&format!("command did not execute successfully, got: {}", status)); + + if status.success() { + Ok(()) + } else { + Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} did not execute successfully (status code {}).", cmd, program, status))) } } -fn run_output(cmd: &mut Command, program: &str) -> Vec<u8> { +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 child, print) = spawn(cmd, program)?; let mut stdout = vec![]; child.stdout.take().unwrap().read_to_end(&mut stdout).unwrap(); - let status = child.wait().expect("failed to wait on child process"); + 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))), + }; print.join().unwrap(); println!("{}", status); - if !status.success() { - fail(&format!("command did not execute successfully, got: {}", status)); + + if status.success() { + Ok(stdout) + } else { + Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} did not execute successfully (status code {}).", cmd, program, status))) } - stdout } -fn spawn(cmd: &mut Command, program: &str) -> (Child, JoinHandle<()>) { +fn spawn(cmd: &mut Command, program: &str) -> Result<(Child, JoinHandle<()>), Error> { println!("running: {:?}", cmd); // Capture the standard error coming from these programs, and write it out @@ -1186,7 +1651,7 @@ fn spawn(cmd: &mut Command, program: &str) -> (Child, JoinHandle<()>) { println!(""); } }); - (child, print) + Ok((child, print)) } Err(ref e) if e.kind() == io::ErrorKind::NotFound => { let extra = if cfg!(windows) { @@ -1195,17 +1660,25 @@ fn spawn(cmd: &mut Command, program: &str) -> (Child, JoinHandle<()>) { } else { "" }; - fail(&format!("failed to execute command: {}\nIs `{}` \ - not installed?{}", - e, - program, - extra)); + Err(Error::new(ErrorKind::ToolNotFound, &format!("Failed to find tool. Is `{}` installed?{}", program, extra))) } - Err(e) => fail(&format!("failed to execute command: {}", e)), + Err(_) => Err(Error::new(ErrorKind::ToolExecError, &format!("Command {:?} with args {:?} failed to start.", cmd, program))), } } fn fail(s: &str) -> ! { - println!("\n\n{}\n\n", s); - panic!() + panic!("\n\nInternal error occurred: {}\n\n", s) +} + + +fn command_add_output_file(cmd: &mut Command, dst: &Path, msvc: bool, is_asm: bool) { + if msvc && is_asm { + cmd.arg("/Fo").arg(dst); + } else if msvc { + let mut s = OsString::from("/Fo"); + s.push(&dst); + cmd.arg(s); + } else { + cmd.arg("-o").arg(&dst); + } } |