autocfg/lib.rs
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//! # let exe = std::env::current_exe().unwrap();
24//! # std::env::set_var("OUT_DIR", exe.parent().unwrap());
25//! let ac = autocfg::new();
26//! ac.emit_has_type("i128");
27//!
28//! // (optional) We don't need to rerun for anything external.
29//! autocfg::rerun_path("build.rs");
30//! }
31//! ```
32//!
33//! If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line
34//! for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the
35//! rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that
36//! should only be used when the compiler supports it.
37//!
38//! ## Caution
39//!
40//! Many of the probing methods of `AutoCfg` document the particular template they
41//! use, **subject to change**. The inputs are not validated to make sure they are
42//! semantically correct for their expected use, so it's _possible_ to escape and
43//! inject something unintended. However, such abuse is unsupported and will not
44//! be considered when making changes to the templates.
45
46#![deny(missing_debug_implementations)]
47#![deny(missing_docs)]
48// allow future warnings that can't be fixed while keeping 1.0 compatibility
49#![allow(unknown_lints)]
50#![allow(bare_trait_objects)]
51#![allow(ellipsis_inclusive_range_patterns)]
52
53/// Local macro to avoid `std::try!`, deprecated in Rust 1.39.
54macro_rules! try {
55 ($result:expr) => {
56 match $result {
57 Ok(value) => value,
58 Err(error) => return Err(error),
59 }
60 };
61}
62
63use std::env;
64use std::ffi::OsString;
65use std::fmt::Arguments;
66use std::fs;
67use std::io::{stderr, Write};
68use std::path::{Path, PathBuf};
69use std::process::Stdio;
70#[allow(deprecated)]
71use std::sync::atomic::ATOMIC_USIZE_INIT;
72use std::sync::atomic::{AtomicUsize, Ordering};
73
74mod error;
75pub use error::Error;
76
77mod rustc;
78use rustc::Rustc;
79
80mod version;
81use version::Version;
82
83#[cfg(test)]
84mod tests;
85
86/// Helper to detect compiler features for `cfg` output in build scripts.
87#[derive(Clone, Debug)]
88pub struct AutoCfg {
89 out_dir: PathBuf,
90 rustc: Rustc,
91 rustc_version: Version,
92 target: Option<OsString>,
93 no_std: bool,
94 edition: Option<String>,
95 rustflags: Vec<String>,
96 uuid: u64,
97}
98
99/// Writes a config flag for rustc on standard out.
100///
101/// This looks like: `cargo:rustc-cfg=CFG`
102///
103/// Cargo will use this in arguments to rustc, like `--cfg CFG`.
104///
105/// This does not automatically call [`emit_possibility`]
106/// so the compiler my generate an [`unexpected_cfgs` warning][check-cfg-flags].
107/// However, all the builtin emit methods on [`AutoCfg`] call [`emit_possibility`] automatically.
108///
109/// [check-cfg-flags]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
110pub fn emit(cfg: &str) {
111 println!("cargo:rustc-cfg={}", cfg);
112}
113
114/// Writes a line telling Cargo to rerun the build script if `path` changes.
115///
116/// This looks like: `cargo:rerun-if-changed=PATH`
117///
118/// This requires at least cargo 0.7.0, corresponding to rustc 1.6.0. Earlier
119/// versions of cargo will simply ignore the directive.
120pub fn rerun_path(path: &str) {
121 println!("cargo:rerun-if-changed={}", path);
122}
123
124/// Writes a line telling Cargo to rerun the build script if the environment
125/// variable `var` changes.
126///
127/// This looks like: `cargo:rerun-if-env-changed=VAR`
128///
129/// This requires at least cargo 0.21.0, corresponding to rustc 1.20.0. Earlier
130/// versions of cargo will simply ignore the directive.
131pub fn rerun_env(var: &str) {
132 println!("cargo:rerun-if-env-changed={}", var);
133}
134
135/// Indicates to rustc that a config flag should not generate an [`unexpected_cfgs` warning][check-cfg-flags]
136///
137/// This looks like `cargo:rustc-check-cfg=cfg(VAR)`
138///
139/// As of rust 1.80, the compiler does [automatic checking of cfgs at compile time][check-cfg-flags].
140/// All custom configuration flags must be known to rustc, or they will generate a warning.
141/// This is done automatically when calling the builtin emit methods on [`AutoCfg`],
142/// but not when calling [`autocfg::emit`](crate::emit) directly.
143///
144/// Versions before rust 1.80 will simply ignore this directive.
145///
146/// This function indicates to the compiler that the config flag never has a value.
147/// If this is not desired, see [the blog post][check-cfg].
148///
149/// [check-cfg-flags]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
150pub fn emit_possibility(cfg: &str) {
151 println!("cargo:rustc-check-cfg=cfg({})", cfg);
152}
153
154/// Creates a new `AutoCfg` instance.
155///
156/// # Panics
157///
158/// Panics if `AutoCfg::new()` returns an error.
159pub fn new() -> AutoCfg {
160 AutoCfg::new().unwrap()
161}
162
163impl AutoCfg {
164 /// Creates a new `AutoCfg` instance.
165 ///
166 /// # Common errors
167 ///
168 /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
169 /// - The version output from `rustc` can't be parsed.
170 /// - `OUT_DIR` is not set in the environment, or is not a writable directory.
171 ///
172 pub fn new() -> Result<Self, Error> {
173 match env::var_os("OUT_DIR") {
174 Some(d) => Self::with_dir(d),
175 None => Err(error::from_str("no OUT_DIR specified!")),
176 }
177 }
178
179 /// Creates a new `AutoCfg` instance with the specified output directory.
180 ///
181 /// # Common errors
182 ///
183 /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
184 /// - The version output from `rustc` can't be parsed.
185 /// - `dir` is not a writable directory.
186 ///
187 pub fn with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error> {
188 let rustc = Rustc::new();
189 let rustc_version = try!(rustc.version());
190
191 let target = env::var_os("TARGET");
192
193 // Sanity check the output directory
194 let dir = dir.into();
195 let meta = try!(fs::metadata(&dir).map_err(error::from_io));
196 if !meta.is_dir() || meta.permissions().readonly() {
197 return Err(error::from_str("output path is not a writable directory"));
198 }
199
200 let mut ac = AutoCfg {
201 rustflags: rustflags(&target, &dir),
202 out_dir: dir,
203 rustc: rustc,
204 rustc_version: rustc_version,
205 target: target,
206 no_std: false,
207 edition: None,
208 uuid: new_uuid(),
209 };
210
211 // Sanity check with and without `std`.
212 if ac.probe_raw("").is_err() {
213 if ac.probe_raw("#![no_std]").is_ok() {
214 ac.no_std = true;
215 } else {
216 // Neither worked, so assume nothing...
217 let warning = b"warning: autocfg could not probe for `std`\n";
218 stderr().write_all(warning).ok();
219 }
220 }
221 Ok(ac)
222 }
223
224 /// Returns whether `AutoCfg` is using `#![no_std]` in its probes.
225 ///
226 /// This is automatically detected during construction -- if an empty probe
227 /// fails while one with `#![no_std]` succeeds, then the attribute will be
228 /// used for all further probes. This is usually only necessary when the
229 /// `TARGET` lacks `std` altogether. If neither succeeds, `no_std` is not
230 /// set, but that `AutoCfg` will probably only work for version checks.
231 ///
232 /// This attribute changes the implicit [prelude] from `std` to `core`,
233 /// which may affect the paths you need to use in other probes. It also
234 /// restricts some types that otherwise get additional methods in `std`,
235 /// like floating-point trigonometry and slice sorting.
236 ///
237 /// See also [`set_no_std`](#method.set_no_std).
238 ///
239 /// [prelude]: https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute
240 pub fn no_std(&self) -> bool {
241 self.no_std
242 }
243
244 /// Sets whether `AutoCfg` should use `#![no_std]` in its probes.
245 ///
246 /// See also [`no_std`](#method.no_std).
247 pub fn set_no_std(&mut self, no_std: bool) {
248 self.no_std = no_std;
249 }
250
251 /// Returns the `--edition` string that is currently being passed to `rustc`, if any,
252 /// as configured by the [`set_edition`][Self::set_edition] method.
253 pub fn edition(&self) -> Option<&str> {
254 match self.edition {
255 Some(ref edition) => Some(&**edition),
256 None => None,
257 }
258 }
259
260 /// Sets the `--edition` string that will be passed to `rustc`,
261 /// or `None` to leave the compiler at its default edition.
262 ///
263 /// See also [The Rust Edition Guide](https://doc.rust-lang.org/edition-guide/).
264 ///
265 /// **Warning:** Setting an unsupported edition will likely cause **all** subsequent probes to
266 /// fail! As of this writing, the known editions and their minimum Rust versions are:
267 ///
268 /// | Edition | Version |
269 /// | ------- | ---------- |
270 /// | 2015 | 1.27.0[^1] |
271 /// | 2018 | 1.31.0 |
272 /// | 2021 | 1.56.0 |
273 /// | 2024 | 1.85.0 |
274 ///
275 /// [^1]: Prior to 1.27.0, Rust was effectively 2015 Edition by default, but the concept hadn't
276 /// been established yet, so the explicit `--edition` flag wasn't supported either.
277 pub fn set_edition(&mut self, edition: Option<String>) {
278 self.edition = edition;
279 }
280
281 /// Tests whether the current `rustc` reports a version greater than
282 /// or equal to "`major`.`minor`".
283 pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
284 self.rustc_version >= Version::new(major, minor, 0)
285 }
286
287 /// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`,
288 /// if the current `rustc` is at least that version.
289 pub fn emit_rustc_version(&self, major: usize, minor: usize) {
290 let cfg_flag = format!("rustc_{}_{}", major, minor);
291 emit_possibility(&cfg_flag);
292 if self.probe_rustc_version(major, minor) {
293 emit(&cfg_flag);
294 }
295 }
296
297 /// Returns a new (hopefully unique) crate name for probes.
298 fn new_crate_name(&self) -> String {
299 #[allow(deprecated)]
300 static ID: AtomicUsize = ATOMIC_USIZE_INIT;
301
302 let id = ID.fetch_add(1, Ordering::Relaxed);
303 format!("autocfg_{:016x}_{}", self.uuid, id)
304 }
305
306 fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> {
307 let crate_name = self.new_crate_name();
308 let mut command = self.rustc.command();
309 command
310 .arg("--crate-name")
311 .arg(&crate_name)
312 .arg("--crate-type=lib")
313 .arg("--out-dir")
314 .arg(&self.out_dir)
315 .arg("--emit=llvm-ir");
316
317 if let Some(edition) = self.edition.as_ref() {
318 command.arg("--edition").arg(edition);
319 }
320
321 if let Some(target) = self.target.as_ref() {
322 command.arg("--target").arg(target);
323 }
324
325 command.args(&self.rustflags);
326
327 command.arg("-").stdin(Stdio::piped());
328 let mut child = try!(command.spawn().map_err(error::from_io));
329 let mut stdin = child.stdin.take().expect("rustc stdin");
330
331 try!(stdin.write_fmt(source).map_err(error::from_io));
332 drop(stdin);
333
334 match child.wait() {
335 Ok(status) if status.success() => {
336 // Try to remove the output file so it doesn't look like a build product for
337 // systems like bazel -- but this is best-effort, so we can ignore failure.
338 // The probe itself is already considered successful at this point.
339 let mut file = self.out_dir.join(crate_name);
340 file.set_extension("ll");
341 let _ = fs::remove_file(file);
342
343 Ok(())
344 }
345 Ok(status) => Err(error::from_exit(status)),
346 Err(error) => Err(error::from_io(error)),
347 }
348 }
349
350 fn probe<'a>(&self, code: Arguments<'a>) -> bool {
351 let result = if self.no_std {
352 self.probe_fmt(format_args!("#![no_std]\n{}", code))
353 } else {
354 self.probe_fmt(code)
355 };
356 result.is_ok()
357 }
358
359 /// Tests whether the given code can be compiled as a Rust library.
360 ///
361 /// This will only return `Ok` if the compiler ran and exited successfully,
362 /// per `ExitStatus::success()`.
363 /// The code is passed to the compiler exactly as-is, notably not even
364 /// adding the [`#![no_std]`][Self::no_std] attribute like other probes.
365 ///
366 /// Raw probes are useful for testing functionality that's not yet covered
367 /// by the rest of the `AutoCfg` API. For example, the following attribute
368 /// **must** be used at the crate level, so it wouldn't work within the code
369 /// templates used by other `probe_*` methods.
370 ///
371 /// ```
372 /// # extern crate autocfg;
373 /// # // Normally, cargo will set `OUT_DIR` for build scripts.
374 /// # let exe = std::env::current_exe().unwrap();
375 /// # std::env::set_var("OUT_DIR", exe.parent().unwrap());
376 /// let ac = autocfg::new();
377 /// assert!(ac.probe_raw("#![no_builtins]").is_ok());
378 /// ```
379 ///
380 /// Rust nightly features could be tested as well -- ideally including a
381 /// code sample to ensure the unstable feature still works as expected.
382 /// For example, `slice::group_by` was renamed to `chunk_by` when it was
383 /// stabilized, even though the feature name was unchanged, so testing the
384 /// `#![feature(..)]` alone wouldn't reveal that. For larger snippets,
385 /// [`include_str!`] may be useful to load them from separate files.
386 ///
387 /// ```
388 /// # extern crate autocfg;
389 /// # // Normally, cargo will set `OUT_DIR` for build scripts.
390 /// # let exe = std::env::current_exe().unwrap();
391 /// # std::env::set_var("OUT_DIR", exe.parent().unwrap());
392 /// let ac = autocfg::new();
393 /// let code = r#"
394 /// #![feature(slice_group_by)]
395 /// pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
396 /// slice.group_by(|a, b| a == b)
397 /// }
398 /// "#;
399 /// if ac.probe_raw(code).is_ok() {
400 /// autocfg::emit("has_slice_group_by");
401 /// }
402 /// ```
403 pub fn probe_raw(&self, code: &str) -> Result<(), Error> {
404 self.probe_fmt(format_args!("{}", code))
405 }
406
407 /// Tests whether the given sysroot crate can be used.
408 ///
409 /// The test code is subject to change, but currently looks like:
410 ///
411 /// ```ignore
412 /// extern crate CRATE as probe;
413 /// ```
414 pub fn probe_sysroot_crate(&self, name: &str) -> bool {
415 // Note: `as _` wasn't stabilized until Rust 1.33
416 self.probe(format_args!("extern crate {} as probe;", name))
417 }
418
419 /// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
420 pub fn emit_sysroot_crate(&self, name: &str) {
421 let cfg_flag = format!("has_{}", mangle(name));
422 emit_possibility(&cfg_flag);
423 if self.probe_sysroot_crate(name) {
424 emit(&cfg_flag);
425 }
426 }
427
428 /// Tests whether the given path can be used.
429 ///
430 /// The test code is subject to change, but currently looks like:
431 ///
432 /// ```ignore
433 /// pub use PATH;
434 /// ```
435 pub fn probe_path(&self, path: &str) -> bool {
436 self.probe(format_args!("pub use {};", path))
437 }
438
439 /// Emits a config value `has_PATH` if `probe_path` returns true.
440 ///
441 /// Any non-identifier characters in the `path` will be replaced with
442 /// `_` in the generated config value.
443 pub fn emit_has_path(&self, path: &str) {
444 self.emit_path_cfg(path, &format!("has_{}", mangle(path)));
445 }
446
447 /// Emits the given `cfg` value if `probe_path` returns true.
448 pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
449 emit_possibility(cfg);
450 if self.probe_path(path) {
451 emit(cfg);
452 }
453 }
454
455 /// Tests whether the given trait can be used.
456 ///
457 /// The test code is subject to change, but currently looks like:
458 ///
459 /// ```ignore
460 /// pub trait Probe: TRAIT + Sized {}
461 /// ```
462 pub fn probe_trait(&self, name: &str) -> bool {
463 self.probe(format_args!("pub trait Probe: {} + Sized {{}}", name))
464 }
465
466 /// Emits a config value `has_TRAIT` if `probe_trait` returns true.
467 ///
468 /// Any non-identifier characters in the trait `name` will be replaced with
469 /// `_` in the generated config value.
470 pub fn emit_has_trait(&self, name: &str) {
471 self.emit_trait_cfg(name, &format!("has_{}", mangle(name)));
472 }
473
474 /// Emits the given `cfg` value if `probe_trait` returns true.
475 pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
476 emit_possibility(cfg);
477 if self.probe_trait(name) {
478 emit(cfg);
479 }
480 }
481
482 /// Tests whether the given type can be used.
483 ///
484 /// The test code is subject to change, but currently looks like:
485 ///
486 /// ```ignore
487 /// pub type Probe = TYPE;
488 /// ```
489 pub fn probe_type(&self, name: &str) -> bool {
490 self.probe(format_args!("pub type Probe = {};", name))
491 }
492
493 /// Emits a config value `has_TYPE` if `probe_type` returns true.
494 ///
495 /// Any non-identifier characters in the type `name` will be replaced with
496 /// `_` in the generated config value.
497 pub fn emit_has_type(&self, name: &str) {
498 self.emit_type_cfg(name, &format!("has_{}", mangle(name)));
499 }
500
501 /// Emits the given `cfg` value if `probe_type` returns true.
502 pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
503 emit_possibility(cfg);
504 if self.probe_type(name) {
505 emit(cfg);
506 }
507 }
508
509 /// Tests whether the given expression can be used.
510 ///
511 /// The test code is subject to change, but currently looks like:
512 ///
513 /// ```ignore
514 /// pub fn probe() { let _ = EXPR; }
515 /// ```
516 pub fn probe_expression(&self, expr: &str) -> bool {
517 self.probe(format_args!("pub fn probe() {{ let _ = {}; }}", expr))
518 }
519
520 /// Emits the given `cfg` value if `probe_expression` returns true.
521 pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) {
522 emit_possibility(cfg);
523 if self.probe_expression(expr) {
524 emit(cfg);
525 }
526 }
527
528 /// Tests whether the given constant expression can be used.
529 ///
530 /// The test code is subject to change, but currently looks like:
531 ///
532 /// ```ignore
533 /// pub const PROBE: () = ((), EXPR).0;
534 /// ```
535 pub fn probe_constant(&self, expr: &str) -> bool {
536 self.probe(format_args!("pub const PROBE: () = ((), {}).0;", expr))
537 }
538
539 /// Emits the given `cfg` value if `probe_constant` returns true.
540 pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
541 emit_possibility(cfg);
542 if self.probe_constant(expr) {
543 emit(cfg);
544 }
545 }
546}
547
548fn mangle(s: &str) -> String {
549 s.chars()
550 .map(|c| match c {
551 'A'...'Z' | 'a'...'z' | '0'...'9' => c,
552 _ => '_',
553 })
554 .collect()
555}
556
557fn dir_contains_target(
558 target: &Option<OsString>,
559 dir: &Path,
560 cargo_target_dir: Option<OsString>,
561) -> bool {
562 target
563 .as_ref()
564 .and_then(|target| {
565 dir.to_str().and_then(|dir| {
566 let mut cargo_target_dir = cargo_target_dir
567 .map(PathBuf::from)
568 .unwrap_or_else(|| PathBuf::from("target"));
569 cargo_target_dir.push(target);
570
571 cargo_target_dir
572 .to_str()
573 .map(|cargo_target_dir| dir.contains(cargo_target_dir))
574 })
575 })
576 .unwrap_or(false)
577}
578
579fn rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String> {
580 // Starting with rust-lang/cargo#9601, shipped in Rust 1.55, Cargo always sets
581 // CARGO_ENCODED_RUSTFLAGS for any host/target build script invocation. This
582 // includes any source of flags, whether from the environment, toml config, or
583 // whatever may come in the future. The value is either an empty string, or a
584 // list of arguments separated by the ASCII unit separator (US), 0x1f.
585 if let Ok(a) = env::var("CARGO_ENCODED_RUSTFLAGS") {
586 return if a.is_empty() {
587 Vec::new()
588 } else {
589 a.split('\x1f').map(str::to_string).collect()
590 };
591 }
592
593 // Otherwise, we have to take a more heuristic approach, and we don't
594 // support values from toml config at all.
595 //
596 // Cargo only applies RUSTFLAGS for building TARGET artifact in
597 // cross-compilation environment. Sadly, we don't have a way to detect
598 // when we're building HOST artifact in a cross-compilation environment,
599 // so for now we only apply RUSTFLAGS when cross-compiling an artifact.
600 //
601 // See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
602 if *target != env::var_os("HOST")
603 || dir_contains_target(target, dir, env::var_os("CARGO_TARGET_DIR"))
604 {
605 if let Ok(rustflags) = env::var("RUSTFLAGS") {
606 // This is meant to match how cargo handles the RUSTFLAGS environment variable.
607 // See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
608 return rustflags
609 .split(' ')
610 .map(str::trim)
611 .filter(|s| !s.is_empty())
612 .map(str::to_string)
613 .collect();
614 }
615 }
616
617 Vec::new()
618}
619
620/// Generates a numeric ID to use in probe crate names.
621///
622/// This attempts to be random, within the constraints of Rust 1.0 and no dependencies.
623fn new_uuid() -> u64 {
624 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
625 const FNV_PRIME: u64 = 0x100_0000_01b3;
626
627 // This set should have an actual random hasher.
628 let set: std::collections::HashSet<u64> = (0..256).collect();
629
630 // Feed the `HashSet`-shuffled order into FNV-1a.
631 let mut hash: u64 = FNV_OFFSET_BASIS;
632 for x in set {
633 hash = (hash ^ x).wrapping_mul(FNV_PRIME);
634 }
635 hash
636}