extern crate cc;
use std::env;
use std::ffi::{OsString, OsStr};
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::process::Command;
enum Kind {
Enable,
Disable,
With,
Without,
}
pub struct Config {
enable_shared: bool,
enable_static: bool,
path: PathBuf,
cflags: OsString,
cxxflags: OsString,
ldflags: OsString,
options: Vec<(Kind, OsString, Option<OsString>)>,
target: Option<String>,
make_args: Option<Vec<String>>,
make_targets: Option<Vec<String>>,
host: Option<String>,
out_dir: Option<PathBuf>,
env: Vec<(OsString, OsString)>,
reconfig: Option<OsString>,
build_insource: bool,
}
pub fn build<P: AsRef<Path>>(path: P) -> PathBuf {
Config::new(path.as_ref())
.build()
}
impl Config {
pub fn new<P: AsRef<Path>>(path: P) -> Config {
Config {
enable_shared: false,
enable_static: true,
path: env::current_dir().unwrap().join(path),
cflags: OsString::new(),
cxxflags: OsString::new(),
ldflags: OsString::new(),
options: Vec::new(),
make_args: None,
make_targets: None,
out_dir: None,
target: None,
host: None,
env: Vec::new(),
reconfig: None,
build_insource: false,
}
}
pub fn enable_shared(&mut self) -> &mut Config {
self.enable_shared = true;
self
}
pub fn disable_shared(&mut self) -> &mut Config {
self.enable_shared = false;
self
}
pub fn enable_static(&mut self) -> &mut Config {
self.enable_static = true;
self
}
pub fn disable_static(&mut self) -> &mut Config {
self.enable_static = false;
self
}
pub fn make_args(&mut self, flags: Vec<String>) -> &mut Config {
self.make_args = Some(flags);
self
}
fn set_opt<P: AsRef<OsStr>>(&mut self, kind: Kind, opt: P, optarg: Option<P>) -> &mut Config {
let optarg = optarg.as_ref().map(|v| v.as_ref().to_owned());
self.options.push((kind, opt.as_ref().to_owned(),
optarg));
self
}
pub fn enable<P: AsRef<OsStr>>(&mut self, opt: P, optarg: Option<P>) -> &mut Config {
self.set_opt(Kind::Enable, opt, optarg)
}
pub fn disable<P: AsRef<OsStr>>(&mut self, opt: P, optarg: Option<P>) -> &mut Config {
self.set_opt(Kind::Disable, opt, optarg)
}
pub fn with<P: AsRef<OsStr>>(&mut self, opt: P, optarg: Option<P>) -> &mut Config {
self.set_opt(Kind::With, opt, optarg)
}
pub fn without<P: AsRef<OsStr>>(&mut self, opt: P, optarg: Option<P>) -> &mut Config {
self.set_opt(Kind::Without, opt, optarg)
}
pub fn cflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
self.cflags.push(" ");
self.cflags.push(flag.as_ref());
self
}
pub fn cxxflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
self.cxxflags.push(" ");
self.cxxflags.push(flag.as_ref());
self
}
pub fn ldflag<P: AsRef<OsStr>>(&mut self, flag: P) -> &mut Config {
self.ldflags.push(" ");
self.ldflags.push(flag.as_ref());
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 out_dir<P: AsRef<Path>>(&mut self, out: P) -> &mut Config {
self.out_dir = Some(out.as_ref().to_path_buf());
self
}
pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Config
where K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.env.push((key.as_ref().to_owned(), value.as_ref().to_owned()));
self
}
pub fn reconf<P: AsRef<OsStr>>(&mut self, flags: P) -> &mut Config {
self.reconfig = Some(flags.as_ref().to_os_string());
self
}
pub fn make_target(&mut self, make_target: &str) -> &mut Config {
self.make_targets.get_or_insert_with(Vec::new).push(make_target.to_owned());
self
}
pub fn insource(&mut self, build_insource: bool) -> &mut Config {
self.build_insource = build_insource;
self
}
pub fn build(&mut self) -> PathBuf {
let target = self.target.clone().unwrap_or_else(|| {
getenv_unwrap("TARGET")
});
let host = self.host.clone().unwrap_or_else(|| {
getenv_unwrap("HOST")
});
let mut c_cfg = cc::Build::new();
c_cfg.cargo_metadata(false)
.opt_level(0)
.debug(false)
.target(&target)
.warnings(false)
.host(&host);
let mut cxx_cfg = cc::Build::new();
cxx_cfg.cargo_metadata(false)
.cpp(true)
.opt_level(0)
.debug(false)
.target(&target)
.warnings(false)
.host(&host);
let c_compiler = c_cfg.get_compiler();
let cxx_compiler = cxx_cfg.get_compiler();
let dst;
let build;
if self.build_insource {
dst = self.path.clone();
build = dst.clone();
} else {
dst = self.out_dir.clone().unwrap_or_else(|| {
PathBuf::from(getenv_unwrap("OUT_DIR"))
});
build = dst.join("build");
self.maybe_clear(&build);
let _ = fs::create_dir(&build);
}
if let Some(ref opts) = self.reconfig {
let executable = "autoreconf".to_owned();
let mut cmd = Command::new(executable);
cmd.current_dir(&self.path);
run(cmd.arg(opts), "autoreconf");
}
let executable = PathBuf::from(&self.path).join("configure");
let mut cmd = Command::new(executable);
cmd.arg(format!("--host={}", host));
cmd.arg(format!("--prefix={}", dst.display()));
if self.enable_shared {
cmd.arg("--enable-shared");
} else {
cmd.arg("--disable-shared");
}
if self.enable_static {
cmd.arg("--enable-static");
} else {
cmd.arg("--disable-static");
}
if !self.cflags.is_empty() {
cmd.env("CFLAGS", &self.cflags);
}
if !self.cxxflags.is_empty() {
cmd.env("CXXFLAGS", &self.cxxflags);
}
if !self.ldflags.is_empty() {
cmd.env("LDFLAGS", &self.ldflags);
}
for &(ref kind, ref k, ref v) in &self.options {
let mut os = OsString::from("--");
match *kind {
Kind::Enable => os.push("enable"),
Kind::Disable => os.push("disable"),
Kind::With => os.push("with"),
Kind::Without => os.push("without")
};
os.push("-");
os.push(k);
if let &Some(ref v) = v {
os.push("=");
os.push(v);
}
cmd.arg(os);
}
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
cmd.env(k, v);
}
for &(ref k, ref v) in cxx_compiler.env().iter().chain(&self.env) {
cmd.env(k, v);
}
run(cmd.current_dir(&build), "configure");
let executable = env::var("MAKE").unwrap_or("make".to_owned());
let mut cmd = Command::new(executable);
cmd.current_dir(&build);
let mut makeflags = None;
let mut make_args = Vec::new();
if let Some(args) = &self.make_args {
make_args.extend_from_slice(args);
}
if let Ok(s) = env::var("NUM_JOBS") {
match env::var_os("CARGO_MAKEFLAGS") {
Some(ref s) if !(cfg!(windows) ||
cfg!(target_os = "openbsd") ||
cfg!(target_os = "netbsd") ||
cfg!(target_os = "freebsd") ||
cfg!(target_os = "bitrig") ||
cfg!(target_os = "dragonflybsd")
) => makeflags = Some(s.clone()),
_ => make_args.push(format!("-j{}", s)),
}
}
let make_targets = self.make_targets
.get_or_insert(vec!["install".to_string()]);
if let Some(flags) = makeflags {
cmd.env("MAKEFLAGS", flags);
}
run(cmd.args(make_targets)
.args(&make_args)
.current_dir(&build), "make");
println!("cargo:root={}", dst.display());
dst
}
fn maybe_clear(&self, _dir: &Path) {
}
}
fn run(cmd: &mut Command, program: &str) {
println!("running: {:?}", cmd);
let status = match cmd.status() {
Ok(status) => status,
Err(ref e) if e.kind() == ErrorKind::NotFound => {
fail(&format!("failed to execute command: {}\nis `{}` not installed?",
e, program));
}
Err(e) => fail(&format!("failed to execute command: {}", e)),
};
if !status.success() {
fail(&format!("command did not execute successfully, got: {}", status));
}
}
fn getenv_unwrap(v: &str) -> String {
match env::var(v) {
Ok(s) => s,
Err(..) => fail(&format!("environment variable `{}` not defined", v)),
}
}
fn fail(s: &str) -> ! {
panic!("\n{}\n\nbuild script failed, must exit now", s)
}