| //! # CFG Aliases |
| //! |
| //! 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. |
| //! |
| //! 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)]`. |
| //! |
| //! ## Example |
| //! |
| //! **Cargo.toml:** |
| //! |
| //! ```toml |
| //! [build-dependencies] |
| //! cfg_aliases = "0.1.0" |
| //! ``` |
| //! |
| //! **build.rs:** |
| //! |
| //! ```rust |
| //! use cfg_aliases::cfg_aliases; |
| //! |
| //! fn main() { |
| //! // Setup cfg aliases |
| //! cfg_aliases! { |
| //! // Platforms |
| //! wasm: { target_arch = "wasm32" }, |
| //! android: { target_os = "android" }, |
| //! macos: { target_os = "macos" }, |
| //! linux: { target_os = "linux" }, |
| //! // Backends |
| //! surfman: { all(unix, feature = "surfman", not(wasm)) }, |
| //! glutin: { all(feature = "glutin", not(wasm)) }, |
| //! wgl: { all(windows, feature = "wgl", not(wasm)) }, |
| //! dummy: { not(any(wasm, glutin, wgl, surfman)) }, |
| //! } |
| //! } |
| //! ``` |
| //! |
| //! Now that we have our aliases setup we can use them just like you would expect: |
| //! |
| //! ```rust |
| //! #[cfg(wasm)] |
| //! println!("This is running in WASM"); |
| //! |
| //! #[cfg(surfman)] |
| //! { |
| //! // Do stuff related to surfman |
| //! } |
| //! |
| //! #[cfg(dummy)] |
| //! println!("We're in dummy mode, specify another feature if you want a smarter app!"); |
| //! ``` |
| //! |
| //! This greatly improves what would otherwise look like this without the aliases: |
| //! |
| //! ```rust |
| //! #[cfg(target_arch = "wasm32")] |
| //! println!("We're running in WASM"); |
| //! |
| //! #[cfg(all(unix, feature = "surfman", not(target_arch = "22")))] |
| //! { |
| //! // Do stuff related to surfman |
| //! } |
| //! |
| //! #[cfg(not(any( |
| //! target_arch = "wasm32", |
| //! all(unix, feature = "surfman", not(target_arch = "wasm32")), |
| //! all(windows, feature = "wgl", not(target_arch = "wasm32")), |
| //! all(feature = "glutin", not(target_arch = "wasm32")), |
| //! )))] |
| //! println!("We're in dummy mode, specify another feature if you want a smarter app!"); |
| //! ``` |
| //! |
| //! 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! |
| //! |
| //! ```rust |
| //! if cfg!(glutin) { |
| //! // use glutin |
| //! } else { |
| //! // Do something else |
| //! } |
| //! |
| //! #[cfg(all(glutin, surfman))] |
| //! compile_error!("You cannot specify both `glutin` and `surfman` features"); |
| //! ``` |
| //! |
| //! ## Syntax and Error Messages |
| //! |
| //! 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. |
| //! |
| //! 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: |
| //! |
| //! ```rust,ignore |
| //! // This causes an error! |
| //! cfg_aliases! { |
| //! test1: { not(test2) }, |
| //! test2: { not(test1) }, |
| //! } |
| //! ``` |
| //! |
| //! ## Attribution and Thanks |
| //! |
| //! - Thanks to my God and Father who led me through figuring this out and to whome I owe everything. |
| //! - Thanks to @Yandros on the Rust forum for [showing me][sm] some crazy macro hacks! |
| //! - Thanks to @sfackler for [pointing out][po] the way to make cargo add the cfg flags. |
| //! - 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. |
| //! |
| //! [`tectonic_cfg_support::target_cfg`]: https://docs.rs/tectonic_cfg_support/0.0.1/src/tectonic_cfg_support/lib.rs.html#166-298 |
| //! [po]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/2 |
| //! [bip]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/13 |
| //! [sm]: https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100/3 |
| |
| #![allow(clippy::needless_doctest_main)] |
| |
| // In the `cfg_aliases!` macro below, all of the rules that start with @parser were derived from |
| // the `target_cfg!` macro here: |
| // |
| // https://docs.rs/tectonic_cfg_support/0.0.1/src/tectonic_cfg_support/lib.rs.html#166-298. |
| // |
| // The `target_cfg!` macro is excellently commented while the one below is not very well commented |
| // yet, so if you need some help understanding it you might benefit by reading that implementation. |
| // Also check out this forum topic for more history on the macro development: |
| // |
| // https://users.rust-lang.org/t/any-such-thing-as-cfg-aliases/40100?u=zicklag |
| |
| /// Create `cfg` aliases |
| /// |
| /// **build.rs:** |
| /// |
| /// ```rust |
| /// # use cfg_aliases::cfg_aliases; |
| /// // Setup cfg aliases |
| /// cfg_aliases! { |
| /// // Platforms |
| /// wasm: { target_arch = "wasm32" }, |
| /// android: { target_os = "android" }, |
| /// macos: { target_os = "macos" }, |
| /// linux: { target_os = "linux" }, |
| /// // Backends |
| /// surfman: { all(unix, feature = "surfman", not(wasm)) }, |
| /// glutin: { all(feature = "glutin", not(wasm)) }, |
| /// wgl: { all(windows, feature = "wgl", not(wasm)) }, |
| /// dummy: { not(any(wasm, glutin, wgl, surfman)) }, |
| /// } |
| /// ``` |
| /// |
| /// After you put this in your build script you can then check for those conditions like so: |
| /// |
| /// ```rust |
| /// #[cfg(surfman)] |
| /// { |
| /// // Do stuff related to surfman |
| /// } |
| /// |
| /// #[cfg(dummy)] |
| /// println!("We're in dummy mode, specify another feature if you want a smarter app!"); |
| /// ``` |
| /// |
| /// This greatly improves what would otherwise look like this without the aliases: |
| /// |
| /// ```rust |
| /// #[cfg(all(unix, feature = "surfman", not(target_arch = "wasm32")))] |
| /// { |
| /// // Do stuff related to surfman |
| /// } |
| /// |
| /// #[cfg(not(any( |
| /// target_arch = "wasm32", |
| /// all(unix, feature = "surfman", not(target_arch = "wasm32")), |
| /// all(windows, feature = "wgl", not(target_arch = "wasm32")), |
| /// all(feature = "glutin", not(target_arch = "wasm32")), |
| /// )))] |
| /// println!("We're in dummy mode, specify another feature if you want a smarter app!"); |
| /// ``` |
| #[macro_export] |
| macro_rules! cfg_aliases { |
| // Helper that just checks whether the CFG environment variable is set |
| (@cfg_is_set $cfgname:ident) => { |
| std::env::var( |
| format!( |
| "CARGO_CFG_{}", |
| &stringify!($cfgname).to_uppercase().replace("-", "_") |
| ) |
| ).is_ok() |
| }; |
| // Helper to check for the presense of a feature |
| (@cfg_has_feature $feature:expr) => { |
| { |
| std::env::var( |
| format!( |
| "CARGO_FEATURE_{}", |
| &stringify!($feature).to_uppercase().replace("-", "_").replace('"', "") |
| ) |
| ).map(|x| x == "1").unwrap_or(false) |
| } |
| }; |
| |
| // Helper that checks whether a CFG environment contains the given value |
| (@cfg_contains $cfgname:ident = $cfgvalue:expr) => { |
| std::env::var( |
| format!( |
| "CARGO_CFG_{}", |
| &stringify!($cfgname).to_uppercase().replace("-", "_") |
| ) |
| ).unwrap_or("".to_string()).split(",").find(|x| x == &$cfgvalue).is_some() |
| }; |
| |
| // Emitting `any(clause1,clause2,...)`: convert to `$crate::cfg_aliases!(clause1) && $crate::cfg_aliases!(clause2) && ...` |
| ( |
| @parser_emit |
| all |
| $({$($grouped:tt)+})+ |
| ) => { |
| ($( |
| ($crate::cfg_aliases!(@parser $($grouped)+)) |
| )&&+) |
| }; |
| |
| // Likewise for `all(clause1,clause2,...)`. |
| ( |
| @parser_emit |
| any |
| $({$($grouped:tt)+})+ |
| ) => { |
| ($( |
| ($crate::cfg_aliases!(@parser $($grouped)+)) |
| )||+) |
| }; |
| |
| // "@clause" rules are used to parse the comma-separated lists. They munch |
| // their inputs token-by-token and finally invoke an "@emit" rule when the |
| // list is all grouped. The general pattern for recording the parser state |
| // is: |
| // |
| // ``` |
| // $crate::cfg_aliases!( |
| // @clause $operation |
| // [{grouped-clause-1} {grouped-clause-2...}] |
| // [not-yet-parsed-tokens...] |
| // current-clause-tokens... |
| // ) |
| // ``` |
| |
| // This rule must come first in this section. It fires when the next token |
| // to parse is a comma. When this happens, we take the tokens in the |
| // current clause and add them to the list of grouped clauses, adding |
| // delimeters so that the grouping can be easily extracted again in the |
| // emission stage. |
| ( |
| @parser_clause |
| $op:ident |
| [$({$($grouped:tt)+})*] |
| [, $($rest:tt)*] |
| $($current:tt)+ |
| ) => { |
| $crate::cfg_aliases!(@parser_clause $op [ |
| $( |
| {$($grouped)+} |
| )* |
| {$($current)+} |
| ] [ |
| $($rest)* |
| ]); |
| }; |
| |
| // This rule comes next. It fires when the next un-parsed token is *not* a |
| // comma. In this case, we add that token to the list of tokens in the |
| // current clause, then move on to the next one. |
| ( |
| @parser_clause |
| $op:ident |
| [$({$($grouped:tt)+})*] |
| [$tok:tt $($rest:tt)*] |
| $($current:tt)* |
| ) => { |
| $crate::cfg_aliases!(@parser_clause $op [ |
| $( |
| {$($grouped)+} |
| )* |
| ] [ |
| $($rest)* |
| ] $($current)* $tok); |
| }; |
| |
| // This rule fires when there are no more tokens to parse in this list. We |
| // finish off the "current" token group, then delegate to the emission |
| // rule. |
| ( |
| @parser_clause |
| $op:ident |
| [$({$($grouped:tt)+})*] |
| [] |
| $($current:tt)+ |
| ) => { |
| $crate::cfg_aliases!(@parser_emit $op |
| $( |
| {$($grouped)+} |
| )* |
| {$($current)+} |
| ); |
| }; |
| |
| |
| // `all(clause1, clause2...)` : we must parse this comma-separated list and |
| // partner with `@emit all` to output a bunch of && terms. |
| ( |
| @parser |
| all($($tokens:tt)+) |
| ) => { |
| $crate::cfg_aliases!(@parser_clause all [] [$($tokens)+]) |
| }; |
| |
| // Likewise for `any(clause1, clause2...)` |
| ( |
| @parser |
| any($($tokens:tt)+) |
| ) => { |
| $crate::cfg_aliases!(@parser_clause any [] [$($tokens)+]) |
| }; |
| |
| // `not(clause)`: compute the inner clause, then just negate it. |
| ( |
| @parser |
| not($($tokens:tt)+) |
| ) => { |
| !($crate::cfg_aliases!(@parser $($tokens)+)) |
| }; |
| |
| // `feature = value`: test for a feature. |
| (@parser feature = $value:expr) => { |
| $crate::cfg_aliases!(@cfg_has_feature $value) |
| }; |
| // `param = value`: test for equality. |
| (@parser $key:ident = $value:expr) => { |
| $crate::cfg_aliases!(@cfg_contains $key = $value) |
| }; |
| // Parse a lone identifier that might be an alias |
| (@parser $e:ident) => { |
| __cfg_aliases_matcher__!($e) |
| }; |
| |
| // Entrypoint that defines the matcher |
| ( |
| @with_dollar[$dol:tt] |
| $( $alias:ident : { $($config:tt)* } ),* $(,)? |
| ) => { |
| // Create a macro that expands other aliases and outputs any non |
| // alias by checking whether that CFG value is set |
| macro_rules! __cfg_aliases_matcher__ { |
| // Parse config expression for the alias |
| $( |
| ( $alias ) => { |
| $crate::cfg_aliases!(@parser $($config)*) |
| }; |
| )* |
| // Anything that doesn't match evaluate the item |
| ( $dol e:ident ) => { |
| $crate::cfg_aliases!(@cfg_is_set $dol e) |
| }; |
| } |
| |
| $( |
| if $crate::cfg_aliases!(@parser $($config)*) { |
| println!("cargo:rustc-cfg={}", stringify!($alias)); |
| } |
| )* |
| }; |
| |
| // Catch all that starts the macro |
| ($($tokens:tt)*) => { |
| $crate::cfg_aliases!(@with_dollar[$] $($tokens)*) |
| } |
| } |