| //! 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) |
| } |