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::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: Option<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 // Cargo only applies RUSTFLAGS for building TARGET artifact in
170 // cross-compilation environment. Sadly, we don't have a way to detect
171 // when we're building HOST artifact in a cross-compilation environment,
172 // so for now we only apply RUSTFLAGS when cross-compiling an artifact.
173 //
174 // See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
175 let rustflags = if target != env::var_os("HOST")
176 || dir_contains_target(&target, &dir, env::var_os("CARGO_TARGET_DIR"))
177 {
178 env::var("RUSTFLAGS").ok().map(|rustflags| {
179 // This is meant to match how cargo handles the RUSTFLAG environment
180 // variable.
181 // See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
182 rustflags
183 .split(' ')
184 .map(str::trim)
185 .filter(|s| !s.is_empty())
186 .map(str::to_string)
187 .collect::<Vec<String>>()
188 })
189 } else {
190 None
191 };
192
193 let mut ac = AutoCfg {
194 out_dir: dir,
195 rustc: rustc,
196 rustc_version: rustc_version,
197 target: target,
198 no_std: false,
199 rustflags: rustflags,
200 };
201
202 // Sanity check with and without `std`.
203 if !ac.probe("").unwrap_or(false) {
204 ac.no_std = true;
205 if !ac.probe("").unwrap_or(false) {
206 // Neither worked, so assume nothing...
207 ac.no_std = false;
208 let warning = b"warning: autocfg could not probe for `std`\n";
209 stderr().write_all(warning).ok();
210 }
211 }
212 Ok(ac)
213 }
214
215 /// Test whether the current `rustc` reports a version greater than
216 /// or equal to "`major`.`minor`".
probe_rustc_version(&self, major: usize, minor: usize) -> bool217 pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
218 self.rustc_version >= Version::new(major, minor, 0)
219 }
220
221 /// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`,
222 /// if the current `rustc` is at least that version.
emit_rustc_version(&self, major: usize, minor: usize)223 pub fn emit_rustc_version(&self, major: usize, minor: usize) {
224 if self.probe_rustc_version(major, minor) {
225 emit(&format!("rustc_{}_{}", major, minor));
226 }
227 }
228
probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error>229 fn probe<T: AsRef<[u8]>>(&self, code: T) -> Result<bool, Error> {
230 #[allow(deprecated)]
231 static ID: AtomicUsize = ATOMIC_USIZE_INIT;
232
233 let id = ID.fetch_add(1, Ordering::Relaxed);
234 let mut command = Command::new(&self.rustc);
235 command
236 .arg("--crate-name")
237 .arg(format!("probe{}", id))
238 .arg("--crate-type=lib")
239 .arg("--out-dir")
240 .arg(&self.out_dir)
241 .arg("--emit=llvm-ir");
242
243 if let &Some(ref rustflags) = &self.rustflags {
244 command.args(rustflags);
245 }
246
247 if let Some(target) = self.target.as_ref() {
248 command.arg("--target").arg(target);
249 }
250
251 command.arg("-").stdin(Stdio::piped());
252 let mut child = try!(command.spawn().map_err(error::from_io));
253 let mut stdin = child.stdin.take().expect("rustc stdin");
254
255 if self.no_std {
256 try!(stdin.write_all(b"#![no_std]\n").map_err(error::from_io));
257 }
258 try!(stdin.write_all(code.as_ref()).map_err(error::from_io));
259 drop(stdin);
260
261 let status = try!(child.wait().map_err(error::from_io));
262 Ok(status.success())
263 }
264
265 /// Tests whether the given sysroot crate can be used.
266 ///
267 /// The test code is subject to change, but currently looks like:
268 ///
269 /// ```ignore
270 /// extern crate CRATE as probe;
271 /// ```
probe_sysroot_crate(&self, name: &str) -> bool272 pub fn probe_sysroot_crate(&self, name: &str) -> bool {
273 self.probe(format!("extern crate {} as probe;", name)) // `as _` wasn't stabilized until Rust 1.33
274 .unwrap_or(false)
275 }
276
277 /// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
emit_sysroot_crate(&self, name: &str)278 pub fn emit_sysroot_crate(&self, name: &str) {
279 if self.probe_sysroot_crate(name) {
280 emit(&format!("has_{}", mangle(name)));
281 }
282 }
283
284 /// Tests whether the given path can be used.
285 ///
286 /// The test code is subject to change, but currently looks like:
287 ///
288 /// ```ignore
289 /// pub use PATH;
290 /// ```
probe_path(&self, path: &str) -> bool291 pub fn probe_path(&self, path: &str) -> bool {
292 self.probe(format!("pub use {};", path)).unwrap_or(false)
293 }
294
295 /// Emits a config value `has_PATH` if `probe_path` returns true.
296 ///
297 /// Any non-identifier characters in the `path` will be replaced with
298 /// `_` in the generated config value.
emit_has_path(&self, path: &str)299 pub fn emit_has_path(&self, path: &str) {
300 if self.probe_path(path) {
301 emit(&format!("has_{}", mangle(path)));
302 }
303 }
304
305 /// Emits the given `cfg` value if `probe_path` returns true.
emit_path_cfg(&self, path: &str, cfg: &str)306 pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
307 if self.probe_path(path) {
308 emit(cfg);
309 }
310 }
311
312 /// Tests whether the given trait can be used.
313 ///
314 /// The test code is subject to change, but currently looks like:
315 ///
316 /// ```ignore
317 /// pub trait Probe: TRAIT + Sized {}
318 /// ```
probe_trait(&self, name: &str) -> bool319 pub fn probe_trait(&self, name: &str) -> bool {
320 self.probe(format!("pub trait Probe: {} + Sized {{}}", name))
321 .unwrap_or(false)
322 }
323
324 /// Emits a config value `has_TRAIT` if `probe_trait` returns true.
325 ///
326 /// Any non-identifier characters in the trait `name` will be replaced with
327 /// `_` in the generated config value.
emit_has_trait(&self, name: &str)328 pub fn emit_has_trait(&self, name: &str) {
329 if self.probe_trait(name) {
330 emit(&format!("has_{}", mangle(name)));
331 }
332 }
333
334 /// Emits the given `cfg` value if `probe_trait` returns true.
emit_trait_cfg(&self, name: &str, cfg: &str)335 pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
336 if self.probe_trait(name) {
337 emit(cfg);
338 }
339 }
340
341 /// Tests whether the given type can be used.
342 ///
343 /// The test code is subject to change, but currently looks like:
344 ///
345 /// ```ignore
346 /// pub type Probe = TYPE;
347 /// ```
probe_type(&self, name: &str) -> bool348 pub fn probe_type(&self, name: &str) -> bool {
349 self.probe(format!("pub type Probe = {};", name))
350 .unwrap_or(false)
351 }
352
353 /// Emits a config value `has_TYPE` if `probe_type` returns true.
354 ///
355 /// Any non-identifier characters in the type `name` will be replaced with
356 /// `_` in the generated config value.
emit_has_type(&self, name: &str)357 pub fn emit_has_type(&self, name: &str) {
358 if self.probe_type(name) {
359 emit(&format!("has_{}", mangle(name)));
360 }
361 }
362
363 /// Emits the given `cfg` value if `probe_type` returns true.
emit_type_cfg(&self, name: &str, cfg: &str)364 pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
365 if self.probe_type(name) {
366 emit(cfg);
367 }
368 }
369
370 /// Tests whether the given expression can be used.
371 ///
372 /// The test code is subject to change, but currently looks like:
373 ///
374 /// ```ignore
375 /// pub fn probe() { let _ = EXPR; }
376 /// ```
probe_expression(&self, expr: &str) -> bool377 pub fn probe_expression(&self, expr: &str) -> bool {
378 self.probe(format!("pub fn probe() {{ let _ = {}; }}", expr))
379 .unwrap_or(false)
380 }
381
382 /// Emits the given `cfg` value if `probe_expression` returns true.
emit_expression_cfg(&self, expr: &str, cfg: &str)383 pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) {
384 if self.probe_expression(expr) {
385 emit(cfg);
386 }
387 }
388
389 /// Tests whether the given constant expression can be used.
390 ///
391 /// The test code is subject to change, but currently looks like:
392 ///
393 /// ```ignore
394 /// pub const PROBE: () = ((), EXPR).0;
395 /// ```
probe_constant(&self, expr: &str) -> bool396 pub fn probe_constant(&self, expr: &str) -> bool {
397 self.probe(format!("pub const PROBE: () = ((), {}).0;", expr))
398 .unwrap_or(false)
399 }
400
401 /// Emits the given `cfg` value if `probe_constant` returns true.
emit_constant_cfg(&self, expr: &str, cfg: &str)402 pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
403 if self.probe_constant(expr) {
404 emit(cfg);
405 }
406 }
407 }
408
mangle(s: &str) -> String409 fn mangle(s: &str) -> String {
410 s.chars()
411 .map(|c| match c {
412 'A'...'Z' | 'a'...'z' | '0'...'9' => c,
413 _ => '_',
414 })
415 .collect()
416 }
417
dir_contains_target( target: &Option<OsString>, dir: &PathBuf, cargo_target_dir: Option<OsString>, ) -> bool418 fn dir_contains_target(
419 target: &Option<OsString>,
420 dir: &PathBuf,
421 cargo_target_dir: Option<OsString>,
422 ) -> bool {
423 target
424 .as_ref()
425 .and_then(|target| {
426 dir.to_str().and_then(|dir| {
427 let mut cargo_target_dir = cargo_target_dir
428 .map(PathBuf::from)
429 .unwrap_or_else(|| PathBuf::from("target"));
430 cargo_target_dir.push(target);
431
432 cargo_target_dir
433 .to_str()
434 .map(|cargo_target_dir| dir.contains(&cargo_target_dir))
435 })
436 })
437 .unwrap_or(false)
438 }
439