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. 53macro_rules! try { 54 ($result:expr) => { 55 match $result { 56 Ok(value) => value, 57 Err(error) => return Err(error), 58 } 59 }; 60} 61 62use std::env; 63use std::ffi::OsString; 64use std::fs; 65use std::io::{stderr, Write}; 66use std::path::{Path, PathBuf}; 67use std::process::{Command, Stdio}; 68#[allow(deprecated)] 69use std::sync::atomic::ATOMIC_USIZE_INIT; 70use std::sync::atomic::{AtomicUsize, Ordering}; 71 72mod error; 73pub use error::Error; 74 75mod version; 76use version::Version; 77 78#[cfg(test)] 79mod tests; 80 81/// Helper to detect compiler features for `cfg` output in build scripts. 82#[derive(Clone, Debug)] 83pub 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`. 97pub 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. 107pub 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. 118pub 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. 127pub fn new() -> AutoCfg { 128 AutoCfg::new().unwrap() 129} 130 131impl 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 383fn 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 392fn 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 414fn 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