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