#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
mod error;
pub use error::Error;
mod version;
use version::Version;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug)]
pub struct AutoCfg {
out_dir: PathBuf,
rustc: PathBuf,
rustc_version: Version,
target: Option<OsString>,
}
pub fn emit(cfg: &str) {
println!("cargo:rustc-cfg={}", cfg);
}
pub fn rerun_path(path: &str) {
println!("cargo:rerun-if-changed={}", path);
}
pub fn rerun_env(var: &str) {
println!("cargo:rerun-if-env-changed={}", var);
}
pub fn new() -> AutoCfg {
AutoCfg::new().unwrap()
}
impl AutoCfg {
pub fn new() -> Result<Self, Error> {
match env::var_os("OUT_DIR") {
Some(d) => Self::with_dir(d),
None => Err(error::from_str("no OUT_DIR specified!")),
}
}
pub fn with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error> {
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
let rustc: PathBuf = rustc.into();
let rustc_version = try!(Version::from_rustc(&rustc));
let dir = dir.into();
let meta = try!(fs::metadata(&dir).map_err(error::from_io));
if !meta.is_dir() || meta.permissions().readonly() {
return Err(error::from_str("output path is not a writable directory"));
}
Ok(AutoCfg {
out_dir: dir,
rustc: rustc,
rustc_version: rustc_version,
target: env::var_os("TARGET"),
})
}
pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
self.rustc_version >= Version::new(major, minor, 0)
}
pub fn emit_rustc_version(&self, major: usize, minor: usize) {
if self.probe_rustc_version(major, minor) {
emit(&format!("rustc_{}_{}", major, minor));
}
}
fn probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error> {
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
static ID: AtomicUsize = ATOMIC_USIZE_INIT;
let id = ID.fetch_add(1, Ordering::Relaxed);
let mut command = Command::new(&self.rustc);
command
.arg(format!("--crate-name=probe{}", id))
.arg("--crate-type=lib")
.arg("--out-dir")
.arg(&self.out_dir)
.arg("--emit=llvm-ir");
if let Some(target) = self.target.as_ref() {
command.arg("--target").arg(target);
}
command.arg("-").stdin(Stdio::piped());
let mut child = try!(command.spawn().map_err(error::from_io));
try!(
child
.stdin
.take()
.expect("rustc stdin")
.write_all(code.as_ref())
.map_err(error::from_io)
);
let status = try!(child.wait().map_err(error::from_io));
Ok(status.success())
}
pub fn probe_path(&self, path: &str) -> bool {
self.probe(format!("pub use {};", path)).unwrap_or(false)
}
pub fn emit_has_path(&self, path: &str) {
if self.probe_path(path) {
emit(&format!("has_{}", mangle(path)));
}
}
pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
if self.probe_path(path) {
emit(cfg);
}
}
pub fn probe_trait(&self, name: &str) -> bool {
self.probe(format!("pub trait Probe: {} + Sized {{}}", name))
.unwrap_or(false)
}
pub fn emit_has_trait(&self, name: &str) {
if self.probe_trait(name) {
emit(&format!("has_{}", mangle(name)));
}
}
pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
if self.probe_trait(name) {
emit(cfg);
}
}
pub fn probe_type(&self, name: &str) -> bool {
self.probe(format!("pub type Probe = {};", name))
.unwrap_or(false)
}
pub fn emit_has_type(&self, name: &str) {
if self.probe_type(name) {
emit(&format!("has_{}", mangle(name)));
}
}
pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
if self.probe_type(name) {
emit(cfg);
}
}
}
fn mangle(s: &str) -> String {
s.chars()
.map(|c| match c {
'A'...'Z' | 'a'...'z' | '0'...'9' => c,
_ => '_',
}).collect()
}