| 1 | //! A Rust library for build scripts to automatically configure code based on |
| 2 | //! compiler support. Code snippets are dynamically tested to see if the `rustc` |
| 3 | //! will accept them, rather than hard-coding specific version support. |
| 4 | //! |
| 5 | //! |
| 6 | //! ## Usage |
| 7 | //! |
| 8 | //! Add this to your `Cargo.toml`: |
| 9 | //! |
| 10 | //! ```toml |
| 11 | //! [build-dependencies] |
| 12 | //! autocfg = "1" |
| 13 | //! ``` |
| 14 | //! |
| 15 | //! Then use it in your `build.rs` script to detect compiler features. For |
| 16 | //! example, to test for 128-bit integer support, it might look like: |
| 17 | //! |
| 18 | //! ```rust |
| 19 | //! extern crate autocfg; |
| 20 | //! |
| 21 | //! fn main() { |
| 22 | //! # // Normally, cargo will set `OUT_DIR` for build scripts. |
| 23 | //! # std::env::set_var("OUT_DIR" , "target" ); |
| 24 | //! let ac = autocfg::new(); |
| 25 | //! ac.emit_has_type("i128" ); |
| 26 | //! |
| 27 | //! // (optional) We don't need to rerun for anything external. |
| 28 | //! autocfg::rerun_path("build.rs" ); |
| 29 | //! } |
| 30 | //! ``` |
| 31 | //! |
| 32 | //! If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line |
| 33 | //! for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the |
| 34 | //! rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that |
| 35 | //! should only be used when the compiler supports it. |
| 36 | //! |
| 37 | //! ## Caution |
| 38 | //! |
| 39 | //! Many of the probing methods of `AutoCfg` document the particular template they |
| 40 | //! use, **subject to change**. The inputs are not validated to make sure they are |
| 41 | //! semantically correct for their expected use, so it's _possible_ to escape and |
| 42 | //! inject something unintended. However, such abuse is unsupported and will not |
| 43 | //! be considered when making changes to the templates. |
| 44 | |
| 45 | #![deny (missing_debug_implementations)] |
| 46 | #![deny (missing_docs)] |
| 47 | // allow future warnings that can't be fixed while keeping 1.0 compatibility |
| 48 | #![allow (unknown_lints)] |
| 49 | #![allow (bare_trait_objects)] |
| 50 | #![allow (ellipsis_inclusive_range_patterns)] |
| 51 | |
| 52 | /// Local macro to avoid `std::try!`, deprecated in Rust 1.39. |
| 53 | macro_rules! try { |
| 54 | ($result:expr) => { |
| 55 | match $result { |
| 56 | Ok(value) => value, |
| 57 | Err(error) => return Err(error), |
| 58 | } |
| 59 | }; |
| 60 | } |
| 61 | |
| 62 | use std::env; |
| 63 | use std::ffi::OsString; |
| 64 | use std::fs; |
| 65 | use std::io::{stderr, Write}; |
| 66 | use std::path::{Path, PathBuf}; |
| 67 | use std::process::{Command, Stdio}; |
| 68 | #[allow (deprecated)] |
| 69 | use std::sync::atomic::ATOMIC_USIZE_INIT; |
| 70 | use std::sync::atomic::{AtomicUsize, Ordering}; |
| 71 | |
| 72 | mod error; |
| 73 | pub use error::Error; |
| 74 | |
| 75 | mod version; |
| 76 | use version::Version; |
| 77 | |
| 78 | #[cfg (test)] |
| 79 | mod tests; |
| 80 | |
| 81 | /// Helper to detect compiler features for `cfg` output in build scripts. |
| 82 | #[derive(Clone, Debug)] |
| 83 | pub struct AutoCfg { |
| 84 | out_dir: PathBuf, |
| 85 | rustc: PathBuf, |
| 86 | rustc_version: Version, |
| 87 | target: Option<OsString>, |
| 88 | no_std: bool, |
| 89 | rustflags: Vec<String>, |
| 90 | } |
| 91 | |
| 92 | /// Writes a config flag for rustc on standard out. |
| 93 | /// |
| 94 | /// This looks like: `cargo:rustc-cfg=CFG` |
| 95 | /// |
| 96 | /// Cargo will use this in arguments to rustc, like `--cfg CFG`. |
| 97 | pub fn emit(cfg: &str) { |
| 98 | println!("cargo:rustc-cfg={}" , cfg); |
| 99 | } |
| 100 | |
| 101 | /// Writes a line telling Cargo to rerun the build script if `path` changes. |
| 102 | /// |
| 103 | /// This looks like: `cargo:rerun-if-changed=PATH` |
| 104 | /// |
| 105 | /// This requires at least cargo 0.7.0, corresponding to rustc 1.6.0. Earlier |
| 106 | /// versions of cargo will simply ignore the directive. |
| 107 | pub fn rerun_path(path: &str) { |
| 108 | println!("cargo:rerun-if-changed={}" , path); |
| 109 | } |
| 110 | |
| 111 | /// Writes a line telling Cargo to rerun the build script if the environment |
| 112 | /// variable `var` changes. |
| 113 | /// |
| 114 | /// This looks like: `cargo:rerun-if-env-changed=VAR` |
| 115 | /// |
| 116 | /// This requires at least cargo 0.21.0, corresponding to rustc 1.20.0. Earlier |
| 117 | /// versions of cargo will simply ignore the directive. |
| 118 | pub fn rerun_env(var: &str) { |
| 119 | println!("cargo:rerun-if-env-changed={}" , var); |
| 120 | } |
| 121 | |
| 122 | /// Create a new `AutoCfg` instance. |
| 123 | /// |
| 124 | /// # Panics |
| 125 | /// |
| 126 | /// Panics if `AutoCfg::new()` returns an error. |
| 127 | pub fn new() -> AutoCfg { |
| 128 | AutoCfg::new().unwrap() |
| 129 | } |
| 130 | |
| 131 | impl AutoCfg { |
| 132 | /// Create a new `AutoCfg` instance. |
| 133 | /// |
| 134 | /// # Common errors |
| 135 | /// |
| 136 | /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`. |
| 137 | /// - The version output from `rustc` can't be parsed. |
| 138 | /// - `OUT_DIR` is not set in the environment, or is not a writable directory. |
| 139 | /// |
| 140 | pub fn new() -> Result<Self, Error> { |
| 141 | match env::var_os("OUT_DIR" ) { |
| 142 | Some(d) => Self::with_dir(d), |
| 143 | None => Err(error::from_str("no OUT_DIR specified!" )), |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | /// Create a new `AutoCfg` instance with the specified output directory. |
| 148 | /// |
| 149 | /// # Common errors |
| 150 | /// |
| 151 | /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`. |
| 152 | /// - The version output from `rustc` can't be parsed. |
| 153 | /// - `dir` is not a writable directory. |
| 154 | /// |
| 155 | pub fn with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error> { |
| 156 | let rustc = env::var_os("RUSTC" ).unwrap_or_else(|| "rustc" .into()); |
| 157 | let rustc: PathBuf = rustc.into(); |
| 158 | let rustc_version = try!(Version::from_rustc(&rustc)); |
| 159 | |
| 160 | let target = env::var_os("TARGET" ); |
| 161 | |
| 162 | // Sanity check the output directory |
| 163 | let dir = dir.into(); |
| 164 | let meta = try!(fs::metadata(&dir).map_err(error::from_io)); |
| 165 | if !meta.is_dir() || meta.permissions().readonly() { |
| 166 | return Err(error::from_str("output path is not a writable directory" )); |
| 167 | } |
| 168 | |
| 169 | let mut ac = AutoCfg { |
| 170 | rustflags: rustflags(&target, &dir), |
| 171 | out_dir: dir, |
| 172 | rustc: rustc, |
| 173 | rustc_version: rustc_version, |
| 174 | target: target, |
| 175 | no_std: false, |
| 176 | }; |
| 177 | |
| 178 | // Sanity check with and without `std`. |
| 179 | if !ac.probe("" ).unwrap_or(false) { |
| 180 | ac.no_std = true; |
| 181 | if !ac.probe("" ).unwrap_or(false) { |
| 182 | // Neither worked, so assume nothing... |
| 183 | ac.no_std = false; |
| 184 | let warning = b"warning: autocfg could not probe for `std` \n" ; |
| 185 | stderr().write_all(warning).ok(); |
| 186 | } |
| 187 | } |
| 188 | Ok(ac) |
| 189 | } |
| 190 | |
| 191 | /// Test whether the current `rustc` reports a version greater than |
| 192 | /// or equal to "`major`.`minor`". |
| 193 | pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool { |
| 194 | self.rustc_version >= Version::new(major, minor, 0) |
| 195 | } |
| 196 | |
| 197 | /// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`, |
| 198 | /// if the current `rustc` is at least that version. |
| 199 | pub fn emit_rustc_version(&self, major: usize, minor: usize) { |
| 200 | if self.probe_rustc_version(major, minor) { |
| 201 | emit(&format!("rustc_{}_{}" , major, minor)); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | fn probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error> { |
| 206 | #[allow (deprecated)] |
| 207 | static ID: AtomicUsize = ATOMIC_USIZE_INIT; |
| 208 | |
| 209 | let id = ID.fetch_add(1, Ordering::Relaxed); |
| 210 | let mut command = Command::new(&self.rustc); |
| 211 | command |
| 212 | .arg("--crate-name" ) |
| 213 | .arg(format!("probe{}" , id)) |
| 214 | .arg("--crate-type=lib" ) |
| 215 | .arg("--out-dir" ) |
| 216 | .arg(&self.out_dir) |
| 217 | .arg("--emit=llvm-ir" ); |
| 218 | |
| 219 | if let Some(target) = self.target.as_ref() { |
| 220 | command.arg("--target" ).arg(target); |
| 221 | } |
| 222 | |
| 223 | command.args(&self.rustflags); |
| 224 | |
| 225 | command.arg("-" ).stdin(Stdio::piped()); |
| 226 | let mut child = try!(command.spawn().map_err(error::from_io)); |
| 227 | let mut stdin = child.stdin.take().expect("rustc stdin" ); |
| 228 | |
| 229 | if self.no_std { |
| 230 | try!(stdin.write_all(b"#![no_std] \n" ).map_err(error::from_io)); |
| 231 | } |
| 232 | try!(stdin.write_all(code.as_ref()).map_err(error::from_io)); |
| 233 | drop(stdin); |
| 234 | |
| 235 | let status = try!(child.wait().map_err(error::from_io)); |
| 236 | Ok(status.success()) |
| 237 | } |
| 238 | |
| 239 | /// Tests whether the given sysroot crate can be used. |
| 240 | /// |
| 241 | /// The test code is subject to change, but currently looks like: |
| 242 | /// |
| 243 | /// ```ignore |
| 244 | /// extern crate CRATE as probe; |
| 245 | /// ``` |
| 246 | pub fn probe_sysroot_crate(&self, name: &str) -> bool { |
| 247 | self.probe(format!("extern crate {} as probe;" , name)) // `as _` wasn't stabilized until Rust 1.33 |
| 248 | .unwrap_or(false) |
| 249 | } |
| 250 | |
| 251 | /// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true. |
| 252 | pub fn emit_sysroot_crate(&self, name: &str) { |
| 253 | if self.probe_sysroot_crate(name) { |
| 254 | emit(&format!("has_{}" , mangle(name))); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | /// Tests whether the given path can be used. |
| 259 | /// |
| 260 | /// The test code is subject to change, but currently looks like: |
| 261 | /// |
| 262 | /// ```ignore |
| 263 | /// pub use PATH; |
| 264 | /// ``` |
| 265 | pub fn probe_path(&self, path: &str) -> bool { |
| 266 | self.probe(format!("pub use {};" , path)).unwrap_or(false) |
| 267 | } |
| 268 | |
| 269 | /// Emits a config value `has_PATH` if `probe_path` returns true. |
| 270 | /// |
| 271 | /// Any non-identifier characters in the `path` will be replaced with |
| 272 | /// `_` in the generated config value. |
| 273 | pub fn emit_has_path(&self, path: &str) { |
| 274 | if self.probe_path(path) { |
| 275 | emit(&format!("has_{}" , mangle(path))); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /// Emits the given `cfg` value if `probe_path` returns true. |
| 280 | pub fn emit_path_cfg(&self, path: &str, cfg: &str) { |
| 281 | if self.probe_path(path) { |
| 282 | emit(cfg); |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | /// Tests whether the given trait can be used. |
| 287 | /// |
| 288 | /// The test code is subject to change, but currently looks like: |
| 289 | /// |
| 290 | /// ```ignore |
| 291 | /// pub trait Probe: TRAIT + Sized {} |
| 292 | /// ``` |
| 293 | pub fn probe_trait(&self, name: &str) -> bool { |
| 294 | self.probe(format!("pub trait Probe: {} + Sized {{}}" , name)) |
| 295 | .unwrap_or(false) |
| 296 | } |
| 297 | |
| 298 | /// Emits a config value `has_TRAIT` if `probe_trait` returns true. |
| 299 | /// |
| 300 | /// Any non-identifier characters in the trait `name` will be replaced with |
| 301 | /// `_` in the generated config value. |
| 302 | pub fn emit_has_trait(&self, name: &str) { |
| 303 | if self.probe_trait(name) { |
| 304 | emit(&format!("has_{}" , mangle(name))); |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | /// Emits the given `cfg` value if `probe_trait` returns true. |
| 309 | pub fn emit_trait_cfg(&self, name: &str, cfg: &str) { |
| 310 | if self.probe_trait(name) { |
| 311 | emit(cfg); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | /// Tests whether the given type can be used. |
| 316 | /// |
| 317 | /// The test code is subject to change, but currently looks like: |
| 318 | /// |
| 319 | /// ```ignore |
| 320 | /// pub type Probe = TYPE; |
| 321 | /// ``` |
| 322 | pub fn probe_type(&self, name: &str) -> bool { |
| 323 | self.probe(format!("pub type Probe = {};" , name)) |
| 324 | .unwrap_or(false) |
| 325 | } |
| 326 | |
| 327 | /// Emits a config value `has_TYPE` if `probe_type` returns true. |
| 328 | /// |
| 329 | /// Any non-identifier characters in the type `name` will be replaced with |
| 330 | /// `_` in the generated config value. |
| 331 | pub fn emit_has_type(&self, name: &str) { |
| 332 | if self.probe_type(name) { |
| 333 | emit(&format!("has_{}" , mangle(name))); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | /// Emits the given `cfg` value if `probe_type` returns true. |
| 338 | pub fn emit_type_cfg(&self, name: &str, cfg: &str) { |
| 339 | if self.probe_type(name) { |
| 340 | emit(cfg); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | /// Tests whether the given expression can be used. |
| 345 | /// |
| 346 | /// The test code is subject to change, but currently looks like: |
| 347 | /// |
| 348 | /// ```ignore |
| 349 | /// pub fn probe() { let _ = EXPR; } |
| 350 | /// ``` |
| 351 | pub fn probe_expression(&self, expr: &str) -> bool { |
| 352 | self.probe(format!("pub fn probe() {{ let _ = {}; }}" , expr)) |
| 353 | .unwrap_or(false) |
| 354 | } |
| 355 | |
| 356 | /// Emits the given `cfg` value if `probe_expression` returns true. |
| 357 | pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) { |
| 358 | if self.probe_expression(expr) { |
| 359 | emit(cfg); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | /// Tests whether the given constant expression can be used. |
| 364 | /// |
| 365 | /// The test code is subject to change, but currently looks like: |
| 366 | /// |
| 367 | /// ```ignore |
| 368 | /// pub const PROBE: () = ((), EXPR).0; |
| 369 | /// ``` |
| 370 | pub fn probe_constant(&self, expr: &str) -> bool { |
| 371 | self.probe(format!("pub const PROBE: () = ((), {}).0;" , expr)) |
| 372 | .unwrap_or(false) |
| 373 | } |
| 374 | |
| 375 | /// Emits the given `cfg` value if `probe_constant` returns true. |
| 376 | pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) { |
| 377 | if self.probe_constant(expr) { |
| 378 | emit(cfg); |
| 379 | } |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | fn mangle(s: &str) -> String { |
| 384 | s.chars() |
| 385 | .map(|c| match c { |
| 386 | 'A' ...'Z' | 'a' ...'z' | '0' ...'9' => c, |
| 387 | _ => '_' , |
| 388 | }) |
| 389 | .collect() |
| 390 | } |
| 391 | |
| 392 | fn dir_contains_target( |
| 393 | target: &Option<OsString>, |
| 394 | dir: &Path, |
| 395 | cargo_target_dir: Option<OsString>, |
| 396 | ) -> bool { |
| 397 | target |
| 398 | .as_ref() |
| 399 | .and_then(|target| { |
| 400 | dir.to_str().and_then(|dir| { |
| 401 | let mut cargo_target_dir = cargo_target_dir |
| 402 | .map(PathBuf::from) |
| 403 | .unwrap_or_else(|| PathBuf::from("target" )); |
| 404 | cargo_target_dir.push(target); |
| 405 | |
| 406 | cargo_target_dir |
| 407 | .to_str() |
| 408 | .map(|cargo_target_dir| dir.contains(&cargo_target_dir)) |
| 409 | }) |
| 410 | }) |
| 411 | .unwrap_or(false) |
| 412 | } |
| 413 | |
| 414 | fn rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String> { |
| 415 | // Starting with rust-lang/cargo#9601, shipped in Rust 1.55, Cargo always sets |
| 416 | // CARGO_ENCODED_RUSTFLAGS for any host/target build script invocation. This |
| 417 | // includes any source of flags, whether from the environment, toml config, or |
| 418 | // whatever may come in the future. The value is either an empty string, or a |
| 419 | // list of arguments separated by the ASCII unit separator (US), 0x1f. |
| 420 | if let Ok(a) = env::var("CARGO_ENCODED_RUSTFLAGS" ) { |
| 421 | return if a.is_empty() { |
| 422 | Vec::new() |
| 423 | } else { |
| 424 | a.split(' \x1f' ).map(str::to_string).collect() |
| 425 | }; |
| 426 | } |
| 427 | |
| 428 | // Otherwise, we have to take a more heuristic approach, and we don't |
| 429 | // support values from toml config at all. |
| 430 | // |
| 431 | // Cargo only applies RUSTFLAGS for building TARGET artifact in |
| 432 | // cross-compilation environment. Sadly, we don't have a way to detect |
| 433 | // when we're building HOST artifact in a cross-compilation environment, |
| 434 | // so for now we only apply RUSTFLAGS when cross-compiling an artifact. |
| 435 | // |
| 436 | // See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030. |
| 437 | if *target != env::var_os("HOST" ) |
| 438 | || dir_contains_target(target, dir, env::var_os("CARGO_TARGET_DIR" )) |
| 439 | { |
| 440 | if let Ok(rustflags) = env::var("RUSTFLAGS" ) { |
| 441 | // This is meant to match how cargo handles the RUSTFLAGS environment variable. |
| 442 | // See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441 |
| 443 | return rustflags |
| 444 | .split(' ' ) |
| 445 | .map(str::trim) |
| 446 | .filter(|s| !s.is_empty()) |
| 447 | .map(str::to_string) |
| 448 | .collect(); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | Vec::new() |
| 453 | } |
| 454 | |