xref: /third_party/rust/crates/autocfg/src/lib.rs (revision f1555e47)
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