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 = "0.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(file!());
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 #![deny(missing_debug_implementations)]
38 #![deny(missing_docs)]
39 // allow future warnings that can't be fixed while keeping 1.0 compatibility
40 #![allow(unknown_lints)]
41 #![allow(bare_trait_objects)]
42 #![allow(ellipsis_inclusive_range_patterns)]
43 
44 /// Local macro to avoid `std::try!`, deprecated in Rust 1.39.
45 macro_rules! try {
46     ($result:expr) => {
47         match $result {
48             Ok(value) => value,
49             Err(error) => return Err(error),
50         }
51     };
52 }
53 
54 use std::env;
55 use std::ffi::OsString;
56 use std::fs;
57 use std::io::{stderr, Write};
58 use std::path::PathBuf;
59 use std::process::{Command, Stdio};
60 #[allow(deprecated)]
61 use std::sync::atomic::ATOMIC_USIZE_INIT;
62 use std::sync::atomic::{AtomicUsize, Ordering};
63 
64 mod error;
65 pub use error::Error;
66 
67 mod version;
68 use version::Version;
69 
70 #[cfg(test)]
71 mod tests;
72 
73 /// Helper to detect compiler features for `cfg` output in build scripts.
74 #[derive(Clone, Debug)]
75 pub struct AutoCfg {
76     out_dir: PathBuf,
77     rustc: PathBuf,
78     rustc_version: Version,
79     target: Option<OsString>,
80     no_std: bool,
81     rustflags: Option<Vec<String>>,
82 }
83 
84 /// Writes a config flag for rustc on standard out.
85 ///
86 /// This looks like: `cargo:rustc-cfg=CFG`
87 ///
88 /// Cargo will use this in arguments to rustc, like `--cfg CFG`.
emit(cfg: &str)89 pub fn emit(cfg: &str) {
90     println!("cargo:rustc-cfg={}", cfg);
91 }
92 
93 /// Writes a line telling Cargo to rerun the build script if `path` changes.
94 ///
95 /// This looks like: `cargo:rerun-if-changed=PATH`
96 ///
97 /// This requires at least cargo 0.7.0, corresponding to rustc 1.6.0.  Earlier
98 /// versions of cargo will simply ignore the directive.
rerun_path(path: &str)99 pub fn rerun_path(path: &str) {
100     println!("cargo:rerun-if-changed={}", path);
101 }
102 
103 /// Writes a line telling Cargo to rerun the build script if the environment
104 /// variable `var` changes.
105 ///
106 /// This looks like: `cargo:rerun-if-env-changed=VAR`
107 ///
108 /// This requires at least cargo 0.21.0, corresponding to rustc 1.20.0.  Earlier
109 /// versions of cargo will simply ignore the directive.
rerun_env(var: &str)110 pub fn rerun_env(var: &str) {
111     println!("cargo:rerun-if-env-changed={}", var);
112 }
113 
114 /// Create a new `AutoCfg` instance.
115 ///
116 /// # Panics
117 ///
118 /// Panics if `AutoCfg::new()` returns an error.
new() -> AutoCfg119 pub fn new() -> AutoCfg {
120     AutoCfg::new().unwrap()
121 }
122 
123 impl AutoCfg {
124     /// Create a new `AutoCfg` instance.
125     ///
126     /// # Common errors
127     ///
128     /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
129     /// - The version output from `rustc` can't be parsed.
130     /// - `OUT_DIR` is not set in the environment, or is not a writable directory.
131     ///
new() -> Result<Self, Error>132     pub fn new() -> Result<Self, Error> {
133         match env::var_os("OUT_DIR") {
134             Some(d) => Self::with_dir(d),
135             None => Err(error::from_str("no OUT_DIR specified!")),
136         }
137     }
138 
139     /// Create a new `AutoCfg` instance with the specified output directory.
140     ///
141     /// # Common errors
142     ///
143     /// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
144     /// - The version output from `rustc` can't be parsed.
145     /// - `dir` is not a writable directory.
146     ///
with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error>147     pub fn with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error> {
148         let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
149         let rustc: PathBuf = rustc.into();
150         let rustc_version = try!(Version::from_rustc(&rustc));
151 
152         // Sanity check the output directory
153         let dir = dir.into();
154         let meta = try!(fs::metadata(&dir).map_err(error::from_io));
155         if !meta.is_dir() || meta.permissions().readonly() {
156             return Err(error::from_str("output path is not a writable directory"));
157         }
158 
159         // Cargo only applies RUSTFLAGS for building TARGET artifact in
160         // cross-compilation environment. Sadly, we don't have a way to detect
161         // when we're building HOST artifact in a cross-compilation environment,
162         // so for now we only apply RUSTFLAGS when cross-compiling an artifact.
163         //
164         // See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
165         let rustflags = if env::var_os("TARGET") != env::var_os("HOST") {
166             env::var("RUSTFLAGS").ok().map(|rustflags| {
167                 // This is meant to match how cargo handles the RUSTFLAG environment
168                 // variable.
169                 // See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
170                 rustflags
171                     .split(' ')
172                     .map(str::trim)
173                     .filter(|s| !s.is_empty())
174                     .map(str::to_string)
175                     .collect::<Vec<String>>()
176             })
177         } else {
178             None
179         };
180 
181         let mut ac = AutoCfg {
182             out_dir: dir,
183             rustc: rustc,
184             rustc_version: rustc_version,
185             target: env::var_os("TARGET"),
186             no_std: false,
187             rustflags: rustflags,
188         };
189 
190         // Sanity check with and without `std`.
191         if !ac.probe("").unwrap_or(false) {
192             ac.no_std = true;
193             if !ac.probe("").unwrap_or(false) {
194                 // Neither worked, so assume nothing...
195                 ac.no_std = false;
196                 let warning = b"warning: autocfg could not probe for `std`\n";
197                 stderr().write_all(warning).ok();
198             }
199         }
200         Ok(ac)
201     }
202 
203     /// Test whether the current `rustc` reports a version greater than
204     /// or equal to "`major`.`minor`".
probe_rustc_version(&self, major: usize, minor: usize) -> bool205     pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
206         self.rustc_version >= Version::new(major, minor, 0)
207     }
208 
209     /// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`,
210     /// if the current `rustc` is at least that version.
emit_rustc_version(&self, major: usize, minor: usize)211     pub fn emit_rustc_version(&self, major: usize, minor: usize) {
212         if self.probe_rustc_version(major, minor) {
213             emit(&format!("rustc_{}_{}", major, minor));
214         }
215     }
216 
probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error>217     fn probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error> {
218         #[allow(deprecated)]
219         static ID: AtomicUsize = ATOMIC_USIZE_INIT;
220 
221         let id = ID.fetch_add(1, Ordering::Relaxed);
222         let mut command = Command::new(&self.rustc);
223         command
224             .arg("--crate-name")
225             .arg(format!("probe{}", id))
226             .arg("--crate-type=lib")
227             .arg("--out-dir")
228             .arg(&self.out_dir)
229             .arg("--emit=llvm-ir");
230 
231         if let &Some(ref rustflags) = &self.rustflags {
232             command.args(rustflags);
233         }
234 
235         if let Some(target) = self.target.as_ref() {
236             command.arg("--target").arg(target);
237         }
238 
239         command.arg("-").stdin(Stdio::piped());
240         let mut child = try!(command.spawn().map_err(error::from_io));
241         let mut stdin = child.stdin.take().expect("rustc stdin");
242 
243         if self.no_std {
244             try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io));
245         }
246         try!(stdin.write_all(code.as_ref()).map_err(error::from_io));
247         drop(stdin);
248 
249         let status = try!(child.wait().map_err(error::from_io));
250         Ok(status.success())
251     }
252 
253     /// Tests whether the given sysroot crate can be used.
254     ///
255     /// The test code is subject to change, but currently looks like:
256     ///
257     /// ```ignore
258     /// extern crate CRATE as probe;
259     /// ```
probe_sysroot_crate(&self, name: &str) -> bool260     pub fn probe_sysroot_crate(&self, name: &str) -> bool {
261         self.probe(format!("extern crate {} as probe;", name)) // `as _` wasn't stabilized until Rust 1.33
262             .unwrap_or(false)
263     }
264 
265     /// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
emit_sysroot_crate(&self, name: &str)266     pub fn emit_sysroot_crate(&self, name: &str) {
267         if self.probe_sysroot_crate(name) {
268             emit(&format!("has_{}", mangle(name)));
269         }
270     }
271 
272     /// Tests whether the given path can be used.
273     ///
274     /// The test code is subject to change, but currently looks like:
275     ///
276     /// ```ignore
277     /// pub use PATH;
278     /// ```
probe_path(&self, path: &str) -> bool279     pub fn probe_path(&self, path: &str) -> bool {
280         self.probe(format!("pub use {};", path)).unwrap_or(false)
281     }
282 
283     /// Emits a config value `has_PATH` if `probe_path` returns true.
284     ///
285     /// Any non-identifier characters in the `path` will be replaced with
286     /// `_` in the generated config value.
emit_has_path(&self, path: &str)287     pub fn emit_has_path(&self, path: &str) {
288         if self.probe_path(path) {
289             emit(&format!("has_{}", mangle(path)));
290         }
291     }
292 
293     /// Emits the given `cfg` value if `probe_path` returns true.
emit_path_cfg(&self, path: &str, cfg: &str)294     pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
295         if self.probe_path(path) {
296             emit(cfg);
297         }
298     }
299 
300     /// Tests whether the given trait can be used.
301     ///
302     /// The test code is subject to change, but currently looks like:
303     ///
304     /// ```ignore
305     /// pub trait Probe: TRAIT + Sized {}
306     /// ```
probe_trait(&self, name: &str) -> bool307     pub fn probe_trait(&self, name: &str) -> bool {
308         self.probe(format!("pub trait Probe: {} + Sized {{}}", name))
309             .unwrap_or(false)
310     }
311 
312     /// Emits a config value `has_TRAIT` if `probe_trait` returns true.
313     ///
314     /// Any non-identifier characters in the trait `name` will be replaced with
315     /// `_` in the generated config value.
emit_has_trait(&self, name: &str)316     pub fn emit_has_trait(&self, name: &str) {
317         if self.probe_trait(name) {
318             emit(&format!("has_{}", mangle(name)));
319         }
320     }
321 
322     /// Emits the given `cfg` value if `probe_trait` returns true.
emit_trait_cfg(&self, name: &str, cfg: &str)323     pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
324         if self.probe_trait(name) {
325             emit(cfg);
326         }
327     }
328 
329     /// Tests whether the given type can be used.
330     ///
331     /// The test code is subject to change, but currently looks like:
332     ///
333     /// ```ignore
334     /// pub type Probe = TYPE;
335     /// ```
probe_type(&self, name: &str) -> bool336     pub fn probe_type(&self, name: &str) -> bool {
337         self.probe(format!("pub type Probe = {};", name))
338             .unwrap_or(false)
339     }
340 
341     /// Emits a config value `has_TYPE` if `probe_type` returns true.
342     ///
343     /// Any non-identifier characters in the type `name` will be replaced with
344     /// `_` in the generated config value.
emit_has_type(&self, name: &str)345     pub fn emit_has_type(&self, name: &str) {
346         if self.probe_type(name) {
347             emit(&format!("has_{}", mangle(name)));
348         }
349     }
350 
351     /// Emits the given `cfg` value if `probe_type` returns true.
emit_type_cfg(&self, name: &str, cfg: &str)352     pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
353         if self.probe_type(name) {
354             emit(cfg);
355         }
356     }
357 }
358 
mangle(s: &str) -> String359 fn mangle(s: &str) -> String {
360     s.chars()
361         .map(|c| match c {
362             'A'...'Z' | 'a'...'z' | '0'...'9' => c,
363             _ => '_',
364         })
365         .collect()
366 }
367