439 lines
14 KiB
Rust
439 lines
14 KiB
Rust
//! A Rust library for build scripts to automatically configure code based on
|
|
//! compiler support. Code snippets are dynamically tested to see if the `rustc`
|
|
//! will accept them, rather than hard-coding specific version support.
|
|
//!
|
|
//!
|
|
//! ## Usage
|
|
//!
|
|
//! Add this to your `Cargo.toml`:
|
|
//!
|
|
//! ```toml
|
|
//! [build-dependencies]
|
|
//! autocfg = "1"
|
|
//! ```
|
|
//!
|
|
//! Then use it in your `build.rs` script to detect compiler features. For
|
|
//! example, to test for 128-bit integer support, it might look like:
|
|
//!
|
|
//! ```rust
|
|
//! extern crate autocfg;
|
|
//!
|
|
//! fn main() {
|
|
//! # // Normally, cargo will set `OUT_DIR` for build scripts.
|
|
//! # std::env::set_var("OUT_DIR", "target");
|
|
//! let ac = autocfg::new();
|
|
//! ac.emit_has_type("i128");
|
|
//!
|
|
//! // (optional) We don't need to rerun for anything external.
|
|
//! autocfg::rerun_path("build.rs");
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line
|
|
//! for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the
|
|
//! rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that
|
|
//! should only be used when the compiler supports it.
|
|
//!
|
|
//! ## Caution
|
|
//!
|
|
//! Many of the probing methods of `AutoCfg` document the particular template they
|
|
//! use, **subject to change**. The inputs are not validated to make sure they are
|
|
//! semantically correct for their expected use, so it's _possible_ to escape and
|
|
//! inject something unintended. However, such abuse is unsupported and will not
|
|
//! be considered when making changes to the templates.
|
|
|
|
#![deny(missing_debug_implementations)]
|
|
#![deny(missing_docs)]
|
|
// allow future warnings that can't be fixed while keeping 1.0 compatibility
|
|
#![allow(unknown_lints)]
|
|
#![allow(bare_trait_objects)]
|
|
#![allow(ellipsis_inclusive_range_patterns)]
|
|
|
|
/// Local macro to avoid `std::try!`, deprecated in Rust 1.39.
|
|
macro_rules! try {
|
|
($result:expr) => {
|
|
match $result {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
}
|
|
};
|
|
}
|
|
|
|
use std::env;
|
|
use std::ffi::OsString;
|
|
use std::fs;
|
|
use std::io::{stderr, Write};
|
|
use std::path::PathBuf;
|
|
use std::process::{Command, Stdio};
|
|
#[allow(deprecated)]
|
|
use std::sync::atomic::ATOMIC_USIZE_INIT;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
mod error;
|
|
pub use error::Error;
|
|
|
|
mod version;
|
|
use version::Version;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
/// Helper to detect compiler features for `cfg` output in build scripts.
|
|
#[derive(Clone, Debug)]
|
|
pub struct AutoCfg {
|
|
out_dir: PathBuf,
|
|
rustc: PathBuf,
|
|
rustc_version: Version,
|
|
target: Option<OsString>,
|
|
no_std: bool,
|
|
rustflags: Option<Vec<String>>,
|
|
}
|
|
|
|
/// Writes a config flag for rustc on standard out.
|
|
///
|
|
/// This looks like: `cargo:rustc-cfg=CFG`
|
|
///
|
|
/// Cargo will use this in arguments to rustc, like `--cfg CFG`.
|
|
pub fn emit(cfg: &str) {
|
|
println!("cargo:rustc-cfg={}", cfg);
|
|
}
|
|
|
|
/// Writes a line telling Cargo to rerun the build script if `path` changes.
|
|
///
|
|
/// This looks like: `cargo:rerun-if-changed=PATH`
|
|
///
|
|
/// This requires at least cargo 0.7.0, corresponding to rustc 1.6.0. Earlier
|
|
/// versions of cargo will simply ignore the directive.
|
|
pub fn rerun_path(path: &str) {
|
|
println!("cargo:rerun-if-changed={}", path);
|
|
}
|
|
|
|
/// Writes a line telling Cargo to rerun the build script if the environment
|
|
/// variable `var` changes.
|
|
///
|
|
/// This looks like: `cargo:rerun-if-env-changed=VAR`
|
|
///
|
|
/// This requires at least cargo 0.21.0, corresponding to rustc 1.20.0. Earlier
|
|
/// versions of cargo will simply ignore the directive.
|
|
pub fn rerun_env(var: &str) {
|
|
println!("cargo:rerun-if-env-changed={}", var);
|
|
}
|
|
|
|
/// Create a new `AutoCfg` instance.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `AutoCfg::new()` returns an error.
|
|
pub fn new() -> AutoCfg {
|
|
AutoCfg::new().unwrap()
|
|
}
|
|
|
|
impl AutoCfg {
|
|
/// Create a new `AutoCfg` instance.
|
|
///
|
|
/// # Common errors
|
|
///
|
|
/// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
|
|
/// - The version output from `rustc` can't be parsed.
|
|
/// - `OUT_DIR` is not set in the environment, or is not a writable directory.
|
|
///
|
|
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!")),
|
|
}
|
|
}
|
|
|
|
/// Create a new `AutoCfg` instance with the specified output directory.
|
|
///
|
|
/// # Common errors
|
|
///
|
|
/// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
|
|
/// - The version output from `rustc` can't be parsed.
|
|
/// - `dir` is not a writable directory.
|
|
///
|
|
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 target = env::var_os("TARGET");
|
|
|
|
// Sanity check the output directory
|
|
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"));
|
|
}
|
|
|
|
// Cargo only applies RUSTFLAGS for building TARGET artifact in
|
|
// cross-compilation environment. Sadly, we don't have a way to detect
|
|
// when we're building HOST artifact in a cross-compilation environment,
|
|
// so for now we only apply RUSTFLAGS when cross-compiling an artifact.
|
|
//
|
|
// See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
|
|
let rustflags = if target != env::var_os("HOST")
|
|
|| dir_contains_target(&target, &dir, env::var_os("CARGO_TARGET_DIR"))
|
|
{
|
|
env::var("RUSTFLAGS").ok().map(|rustflags| {
|
|
// This is meant to match how cargo handles the RUSTFLAG environment
|
|
// variable.
|
|
// See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
|
|
rustflags
|
|
.split(' ')
|
|
.map(str::trim)
|
|
.filter(|s| !s.is_empty())
|
|
.map(str::to_string)
|
|
.collect::<Vec<String>>()
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut ac = AutoCfg {
|
|
out_dir: dir,
|
|
rustc: rustc,
|
|
rustc_version: rustc_version,
|
|
target: target,
|
|
no_std: false,
|
|
rustflags: rustflags,
|
|
};
|
|
|
|
// Sanity check with and without `std`.
|
|
if !ac.probe("").unwrap_or(false) {
|
|
ac.no_std = true;
|
|
if !ac.probe("").unwrap_or(false) {
|
|
// Neither worked, so assume nothing...
|
|
ac.no_std = false;
|
|
let warning = b"warning: autocfg could not probe for `std`\n";
|
|
stderr().write_all(warning).ok();
|
|
}
|
|
}
|
|
Ok(ac)
|
|
}
|
|
|
|
/// Test whether the current `rustc` reports a version greater than
|
|
/// or equal to "`major`.`minor`".
|
|
pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
|
|
self.rustc_version >= Version::new(major, minor, 0)
|
|
}
|
|
|
|
/// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`,
|
|
/// if the current `rustc` is at least that version.
|
|
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> {
|
|
#[allow(deprecated)]
|
|
static ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
|
|
|
let id = ID.fetch_add(1, Ordering::Relaxed);
|
|
let mut command = Command::new(&self.rustc);
|
|
command
|
|
.arg("--crate-name")
|
|
.arg(format!("probe{}", id))
|
|
.arg("--crate-type=lib")
|
|
.arg("--out-dir")
|
|
.arg(&self.out_dir)
|
|
.arg("--emit=llvm-ir");
|
|
|
|
if let &Some(ref rustflags) = &self.rustflags {
|
|
command.args(rustflags);
|
|
}
|
|
|
|
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));
|
|
let mut stdin = child.stdin.take().expect("rustc stdin");
|
|
|
|
if self.no_std {
|
|
try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io));
|
|
}
|
|
try!(stdin.write_all(code.as_ref()).map_err(error::from_io));
|
|
drop(stdin);
|
|
|
|
let status = try!(child.wait().map_err(error::from_io));
|
|
Ok(status.success())
|
|
}
|
|
|
|
/// Tests whether the given sysroot crate can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// extern crate CRATE as probe;
|
|
/// ```
|
|
pub fn probe_sysroot_crate(&self, name: &str) -> bool {
|
|
self.probe(format!("extern crate {} as probe;", name)) // `as _` wasn't stabilized until Rust 1.33
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
|
|
pub fn emit_sysroot_crate(&self, name: &str) {
|
|
if self.probe_sysroot_crate(name) {
|
|
emit(&format!("has_{}", mangle(name)));
|
|
}
|
|
}
|
|
|
|
/// Tests whether the given path can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// pub use PATH;
|
|
/// ```
|
|
pub fn probe_path(&self, path: &str) -> bool {
|
|
self.probe(format!("pub use {};", path)).unwrap_or(false)
|
|
}
|
|
|
|
/// Emits a config value `has_PATH` if `probe_path` returns true.
|
|
///
|
|
/// Any non-identifier characters in the `path` will be replaced with
|
|
/// `_` in the generated config value.
|
|
pub fn emit_has_path(&self, path: &str) {
|
|
if self.probe_path(path) {
|
|
emit(&format!("has_{}", mangle(path)));
|
|
}
|
|
}
|
|
|
|
/// Emits the given `cfg` value if `probe_path` returns true.
|
|
pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
|
|
if self.probe_path(path) {
|
|
emit(cfg);
|
|
}
|
|
}
|
|
|
|
/// Tests whether the given trait can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// pub trait Probe: TRAIT + Sized {}
|
|
/// ```
|
|
pub fn probe_trait(&self, name: &str) -> bool {
|
|
self.probe(format!("pub trait Probe: {} + Sized {{}}", name))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Emits a config value `has_TRAIT` if `probe_trait` returns true.
|
|
///
|
|
/// Any non-identifier characters in the trait `name` will be replaced with
|
|
/// `_` in the generated config value.
|
|
pub fn emit_has_trait(&self, name: &str) {
|
|
if self.probe_trait(name) {
|
|
emit(&format!("has_{}", mangle(name)));
|
|
}
|
|
}
|
|
|
|
/// Emits the given `cfg` value if `probe_trait` returns true.
|
|
pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
|
|
if self.probe_trait(name) {
|
|
emit(cfg);
|
|
}
|
|
}
|
|
|
|
/// Tests whether the given type can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// pub type Probe = TYPE;
|
|
/// ```
|
|
pub fn probe_type(&self, name: &str) -> bool {
|
|
self.probe(format!("pub type Probe = {};", name))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Emits a config value `has_TYPE` if `probe_type` returns true.
|
|
///
|
|
/// Any non-identifier characters in the type `name` will be replaced with
|
|
/// `_` in the generated config value.
|
|
pub fn emit_has_type(&self, name: &str) {
|
|
if self.probe_type(name) {
|
|
emit(&format!("has_{}", mangle(name)));
|
|
}
|
|
}
|
|
|
|
/// Emits the given `cfg` value if `probe_type` returns true.
|
|
pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
|
|
if self.probe_type(name) {
|
|
emit(cfg);
|
|
}
|
|
}
|
|
|
|
/// Tests whether the given expression can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// pub fn probe() { let _ = EXPR; }
|
|
/// ```
|
|
pub fn probe_expression(&self, expr: &str) -> bool {
|
|
self.probe(format!("pub fn probe() {{ let _ = {}; }}", expr))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Emits the given `cfg` value if `probe_expression` returns true.
|
|
pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) {
|
|
if self.probe_expression(expr) {
|
|
emit(cfg);
|
|
}
|
|
}
|
|
|
|
/// Tests whether the given constant expression can be used.
|
|
///
|
|
/// The test code is subject to change, but currently looks like:
|
|
///
|
|
/// ```ignore
|
|
/// pub const PROBE: () = ((), EXPR).0;
|
|
/// ```
|
|
pub fn probe_constant(&self, expr: &str) -> bool {
|
|
self.probe(format!("pub const PROBE: () = ((), {}).0;", expr))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Emits the given `cfg` value if `probe_constant` returns true.
|
|
pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
|
|
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: &PathBuf,
|
|
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)
|
|
}
|