From 0cb5e4e82d5b50a308cdeb041ad7164d9d76701c Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Thu, 21 Mar 2024 15:36:33 +0000 Subject: [PATCH] Add the `esp-build` package, update `esp-hal` and `esp-lp-hal` to use it in their build scripts (#1325) * Create the `esp-build` package * Update `esp-hal` and `esp-lp-hal` to use `esp-build` --- Cargo.toml | 1 + esp-build/Cargo.toml | 16 +++ esp-build/README.md | 30 ++++++ esp-build/src/lib.rs | 222 ++++++++++++++++++++++++++++++++++++++++++ esp-hal/Cargo.toml | 1 + esp-hal/build.rs | 119 ++++++---------------- esp-lp-hal/Cargo.toml | 3 + esp-lp-hal/build.rs | 37 +------ 8 files changed, 304 insertions(+), 125 deletions(-) create mode 100644 esp-build/Cargo.toml create mode 100644 esp-build/README.md create mode 100644 esp-build/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b86194ea7..3cce6d82f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = ["xtask"] exclude = [ + "esp-build", "esp-hal", "esp-hal-procmacros", "esp-hal-smartled", diff --git a/esp-build/Cargo.toml b/esp-build/Cargo.toml new file mode 100644 index 000000000..d2d500689 --- /dev/null +++ b/esp-build/Cargo.toml @@ -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" diff --git a/esp-build/README.md b/esp-build/README.md new file mode 100644 index 000000000..94d9c4dbf --- /dev/null +++ b/esp-build/README.md @@ -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. diff --git a/esp-build/src/lib.rs b/esp-build/src/lib.rs new file mode 100644 index 000000000..9999ee38b --- /dev/null +++ b/esp-build/src/lib.rs @@ -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::parse_terminated) + .into_iter() + .collect::>(); + + 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::>(), + ); + + 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::parse_terminated) + .into_iter() + .collect::>(); + + let message = format!( + r#" +ERROR: expected at least one enabled feature from feature group: + {:?} + "#, + features.iter().map(|lit| lit.value()).collect::>() + ); + + 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::parse_terminated) + .into_iter() + .collect::>(); + + 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::>() + ); + + 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) -> 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 +} diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index e6a594fab..48e03e979 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -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"] } diff --git a/esp-hal/build.rs b/esp-hal/build.rs index efc6e90ab..755a77488 100644 --- a/esp-hal/build.rs +++ b/esp-hal/build.rs @@ -7,45 +7,9 @@ use std::{ str::FromStr, }; +use esp_build::assert_unique_used_features; use esp_metadata::{Chip, Config}; -// Macros taken from: -// https://github.com/TheDan64/inkwell/blob/36c3b10/src/lib.rs#L81-L110 - -// Given some features, assert that AT MOST one of the features is enabled. -macro_rules! assert_unique_features { - () => {}; - - ( $first:tt $(,$rest:tt)* ) => { - $( - #[cfg(all(feature = $first, feature = $rest))] - compile_error!(concat!("Features \"", $first, "\" and \"", $rest, "\" cannot be used together")); - )* - assert_unique_features!($($rest),*); - }; -} - -// Given some features, assert that AT LEAST one of the features is enabled. -macro_rules! assert_used_features { - ( $all:tt ) => { - #[cfg(not(feature = $all))] - compile_error!(concat!("The feature flag must be provided: ", $all)); - }; - - ( $($all:tt),+ ) => { - #[cfg(not(any($(feature = $all),*)))] - compile_error!(concat!("One of the feature flags must be provided: ", $($all, ", "),*)); - }; -} - -// Given some features, assert that EXACTLY one of the features is enabled. -macro_rules! assert_unique_used_features { - ( $($all:tt),* ) => { - assert_unique_features!($($all),*); - assert_used_features!($($all),*); - } -} - fn main() -> Result<(), Box> { // NOTE: update when adding new device support! // Ensure that exactly one chip has been specified: @@ -56,23 +20,21 @@ fn main() -> Result<(), Box> { // If the `embassy` feature is enabled, ensure that a time driver implementation // is available: #[cfg(feature = "embassy")] - { - cfg_if::cfg_if! { - if #[cfg(feature = "esp32")] { - assert_unique_used_features!("embassy-time-timg0"); - } else if #[cfg(feature = "esp32s2")] { - assert_unique_used_features!("embassy-time-systick-80mhz", "embassy-time-timg0"); - } else { - assert_unique_used_features!("embassy-time-systick-16mhz", "embassy-time-timg0"); - } + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + assert_unique_used_features!("embassy-time-timg0"); + } else if #[cfg(feature = "esp32s2")] { + assert_unique_used_features!("embassy-time-systick-80mhz", "embassy-time-timg0"); + } else { + assert_unique_used_features!("embassy-time-systick-16mhz", "embassy-time-timg0"); } } - #[cfg(feature = "flip-link")] - { - #[cfg(not(any(feature = "esp32c6", feature = "esp32h2")))] - panic!("flip-link is only available on ESP32-C6/ESP32-H2"); - } + #[cfg(all( + feature = "flip-link", + not(any(feature = "esp32c6", feature = "esp32h2")) + ))] + esp_build::error!("flip-link is only available on ESP32-C6/ESP32-H2"); // NOTE: update when adding new device support! // Determine the name of the configured device: @@ -96,12 +58,6 @@ fn main() -> Result<(), Box> { unreachable!() // We've confirmed exactly one known device was selected }; - if detect_atomic_extension("a") || detect_atomic_extension("s32c1i") { - panic!( - "Atomic emulation flags detected in `.cargo/config.toml`, this is no longer supported!" - ); - } - // Load the configuration file for the configured device: let chip = Chip::from_str(device_name)?; let config = Config::for_chip(&chip); @@ -126,8 +82,14 @@ fn main() -> Result<(), Box> { let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out.display()); + // RISC-V and Xtensa devices each require some special handling and processing + // of linker scripts: + if cfg!(feature = "esp32") || cfg!(feature = "esp32s2") || cfg!(feature = "esp32s3") { - fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?; + // Xtensa devices: + + #[cfg(any(feature = "esp32", feature = "esp32s2"))] + File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?; let (irtc, drtc) = if cfg!(feature = "esp32s3") { ("rtc_fast_seg", "rtc_fast_seg") @@ -148,25 +110,30 @@ fn main() -> Result<(), Box> { ); fs::write(out.join("alias.x"), alias)?; + fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?; } else { + // RISC-V devices: + + preprocess_file(&config_symbols, "ld/riscv/asserts.x", out.join("asserts.x"))?; + preprocess_file(&config_symbols, "ld/riscv/debug.x", out.join("debug.x"))?; preprocess_file( &config_symbols, "ld/riscv/hal-defaults.x", out.join("hal-defaults.x"), )?; - preprocess_file(&config_symbols, "ld/riscv/asserts.x", out.join("asserts.x"))?; - preprocess_file(&config_symbols, "ld/riscv/debug.x", out.join("debug.x"))?; } + // With the architecture-specific linker scripts taken care of, we can copy all + // remaining linker scripts which are common to all devices: copy_dir_all(&config_symbols, "ld/sections", &out)?; copy_dir_all(&config_symbols, format!("ld/{device_name}"), &out)?; - #[cfg(any(feature = "esp32", feature = "esp32s2"))] - File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?; - Ok(()) } +// ---------------------------------------------------------------------------- +// Helper Functions + fn copy_dir_all( config_symbols: &Vec, src: impl AsRef, @@ -234,32 +201,6 @@ fn preprocess_file( Ok(()) } -fn detect_atomic_extension(ext: &str) -> bool { - let rustflags = env::var_os("CARGO_ENCODED_RUSTFLAGS") - .unwrap() - .into_string() - .unwrap(); - - // Users can pass -Ctarget-feature to the compiler multiple times, so we have to - // handle that - let target_flags = rustflags - .split(0x1f as char) - .filter_map(|s| s.strip_prefix("target-feature=")); - for tf in target_flags { - let tf = tf - .split(',') - .map(|s| s.trim()) - .filter_map(|s| s.strip_prefix('+')); - for tf in tf { - if tf == ext { - return true; - } - } - } - - false -} - #[cfg(feature = "esp32")] fn generate_memory_extras() -> Vec { let reserve_dram = if cfg!(feature = "bluetooth") { diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index e0b840571..946d1620a 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -35,6 +35,9 @@ riscv = { version = "0.11.0", features = ["critical-section-single-har [dev-dependencies] panic-halt = "0.2.0" +[build-dependencies] +esp-build = { version = "0.1.0", path = "../esp-build" } + [features] default = ["embedded-hal-02"] diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs index 93391ecd7..855e816cd 100644 --- a/esp-lp-hal/build.rs +++ b/esp-lp-hal/build.rs @@ -1,41 +1,6 @@ use std::{env, error::Error, fs, path::PathBuf}; -// Macros taken from: -// https://github.com/TheDan64/inkwell/blob/36c3b10/src/lib.rs#L81-L110 - -// Given some features, assert that AT MOST one of the features is enabled. -macro_rules! assert_unique_features { - () => {}; - - ( $first:tt $(,$rest:tt)* ) => { - $( - #[cfg(all(feature = $first, feature = $rest))] - compile_error!(concat!("Features \"", $first, "\" and \"", $rest, "\" cannot be used together")); - )* - assert_unique_features!($($rest),*); - }; -} - -// Given some features, assert that AT LEAST one of the features is enabled. -macro_rules! assert_used_features { - ( $all:tt ) => { - #[cfg(not(feature = $all))] - compile_error!(concat!("The feature flag must be provided: ", $all)); - }; - - ( $($all:tt),+ ) => { - #[cfg(not(any($(feature = $all),*)))] - compile_error!(concat!("One of the feature flags must be provided: ", $($all, ", "),*)); - }; -} - -// Given some features, assert that EXACTLY one of the features is enabled. -macro_rules! assert_unique_used_features { - ( $($all:tt),* ) => { - assert_unique_features!($($all),*); - assert_used_features!($($all),*); - } -} +use esp_build::assert_unique_used_features; fn main() -> Result<(), Box> { // NOTE: update when adding new device support!