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`. |
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. |
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. |
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. |
127 | 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 | /// |
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 | |
383 | fn mangle(s: &str) -> String { |
384 | simpl Iterator .chars() |
385 | .map(|c: char| match c { |
386 | 'A' ...'Z' | 'a' ...'z' | '0' ...'9' => c, |
387 | _ => '_' , |
388 | }) |
389 | .collect() |
390 | } |
391 | |
392 | 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(default:false) |
412 | } |
413 | |
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 | |