diff options
Diffstat (limited to 'gcc/src')
| -rw-r--r-- | gcc/src/lib.rs | 891 | ||||
| -rw-r--r-- | gcc/src/winapi.rs | 4 | ||||
| -rw-r--r-- | gcc/src/windows_registry.rs | 21 | 
3 files changed, 701 insertions, 215 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); +    }  } diff --git a/gcc/src/winapi.rs b/gcc/src/winapi.rs index 010d165..3fb0408 100644 --- a/gcc/src/winapi.rs +++ b/gcc/src/winapi.rs @@ -67,8 +67,8 @@ pub trait Interface {      fn uuidof() -> GUID;  } -#[link(name = "Ole32")] -#[link(name = "OleAut32")] +#[link(name = "ole32")] +#[link(name = "oleaut32")]  extern { }  extern "system" { diff --git a/gcc/src/windows_registry.rs b/gcc/src/windows_registry.rs index d05fc1d..9099e0f 100644 --- a/gcc/src/windows_registry.rs +++ b/gcc/src/windows_registry.rs @@ -87,6 +87,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {  }  /// A version of Visual Studio +#[derive(Debug, PartialEq, Eq, Copy, Clone)]  pub enum VsVers {      /// Visual Studio 12 (2013)      Vs12, @@ -99,6 +100,7 @@ pub enum VsVers {      /// handle an enumeration of `VsVers` instances should always have a default      /// case meaning that it's a VS version they don't understand.      #[doc(hidden)] +    #[allow(bad_style)]      __Nonexhaustive_do_not_match_this_or_your_code_will_break,  } @@ -195,6 +197,10 @@ mod impl_ {      // In MSVC 15 (2017) MS once again changed the scheme for locating      // the tooling.  Now we must go through some COM interfaces, which      // is super fun for Rust. +    // +    // Note that much of this logic can be found [online] wrt paths, COM, etc. +    // +    // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/      pub fn find_msvc_15(tool: &str, target: &str) -> Option<Tool> {          otry!(com::initialize().ok()); @@ -213,12 +219,12 @@ mod impl_ {      fn tool_from_vs15_instance(tool: &str, target: &str,                                 instance: &SetupInstance) -> Option<Tool> { -        let (bin_path, lib_path, include_path) = otry!(vs15_vc_paths(target, instance)); +        let (bin_path, host_dylib_path, lib_path, include_path) = otry!(vs15_vc_paths(target, instance));          let tool_path = bin_path.join(tool);          if !tool_path.exists() { return None };          let mut tool = MsvcTool::new(tool_path); -        tool.path.push(bin_path.clone()); +        tool.path.push(host_dylib_path);          tool.libs.push(lib_path);          tool.include.push(include_path); @@ -232,7 +238,7 @@ mod impl_ {          Some(tool.into_tool())      } -    fn vs15_vc_paths(target: &str, instance: &SetupInstance) -> Option<(PathBuf, PathBuf, PathBuf)> { +    fn vs15_vc_paths(target: &str, instance: &SetupInstance) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)> {          let instance_path: PathBuf = otry!(instance.installation_path().ok()).into();          let version_path = instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");          let mut version_file = otry!(File::open(version_path).ok()); @@ -245,11 +251,18 @@ mod impl_ {              _ => return None,          };          let target = otry!(lib_subdir(target)); +        // The directory layout here is MSVC/bin/Host$host/$target/          let path = instance_path.join(r"VC\Tools\MSVC").join(version); +        // This is the path to the toolchain for a particular target, running +        // on a given host          let bin_path = path.join("bin").join(&format!("Host{}", host)).join(&target); +        // But! we also need PATH to contain the target directory for the host +        // architecture, because it contains dlls like mspdb140.dll compiled for +        // the host architecture. +        let host_dylib_path = path.join("bin").join(&format!("Host{}", host)).join(&host.to_lowercase());          let lib_path = path.join("lib").join(&target);          let include_path = path.join("include"); -        Some((bin_path, lib_path, include_path)) +        Some((bin_path, host_dylib_path, lib_path, include_path))      }      fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { | 
