#![doc(html_root_url = "http://alexcrichton.com/gcc-rs")]
#![cfg_attr(test, deny(warnings))]
#![deny(missing_docs)]
use std::env;
use std::ffi::{OsString, OsStr};
use std::fs;
use std::io;
use std::path::{PathBuf, Path};
use std::process::{Command, Stdio};
#[cfg(windows)]
mod registry;
pub mod windows_registry;
pub struct Config {
include_directories: Vec<PathBuf>,
definitions: Vec<(String, Option<String>)>,
objects: Vec<PathBuf>,
flags: Vec<String>,
files: Vec<PathBuf>,
cpp: bool,
cpp_link_stdlib: Option<Option<String>>,
cpp_set_stdlib: Option<String>,
target: Option<String>,
host: Option<String>,
out_dir: Option<PathBuf>,
opt_level: Option<u32>,
debug: Option<bool>,
env: Vec<(OsString, OsString)>,
compiler: Option<PathBuf>,
archiver: Option<PathBuf>,
cargo_metadata: bool,
}
pub struct Tool {
path: PathBuf,
args: Vec<OsString>,
env: Vec<(OsString, OsString)>,
}
pub fn compile_library(output: &str, files: &[&str]) {
let mut c = Config::new();
for f in files.iter() {
c.file(*f);
}
c.compile(output)
}
impl Config {
pub fn new() -> Config {
Config {
include_directories: Vec::new(),
definitions: Vec::new(),
objects: Vec::new(),
flags: Vec::new(),
files: Vec::new(),
cpp: false,
cpp_link_stdlib: None,
cpp_set_stdlib: None,
target: None,
host: None,
out_dir: None,
opt_level: None,
debug: None,
env: Vec::new(),
compiler: None,
archiver: None,
cargo_metadata: true
}
}
pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Config {
self.include_directories.push(dir.as_ref().to_path_buf());
self
}
pub fn define(&mut self, var: &str, val: Option<&str>) -> &mut Config {
self.definitions.push((var.to_string(), val.map(|s| s.to_string())));
self
}
pub fn object<P: AsRef<Path>>(&mut self, obj: P) -> &mut Config {
self.objects.push(obj.as_ref().to_path_buf());
self
}
pub fn flag(&mut self, flag: &str) -> &mut Config {
self.flags.push(flag.to_string());
self
}
pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Config {
self.files.push(p.as_ref().to_path_buf());
self
}
pub fn cpp(&mut self, cpp: bool) -> &mut Config {
self.cpp = cpp;
self
}
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()));
self
}
pub fn cpp_set_stdlib(&mut self, cpp_set_stdlib: Option<&str>)
-> &mut Config {
self.cpp_set_stdlib = cpp_set_stdlib.map(|s| s.into());
self.cpp_link_stdlib(cpp_set_stdlib);
self
}
pub fn target(&mut self, target: &str) -> &mut Config {
self.target = Some(target.to_string());
self
}
pub fn host(&mut self, host: &str) -> &mut Config {
self.host = Some(host.to_string());
self
}
pub fn opt_level(&mut self, opt_level: u32) -> &mut Config {
self.opt_level = Some(opt_level);
self
}
pub fn debug(&mut self, debug: bool) -> &mut Config {
self.debug = Some(debug);
self
}
pub fn out_dir<P: AsRef<Path>>(&mut self, out_dir: P) -> &mut Config {
self.out_dir = Some(out_dir.as_ref().to_owned());
self
}
pub fn compiler<P: AsRef<Path>>(&mut self, compiler: P) -> &mut Config {
self.compiler = Some(compiler.as_ref().to_owned());
self
}
pub fn archiver<P: AsRef<Path>>(&mut self, archiver: P) -> &mut Config {
self.archiver = Some(archiver.as_ref().to_owned());
self
}
pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
self.cargo_metadata = cargo_metadata;
self
}
#[doc(hidden)]
pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Config
where A: AsRef<OsStr>, B: AsRef<OsStr>
{
self.env.push((a.as_ref().to_owned(), b.as_ref().to_owned()));
self
}
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();
let mut objects = Vec::new();
for file in self.files.iter() {
let obj = dst.join(file).with_extension("o");
self.compile_object(file, &obj);
objects.push(obj);
}
self.assemble(lib_name, &dst.join(output), &objects);
self.print(&format!("cargo:rustc-link-lib=static={}",
&output[3..output.len() - 2]));
self.print(&format!("cargo:rustc-link-search=native={}", dst.display()));
if self.cpp {
if let Some(stdlib) = self.get_cpp_link_stdlib() {
self.print(&format!("cargo:rustc-link-lib={}", stdlib));
}
}
}
fn compile_object(&self, file: &Path, dst: &Path) {
let is_asm = file.extension().and_then(|s| s.to_str()) == Some("asm");
let msvc = self.get_target().contains("msvc");
let (mut cmd, name) = if msvc && is_asm {
self.msvc_macro_assembler()
} else {
let compiler = self.get_compiler();
let mut cmd = compiler.to_command();
for &(ref a, ref b) in self.env.iter() {
cmd.env(a, b);
}
(cmd, compiler.path.file_name().unwrap()
.to_string_lossy().into_owned())
};
fs::create_dir_all(&dst.parent().unwrap()).unwrap();
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);
}
cmd.arg(if msvc {"/c"} else {"-c"});
cmd.arg(file);
run(&mut cmd, &name);
}
pub fn get_compiler(&self) -> Tool {
let opt_level = self.get_opt_level();
let debug = self.get_debug();
let target = self.get_target();
let msvc = target.contains("msvc");
self.print(&format!("debug={} opt-level={}", debug, opt_level));
let mut cmd = self.get_base_compiler();
if msvc {
cmd.args.push("/nologo".into());
cmd.args.push("/MD".into()); if opt_level != 0 {
cmd.args.push("/O2".into());
}
} else {
cmd.args.push(format!("-O{}", opt_level).into());
cmd.args.push("-ffunction-sections".into());
cmd.args.push("-fdata-sections".into());
}
for arg in self.envflags(if self.cpp {"CXXFLAGS"} else {"CFLAGS"}) {
cmd.args.push(arg.into());
}
if debug {
cmd.args.push(if msvc {"/Z7"} else {"-g"}.into());
}
if target.contains("-ios") {
self.ios_flags(&mut cmd);
} else if !msvc {
if target.contains("i686") {
cmd.args.push("-m32".into());
} else if target.contains("x86_64") {
cmd.args.push("-m64".into());
}
if !target.contains("i686") && !target.contains("windows-gnu") {
cmd.args.push("-fPIC".into());
}
if target.contains("musl") {
cmd.args.push("-static".into());
}
}
if self.cpp && !msvc {
if let Some(ref stdlib) = self.cpp_set_stdlib {
cmd.args.push(format!("-stdlib=lib{}", stdlib).into());
}
}
for directory in self.include_directories.iter() {
cmd.args.push(if msvc {"/I"} else {"-I"}.into());
cmd.args.push(directory.into());
}
for flag in self.flags.iter() {
cmd.args.push(flag.into());
}
for &(ref key, ref value) in self.definitions.iter() {
let lead = if msvc {"/"} else {"-"};
if let &Some(ref value) = value {
cmd.args.push(format!("{}D{}={}", lead, key, value).into());
} else {
cmd.args.push(format!("{}D{}", lead, key).into());
}
}
cmd
}
fn msvc_macro_assembler(&self) -> (Command, String) {
let target = self.get_target();
let tool = if target.contains("x86_64") {"ml64.exe"} else {"ml.exe"};
let mut cmd = windows_registry::find(&target, tool).unwrap_or_else(|| {
self.cmd(tool)
});
for directory in self.include_directories.iter() {
cmd.arg("/I").arg(directory);
}
for &(ref key, ref value) in self.definitions.iter() {
if let &Some(ref value) = value {
cmd.arg(&format!("/D{}={}", key, value));
} else {
cmd.arg(&format!("/D{}", key));
}
}
(cmd, tool.to_string())
}
fn assemble(&self, lib_name: &str, dst: &Path, objects: &[PathBuf]) {
let target = self.get_target();
if target.contains("msvc") {
let mut cmd = match self.archiver {
Some(ref s) => self.cmd(s),
None => windows_registry::find(&target, "lib.exe")
.unwrap_or(self.cmd("lib.exe")),
};
let mut out = OsString::from("/OUT:");
out.push(dst);
run(cmd.arg(out).arg("/nologo")
.args(objects)
.args(&self.objects), "lib.exe");
let lib_dst = dst.with_file_name(format!("{}.lib", lib_name));
let _ = fs::remove_file(&lib_dst);
fs::hard_link(&dst, &lib_dst).or_else(|_| {
fs::copy(&dst, &lib_dst).map(|_| ())
}).ok().expect("Copying from {:?} to {:?} failed.");;
} else {
let ar = self.get_ar();
let cmd = ar.file_name().unwrap().to_string_lossy();
run(self.cmd(&ar).arg("crus")
.arg(dst)
.args(objects)
.args(&self.objects), &cmd);
}
}
fn ios_flags(&self, cmd: &mut Tool) {
enum ArchSpec {
Device(&'static str),
Simulator(&'static str),
}
let target = self.get_target();
let arch = target.split('-').nth(0).unwrap();
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")
};
let sdk = match arch {
ArchSpec::Device(arch) => {
cmd.args.push("-arch".into());
cmd.args.push(arch.into());
cmd.args.push("-miphoneos-version-min=7.0".into());
"iphoneos"
},
ArchSpec::Simulator(arch) => {
cmd.args.push(arch.into());
cmd.args.push("-mios-simulator-version-min=7.0".into());
"iphonesimulator"
}
};
self.print(&format!("Detecting iOS SDK path for {}", sdk));
let sdk_path = self.cmd("xcrun")
.arg("--show-sdk-path")
.arg("--sdk")
.arg(sdk)
.stderr(Stdio::inherit())
.output()
.unwrap()
.stdout;
let sdk_path = String::from_utf8(sdk_path).unwrap();
cmd.args.push("-isysroot".into());
cmd.args.push(sdk_path.trim().into());
}
fn cmd<P: AsRef<OsStr>>(&self, prog: P) -> Command {
let mut cmd = Command::new(prog);
for &(ref a, ref b) in self.env.iter() {
cmd.env(a, b);
}
return cmd
}
fn get_base_compiler(&self) -> Tool {
if let Some(ref c) = self.compiler {
return Tool::new(c.clone())
}
let host = self.get_host();
let target = self.get_target();
let (env, msvc, gnu, default) = if self.cpp {
("CXX", "cl.exe", "g++", "c++")
} else {
("CC", "cl.exe", "gcc", "cc")
};
self.get_var(env).ok().map(|env| {
Tool::new(PathBuf::from(env))
}).or_else(|| {
windows_registry::find_tool(&target, "cl.exe")
}).unwrap_or_else(|| {
let compiler = if host.contains("windows") &&
target.contains("windows") {
if target.contains("msvc") {
msvc.to_string()
} else {
format!("{}.exe", gnu)
}
} else if target.contains("android") {
format!("{}-{}", target, gnu)
} else if self.get_host() != target {
let prefix = match &target[..] {
"aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"),
"arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"),
"arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"),
"powerpc-unknown-linux-gnu" => Some("powerpc-linux-gnu"),
"mips-unknown-linux-gnu" => Some("mips-linux-gnu"),
"i686-pc-windows-gnu" => Some("i686-w64-mingw32"),
"x86_64-pc-windows-gnu" => Some("x86_64-w64-mingw32"),
"x86_64-unknown-linux-musl" => Some("musl"),
"x86_64-rumprun-netbsd" => Some("x86_64-rumprun-netbsd"),
_ => None,
};
match prefix {
Some(prefix) => format!("{}-{}", prefix, gnu),
None => default.to_string(),
}
} else {
default.to_string()
};
Tool::new(PathBuf::from(compiler))
})
}
fn get_var(&self, var_base: &str) -> Result<String, String> {
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))
.or_else(|| self.getenv(&format!("{}_{}", var_base, target_u)))
.or_else(|| self.getenv(&format!("{}_{}", kind, var_base)))
.or_else(|| self.getenv(var_base));
match res {
Some(res) => Ok(res),
None => Err("Could not get environment variable".to_string()),
}
}
fn envflags(&self, name: &str) -> Vec<String> {
self.get_var(name).unwrap_or(String::new())
.split(|c: char| c.is_whitespace()).filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect()
}
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 {
Some("stdc++".to_string())
}
})
}
fn get_ar(&self) -> PathBuf {
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()))
} else {
PathBuf::from("ar")
}
})
}
fn get_target(&self) -> String {
self.target.clone().unwrap_or_else(|| self.getenv_unwrap("TARGET"))
}
fn get_host(&self) -> String {
self.host.clone().unwrap_or_else(|| self.getenv_unwrap("HOST"))
}
fn get_opt_level(&self) -> u32 {
self.opt_level.unwrap_or_else(|| {
self.getenv_unwrap("OPT_LEVEL").parse().unwrap()
})
}
fn get_debug(&self) -> bool {
self.debug.unwrap_or_else(|| self.getenv_unwrap("PROFILE") == "debug")
}
fn get_out_dir(&self) -> PathBuf {
self.out_dir.clone().unwrap_or_else(|| {
env::var_os("OUT_DIR").map(PathBuf::from).unwrap()
})
}
fn getenv(&self, v: &str) -> Option<String> {
let r = env::var(v).ok();
self.print(&format!("{} = {:?}", v, r));
r
}
fn getenv_unwrap(&self, v: &str) -> String {
match self.getenv(v) {
Some(s) => s,
None => fail(&format!("environment variable `{}` not defined", v)),
}
}
fn print(&self, s: &str) {
if self.cargo_metadata {
println!("{}", s);
}
}
}
impl Tool {
fn new(path: PathBuf) -> Tool {
Tool {
path: path,
args: Vec::new(),
env: Vec::new(),
}
}
pub fn to_command(&self) -> Command {
let mut cmd = Command::new(&self.path);
cmd.args(&self.args);
for &(ref k, ref v) in self.env.iter() {
cmd.env(k, v);
}
return cmd
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn args(&self) -> &[OsString] {
&self.args
}
pub fn env(&self) -> &[(OsString, OsString)] {
&self.env
}
}
fn run(cmd: &mut Command, program: &str) {
println!("running: {:?}", cmd);
let status = match cmd.status() {
Ok(status) => status,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
let extra = if cfg!(windows) {
" (see https://github.com/alexcrichton/gcc-rs#compile-time-requirements \
for help)"
} else {
""
};
fail(&format!("failed to execute command: {}\nIs `{}` \
not installed?{}", e, program, extra));
}
Err(e) => fail(&format!("failed to execute command: {}", e)),
};
println!("{:?}", status);
if !status.success() {
fail(&format!("command did not execute successfully, got: {}", status));
}
}
fn fail(s: &str) -> ! {
println!("\n\n{}\n\n", s);
panic!()
}