| 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 ] |
| 175 | macro_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 | |