1//! # CFG Aliases
2//!
3//! CFG Aliases is a tiny utility to help save you a lot of effort with long winded `#[cfg()]` checks. This crate provides a single [`cfg_aliases!`] macro that doesn't have any dependencies and specifically avoids pulling in `syn` or `quote` so that the impact on your comile times should be negligible.
4//!
5//! You use the the [`cfg_aliases!`] macro in your `build.rs` script to define aliases such as `x11` that could then be used in the `cfg` attribute or macro for conditional compilation: `#[cfg(x11)]`.
6//!
7//! ## Example
8//!
9//! **Cargo.toml:**
10//!
11//! ```toml
12//! [build-dependencies]
13//! cfg_aliases = "0.1.0"
14//! ```
15//!
16//! **build.rs:**
17//!
18//! ```rust
19//! use cfg_aliases::cfg_aliases;
20//!
21//! fn main() {
22//! // Setup cfg aliases
23//! cfg_aliases! {
24//! // Platforms
25//! wasm: { target_arch = "wasm32" },
26//! android: { target_os = "android" },
27//! macos: { target_os = "macos" },
28//! linux: { target_os = "linux" },
29//! // Backends
30//! surfman: { all(unix, feature = "surfman", not(wasm)) },
31//! glutin: { all(feature = "glutin", not(wasm)) },
32//! wgl: { all(windows, feature = "wgl", not(wasm)) },
33//! dummy: { not(any(wasm, glutin, wgl, surfman)) },
34//! }
35//! }
36//! ```
37//!
38//! Now that we have our aliases setup we can use them just like you would expect:
39//!
40//! ```rust
41//! #[cfg(wasm)]
42//! println!("This is running in WASM");
43//!
44//! #[cfg(surfman)]
45//! {
46//! // Do stuff related to surfman
47//! }
48//!
49//! #[cfg(dummy)]
50//! println!("We're in dummy mode, specify another feature if you want a smarter app!");
51//! ```
52//!
53//! This greatly improves what would otherwise look like this without the aliases:
54//!
55//! ```rust
56//! #[cfg(target_arch = "wasm32")]
57//! println!("We're running in WASM");
58//!
59//! #[cfg(all(unix, feature = "surfman", not(target_arch = "22")))]
60//! {
61//! // Do stuff related to surfman
62//! }
63//!
64//! #[cfg(not(any(
65//! target_arch = "wasm32",
66//! all(unix, feature = "surfman", not(target_arch = "wasm32")),
67//! all(windows, feature = "wgl", not(target_arch = "wasm32")),
68//! all(feature = "glutin", not(target_arch = "wasm32")),
69//! )))]
70//! println!("We're in dummy mode, specify another feature if you want a smarter app!");
71//! ```
72//!
73//! You can also use the `cfg!` macro or combine your aliases with other checks using `all()`, `not()`, and `any()`. Your aliases are genuine `cfg` flags now!
74//!
75//! ```rust
76//! if cfg!(glutin) {
77//! // use glutin
78//! } else {
79//! // Do something else
80//! }
81//!
82//! #[cfg(all(glutin, surfman))]
83//! compile_error!("You cannot specify both `glutin` and `surfman` features");
84//! ```
85//!
86//! ## Syntax and Error Messages
87//!
88//! The aliase names are restricted to the same rules as rust identifiers which, for one, means that they cannot have dashes ( `-` ) in them. Additionally, if you get certain syntax elements wrong, such as the alias name, the macro will error saying that the recursion limit was reached instead of giving a clear indication of what actually went wrong. This is due to a nuance with the macro parser and it might be fixed in a later release of this crate. It is also possible that aliases with dashes in the name might be supported in a later release. Open an issue if that is something that you would like implemented.
89//!
90//! Finally, you can also induce an infinite recursion by having rules that both reference each-other, but this isn't a real limitation because that doesn't make logical sense anyway:
91//!
92//! ```rust,ignore
93//! // This causes an error!
94//! cfg_aliases! {
95//! test1: { not(test2) },
96//! test2: { not(test1) },
97//! }
98//! ```
99//!
100//! ## Attribution and Thanks
101//!
102//! - Thanks to my God and Father who led me through figuring this out and to whome I owe everything.
103//! - Thanks to @Yandros on the Rust forum for [showing me][sm] some crazy macro hacks!
104//! - Thanks to @sfackler for [pointing out][po] the way to make cargo add the cfg flags.
105//! - Thanks to the authors of the [`tectonic_cfg_support::target_cfg`] macro from which most of the cfg attribute parsing logic is taken from. Also thanks to @ratmice for [bringing it up][bip] on the Rust forum.
106//!
107//! [`tectonic_cfg_support::target_cfg`]: https://docs.rs/tectonic_cfg_support/0.0.1/src/tectonic_cfg_support/lib.rs.html#166-298
108//! [po]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/2
109//! [bip]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/13
110//! [sm]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/3
111
112#![allow(clippy::needless_doctest_main)]
113
114// In the `cfg_aliases!` macro below, all of the rules that start with @parser were derived from
115// the `target_cfg!` macro here:
116//
117// https://docs.rs/tectonic_cfg_support/0.0.1/src/tectonic_cfg_support/lib.rs.html#166-298.
118//
119// The `target_cfg!` macro is excellently commented while the one below is not very well commented
120// yet, so if you need some help understanding it you might benefit by reading that implementation.
121// Also check out this forum topic for more history on the macro development:
122//
123// https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100?u=zicklag
124
125/// Create `cfg` aliases
126///
127/// **build.rs:**
128///
129/// ```rust
130/// # use cfg_aliases::cfg_aliases;
131/// // Setup cfg aliases
132/// cfg_aliases! {
133/// // Platforms
134/// wasm: { target_arch = "wasm32" },
135/// android: { target_os = "android" },
136/// macos: { target_os = "macos" },
137/// linux: { target_os = "linux" },
138/// // Backends
139/// surfman: { all(unix, feature = "surfman", not(wasm)) },
140/// glutin: { all(feature = "glutin", not(wasm)) },
141/// wgl: { all(windows, feature = "wgl", not(wasm)) },
142/// dummy: { not(any(wasm, glutin, wgl, surfman)) },
143/// }
144/// ```
145///
146/// After you put this in your build script you can then check for those conditions like so:
147///
148/// ```rust
149/// #[cfg(surfman)]
150/// {
151/// // Do stuff related to surfman
152/// }
153///
154/// #[cfg(dummy)]
155/// println!("We're in dummy mode, specify another feature if you want a smarter app!");
156/// ```
157///
158/// This greatly improves what would otherwise look like this without the aliases:
159///
160/// ```rust
161/// #[cfg(all(unix, feature = "surfman", not(target_arch = "wasm32")))]
162/// {
163/// // Do stuff related to surfman
164/// }
165///
166/// #[cfg(not(any(
167/// target_arch = "wasm32",
168/// all(unix, feature = "surfman", not(target_arch = "wasm32")),
169/// all(windows, feature = "wgl", not(target_arch = "wasm32")),
170/// all(feature = "glutin", not(target_arch = "wasm32")),
171/// )))]
172/// println!("We're in dummy mode, specify another feature if you want a smarter app!");
173/// ```
174#[macro_export]
175macro_rules! cfg_aliases {
176 // Helper that just checks whether the CFG environment variable is set
177 (@cfg_is_set $cfgname:ident) => {
178 {
179 let cfg_var = stringify!($cfgname).to_uppercase().replace("-", "_");
180 let result = std::env::var(format!("CARGO_CFG_{}", &cfg_var)).is_ok();
181
182 // CARGO_CFG_DEBUG_ASSERTIONS _should_ be set for when debug assertions are enabled,
183 // but as of writing is not: see https://github.com/rust-lang/cargo/issues/5777
184 if !result && cfg_var == "DEBUG_ASSERTIONS" {
185 std::env::var("PROFILE") == Ok("debug".to_owned())
186 } else {
187 result
188 }
189 }
190 };
191 // Helper to check for the presense of a feature
192 (@cfg_has_feature $feature:expr) => {
193 {
194 std::env::var(
195 format!(
196 "CARGO_FEATURE_{}",
197 &stringify!($feature).to_uppercase().replace("-", "_").replace('"', "")
198 )
199 ).map(|x| x == "1").unwrap_or(false)
200 }
201 };
202
203 // Helper that checks whether a CFG environment contains the given value
204 (@cfg_contains $cfgname:ident = $cfgvalue:expr) => {
205 std::env::var(
206 format!(
207 "CARGO_CFG_{}",
208 &stringify!($cfgname).to_uppercase().replace("-", "_")
209 )
210 ).unwrap_or("".to_owned()).split(",").find(|x| x == &$cfgvalue).is_some()
211 };
212
213 // Emitting `any(clause1,clause2,...)`: convert to `$crate::cfg_aliases!(clause1) && $crate::cfg_aliases!(clause2) && ...`
214 (
215 @parser_emit
216 all
217 $({$($grouped:tt)+})+
218 ) => {
219 ($(
220 ($crate::cfg_aliases!(@parser $($grouped)+))
221 )&&+)
222 };
223
224 // Likewise for `all(clause1,clause2,...)`.
225 (
226 @parser_emit
227 any
228 $({$($grouped:tt)+})+
229 ) => {
230 ($(
231 ($crate::cfg_aliases!(@parser $($grouped)+))
232 )||+)
233 };
234
235 // "@clause" rules are used to parse the comma-separated lists. They munch
236 // their inputs token-by-token and finally invoke an "@emit" rule when the
237 // list is all grouped. The general pattern for recording the parser state
238 // is:
239 //
240 // ```
241 // $crate::cfg_aliases!(
242 // @clause $operation
243 // [{grouped-clause-1} {grouped-clause-2...}]
244 // [not-yet-parsed-tokens...]
245 // current-clause-tokens...
246 // )
247 // ```
248
249 // This rule must come first in this section. It fires when the next token
250 // to parse is a comma. When this happens, we take the tokens in the
251 // current clause and add them to the list of grouped clauses, adding
252 // delimeters so that the grouping can be easily extracted again in the
253 // emission stage.
254 (
255 @parser_clause
256 $op:ident
257 [$({$($grouped:tt)+})*]
258 [, $($rest:tt)*]
259 $($current:tt)+
260 ) => {
261 $crate::cfg_aliases!(@parser_clause $op [
262 $(
263 {$($grouped)+}
264 )*
265 {$($current)+}
266 ] [
267 $($rest)*
268 ]);
269 };
270
271 // This rule comes next. It fires when the next un-parsed token is *not* a
272 // comma. In this case, we add that token to the list of tokens in the
273 // current clause, then move on to the next one.
274 (
275 @parser_clause
276 $op:ident
277 [$({$($grouped:tt)+})*]
278 [$tok:tt $($rest:tt)*]
279 $($current:tt)*
280 ) => {
281 $crate::cfg_aliases!(@parser_clause $op [
282 $(
283 {$($grouped)+}
284 )*
285 ] [
286 $($rest)*
287 ] $($current)* $tok);
288 };
289
290 // This rule fires when there are no more tokens to parse in this list. We
291 // finish off the "current" token group, then delegate to the emission
292 // rule.
293 (
294 @parser_clause
295 $op:ident
296 [$({$($grouped:tt)+})*]
297 []
298 $($current:tt)+
299 ) => {
300 $crate::cfg_aliases!(@parser_emit $op
301 $(
302 {$($grouped)+}
303 )*
304 {$($current)+}
305 );
306 };
307
308
309 // `all(clause1, clause2...)` : we must parse this comma-separated list and
310 // partner with `@emit all` to output a bunch of && terms.
311 (
312 @parser
313 all($($tokens:tt)+)
314 ) => {
315 $crate::cfg_aliases!(@parser_clause all [] [$($tokens)+])
316 };
317
318 // Likewise for `any(clause1, clause2...)`
319 (
320 @parser
321 any($($tokens:tt)+)
322 ) => {
323 $crate::cfg_aliases!(@parser_clause any [] [$($tokens)+])
324 };
325
326 // `not(clause)`: compute the inner clause, then just negate it.
327 (
328 @parser
329 not($($tokens:tt)+)
330 ) => {
331 !($crate::cfg_aliases!(@parser $($tokens)+))
332 };
333
334 // `feature = value`: test for a feature.
335 (@parser feature = $value:expr) => {
336 $crate::cfg_aliases!(@cfg_has_feature $value)
337 };
338 // `param = value`: test for equality.
339 (@parser $key:ident = $value:expr) => {
340 $crate::cfg_aliases!(@cfg_contains $key = $value)
341 };
342 // Parse a lone identifier that might be an alias
343 (@parser $e:ident) => {
344 __cfg_aliases_matcher__!($e)
345 };
346
347 // Entrypoint that defines the matcher
348 (
349 @with_dollar[$dol:tt]
350 $( $alias:ident : { $($config:tt)* } ),* $(,)?
351 ) => {
352 // Create a macro that expands other aliases and outputs any non
353 // alias by checking whether that CFG value is set
354 macro_rules! __cfg_aliases_matcher__ {
355 // Parse config expression for the alias
356 $(
357 ( $alias ) => {
358 $crate::cfg_aliases!(@parser $($config)*)
359 };
360 )*
361 // Anything that doesn't match evaluate the item
362 ( $dol e:ident ) => {
363 $crate::cfg_aliases!(@cfg_is_set $dol e)
364 };
365 }
366
367 $(
368 println!("cargo:rustc-check-cfg=cfg({})", stringify!($alias));
369 if $crate::cfg_aliases!(@parser $($config)*) {
370 println!("cargo:rustc-cfg={}", stringify!($alias));
371 }
372 )*
373 };
374
375 // Catch all that starts the macro
376 ($($tokens:tt)*) => {
377 $crate::cfg_aliases!(@with_dollar[$] $($tokens)*)
378 }
379}
380