esp_build/
lib.rs

1//! Build utilities for esp-hal.
2
3#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
4
5use std::io::Write as _;
6
7use proc_macro::TokenStream;
8use quote::ToTokens;
9use syn::{LitStr, Token, parse_macro_input, punctuated::Punctuated};
10use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
11
12/// Print a build error and terminate the process.
13///
14/// It should be noted that the error will be printed BEFORE the main function
15/// is called, and as such this should NOT be thought analogous to `println!` or
16/// similar utilities.
17///
18/// ## Example
19///
20/// ```rust
21/// esp_build::error! {"
22/// ERROR: something really bad has happened!
23/// "}
24/// // Process exits with exit code 1
25/// ```
26#[proc_macro]
27pub fn error(input: TokenStream) -> TokenStream {
28    do_alert(Color::Red, input);
29    panic!("Build failed");
30}
31
32/// Print a build warning.
33///
34/// It should be noted that the warning will be printed BEFORE the main function
35/// is called, and as such this should NOT be thought analogous to `println!` or
36/// similar utilities.
37///
38/// ## Example
39///
40/// ```rust
41/// esp_build::warning! {"
42/// WARNING: something unpleasant has happened!
43/// "};
44/// ```
45#[proc_macro]
46pub fn warning(input: TokenStream) -> TokenStream {
47    do_alert(Color::Yellow, input)
48}
49
50/// Given some features, assert that **at most** one of the features is enabled.
51///
52/// ## Example
53/// ```rust
54/// assert_unique_features!("foo", "bar", "baz");
55/// ```
56#[proc_macro]
57pub fn assert_unique_features(input: TokenStream) -> TokenStream {
58    let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
59        .into_iter()
60        .collect::<Vec<_>>();
61
62    let unique = impl_unique_features(&features, "exactly zero or one");
63
64    quote::quote! {
65        #unique
66    }
67    .into()
68}
69
70/// Given some features, assert that **at least** one of the features is
71/// enabled.
72///
73/// ## Example
74/// ```rust
75/// assert_used_features!("foo", "bar", "baz");
76/// ```
77#[proc_macro]
78pub fn assert_used_features(input: TokenStream) -> TokenStream {
79    let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
80        .into_iter()
81        .collect::<Vec<_>>();
82
83    let used = impl_used_features(&features, "at least one");
84
85    quote::quote! {
86        #used
87    }
88    .into()
89}
90
91/// Given some features, assert that **exactly** one of the features is enabled.
92///
93/// ## Example
94/// ```rust
95/// assert_unique_used_features!("foo", "bar", "baz");
96/// ```
97#[proc_macro]
98pub fn assert_unique_used_features(input: TokenStream) -> TokenStream {
99    let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
100        .into_iter()
101        .collect::<Vec<_>>();
102
103    let unique = impl_unique_features(&features, "exactly one");
104    let used = impl_used_features(&features, "exactly one");
105
106    quote::quote! {
107        #unique
108        #used
109    }
110    .into()
111}
112
113// ----------------------------------------------------------------------------
114// Helper Functions
115
116fn impl_unique_features(features: &[LitStr], expectation: &str) -> impl ToTokens + use<> {
117    let pairs = unique_pairs(features);
118    let unique_cfgs = pairs
119        .iter()
120        .map(|(a, b)| quote::quote! { all(feature = #a, feature = #b) });
121
122    let message = format!(
123        r#"
124ERROR: expected {expectation} enabled feature from feature group:
125  {:?}
126"#,
127        features.iter().map(|lit| lit.value()).collect::<Vec<_>>(),
128    );
129
130    quote::quote! {
131        #[cfg(any(#(#unique_cfgs),*))]
132        ::esp_build::error! { #message }
133    }
134}
135
136fn impl_used_features(features: &[LitStr], expectation: &str) -> impl ToTokens + use<> {
137    let message = format!(
138        r#"
139ERROR: expected {expectation} enabled feature from feature group:
140  {:?}
141    "#,
142        features.iter().map(|lit| lit.value()).collect::<Vec<_>>()
143    );
144
145    quote::quote! {
146        #[cfg(not(any(#(feature = #features),*)))]
147        ::esp_build::error! { #message }
148    }
149}
150
151// Adapted from:
152// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L54-L93
153fn do_alert(color: Color, input: TokenStream) -> TokenStream {
154    let message = parse_macro_input!(input as LitStr).value();
155
156    let stderr = &mut StandardStream::stderr(ColorChoice::Auto);
157    let color_spec = ColorSpec::new().set_fg(Some(color)).clone();
158
159    let mut has_nonspace = false;
160
161    for mut line in message.lines() {
162        if !has_nonspace {
163            let (maybe_heading, rest) = split_heading(line);
164
165            if let Some(heading) = maybe_heading {
166                stderr.set_color(color_spec.clone().set_bold(true)).ok();
167                write!(stderr, "\n{heading}").ok();
168                has_nonspace = true;
169            }
170
171            line = rest;
172        }
173
174        if line.is_empty() {
175            writeln!(stderr).ok();
176        } else {
177            stderr.set_color(&color_spec).ok();
178            writeln!(stderr, "{line}").ok();
179
180            has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' ');
181        }
182    }
183
184    stderr.reset().ok();
185    writeln!(stderr).ok();
186
187    TokenStream::new()
188}
189
190// Adapted from:
191// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L95-L114
192fn split_heading(s: &str) -> (Option<&str>, &str) {
193    let mut end = 0;
194    while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) {
195        end += 1;
196    }
197
198    if end >= 3 && (end == s.len() || s[end..].starts_with(':')) {
199        let (heading, rest) = s.split_at(end);
200        (Some(heading), rest)
201    } else {
202        (None, s)
203    }
204}
205
206fn unique_pairs(features: &[LitStr]) -> Vec<(&LitStr, &LitStr)> {
207    let mut pairs = Vec::new();
208
209    let mut i = 0;
210    let mut j = 0;
211
212    while i < features.len() {
213        let a = &features[i];
214        let b = &features[j];
215
216        if a.value() != b.value() {
217            pairs.push((a, b));
218        }
219
220        j += 1;
221
222        if j >= features.len() {
223            i += 1;
224            j = i;
225        }
226    }
227
228    pairs
229}