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 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
109pub 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.
119pub 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.
130pub 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
149pub 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.
158pub fn new() -> AutoCfg {
159 AutoCfg::new().unwrap()
160}
161
162impl 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 ///
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 ///
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
238 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).
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`".
251 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.
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.
266 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
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
304 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 /// ```
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 /// ```
368 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.
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 /// ```
389 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.
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.
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 /// ```
416 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.
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.
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 /// ```
443 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.
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.
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 /// ```
470 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.
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 /// ```
489 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.
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
502fn mangle(s: &str) -> String {
503 simpl Iterator.chars()
504 .map(|c: char| match c {
505 'A'...'Z' | 'a'...'z' | '0'...'9' => c,
506 _ => '_',
507 })
508 .collect()
509}
510
511fn 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(default:false)
531}
532
533fn 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.
577fn 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: u64 in set {
587 hash = (hash ^ x).wrapping_mul(FNV_PRIME);
588 }
589 hash
590}
591

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more