extern crate cc;
use std::collections::HashSet;
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str;
enum Kind {
Enable,
Disable,
With,
Without,
Arbitrary,
}
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,
forbidden_args: HashSet<String>,
fast_build: 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 {
match Config::try_new(path) {
Ok(config) => config,
Err(msg) => fail(&msg),
}
}
pub fn try_new<P: AsRef<Path>>(path: P) -> Result<Config, String> {
let arg: String = if cfg!(windows) {
let out_dir = env::var_os("OUT_DIR").expect("missing OUT_DIR");
let path = PathBuf::from(out_dir).join("test.sh");
fs::write(&path, "#!/bin/sh\ntrue\n").expect("can't write to OUT_DIR");
path.to_str()
.expect("invalid UTF-8 in path")
.escape_default()
.flat_map(char::escape_default)
.collect()
} else {
"true".into()
};
if let Ok(output) = Command::new("sh")
.arg("-c")
.arg(format!("echo test; {}", arg))
.output()
{
if !output.status.success() {
println!("{}", str::from_utf8(&output.stdout).unwrap_or_default());
eprintln!("{}", str::from_utf8(&output.stderr).unwrap_or_default());
if cfg!(windows) && output.stdout == b"test\n" {
return Err("`sh` does not parse shebangs".to_owned());
} else {
return Err("`sh` is not standard or is otherwise broken".to_owned());
}
}
} else {
return Err("`sh` is required to run `configure`".to_owned());
}
Ok(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,
forbidden_args: HashSet::new(),
fast_build: 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 config_option<P: AsRef<OsStr>>(&mut self, opt: P, optarg: Option<P>) -> &mut Config {
self.set_opt(Kind::Arbitrary, opt, optarg)
}
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 forbid<T: ToString>(&mut self, arg: T) -> &mut Config {
self.forbidden_args.insert(arg.to_string());
self
}
pub fn fast_build(&mut self, fast: bool) -> &mut Config {
self.fast_build = fast;
self
}
fn try_get_paths(&self) -> Result<(PathBuf, PathBuf), String> {
if self.build_insource {
let dst = self.path.clone();
let build = dst.clone();
Ok((dst, build))
} else {
let dst = match self.out_dir.clone() {
Some(dst) => dst,
None => PathBuf::from(try_getenv_unwrap("OUT_DIR")?),
};
let build = dst.join("build");
self.maybe_clear(&build);
let _ = fs::create_dir(&build);
Ok((dst, build))
}
}
pub fn configure(&mut self) -> PathBuf {
match self.try_configure() {
Ok(path) => path,
Err(msg) => fail(&msg),
}
}
pub fn try_configure(&mut self) -> Result<PathBuf, String> {
let target = match self.target.clone() {
Some(target) => target,
None => try_getenv_unwrap("TARGET")?,
};
let host = match self.host.clone() {
Some(host) => host,
None => try_getenv_unwrap("HOST")?,
};
let mut c_cfg = cc::Build::new();
c_cfg
.cargo_metadata(false)
.target(&target)
.warnings(false)
.host(&host);
let mut cxx_cfg = cc::Build::new();
cxx_cfg
.cargo_metadata(false)
.cpp(true)
.target(&target)
.warnings(false)
.host(&host);
let c_compiler = c_cfg.get_compiler();
let cxx_compiler = cxx_cfg.get_compiler();
let (dst, build) = self.try_get_paths()?;
if let Some(ref opts) = self.reconfig {
let executable = "autoreconf".to_owned();
let mut cmd = new_command(executable);
cmd.current_dir(&self.path);
try_run(cmd.arg(opts), "autoreconf")?;
}
let mut cmd;
let mut program = "configure";
let mut args: Vec<String> = Vec::new();
let executable = PathBuf::from(&self.path).join(program);
if target.contains("emscripten") {
program = "emconfigure";
cmd = new_command(program);
args.push(executable.to_string_lossy().to_string());
} else {
cmd = new_command(executable);
}
args.push(format!("--prefix={}", dst.display()));
if cfg!(windows) {
let cygpath = Command::new("cygpath")
.args(["--unix", "--codepage=UTF8"])
.args([&dst, &self.path])
.output();
if let Ok(output) = cygpath {
if output.status.success() {
let output = String::from_utf8(output.stdout).unwrap();
let mut lines = output.lines();
let prefix = lines.next().unwrap();
let srcdir = lines.next().unwrap();
args.push(format!("--prefix={}", prefix));
args.push(format!("--srcdir={}", srcdir));
}
}
}
if self.enable_shared {
args.push("--enable-shared".to_string());
} else {
args.push("--disable-shared".to_string());
}
if self.enable_static {
args.push("--enable-static".to_string());
} else {
args.push("--disable-static".to_string());
}
let mut cflags = c_compiler.cflags_env();
match env::var_os("CFLAGS") {
None => (),
Some(flags) => {
cflags.push(" ");
cflags.push(&flags);
}
}
if !self.cflags.is_empty() {
cflags.push(" ");
cflags.push(&self.cflags);
}
cmd.env("CFLAGS", cflags);
let mut cxxflags = cxx_compiler.cflags_env();
match env::var_os("CXXFLAGS") {
None => (),
Some(flags) => {
cxxflags.push(" ");
cxxflags.push(&flags);
}
}
if !self.cxxflags.is_empty() {
cxxflags.push(" ");
cxxflags.push(&self.cxxflags);
}
cmd.env("CXXFLAGS", cxxflags);
if !self.ldflags.is_empty() {
match env::var_os("LDFLAGS") {
None => cmd.env("LDFLAGS", &self.ldflags),
Some(flags) => {
let mut os = flags;
os.push(" ");
os.push(&self.ldflags);
cmd.env("LDFLAGS", &os)
}
};
}
let mut config_host = false;
for (kind, k, 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-"),
Kind::Arbitrary => {
if k == "host" {
config_host = true;
}
}
};
os.push(k);
if let Some(v) = v {
os.push("=");
os.push(v);
}
args.push(os.to_string_lossy().to_string());
}
let cc_path = c_compiler.path().to_str().unwrap();
let cxx_path = cxx_compiler.path().to_str().unwrap();
if !config_host && cc_path != "musl-gcc" {
let host = cc_path
.strip_suffix("-cc")
.or_else(|| cc_path.strip_suffix("-gcc"));
if let Some(host) = host {
args.push(format!("--host={}", host));
}
}
cmd.env("CC", cc_path);
cmd.env("CXX", cxx_path);
for (k, v) in c_compiler.env().iter().chain(&self.env) {
cmd.env(k, v);
}
for (k, v) in cxx_compiler.env().iter().chain(&self.env) {
cmd.env(k, v);
}
cmd.args(args.iter().filter(|x| {
!self.forbidden_args.contains(match x.find('=') {
Some(idx) => x.split_at(idx).0,
None => x.as_str(),
})
}));
let run_config = if self.fast_build {
let config_status_file = build.join("config.status");
let config_params_file = build.join("configure.prev");
let makefile = build.join("Makefile");
if config_status_file.exists() && config_params_file.exists() && makefile.exists() {
let mut config_params = String::new();
let mut f = fs::File::open(&config_params_file).unwrap();
std::io::Read::read_to_string(&mut f, &mut config_params).unwrap();
config_params != format!("{:?}", cmd)
} else {
true
}
} else {
true
};
if run_config {
let config_params_file = build.join("configure.prev");
let mut f = fs::File::create(config_params_file).unwrap();
std::io::Write::write_all(&mut f, format!("{:?}", cmd).as_bytes()).unwrap();
try_run(cmd.current_dir(&build), program)?;
}
Ok(dst)
}
pub fn build(&mut self) -> PathBuf {
match self.try_build() {
Ok(path) => path,
Err(msg) => fail(&msg),
}
}
pub fn try_build(&mut self) -> Result<PathBuf, String> {
self.try_configure()?;
let (dst, build) = self.try_get_paths()?;
let target = match self.target.clone() {
Some(target) => target,
None => try_getenv_unwrap("TARGET")?,
};
let mut program = "make";
let mut cmd;
let executable = env::var("MAKE").unwrap_or_else(|_| program.to_owned());
if target.contains("emscripten") {
program = "emmake";
cmd = new_command("emmake");
cmd.arg(executable);
} else {
cmd = new_command(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(num_jobs_s) = env::var("NUM_JOBS") {
make_args.push(format!("-j{}", num_jobs_s));
match env::var_os("CARGO_MAKEFLAGS") {
Some(ref cargo_make_flags)
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(cargo_make_flags.clone())
}
_ => (),
}
}
let make_targets = self.make_targets.get_or_insert(vec!["install".to_string()]);
if let Some(flags) = makeflags {
cmd.env("MAKEFLAGS", flags);
}
try_run(
cmd.args(make_targets).args(&make_args).current_dir(&build),
program,
)?;
println!("cargo:root={}", dst.display());
Ok(dst)
}
fn maybe_clear(&self, _dir: &Path) {
}
}
fn try_run(cmd: &mut Command, program: &str) -> Result<(), String> {
println!("running: {:?}", cmd);
let status = match cmd.status() {
Ok(status) => status,
Err(ref e) if e.kind() == ErrorKind::NotFound => {
return Err(format!(
"failed to execute command: {}\nis `{}` not installed?",
e, program
));
}
Err(e) => {
return Err(format!("failed to execute command: {}", e));
}
};
if !status.success() {
return Err(format!(
"command did not execute successfully, got: {}",
status
));
}
Ok(())
}
fn new_command<S: AsRef<OsStr>>(program: S) -> Command {
let mut cmd = Command::new("sh");
cmd.args(["-c", "exec \"$0\" \"$@\""]).arg(program);
cmd
}
fn try_getenv_unwrap(v: &str) -> Result<String, String> {
match env::var(v) {
Ok(s) => Ok(s),
Err(..) => Err(format!("environment variable `{}` not defined", v)),
}
}
fn fail(s: &str) -> ! {
panic!("\n{}\n\nbuild script failed, must exit now", s)
}