Skip to content

Commit

Permalink
Add the esp-build package, update esp-hal and esp-lp-hal to use…
Browse files Browse the repository at this point in the history
… it in their build scripts (#1325)

* Create the `esp-build` package

* Update `esp-hal` and `esp-lp-hal` to use `esp-build`
  • Loading branch information
jessebraham authored Mar 21, 2024
1 parent 4e5020f commit 0cb5e4e
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 125 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = ["xtask"]
exclude = [
"esp-build",
"esp-hal",
"esp-hal-procmacros",
"esp-hal-smartled",
Expand Down
16 changes: 16 additions & 0 deletions esp-build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "esp-build"
version = "0.1.0"
edition = "2021"
rust-version = "1.60.0"
description = "Build utilities for esp-hal"
repository = "https://github.com/esp-rs/esp-hal"
license = "MIT OR Apache-2.0"

[lib]
proc-macro = true

[dependencies]
quote = "1.0.35"
syn = { version = "2.0.52", features = ["fold", "full"] }
termcolor = "1.4.1"
30 changes: 30 additions & 0 deletions esp-build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# esp-build

[![Crates.io](https://img.shields.io/crates/v/esp-build?color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-build)
[![docs.rs](https://img.shields.io/docsrs/esp-build?color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-build)
![MSRV](https://img.shields.io/badge/MSRV-1.60-blue?style=flat-square)
![Crates.io](https://img.shields.io/crates/l/esp-build?style=flat-square)

Build utilities for `esp-hal`.

## [Documentation](https://docs.rs/crate/esp-build)

## Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on stable Rust 1.60 and up. It _might_
compile with older versions but that may change in any new patch release.

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without
any additional terms or conditions.
222 changes: 222 additions & 0 deletions esp-build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//! Build utilities for esp-hal.

#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]

use std::{io::Write as _, process};

use proc_macro::TokenStream;
use syn::{parse_macro_input, punctuated::Punctuated, LitStr, Token};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

/// Print a build error and terminate the process.
///
/// It should be noted that the error will be printed BEFORE the main function
/// is called, and as such this should NOT be thought analogous to `println!` or
/// similar utilities.
///
/// ## Example
///
/// ```rust
/// esp_build::error! {"
/// ERROR: something really bad has happened!
/// "}
/// // Process exits with exit code 1
/// ```
#[proc_macro]
pub fn error(input: TokenStream) -> TokenStream {
do_alert(Color::Red, input);
process::exit(1);
}

/// Print a build warning.
///
/// It should be noted that the warning will be printed BEFORE the main function
/// is called, and as such this should NOT be thought analogous to `println!` or
/// similar utilities.
///
/// ## Example
///
/// ```rust
/// esp_build::warning! {"
/// WARNING: something unpleasant has happened!
/// "};
/// ```
#[proc_macro]
pub fn warning(input: TokenStream) -> TokenStream {
do_alert(Color::Yellow, input)
}

/// Given some features, assert that **at most** one of the features is enabled.
///
/// ## Example
/// ```rust
/// assert_unique_features!("foo", "bar", "baz");
/// ```
#[proc_macro]
pub fn assert_unique_features(input: TokenStream) -> TokenStream {
let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
.into_iter()
.collect::<Vec<_>>();

let pairs = unique_pairs(&features);
let unique_cfgs = pairs
.iter()
.map(|(a, b)| quote::quote! { all(feature = #a, feature = #b) });

let message = format!(
r#"
ERROR: expected exactly zero or one enabled feature from feature group:
{:?}
"#,
features.iter().map(|lit| lit.value()).collect::<Vec<_>>(),
);

quote::quote! {
#[cfg(any(#(#unique_cfgs),*))]
::esp_build::error! { #message }
}
.into()
}

/// Given some features, assert that **at least** one of the features is
/// enabled.
///
/// ## Example
/// ```rust
/// assert_used_features!("foo", "bar", "baz");
/// ```
#[proc_macro]
pub fn assert_used_features(input: TokenStream) -> TokenStream {
let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
.into_iter()
.collect::<Vec<_>>();

let message = format!(
r#"
ERROR: expected at least one enabled feature from feature group:
{:?}
"#,
features.iter().map(|lit| lit.value()).collect::<Vec<_>>()
);

quote::quote! {
#[cfg(not(any(#(feature = #features),*)))]
::esp_build::error! { #message }
}
.into()
}

/// Given some features, assert that **exactly** one of the features is enabled.
///
/// ## Example
/// ```rust
/// assert_unique_used_features!("foo", "bar", "baz");
/// ```
#[proc_macro]
pub fn assert_unique_used_features(input: TokenStream) -> TokenStream {
let features = parse_macro_input!(input with Punctuated<LitStr, Token![,]>::parse_terminated)
.into_iter()
.collect::<Vec<_>>();

let pairs = unique_pairs(&features);
let unique_cfgs = pairs
.iter()
.map(|(a, b)| quote::quote! { all(feature = #a, feature = #b) });

let message = format!(
r#"
ERROR: expected exactly one enabled feature from feature group:
{:?}
"#,
features.iter().map(|lit| lit.value()).collect::<Vec<_>>()
);

quote::quote! {
#[cfg(any(any(#(#unique_cfgs),*), not(any(#(feature = #features),*))))]
::esp_build::error! { #message }
}
.into()
}

// ----------------------------------------------------------------------------
// Helper Functions

// Adapted from:
// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L54-L93
fn do_alert(color: Color, input: TokenStream) -> TokenStream {
let message = parse_macro_input!(input as LitStr).value();

let ref mut stderr = StandardStream::stderr(ColorChoice::Auto);
let color_spec = ColorSpec::new().set_fg(Some(color)).clone();

let mut has_nonspace = false;

for mut line in message.lines() {
if !has_nonspace {
let (maybe_heading, rest) = split_heading(line);

if let Some(heading) = maybe_heading {
stderr.set_color(color_spec.clone().set_bold(true)).ok();
write!(stderr, "\n{}", heading).ok();
has_nonspace = true;
}

line = rest;
}

if line.is_empty() {
writeln!(stderr).ok();
} else {
stderr.set_color(&color_spec).ok();
writeln!(stderr, "{}", line).ok();

has_nonspace = has_nonspace || line.contains(|ch: char| ch != ' ');
}
}

stderr.reset().ok();
writeln!(stderr).ok();

TokenStream::new()
}

// Adapted from:
// https://github.com/dtolnay/build-alert/blob/49d060e/src/lib.rs#L95-L114
fn split_heading(s: &str) -> (Option<&str>, &str) {
let mut end = 0;
while end < s.len() && s[end..].starts_with(|ch: char| ch.is_ascii_uppercase()) {
end += 1;
}

if end >= 3 && (end == s.len() || s[end..].starts_with(':')) {
let (heading, rest) = s.split_at(end);
(Some(heading), rest)
} else {
(None, s)
}
}

fn unique_pairs(features: &Vec<LitStr>) -> Vec<(&LitStr, &LitStr)> {
let mut pairs = Vec::new();

let mut i = 0;
let mut j = 0;

while i < features.len() {
let a = &features[i];
let b = &features[j];

if a.value() != b.value() {
pairs.push((a, b));
}

j += 1;

if j >= features.len() {
i += 1;
j = i;
}
}

pairs
}
1 change: 1 addition & 0 deletions esp-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ xtensa-lx-rt = { version = "0.16.0", optional = true }
[build-dependencies]
basic-toml = "0.1.8"
cfg-if = "1.0.0"
esp-build = { version = "0.1.0", path = "../esp-build" }
esp-metadata = { version = "0.1.0", path = "../esp-metadata" }
serde = { version = "1.0.197", features = ["derive"] }

Expand Down
Loading

0 comments on commit 0cb5e4e

Please sign in to comment.