• 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 //! #   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.
53 macro_rules! try {
54     ($result:expr) => {
55         match $result {
56             Ok(value) => value,
57             Err(error) => return Err(error),
58         }
59     };
60 }
61 
62 use std::env;
63 use std::ffi::OsString;
64 use std::fs;
65 use std::io::{stderr, Write};
66 use std::path::{Path, PathBuf};
67 use std::process::{Command, Stdio};
68 #[allow(deprecated)]
69 use std::sync::atomic::ATOMIC_USIZE_INIT;
70 use std::sync::atomic::{AtomicUsize, Ordering};
71 
72 mod error;
73 pub use error::Error;
74 
75 mod version;
76 use version::Version;
77 
78 #[cfg(test)]
79 mod tests;
80 
81 /// Helper to detect compiler features for `cfg` output in build scripts.
82 #[derive(Clone, Debug)]
83 pub 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`.
emit(cfg: &str)97 pub 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.
rerun_path(path: &str)107 pub 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.
rerun_env(var: &str)118 pub 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.
new() -> AutoCfg127 pub fn new() -> AutoCfg {
128     AutoCfg::new().unwrap()
129 }
130 
131 impl 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     ///
new() -> Result<Self, Error>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     ///
with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error>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`".
probe_rustc_version(&self, major: usize, minor: usize) -> bool193     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.
emit_rustc_version(&self, major: usize, minor: usize)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 
probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error>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     /// ```
probe_sysroot_crate(&self, name: &str) -> bool246     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.
emit_sysroot_crate(&self, name: &str)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     /// ```
probe_path(&self, path: &str) -> bool265     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.
emit_has_path(&self, path: &str)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.
emit_path_cfg(&self, path: &str, cfg: &str)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     /// ```
probe_trait(&self, name: &str) -> bool293     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.
emit_has_trait(&self, name: &str)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.
emit_trait_cfg(&self, name: &str, cfg: &str)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     /// ```
probe_type(&self, name: &str) -> bool322     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.
emit_has_type(&self, name: &str)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.
emit_type_cfg(&self, name: &str, cfg: &str)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     /// ```
probe_expression(&self, expr: &str) -> bool351     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.
emit_expression_cfg(&self, expr: &str, cfg: &str)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     /// ```
probe_constant(&self, expr: &str) -> bool370     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.
emit_constant_cfg(&self, expr: &str, cfg: &str)376     pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
377         if self.probe_constant(expr) {
378             emit(cfg);
379         }
380     }
381 }
382 
mangle(s: &str) -> String383 fn 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 
dir_contains_target( target: &Option<OsString>, dir: &Path, cargo_target_dir: Option<OsString>, ) -> bool392 fn 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 
rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String>414 fn 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