#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![allow(unknown_lints)]
#![allow(bare_trait_objects)]
#![allow(ellipsis_inclusive_range_patterns)]
macro_rules! try {
($result:expr) => {
match $result {
Ok(value) => value,
Err(error) => return Err(error),
}
};
}
use std::env;
use std::ffi::OsString;
use std::fmt::Arguments;
use std::fs;
use std::io::{stderr, Write};
use std::path::{Path, PathBuf};
use std::process::Stdio;
#[allow(deprecated)]
use std::sync::atomic::ATOMIC_USIZE_INIT;
use std::sync::atomic::{AtomicUsize, Ordering};
mod error;
pub use error::Error;
mod rustc;
use rustc::Rustc;
mod version;
use version::Version;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug)]
pub struct AutoCfg {
out_dir: PathBuf,
rustc: Rustc,
rustc_version: Version,
target: Option<OsString>,
no_std: bool,
edition: Option<String>,
rustflags: Vec<String>,
uuid: u64,
}
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 emit_possibility(cfg: &str) {
println!("cargo:rustc-check-cfg=cfg({})", cfg);
}
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 = Rustc::new();
let rustc_version = try!(rustc.version());
let target = env::var_os("TARGET");
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"));
}
let mut ac = AutoCfg {
rustflags: rustflags(&target, &dir),
out_dir: dir,
rustc: rustc,
rustc_version: rustc_version,
target: target,
no_std: false,
edition: None,
uuid: new_uuid(),
};
if ac.probe_raw("").is_err() {
if ac.probe_raw("#![no_std]").is_ok() {
ac.no_std = true;
} else {
let warning = b"warning: autocfg could not probe for `std`\n";
stderr().write_all(warning).ok();
}
}
Ok(ac)
}
pub fn no_std(&self) -> bool {
self.no_std
}
pub fn set_no_std(&mut self, no_std: bool) {
self.no_std = no_std;
}
pub fn edition(&self) -> Option<&str> {
match self.edition {
Some(ref edition) => Some(&**edition),
None => None,
}
}
pub fn set_edition(&mut self, edition: Option<String>) {
self.edition = edition;
}
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) {
let cfg_flag = format!("rustc_{}_{}", major, minor);
emit_possibility(&cfg_flag);
if self.probe_rustc_version(major, minor) {
emit(&cfg_flag);
}
}
fn new_crate_name(&self) -> String {
#[allow(deprecated)]
static ID: AtomicUsize = ATOMIC_USIZE_INIT;
let id = ID.fetch_add(1, Ordering::Relaxed);
format!("autocfg_{:016x}_{}", self.uuid, id)
}
fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> {
let crate_name = self.new_crate_name();
let mut command = self.rustc.command();
command
.arg("--crate-name")
.arg(&crate_name)
.arg("--crate-type=lib")
.arg("--out-dir")
.arg(&self.out_dir)
.arg("--emit=llvm-ir");
if let Some(edition) = self.edition.as_ref() {
command.arg("--edition").arg(edition);
}
if let Some(target) = self.target.as_ref() {
command.arg("--target").arg(target);
}
command.args(&self.rustflags);
command.arg("-").stdin(Stdio::piped());
let mut child = try!(command.spawn().map_err(error::from_io));
let mut stdin = child.stdin.take().expect("rustc stdin");
try!(stdin.write_fmt(source).map_err(error::from_io));
drop(stdin);
match child.wait() {
Ok(status) if status.success() => {
let mut file = self.out_dir.join(crate_name);
file.set_extension("ll");
let _ = fs::remove_file(file);
Ok(())
}
Ok(status) => Err(error::from_exit(status)),
Err(error) => Err(error::from_io(error)),
}
}
fn probe<'a>(&self, code: Arguments<'a>) -> bool {
let result = if self.no_std {
self.probe_fmt(format_args!("#![no_std]\n{}", code))
} else {
self.probe_fmt(code)
};
result.is_ok()
}
pub fn probe_raw(&self, code: &str) -> Result<(), Error> {
self.probe_fmt(format_args!("{}", code))
}
pub fn probe_sysroot_crate(&self, name: &str) -> bool {
self.probe(format_args!("extern crate {} as probe;", name))
}
pub fn emit_sysroot_crate(&self, name: &str) {
let cfg_flag = format!("has_{}", mangle(name));
emit_possibility(&cfg_flag);
if self.probe_sysroot_crate(name) {
emit(&cfg_flag);
}
}
pub fn probe_path(&self, path: &str) -> bool {
self.probe(format_args!("pub use {};", path))
}
pub fn emit_has_path(&self, path: &str) {
self.emit_path_cfg(path, &format!("has_{}", mangle(path)));
}
pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
emit_possibility(cfg);
if self.probe_path(path) {
emit(cfg);
}
}
pub fn probe_trait(&self, name: &str) -> bool {
self.probe(format_args!("pub trait Probe: {} + Sized {{}}", name))
}
pub fn emit_has_trait(&self, name: &str) {
self.emit_trait_cfg(name, &format!("has_{}", mangle(name)));
}
pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
emit_possibility(cfg);
if self.probe_trait(name) {
emit(cfg);
}
}
pub fn probe_type(&self, name: &str) -> bool {
self.probe(format_args!("pub type Probe = {};", name))
}
pub fn emit_has_type(&self, name: &str) {
self.emit_type_cfg(name, &format!("has_{}", mangle(name)));
}
pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
emit_possibility(cfg);
if self.probe_type(name) {
emit(cfg);
}
}
pub fn probe_expression(&self, expr: &str) -> bool {
self.probe(format_args!("pub fn probe() {{ let _ = {}; }}", expr))
}
pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) {
emit_possibility(cfg);
if self.probe_expression(expr) {
emit(cfg);
}
}
pub fn probe_constant(&self, expr: &str) -> bool {
self.probe(format_args!("pub const PROBE: () = ((), {}).0;", expr))
}
pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
emit_possibility(cfg);
if self.probe_constant(expr) {
emit(cfg);
}
}
}
fn mangle(s: &str) -> String {
s.chars()
.map(|c| match c {
'A'...'Z' | 'a'...'z' | '0'...'9' => c,
_ => '_',
})
.collect()
}
fn dir_contains_target(
target: &Option<OsString>,
dir: &Path,
cargo_target_dir: Option<OsString>,
) -> bool {
target
.as_ref()
.and_then(|target| {
dir.to_str().and_then(|dir| {
let mut cargo_target_dir = cargo_target_dir
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("target"));
cargo_target_dir.push(target);
cargo_target_dir
.to_str()
.map(|cargo_target_dir| dir.contains(cargo_target_dir))
})
})
.unwrap_or(false)
}
fn rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String> {
if let Ok(a) = env::var("CARGO_ENCODED_RUSTFLAGS") {
return if a.is_empty() {
Vec::new()
} else {
a.split('\x1f').map(str::to_string).collect()
};
}
if *target != env::var_os("HOST")
|| dir_contains_target(target, dir, env::var_os("CARGO_TARGET_DIR"))
{
if let Ok(rustflags) = env::var("RUSTFLAGS") {
return rustflags
.split(' ')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
}
}
Vec::new()
}
fn new_uuid() -> u64 {
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x100_0000_01b3;
let set: std::collections::HashSet<u64> = (0..256).collect();
let mut hash: u64 = FNV_OFFSET_BASIS;
for x in set {
hash = (hash ^ x).wrapping_mul(FNV_PRIME);
}
hash
}