Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the esp-build package, update esp-hal and esp-lp-hal to use it in their build scripts #1325

Merged
merged 2 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Create the esp-build package
  • Loading branch information
jessebraham committed Mar 21, 2024
commit 994b7526c49e043e381b4abeca64eb087a47ea2d
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
}