[go: up one dir, main page]

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}