From cfc972efc60ef650bee29b4c9be37ded65e9593f Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Wed, 11 Sep 2024 17:37:45 +0100 Subject: [PATCH 01/22] Initial esp-config poc, replacing the place-spi-driver-in-ram feature --- Cargo.toml | 1 + esp-config/Cargo.toml | 6 +++ esp-config/src/lib.rs | 79 +++++++++++++++++++++++++++++++++++++++ esp-hal/Cargo.toml | 5 +-- esp-hal/build.rs | 7 ++++ esp-hal/src/spi/master.rs | 22 +++++------ 6 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 esp-config/Cargo.toml create mode 100644 esp-config/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 1a5ae61ce38..7186091d691 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ exclude = [ "esp-alloc", "esp-backtrace", "esp-build", + "esp-config", "esp-hal", "esp-hal-embassy", "esp-hal-procmacros", diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml new file mode 100644 index 00000000000..aeb12181aca --- /dev/null +++ b/esp-config/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "esp-config" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs new file mode 100644 index 00000000000..de285b191db --- /dev/null +++ b/esp-config/src/lib.rs @@ -0,0 +1,79 @@ +use std::{collections::HashMap, env}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ParseError; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Value { + // TODO + // Number(usize), + // String(String), + Bool(bool), +} + +impl Value { + fn parse(&mut self, s: &str) -> Result<(), ParseError> { + *self = match self { + Value::Bool(_) => match s { + "false" => Value::Bool(false), + _ => Value::Bool(true), // TODO should existance of the key mean true? + }, + }; + Ok(()) + } +} + +/// Takes a array of configuration keys and default values +pub fn generate_config(prefix: &str, config: &[(&str, Value)]) { + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Generate the template for the config + let mut configs = HashMap::new(); + for (name, default) in config { + configs.insert(normalize_name(name), *default); + + // Rebuild if config envvar changed. + // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 + println!("cargo:rerun-if-env-changed={name}"); + } + + let mut prefix = prefix.to_owned(); + prefix.make_ascii_uppercase(); + + // Try and capture input from the environment + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { + let name = normalize_name(name); + // TODO should this be lowered to a warning for unknown configuration options? + // we could instead mark this as unknown and _not_ emit + // println!("cargo:rustc-check-cfg=cfg({})", name); + let Some(cfg) = configs.get_mut(&name) else { + panic!("Unknown env var {name}") + }; + + cfg.parse(&value) + .expect(&format!("Invalid value for env var {name}: {value}")); + } + } + + // emit cfgs + for (name, value) in configs.into_iter() { + println!("cargo:rustc-check-cfg=cfg({})", name); + match value { + Value::Bool(val) if val == true => println!("cargo:rustc-cfg={name}"), + _ => {} + } + } +} + +// Converts a symbol name like +// "PLACE-spi_DRIVER-IN_ram" +// to +// "place_spi_driver_in_ram" +fn normalize_name(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_lowercase(); + name +} diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 4f26cff6a53..072d385a795 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -74,6 +74,7 @@ basic-toml = "0.1.9" cfg-if = "1.0.0" esp-build = { version = "0.1.0", path = "../esp-build" } esp-metadata = { version = "0.3.0", path = "../esp-metadata" } +esp-config = { version = "0.1.0", path = "../esp-config" } serde = { version = "1.0.209", features = ["derive"] } [features] @@ -100,8 +101,6 @@ debug = [ ] ## Enable logging output using the `log` crate. log = ["dep:log"] -## Configuration for placing device drivers in the IRAM for faster access. -place-spi-driver-in-ram = [] # Chip Support Feature Flags # Target the ESP32. @@ -164,7 +163,7 @@ opsram-8m = [] opsram-16m = [] # This feature is intended for testing; you probably don't want to enable it: -ci = ["defmt", "bluetooth", "place-spi-driver-in-ram"] +ci = ["defmt", "bluetooth"] [lints.clippy] mixed_attributes_style = "allow" diff --git a/esp-hal/build.rs b/esp-hal/build.rs index d6c28894540..a65581382f4 100644 --- a/esp-hal/build.rs +++ b/esp-hal/build.rs @@ -8,6 +8,7 @@ use std::{ }; use esp_build::assert_unique_used_features; +use esp_config::{generate_config, Value}; use esp_metadata::{Chip, Config}; #[cfg(debug_assertions)] @@ -122,6 +123,12 @@ fn main() -> Result<(), Box> { copy_dir_all(&config_symbols, "ld/sections", &out)?; copy_dir_all(&config_symbols, format!("ld/{device_name}"), &out)?; + // emit config + generate_config( + "esp_hal", + &[("place-spi-driver-in-ram", Value::Bool(false))], + ); + Ok(()) } diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index 5858c3fc6a1..f646d4101da 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -66,7 +66,7 @@ use enumset::EnumSet; #[cfg(gdma)] use enumset::EnumSetType; use fugit::HertzU32; -#[cfg(feature = "place-spi-driver-in-ram")] +#[cfg(place_spi_driver_in_ram)] use procmacros::ram; use super::{ @@ -1199,7 +1199,7 @@ mod dma { /// SPI instance. The maximum amount of data to be sent is 32736 /// bytes. #[allow(clippy::type_complexity)] - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] pub fn dma_write( mut self, mut buffer: TX, @@ -1226,7 +1226,7 @@ mod dma { /// the SPI instance. The maximum amount of data to be /// received is 32736 bytes. #[allow(clippy::type_complexity)] - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] pub fn dma_read( mut self, mut buffer: RX, @@ -1301,7 +1301,7 @@ mod dma { { /// Perform a half-duplex read operation using DMA. #[allow(clippy::type_complexity)] - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] pub fn read( mut self, data_mode: SpiDataMode, @@ -1378,7 +1378,7 @@ mod dma { /// Perform a half-duplex write operation using DMA. #[allow(clippy::type_complexity)] - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] pub fn write( mut self, data_mode: SpiDataMode, @@ -2247,7 +2247,7 @@ pub trait InstanceDma: Instance { Ok(()) } - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] unsafe fn start_write_bytes_dma( &mut self, buffer: &mut impl DmaTxBuffer, @@ -2295,7 +2295,7 @@ pub trait InstanceDma: Instance { Ok(()) } - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] unsafe fn start_read_bytes_dma( &mut self, buffer: &mut BUF, @@ -3050,7 +3050,7 @@ pub trait Instance: private::Sealed { /// all bytes of the last chunk to transmit have been sent to the wire. If /// you must ensure that the whole messages was written correctly, use /// [`Self::flush`]. - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] fn write_bytes(&mut self, words: &[u8]) -> Result<(), Error> { let num_chunks = words.len() / FIFO_SIZE; @@ -3107,7 +3107,7 @@ pub trait Instance: private::Sealed { /// Sends out a stuffing byte for every byte to read. This function doesn't /// perform flushing. If you want to read the response to something you /// have written before, consider using [`Self::transfer`] instead. - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Error> { let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; @@ -3125,7 +3125,7 @@ pub trait Instance: private::Sealed { /// doesn't perform flushing. If you want to read the response to /// something you have written before, consider using [`Self::transfer`] /// instead. - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Error> { let reg_block = self.register_block(); @@ -3157,7 +3157,7 @@ pub trait Instance: private::Sealed { Ok(()) } - #[cfg_attr(feature = "place-spi-driver-in-ram", ram)] + #[cfg_attr(place_spi_driver_in_ram, ram)] fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> { for chunk in words.chunks_mut(FIFO_SIZE) { self.write_bytes(chunk)?; From 9a0bdd45a72248feab3faa0ef5595d57a3eae602 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Thu, 12 Sep 2024 12:59:29 +0100 Subject: [PATCH 02/22] Allow documentation generation for configuration options --- esp-config/src/lib.rs | 69 ++++++++++++++++++++++++++++++++++++------- esp-hal/build.rs | 6 +++- esp-hal/src/lib.rs | 32 ++++++++++---------- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index de285b191db..daa8c6becce 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -1,4 +1,10 @@ -use std::{collections::HashMap, env}; +use core::fmt::Write; +use std::{collections::HashMap, env, fs, path::PathBuf}; + +const TABLE_HEADER: &str = r#" +| name | description | default value | +|------|-------------|---------------| +"#; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ParseError; @@ -21,31 +27,54 @@ impl Value { }; Ok(()) } + + fn as_string(&self) -> String { + match self { + Value::Bool(value) => String::from(if *value { "true" } else { "false" }), + } + } } -/// Takes a array of configuration keys and default values -pub fn generate_config(prefix: &str, config: &[(&str, Value)]) { +/// This function will parse any screaming snake case environment variables that +/// match the given prefix. It will then attempt to parse the [`Value`]. Once +/// the config has been parsed, this function will emit snake case cfg's +/// _without_ the prefix which can be used in the dependant crate. After that, +/// it will create a markdown table in the `OUT_DIR` under the name +/// `{prefix}_config_table.md` where prefix has also been converted +/// to snake case. This can be included in crate documentation to outline the +/// available configuration options for the crate. +/// +/// The tuple ordering for the`config` array is key, default, description. +/// +/// Unknown keys with the supplied prefix will cause this function to panic. +pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { + let mut prefix = screaming_snake_case(prefix); + + let mut doc_table = String::from(TABLE_HEADER); + for (key, default, desc) in config { + let key = screaming_snake_case(key); + let default = default.as_string(); + writeln!(doc_table, "|**{prefix}_{key}**|{desc}|{default}|").unwrap(); + } + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any // other file changed. println!("cargo:rerun-if-changed=build.rs"); // Generate the template for the config let mut configs = HashMap::new(); - for (name, default) in config { - configs.insert(normalize_name(name), *default); + for (name, default, _) in config { + configs.insert(snake_case(name), *default); // Rebuild if config envvar changed. // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 println!("cargo:rerun-if-env-changed={name}"); } - let mut prefix = prefix.to_owned(); - prefix.make_ascii_uppercase(); - // Try and capture input from the environment for (var, value) in env::vars() { if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { - let name = normalize_name(name); + let name = snake_case(name); // TODO should this be lowered to a warning for unknown configuration options? // we could instead mark this as unknown and _not_ emit // println!("cargo:rustc-check-cfg=cfg({})", name); @@ -66,14 +95,34 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value)]) { _ => {} } } + + // convert to snake case + prefix.make_ascii_lowercase(); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir + .join(format!("{prefix}_config_table.md")) + .to_string_lossy() + .to_string(); + fs::write(out_file, doc_table).unwrap(); } // Converts a symbol name like // "PLACE-spi_DRIVER-IN_ram" // to // "place_spi_driver_in_ram" -fn normalize_name(name: &str) -> String { +fn snake_case(name: &str) -> String { let mut name = name.replace("-", "_"); name.make_ascii_lowercase(); name } + +// Converts a symbol name like +// "PLACE-spi_DRIVER-IN_ram" +// to +// "PLACE_SPI_DRIVER_IN_RAM" +fn screaming_snake_case(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_uppercase(); + name +} diff --git a/esp-hal/build.rs b/esp-hal/build.rs index a65581382f4..96b9ce2152b 100644 --- a/esp-hal/build.rs +++ b/esp-hal/build.rs @@ -126,7 +126,11 @@ fn main() -> Result<(), Box> { // emit config generate_config( "esp_hal", - &[("place-spi-driver-in-ram", Value::Bool(false))], + &[( + "place-spi-driver-in-ram", + Value::Bool(false), + "Places the SPI driver in RAM for better performance", + )], ); Ok(()) diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs index fc4b294df0b..c010207da67 100644 --- a/esp-hal/src/lib.rs +++ b/esp-hal/src/lib.rs @@ -88,31 +88,29 @@ //! } //! ``` //! -//! The steps here are: -//! - Call [`init`] with the desired [`CpuClock`] configuration -//! - Create [`gpio::Io`] which provides access to the GPIO pins -//! - Create an [`gpio::Output`] pin driver which lets us control the logical -//! level of an output pin -//! - Create a [`delay::Delay`] driver -//! - In a loop, toggle the output pin's logical level with a delay of 1000 ms +//! ## Additional configuration //! -//! ## `PeripheralRef` Pattern +//! We've exposed some configuration options that don't fit into cargo +//! features. These can be set via environment variables, or via cargo's `[env]` +//! section inside `.cargo/config.toml`. Below is a table of tunable parameters +//! for this crate: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_config_table.md"))] +#![doc = ""] +//! ## `Peripheral` Pattern //! -//! Generally drivers take pins and peripherals as [peripheral::PeripheralRef]. -//! This means you can pass the pin/peripheral or a mutable reference to the -//! pin/peripheral. +//! Drivers take pins and peripherals as [peripheral::Peripheral] in most +//! circumstances. This means you can pass the pin/peripheral or a mutable +//! reference to the pin/peripheral. //! //! The latter can be used to regain access to the pin when the driver gets //! dropped. Then it's possible to reuse the pin/peripheral for a different //! purpose. //! -//! ## Don't use [core::mem::forget] +//! ## Don't use `core::mem::forget` //! -//! In general drivers are _NOT_ safe to use with [core::mem::forget] -//! -//! You should never use [core::mem::forget] on any type defined in the HAL. -//! -//! Some types heavily rely on their [Drop] implementation to not leave the +//! You should never use `core::mem::forget` on any type defined in the HAL. +//! Some types heavily rely on their `Drop` implementation to not leave the //! hardware in undefined state and causing UB. //! //! You might want to consider using [`#[deny(clippy::mem_forget)`](https://rust-lang.github.io/rust-clippy/v0.0.212/index.html#mem_forget) in your project. From 0ba66775d499e39f63a2726e3976247219fcef69 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 14:03:45 +0100 Subject: [PATCH 03/22] add `Value::Number` and a macro to parse --- esp-config/Cargo.toml | 3 + esp-config/src/generate.rs | 135 +++++++++++++++++++++++++++++++++++ esp-config/src/lib.rs | 140 ++++--------------------------------- esp-hal/Cargo.toml | 2 +- esp-wifi/Cargo.toml | 2 + esp-wifi/build.rs | 7 ++ esp-wifi/src/lib.rs | 4 ++ 7 files changed, 166 insertions(+), 127 deletions(-) create mode 100644 esp-config/src/generate.rs diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index aeb12181aca..afa1609f50a 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] + +[features] +std = [] \ No newline at end of file diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs new file mode 100644 index 00000000000..6aaa723b414 --- /dev/null +++ b/esp-config/src/generate.rs @@ -0,0 +1,135 @@ +use core::fmt::Write; +use std::{collections::HashMap, env, fs, path::PathBuf}; + +const TABLE_HEADER: &str = r#" +| name | description | default value | +|------|-------------|---------------| +"#; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ParseError; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Value { + Number(usize), + Bool(bool), +} + +impl Value { + fn parse(&mut self, s: &str) -> Result<(), ParseError> { + *self = match self { + Value::Bool(_) => match s { + "false" => Value::Bool(false), + _ => Value::Bool(true), // TODO should existance of the key mean true? + }, + Value::Number(_) => Value::Number(s.parse().map_err(|_| ParseError)?), + }; + Ok(()) + } + + fn as_string(&self) -> String { + match self { + Value::Bool(value) => String::from(if *value { "true" } else { "false" }), + Value::Number(value) => format!("{}", value), + } + } +} + +/// This function will parse any screaming snake case environment variables that +/// match the given prefix. It will then attempt to parse the [`Value`]. Once +/// the config has been parsed, this function will emit snake case cfg's +/// _without_ the prefix which can be used in the dependant crate. After that, +/// it will create a markdown table in the `OUT_DIR` under the name +/// `{prefix}_config_table.md` where prefix has also been converted +/// to snake case. This can be included in crate documentation to outline the +/// available configuration options for the crate. +/// +/// The tuple ordering for the`config` array is key, default, description. +/// +/// Unknown keys with the supplied prefix will cause this function to panic. +pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { + let mut prefix = screaming_snake_case(prefix); + + let mut doc_table = String::from(TABLE_HEADER); + for (key, default, desc) in config { + let key = screaming_snake_case(key); + let default = default.as_string(); + writeln!(doc_table, "|**{prefix}_{key}**|{desc}|{default}|").unwrap(); + } + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Generate the template for the config + let mut configs = HashMap::new(); + for (name, default, _) in config { + configs.insert(snake_case(name), *default); + + // Rebuild if config envvar changed. + // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 + println!("cargo:rerun-if-env-changed={name}"); + } + + // Try and capture input from the environment + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { + let name = snake_case(name); + // TODO should this be lowered to a warning for unknown configuration options? + // we could instead mark this as unknown and _not_ emit + // println!("cargo:rustc-check-cfg=cfg({})", name); + let Some(cfg) = configs.get_mut(&name) else { + panic!("Unknown env var {name}") + }; + + cfg.parse(&value) + .expect(&format!("Invalid value for env var {name}: {value}")); + } + } + + // emit cfgs and set envs + for (name, value) in configs.into_iter() { + println!("cargo:rustc-check-cfg=cfg({})", name); + match value { + Value::Bool(val) if val == true => println!("cargo:rustc-cfg={name}"), + _ => {} + } + + // values that haven't been seen will be output here with the default value + println!( + "cargo:rustc-env={}={}", + format!("{prefix}_{}", screaming_snake_case(&name)), + value.as_string() + ); + } + + // convert to snake case + prefix.make_ascii_lowercase(); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir + .join(format!("{prefix}_config_table.md")) + .to_string_lossy() + .to_string(); + fs::write(out_file, doc_table).unwrap(); +} + +// Converts a symbol name like +// "PLACE-spi_DRIVER-IN_ram" +// to +// "place_spi_driver_in_ram" +fn snake_case(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_lowercase(); + name +} + +// Converts a symbol name like +// "PLACE-spi_DRIVER-IN_ram" +// to +// "PLACE_SPI_DRIVER_IN_RAM" +fn screaming_snake_case(name: &str) -> String { + let mut name = name.replace("-", "_"); + name.make_ascii_uppercase(); + name +} diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index daa8c6becce..56b041bbb57 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -1,128 +1,16 @@ -use core::fmt::Write; -use std::{collections::HashMap, env, fs, path::PathBuf}; - -const TABLE_HEADER: &str = r#" -| name | description | default value | -|------|-------------|---------------| -"#; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct ParseError; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Value { - // TODO - // Number(usize), - // String(String), - Bool(bool), -} - -impl Value { - fn parse(&mut self, s: &str) -> Result<(), ParseError> { - *self = match self { - Value::Bool(_) => match s { - "false" => Value::Bool(false), - _ => Value::Bool(true), // TODO should existance of the key mean true? - }, +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +mod generate; +#[cfg(feature = "std")] +pub use generate::*; + +#[macro_export] +macro_rules! esp_config_int { + ($ty:ty, $var:expr) => { + match <$ty>::from_str_radix(env!($var), 10) { + Ok(val) => val, + _ => unreachable!(), }; - Ok(()) - } - - fn as_string(&self) -> String { - match self { - Value::Bool(value) => String::from(if *value { "true" } else { "false" }), - } - } -} - -/// This function will parse any screaming snake case environment variables that -/// match the given prefix. It will then attempt to parse the [`Value`]. Once -/// the config has been parsed, this function will emit snake case cfg's -/// _without_ the prefix which can be used in the dependant crate. After that, -/// it will create a markdown table in the `OUT_DIR` under the name -/// `{prefix}_config_table.md` where prefix has also been converted -/// to snake case. This can be included in crate documentation to outline the -/// available configuration options for the crate. -/// -/// The tuple ordering for the`config` array is key, default, description. -/// -/// Unknown keys with the supplied prefix will cause this function to panic. -pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { - let mut prefix = screaming_snake_case(prefix); - - let mut doc_table = String::from(TABLE_HEADER); - for (key, default, desc) in config { - let key = screaming_snake_case(key); - let default = default.as_string(); - writeln!(doc_table, "|**{prefix}_{key}**|{desc}|{default}|").unwrap(); - } - - // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any - // other file changed. - println!("cargo:rerun-if-changed=build.rs"); - - // Generate the template for the config - let mut configs = HashMap::new(); - for (name, default, _) in config { - configs.insert(snake_case(name), *default); - - // Rebuild if config envvar changed. - // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 - println!("cargo:rerun-if-env-changed={name}"); - } - - // Try and capture input from the environment - for (var, value) in env::vars() { - if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { - let name = snake_case(name); - // TODO should this be lowered to a warning for unknown configuration options? - // we could instead mark this as unknown and _not_ emit - // println!("cargo:rustc-check-cfg=cfg({})", name); - let Some(cfg) = configs.get_mut(&name) else { - panic!("Unknown env var {name}") - }; - - cfg.parse(&value) - .expect(&format!("Invalid value for env var {name}: {value}")); - } - } - - // emit cfgs - for (name, value) in configs.into_iter() { - println!("cargo:rustc-check-cfg=cfg({})", name); - match value { - Value::Bool(val) if val == true => println!("cargo:rustc-cfg={name}"), - _ => {} - } - } - - // convert to snake case - prefix.make_ascii_lowercase(); - - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - let out_file = out_dir - .join(format!("{prefix}_config_table.md")) - .to_string_lossy() - .to_string(); - fs::write(out_file, doc_table).unwrap(); -} - -// Converts a symbol name like -// "PLACE-spi_DRIVER-IN_ram" -// to -// "place_spi_driver_in_ram" -fn snake_case(name: &str) -> String { - let mut name = name.replace("-", "_"); - name.make_ascii_lowercase(); - name -} - -// Converts a symbol name like -// "PLACE-spi_DRIVER-IN_ram" -// to -// "PLACE_SPI_DRIVER_IN_RAM" -fn screaming_snake_case(name: &str) -> String { - let mut name = name.replace("-", "_"); - name.make_ascii_uppercase(); - name + }; } diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 072d385a795..1f6ea2a9411 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -74,7 +74,7 @@ basic-toml = "0.1.9" cfg-if = "1.0.0" esp-build = { version = "0.1.0", path = "../esp-build" } esp-metadata = { version = "0.3.0", path = "../esp-metadata" } -esp-config = { version = "0.1.0", path = "../esp-config" } +esp-config = { version = "0.1.0", path = "../esp-config", features = ["std"] } serde = { version = "1.0.209", features = ["derive"] } [features] diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index ca531fb3b06..49c03b156c3 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -52,10 +52,12 @@ atomic-waker = { version = "1.1.2", default-features = false, features = [ "portable-atomic", ] } bt-hci = { version = "0.1.0", optional = true } +esp-config = { version = "0.1.0", path = "../esp-config" } [build-dependencies] toml-cfg = "0.2.0" esp-build = { version = "0.1.0", path = "../esp-build" } +esp-config = { version = "0.1.0", path = "../esp-config", features = ["std"] } esp-metadata = { version = "0.3.0", path = "../esp-metadata" } [features] diff --git a/esp-wifi/build.rs b/esp-wifi/build.rs index 14f089947e2..44a0d222ad6 100644 --- a/esp-wifi/build.rs +++ b/esp-wifi/build.rs @@ -1,6 +1,7 @@ use std::{error::Error, str::FromStr}; use esp_build::assert_unique_used_features; +use esp_config::{generate_config, Value}; use esp_metadata::{Chip, Config}; fn main() -> Result<(), Box> { @@ -130,6 +131,12 @@ fn main() -> Result<(), Box> { } } + // emit config + generate_config( + "esp_wifi", + &[("dummy", Value::Number(44), "Tests something")], + ); + Ok(()) } diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index 896dd2392ff..b5f341dc400 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -18,6 +18,7 @@ // toolchain doesn't know about that lint, yet) #![allow(unknown_lints)] #![allow(non_local_definitions)] +#![feature(const_int_from_str)] // stable in 1.82 extern crate alloc; @@ -131,6 +132,9 @@ struct Config { // Validate the configuration at compile time #[allow(clippy::assertions_on_constants)] const _: () = { + use esp_config::esp_config_int; + const TEST: usize = esp_config_int!(usize, "ESP_WIFI_DUMMY"); + core::assert!(TEST == 666); // We explicitely use `core` assert here because this evaluation happens at // compile time and won't bloat the binary core::assert!( From aa3f19de5c1cf30cf0d0cd36238d63992189385a Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 15:11:23 +0100 Subject: [PATCH 04/22] Add Value::String and replace esp-wifi's config --- esp-config/src/generate.rs | 7 +- esp-config/src/lib.rs | 17 +++++ esp-wifi/README.md | 53 --------------- esp-wifi/build.rs | 31 ++++++++- esp-wifi/src/lib.rs | 136 +++++++++++++++++++++++++++---------- esp-wifi/tuning.md | 56 --------------- 6 files changed, 152 insertions(+), 148 deletions(-) delete mode 100644 esp-wifi/tuning.md diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 6aaa723b414..eb78891950d 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -9,10 +9,11 @@ const TABLE_HEADER: &str = r#" #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ParseError; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { Number(usize), Bool(bool), + String(String), } impl Value { @@ -23,6 +24,7 @@ impl Value { _ => Value::Bool(true), // TODO should existance of the key mean true? }, Value::Number(_) => Value::Number(s.parse().map_err(|_| ParseError)?), + Value::String(_) => Value::String(String::from(s)), }; Ok(()) } @@ -31,6 +33,7 @@ impl Value { match self { Value::Bool(value) => String::from(if *value { "true" } else { "false" }), Value::Number(value) => format!("{}", value), + Value::String(value) => value.clone(), } } } @@ -64,7 +67,7 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { // Generate the template for the config let mut configs = HashMap::new(); for (name, default, _) in config { - configs.insert(snake_case(name), *default); + configs.insert(snake_case(name), default.clone()); // Rebuild if config envvar changed. // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index 56b041bbb57..019ccfdc150 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -14,3 +14,20 @@ macro_rules! esp_config_int { }; }; } + +#[macro_export] +macro_rules! esp_config_str { + ($var:expr) => { + env!($var) + }; +} + +#[macro_export] +macro_rules! esp_config_bool { + ($var:expr) => { + match env!($var).as_bytes() { + b"false" => false, + _ => true, + } + }; +} diff --git a/esp-wifi/README.md b/esp-wifi/README.md index 3b19baa4877..55bb25becda 100644 --- a/esp-wifi/README.md +++ b/esp-wifi/README.md @@ -24,59 +24,6 @@ If a cell contains an em dash (—) this means that the particular feature i Minimum supported Rust compiler version: 1.72.0.0 -## Usage - -### Importing - -Ensure that the right features are enabled for your chip. See [Examples](https://github.com/esp-rs/esp-hal/tree/main/examples#examples) for more examples. - -```toml -[dependencies.esp-wifi] -# A supported chip needs to be specified, as well as specific use-case features -features = ["esp32s3", "wifi", "esp-now"] -``` - -### Link configuration - -Make sure to include the rom functions for your target: - -```toml -# .cargo/config.toml -rustflags = [ - "-C", "link-arg=-Tlinkall.x", - "-C", "link-arg=-Trom_functions.x", -] -``` - -At the time of writing, you will already have the `linkall` flag if you used `cargo generate`. Generating from a template does not include the `rom_functions` flag. - -### Optimization Level - -It is necessary to build with optimization level 2 or 3 since otherwise, it might not even be able to connect or advertise. - -To make it work also for your debug builds add this to your `Cargo.toml` - -```toml -[profile.dev.package.esp-wifi] -opt-level = 3 -``` - -### Xtensa considerations - -Within this crate, `CCOMPARE0` CPU timer is used for timing, ensure that in your application you are not using this CPU timer. - -## USB-SERIAL-JTAG - -When using USB-SERIAL-JTAG (for example by selecting `jtag-serial` in [`esp-println`](https://crates.io/crates/esp-println)) you have to activate the feature `phy-enable-usb`. - -Don't use this feature if you are _not_ using USB-SERIAL-JTAG as it might reduce WiFi performance. - -## Tuning - -The defaults used by `esp-wifi` and the examples are rather conservative. It is possible to change a few of the important settings. - -See [Tuning](./tuning.md) for details - ## Missing / To be done - Support for non-open SoftAP diff --git a/esp-wifi/build.rs b/esp-wifi/build.rs index 44a0d222ad6..b640320c07b 100644 --- a/esp-wifi/build.rs +++ b/esp-wifi/build.rs @@ -134,7 +134,36 @@ fn main() -> Result<(), Box> { // emit config generate_config( "esp_wifi", - &[("dummy", Value::Number(44), "Tests something")], + &[ + ("rx_queue_size", Value::Number(5), "Size of the RX queue in frames"), + ("tx_queue_size", Value::Number(3), "Size of the TX queue in frames"), + ("static_rx_buf_num", Value::Number(10), "WiFi static RX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("dynamic_rx_buf_num", Value::Number(32), "WiFi dynamic RX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("static_tx_buf_num", Value::Number(0), "WiFi static TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("dynamic_tx_buf_num", Value::Number(32), "WiFi dynamic TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("ampdu_rx_enable", Value::Bool(false), "WiFi AMPDU RX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("ampdu_tx_enable", Value::Bool(false), "WiFi AMPDU TX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("amsdu_tx_enable", Value::Bool(false), "WiFi AMSDU TX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("rx_ba_win", Value::Number(6), "WiFi Block Ack RX window size. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)"), + ("max_burst_size", Value::Number(1), "See [smoltcp's documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_burst_size)"), + ( + "country_code", + Value::String("CN".to_owned()), + "Country code. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)", + ), + ( + "country_code_operating_class", + Value::Number(0), + "If not 0: Operating Class table number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)", + ), + ("mtu", Value::Number(1492), "MTU, see [smoltcp's documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_transmission_unit)"), + ("tick_rate_hz", Value::Number(100), "Tick rate of the internal task scheduler in hertz"), + ("listen_interval", Value::Number(3), "Interval for station to listen to beacon from AP. The unit of listen interval is one beacon interval. For example, if beacon interval is 100 ms and listen interval is 3, the interval for station to listen to beacon is 300 ms"), + ("beacon_timeout", Value::Number(6), "For Station, If the station does not receive a beacon frame from the connected SoftAP during the inactive time, disconnect from SoftAP. Default 6s. Range 6-30"), + ("ap_beacon_timeout", Value::Number(300), "For SoftAP, If the SoftAP doesn’t receive any data from the connected STA during inactive time, the SoftAP will force deauth the STA. Default is 300s"), + ("failure_retry_cnt", Value::Number(1), "Number of connection retries station will do before moving to next AP. scan_method should be set as WIFI_ALL_CHANNEL_SCAN to use this config. Note: Enabling this may cause connection time to increase incase best AP doesn't behave properly. Defaults to 1"), + ("scan_method", Value::Number(0), "0 = WIFI_FAST_SCAN, 1 = WIFI_ALL_CHANNEL_SCAN, defaults to 0"), + ], ); Ok(()) diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index b5f341dc400..83b4d186347 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -1,3 +1,66 @@ +//! This documentation is built for the +#![cfg_attr(esp32, doc = "**ESP32**")] +#![cfg_attr(esp32s2, doc = "**ESP32-S2**")] +#![cfg_attr(esp32s3, doc = "**ESP32-S3**")] +#![cfg_attr(esp32c2, doc = "**ESP32-C2**")] +#![cfg_attr(esp32c3, doc = "**ESP32-C3**")] +#![cfg_attr(esp32c6, doc = "**ESP32-C6**")] +#![cfg_attr(esp32h2, doc = "**ESP32-H2**")] +//! . Please ensure you are reading the correct documentation for your target +//! device. +//! +//! ## Usage +//! +//! ### Importing +//! +//! Ensure that the right features are enabled for your chip. See [Examples](https://github.com/esp-rs/esp-hal/tree/main/examples#examples) for more examples. +//! +//! ```toml +//! [dependencies.esp-wifi] +//! # A supported chip needs to be specified, as well as specific use-case features +//! features = ["esp32s3", "wifi", "esp-now"] +//! ``` +//! +//! ### Link configuration +//! +//! Make sure to include the rom functions for your target: +//! +//! ```toml +//! # .cargo/config.toml +//! rustflags = [ +//! "-C", "link-arg=-Tlinkall.x", +//! "-C", "link-arg=-Trom_functions.x", +//! ] +//! ``` +//! +//! At the time of writing, you will already have the `linkall` flag if you used +//! `cargo generate`. Generating from a template does not include the +//! `rom_functions` flag. +//! +//! ### Optimization Level +//! +//! It is necessary to build with optimization level 2 or 3 since otherwise, it +//! might not even be able to connect or advertise. +//! +//! To make it work also for your debug builds add this to your `Cargo.toml` +//! +//! ```toml +//! [profile.dev.package.esp-wifi] +//! opt-level = 3 +//! ``` +//! +//! ### Xtensa considerations +//! +//! Within this crate, `CCOMPARE0` CPU timer is used for timing, ensure that in +//! your application you are not using this CPU timer. +//! +//! ## USB-SERIAL-JTAG +//! +//! When using USB-SERIAL-JTAG (for example by selecting `jtag-serial` in [`esp-println`](https://crates.io/crates/esp-println)) you have to activate the feature `phy-enable-usb`. +//! +//! Don't use this feature if you are _not_ using USB-SERIAL-JTAG as it might +//! reduce WiFi performance. +//! //! # Features flags //! //! Note that not all features are available on every MCU. For example, `ble` @@ -8,7 +71,15 @@ //! For more information see //! [extras/esp-wifishark/README.md](../extras/esp-wifishark/README.md) #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] -#![doc = include_str!("../README.md")] +//! ## Additional configuration +//! +//! We've exposed some configuration options that don't fit into cargo +//! features. These can be set via environment variables, or via cargo's `[env]` +//! section inside `.cargo/config.toml`. Below is a table of tunable parameters +//! for this crate: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_wifi_config_table.md"))] +#![doc = ""] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] #![no_std] #![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))] @@ -26,6 +97,7 @@ extern crate alloc; mod fmt; use common_adapter::{chip_specific::phy_mem_init, init_radio_clock_control, RADIO_CLOCKS}; +use esp_config::*; use esp_hal as hal; #[cfg(not(feature = "esp32"))] use esp_hal::timer::systimer::Alarm; @@ -74,67 +146,59 @@ pub fn current_millis() -> u64 { ticks_to_millis(get_systimer_count()) } -#[allow(unused)] -#[cfg(debug_assertions)] -const DEFAULT_TICK_RATE_HZ: u32 = 50; - -#[allow(unused)] -#[cfg(not(debug_assertions))] -const DEFAULT_TICK_RATE_HZ: u32 = 100; - #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[toml_cfg::toml_config] /// Tunable parameters for the WiFi driver +#[allow(unused)] // currently there are no ble tunables struct Config { - #[default(5)] rx_queue_size: usize, - #[default(3)] tx_queue_size: usize, - #[default(10)] static_rx_buf_num: usize, - #[default(32)] dynamic_rx_buf_num: usize, - #[default(0)] static_tx_buf_num: usize, - #[default(32)] dynamic_tx_buf_num: usize, - #[default(0)] - ampdu_rx_enable: usize, - #[default(0)] - ampdu_tx_enable: usize, - #[default(0)] - amsdu_tx_enable: usize, - #[default(6)] + ampdu_rx_enable: bool, + ampdu_tx_enable: bool, + amsdu_tx_enable: bool, rx_ba_win: usize, - #[default(1)] max_burst_size: usize, - #[default("CN")] country_code: &'static str, - #[default(0)] country_code_operating_class: u8, - #[default(1492)] mtu: usize, - #[default(DEFAULT_TICK_RATE_HZ)] tick_rate_hz: u32, - #[default(3)] listen_interval: u16, - #[default(6)] beacon_timeout: u16, - #[default(300)] ap_beacon_timeout: u16, - #[default(1)] failure_retry_cnt: u8, - #[default(0)] scan_method: u32, } +pub(crate) const CONFIG: Config = Config { + rx_queue_size: esp_config_int!(usize, "ESP_WIFI_RX_QUEUE_SIZE"), + tx_queue_size: esp_config_int!(usize, "ESP_WIFI_TX_QUEUE_SIZE"), + static_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_STATIC_RX_BUF_NUM"), + dynamic_rx_buf_num: esp_config_int!(usize, "ESP_WIFI_DYNAMIC_RX_BUF_NUM"), + static_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_STATIC_TX_BUF_NUM"), + dynamic_tx_buf_num: esp_config_int!(usize, "ESP_WIFI_DYNAMIC_TX_BUF_NUM"), + ampdu_rx_enable: esp_config_bool!("ESP_WIFI_AMPDU_RX_ENABLE"), + ampdu_tx_enable: esp_config_bool!("ESP_WIFI_AMPDU_TX_ENABLE"), + amsdu_tx_enable: esp_config_bool!("ESP_WIFI_AMSDU_TX_ENABLE"), + rx_ba_win: esp_config_int!(usize, "ESP_WIFI_RX_BA_WIN"), + max_burst_size: esp_config_int!(usize, "ESP_WIFI_MAX_BURST_SIZE"), + country_code: esp_config_str!("ESP_WIFI_COUNTRY_CODE"), + country_code_operating_class: esp_config_int!(u8, "ESP_WIFI_COUNTRY_CODE_OPERATING_CLASS"), + mtu: esp_config_int!(usize, "ESP_WIFI_MTU"), + tick_rate_hz: esp_config_int!(u32, "ESP_WIFI_TICK_RATE_HZ"), + listen_interval: esp_config_int!(u16, "ESP_WIFI_LISTEN_INTERVAL"), + beacon_timeout: esp_config_int!(u16, "ESP_WIFI_BEACON_TIMEOUT"), + ap_beacon_timeout: esp_config_int!(u16, "ESP_WIFI_AP_BEACON_TIMEOUT"), + failure_retry_cnt: esp_config_int!(u8, "ESP_WIFI_FAILURE_RETRY_CNT"), + scan_method: esp_config_int!(u32, "ESP_WIFI_SCAN_METHOD"), +}; + // Validate the configuration at compile time #[allow(clippy::assertions_on_constants)] const _: () = { - use esp_config::esp_config_int; - const TEST: usize = esp_config_int!(usize, "ESP_WIFI_DUMMY"); - core::assert!(TEST == 666); // We explicitely use `core` assert here because this evaluation happens at // compile time and won't bloat the binary core::assert!( diff --git a/esp-wifi/tuning.md b/esp-wifi/tuning.md deleted file mode 100644 index ec80d27e408..00000000000 --- a/esp-wifi/tuning.md +++ /dev/null @@ -1,56 +0,0 @@ -# Tuning the configuration - -You can change a few of the default settings used. Please keep in mind that it's almost always a tradeoff between memory usage and performance. -It's easy to decrease performance by using unfortunate settings or even break the application or making it less stable. - -So you should test every change very carefully. - -Be aware that settings which work fine on one ESP32 model might not work at all on other. Also, settings should be adjusted according to your application's needs. - -## Create a configuration - -We use [toml-cfg](https://crates.io/crates/toml-cfg) for the build time configuration - -You need to add a `cfg.toml` file in the root of your binary crate. When using a _Cargo Workspace_ you should put the file in the root of the workspace directory. - -A configuration file can look like this: -```toml -[esp-wifi] -rx_queue_size = 15 -tx_queue_size = 3 -static_rx_buf_num = 10 -dynamic_rx_buf_num = 16 -ampdu_rx_enable = 0 -ampdu_tx_enable = 0 -rx_ba_win = 32 -max_burst_size = 6 -``` - -You can set the following settings -|Key|Description| -|-|-| -|rx_queue_size|Size of the RX queue in frames| -|tx_queue_size|Size of the TX queue in frames| -|max_burst_size|See [documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_burst_size)| -|static_rx_buf_num|WiFi static RX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|dynamic_rx_buf_num|WiFi dynamic RX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|static_tx_buf_num|WiFi static TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|dynamic_tx_buf_num|WiFi dynamic TX buffer number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|ampdu_rx_enable|WiFi AMPDU RX feature enable flag. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|ampdu_rx_enable|WiFi AMPDU RX feature enable flag. (0 or 1) See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|ampdu_tx_enable|WiFi AMPDU TX feature enable flag. (0 or 1) See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|amsdu_tx_enable|WiFi AMSDU TX feature enable flag. (0 or 1) See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|rx_ba_win|WiFi Block Ack RX window size. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv418wifi_init_config_t)| -|country_code|Country code. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)| -|country_code_operating_class|If not 0: Operating Class table number. See [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-country-code)| -|mtu|MTU, see [documentation](https://docs.rs/smoltcp/0.10.0/smoltcp/phy/struct.DeviceCapabilities.html#structfield.max_transmission_unit)| -|tick_rate_hz|Tick rate of the internal task scheduler in hertz.| -|listen_interval|Interval for station to listen to beacon from AP. The unit of listen interval is one beacon interval. For example, if beacon interval is 100 ms and listen interval is 3, the interval for station to listen to beacon is 300 ms| -|beacon_timeout|For Station, If the station does not receive a beacon frame from the connected SoftAP during the inactive time, disconnect from SoftAP. Default 6s. Range 6-30| -|ap_beacon_timeout|For SoftAP, If the SoftAP doesn’t receive any data from the connected STA during inactive time, the SoftAP will force deauth the STA. Default is 300s.| -|failure_retry_cnt|Number of connection retries station will do before moving to next AP. scan_method should be set as WIFI_ALL_CHANNEL_SCAN to use this config. Note: Enabling this may cause connection time to increase incase best AP doesn't behave properly. Defaults to 1| -|scan_method|0 = WIFI_FAST_SCAN, 1 = WIFI_ALL_CHANNEL_SCAN, defaults to 0| - -## Globally disable logging - -`esp-wifi` contains a lot of trace-level logging statements. For maximum performance you might want to disable logging via a feature flag of the `log` crate. See [documentation](https://docs.rs/log/0.4.19/log/#compile-time-filters). You should set it to `release_max_level_off` From b28e291de44f99e97f1a1f16ea4d22e4bc393148 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 15:24:06 +0100 Subject: [PATCH 05/22] repo maint --- esp-config/Cargo.toml | 1 + esp-config/README.md | 29 +++++++++++++++++++++++++++++ esp-hal/README.md | 2 +- esp-wifi/README.md | 2 +- xtask/src/lib.rs | 1 + 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 esp-config/README.md diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index afa1609f50a..16407e408f3 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -2,6 +2,7 @@ name = "esp-config" version = "0.1.0" edition = "2021" +# rust-version = "1.82.0" [dependencies] diff --git a/esp-config/README.md b/esp-config/README.md new file mode 100644 index 00000000000..402a1f358ec --- /dev/null +++ b/esp-config/README.md @@ -0,0 +1,29 @@ +# esp-config + +[![Crates.io](https://img.shields.io/crates/v/esp-config?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-config) +[![docs.rs](https://img.shields.io/docsrs/esp-config?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-config) +![MSRV](https://img.shields.io/badge/MSRV-1.82-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-config?labelColor=1C2C2E&style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +## [Documentation](https://docs.rs/crate/esp-config) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.82 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-hal/README.md b/esp-hal/README.md index 0bac1874fdf..063fba169e4 100644 --- a/esp-hal/README.md +++ b/esp-hal/README.md @@ -48,7 +48,7 @@ For help getting started with this HAL, please refer to [The Rust on ESP Book] a ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.76 and up. It _might_ +This crate is guaranteed to compile on stable Rust 1.77 and up. It _might_ compile with older versions but that may change in any new patch release. ## License diff --git a/esp-wifi/README.md b/esp-wifi/README.md index 55bb25becda..a61ca294e41 100644 --- a/esp-wifi/README.md +++ b/esp-wifi/README.md @@ -22,7 +22,7 @@ If a cell contains an em dash (—) this means that the particular feature i | ESP32-S2 | ✓ | — | — | ✓ | | ESP32-S3 | ✓ | ✓ | ✓ | ✓ | -Minimum supported Rust compiler version: 1.72.0.0 +Minimum supported Rust compiler version: 1.77.0 ## Missing / To be done diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index ae8a397bac8..1378b70c684 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -36,6 +36,7 @@ pub enum Package { EspAlloc, EspBacktrace, EspBuild, + EspConfig, EspHal, EspHalEmbassy, EspHalProcmacros, From fc9dc715b2d6850b1a63b5702a55a97eac5e938e Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 22:04:39 +0100 Subject: [PATCH 06/22] make bool parsing stricter and number parsing more flexible --- esp-config/src/generate.rs | 55 ++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index eb78891950d..1aa68ebcacf 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -2,12 +2,12 @@ use core::fmt::Write; use std::{collections::HashMap, env, fs, path::PathBuf}; const TABLE_HEADER: &str = r#" -| name | description | default value | +| Name | Description | Default value | |------|-------------|---------------| "#; #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct ParseError; +pub struct ParseError(&'static str); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { @@ -20,10 +20,19 @@ impl Value { fn parse(&mut self, s: &str) -> Result<(), ParseError> { *self = match self { Value::Bool(_) => match s { - "false" => Value::Bool(false), - _ => Value::Bool(true), // TODO should existance of the key mean true? + "false" | "no" | "n" => Value::Bool(false), + "true" | "yes" | "y" => Value::Bool(true), + _ => return Err(ParseError("Invalid boolean value")), }, - Value::Number(_) => Value::Number(s.parse().map_err(|_| ParseError)?), + Value::Number(_) => Value::Number( + match s.as_bytes() { + [b'0', b'x', ..] => usize::from_str_radix(&s[2..], 16), + [b'0', b'o', ..] => usize::from_str_radix(&s[2..], 8), + [b'0', b'b', ..] => usize::from_str_radix(&s[2..], 2), + _ => usize::from_str_radix(&s, 10), + } + .map_err(|_| ParseError("Invalid numerical value"))?, + ), Value::String(_) => Value::String(String::from(s)), }; Ok(()) @@ -136,3 +145,39 @@ fn screaming_snake_case(name: &str) -> String { name.make_ascii_uppercase(); name } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn value_number_formats() { + const INPUTS: &[&str] = &["0xAA", "0o252", "0b0000000010101010", "170"]; + let mut v = Value::Number(0); + + for input in INPUTS { + v.parse(input).unwrap(); + // no matter the input format, the output format should be decimal + assert_eq!(v.as_string(), "170"); + } + } + + #[test] + fn value_bool_inputs() { + const TRUE_INPUTS: &[&str] = &["true", "y", "yes"]; + const FALSE_INPUTS: &[&str] = &["false", "n", "no"]; + let mut v = Value::Bool(false); + + for input in TRUE_INPUTS { + v.parse(input).unwrap(); + // no matter the input variant, the output format should be "true" + assert_eq!(v.as_string(), "true"); + } + + for input in FALSE_INPUTS { + v.parse(input).unwrap(); + // no matter the input variant, the output format should be "false" + assert_eq!(v.as_string(), "false"); + } + } +} From 915e542621b66eb2ac9ad7350f35aa38258c104f Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 22:23:48 +0100 Subject: [PATCH 07/22] use hand rolled const str to int --- esp-config/Cargo.toml | 2 +- esp-config/README.md | 4 ++-- esp-config/src/lib.rs | 15 +++++++++++---- esp-wifi/src/lib.rs | 1 - 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index 16407e408f3..d63e892b17a 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -2,7 +2,7 @@ name = "esp-config" version = "0.1.0" edition = "2021" -# rust-version = "1.82.0" +rust-version = "1.77.0" [dependencies] diff --git a/esp-config/README.md b/esp-config/README.md index 402a1f358ec..87c7181b0d0 100644 --- a/esp-config/README.md +++ b/esp-config/README.md @@ -2,7 +2,7 @@ [![Crates.io](https://img.shields.io/crates/v/esp-config?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-config) [![docs.rs](https://img.shields.io/docsrs/esp-config?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-config) -![MSRV](https://img.shields.io/badge/MSRV-1.82-blue?labelColor=1C2C2E&style=flat-square) +![MSRV](https://img.shields.io/badge/MSRV-1.77-blue?labelColor=1C2C2E&style=flat-square) ![Crates.io](https://img.shields.io/crates/l/esp-config?labelColor=1C2C2E&style=flat-square) [![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) @@ -10,7 +10,7 @@ ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.82 and up. It _might_ +This crate is guaranteed to compile on stable Rust 1.77 and up. It _might_ compile with older versions but that may change in any new patch release. ## License diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index 019ccfdc150..0329f8b2950 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -6,12 +6,19 @@ mod generate; pub use generate::*; #[macro_export] +// TODO from 1.82 we can use <$ty>::from_str_radix(env!($var), 10) instead macro_rules! esp_config_int { ($ty:ty, $var:expr) => { - match <$ty>::from_str_radix(env!($var), 10) { - Ok(val) => val, - _ => unreachable!(), - }; + const { + let mut bytes = env!($var).as_bytes(); + let mut val: $ty = 0; + while let [byte, rest @ ..] = bytes { + core::assert!(b'0' <= *byte && *byte <= b'9', "invalid digit"); + val = val * 10 + (*byte - b'0') as $ty; + bytes = rest; + } + val + } }; } diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index 83b4d186347..13057623da7 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -89,7 +89,6 @@ // toolchain doesn't know about that lint, yet) #![allow(unknown_lints)] #![allow(non_local_definitions)] -#![feature(const_int_from_str)] // stable in 1.82 extern crate alloc; From bea819a3d3830a5d6a4a4b7a1be189d0f51cd685 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 22:33:30 +0100 Subject: [PATCH 08/22] Collect unknown config options --- esp-config/src/generate.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 1aa68ebcacf..ac4e770ad91 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -83,15 +83,15 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { println!("cargo:rerun-if-env-changed={name}"); } + let mut unknown = Vec::new(); + // Try and capture input from the environment for (var, value) in env::vars() { if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { let name = snake_case(name); - // TODO should this be lowered to a warning for unknown configuration options? - // we could instead mark this as unknown and _not_ emit - // println!("cargo:rustc-check-cfg=cfg({})", name); let Some(cfg) = configs.get_mut(&name) else { - panic!("Unknown env var {name}") + unknown.push(format!("{prefix}_{}", screaming_snake_case(&name))); + continue; }; cfg.parse(&value) @@ -99,6 +99,10 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { } } + if !unknown.is_empty() { + panic!("Unknown configuration options detected: {:?}", unknown); + } + // emit cfgs and set envs for (name, value) in configs.into_iter() { println!("cargo:rustc-check-cfg=cfg({})", name); From c48f26f4eb2ddf1e9407a1434b93a5746a4b914f Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 22:44:59 +0100 Subject: [PATCH 09/22] friendly errors --- esp-config/src/generate.rs | 8 ++++---- esp-config/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index ac4e770ad91..b1e609c59cf 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -6,8 +6,8 @@ const TABLE_HEADER: &str = r#" |------|-------------|---------------| "#; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct ParseError(&'static str); +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ParseError(String); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { @@ -22,7 +22,7 @@ impl Value { Value::Bool(_) => match s { "false" | "no" | "n" => Value::Bool(false), "true" | "yes" | "y" => Value::Bool(true), - _ => return Err(ParseError("Invalid boolean value")), + _ => return Err(ParseError(format!("Invalid boolean value: {}", s))), }, Value::Number(_) => Value::Number( match s.as_bytes() { @@ -31,7 +31,7 @@ impl Value { [b'0', b'b', ..] => usize::from_str_radix(&s[2..], 2), _ => usize::from_str_radix(&s, 10), } - .map_err(|_| ParseError("Invalid numerical value"))?, + .map_err(|_| ParseError(format!("Invalid numerical value: {}", s)))?, ), Value::String(_) => Value::String(String::from(s)), }; diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index 0329f8b2950..9c0224155d1 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -13,7 +13,7 @@ macro_rules! esp_config_int { let mut bytes = env!($var).as_bytes(); let mut val: $ty = 0; while let [byte, rest @ ..] = bytes { - core::assert!(b'0' <= *byte && *byte <= b'9', "invalid digit"); + ::core::assert!(b'0' <= *byte && *byte <= b'9', "invalid digit"); val = val * 10 + (*byte - b'0') as $ty; bytes = rest; } From ad302760f02a3708cfe6509449257dfd73fc43c5 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Fri, 13 Sep 2024 23:02:31 +0100 Subject: [PATCH 10/22] also batch invalid values --- esp-config/src/generate.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index b1e609c59cf..774cd7ac884 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -84,6 +84,7 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { } let mut unknown = Vec::new(); + let mut failed = Vec::new(); // Try and capture input from the environment for (var, value) in env::vars() { @@ -94,11 +95,16 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { continue; }; - cfg.parse(&value) - .expect(&format!("Invalid value for env var {name}: {value}")); + if let Err(e) = cfg.parse(&value) { + failed.push(format!("{prefix}_{}: {e:?}", screaming_snake_case(&name))); + } } } + if !failed.is_empty() { + panic!("Invalid configuration options detected: {:?}", failed); + } + if !unknown.is_empty() { panic!("Unknown configuration options detected: {:?}", unknown); } From 50b24853eec45b1db2e89da114b2e5b7de6fe9c1 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 11:18:02 +0100 Subject: [PATCH 11/22] dump msrv to 1.79 --- .github/workflows/ci.yml | 2 +- esp-config/Cargo.toml | 2 +- esp-config/README.md | 4 ++-- esp-hal/CHANGELOG.md | 2 +- esp-hal/Cargo.toml | 2 +- esp-hal/README.md | 2 +- esp-wifi/Cargo.toml | 2 +- esp-wifi/README.md | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df98d99a25c..deb3ea5a4a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ on: env: CARGO_TERM_COLOR: always GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MSRV: "1.77.0" + MSRV: "1.79.0" RUSTDOCFLAGS: -Dwarnings DEFMT_LOG: trace diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index d63e892b17a..91d1b799777 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -2,7 +2,7 @@ name = "esp-config" version = "0.1.0" edition = "2021" -rust-version = "1.77.0" +rust-version = "1.79.0" [dependencies] diff --git a/esp-config/README.md b/esp-config/README.md index 87c7181b0d0..49a923ccbbe 100644 --- a/esp-config/README.md +++ b/esp-config/README.md @@ -2,7 +2,7 @@ [![Crates.io](https://img.shields.io/crates/v/esp-config?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-config) [![docs.rs](https://img.shields.io/docsrs/esp-config?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-config) -![MSRV](https://img.shields.io/badge/MSRV-1.77-blue?labelColor=1C2C2E&style=flat-square) +![MSRV](https://img.shields.io/badge/MSRV-1.79-blue?labelColor=1C2C2E&style=flat-square) ![Crates.io](https://img.shields.io/crates/l/esp-config?labelColor=1C2C2E&style=flat-square) [![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) @@ -10,7 +10,7 @@ ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.77 and up. It _might_ +This crate is guaranteed to compile on stable Rust 1.79 and up. It _might_ compile with older versions but that may change in any new patch release. ## License diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 37c8bdbbfa6..7a4dffc22c5 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Bump MSRV to 1.77.0 (#1971) +- Bump MSRV to 1.79.0 (#1971) ### Added diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 1f6ea2a9411..cec8edd0a9b 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -2,7 +2,7 @@ name = "esp-hal" version = "0.20.1" edition = "2021" -rust-version = "1.77.0" +rust-version = "1.79.0" description = "Bare-metal HAL for Espressif devices" documentation = "https://docs.esp-rs.org/esp-hal/" repository = "https://github.com/esp-rs/esp-hal" diff --git a/esp-hal/README.md b/esp-hal/README.md index 063fba169e4..9b887d93261 100644 --- a/esp-hal/README.md +++ b/esp-hal/README.md @@ -48,7 +48,7 @@ For help getting started with this HAL, please refer to [The Rust on ESP Book] a ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.77 and up. It _might_ +This crate is guaranteed to compile on stable Rust 1.79 and up. It _might_ compile with older versions but that may change in any new patch release. ## License diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index 49c03b156c3..1c3e8822032 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -2,7 +2,7 @@ name = "esp-wifi" version = "0.9.1" edition = "2021" -rust-version = "1.77.0" +rust-version = "1.79.0" authors = ["The ESP-RS team"] description = "A WiFi, Bluetooth and ESP-NOW driver for use with Espressif chips and bare-metal Rust" repository = "https://github.com/esp-rs/esp-hal" diff --git a/esp-wifi/README.md b/esp-wifi/README.md index a61ca294e41..3b14850b6f9 100644 --- a/esp-wifi/README.md +++ b/esp-wifi/README.md @@ -22,7 +22,7 @@ If a cell contains an em dash (—) this means that the particular feature i | ESP32-S2 | ✓ | — | — | ✓ | | ESP32-S3 | ✓ | ✓ | ✓ | ✓ | -Minimum supported Rust compiler version: 1.77.0 +Minimum supported Rust compiler version: 1.79.0 ## Missing / To be done From 82bb3855361b143df96be07df04406ecf59bc5da Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 11:21:22 +0100 Subject: [PATCH 12/22] Mention perf boost from disabling logging --- esp-wifi/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index 13057623da7..91aee3e2199 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -48,6 +48,12 @@ //! [profile.dev.package.esp-wifi] //! opt-level = 3 //! ``` +//! ## Globally disable logging +//! +//! `esp-wifi` contains a lot of trace-level logging statements. +//! For maximum performance you might want to disable logging via +//! a feature flag of the `log` crate. See [documentation](https://docs.rs/log/0.4.19/log/#compile-time-filters). +//! You should set it to `release_max_level_off`. //! //! ### Xtensa considerations //! From 864c9ce0142315b107651fac35a3cc64fb9a5e70 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 11:22:32 +0100 Subject: [PATCH 13/22] review suggestions --- esp-config/src/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 774cd7ac884..6cf1024da08 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -113,7 +113,7 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { for (name, value) in configs.into_iter() { println!("cargo:rustc-check-cfg=cfg({})", name); match value { - Value::Bool(val) if val == true => println!("cargo:rustc-cfg={name}"), + Value::Bool(true) => println!("cargo:rustc-cfg={name}"), _ => {} } From 3153f895bb8faacdd31801e8770db27fa4227347 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 11:39:46 +0100 Subject: [PATCH 14/22] output selected config --- esp-config/src/generate.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 6cf1024da08..7874103eb2b 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -1,10 +1,14 @@ use core::fmt::Write; use std::{collections::HashMap, env, fs, path::PathBuf}; -const TABLE_HEADER: &str = r#" +const DOC_TABLE_HEADER: &str = r#" | Name | Description | Default value | |------|-------------|---------------| "#; +const CHOSEN_TABLE_HEADER: &str = r#" +| Name | Chosen value | +|------|--------------| +"#; #[derive(Clone, Debug, PartialEq, Eq)] pub struct ParseError(String); @@ -62,7 +66,7 @@ impl Value { pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { let mut prefix = screaming_snake_case(prefix); - let mut doc_table = String::from(TABLE_HEADER); + let mut doc_table = String::from(DOC_TABLE_HEADER); for (key, default, desc) in config { let key = screaming_snake_case(key); let default = default.as_string(); @@ -125,6 +129,13 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { ); } + let mut selected_config = String::from(CHOSEN_TABLE_HEADER); + for (key, value, _) in config { + let key = screaming_snake_case(key); + let value = value.as_string(); + writeln!(selected_config, "|**{prefix}_{key}**|{value}|").unwrap(); + } + // convert to snake case prefix.make_ascii_lowercase(); @@ -134,6 +145,13 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { .to_string_lossy() .to_string(); fs::write(out_file, doc_table).unwrap(); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir + .join(format!("{prefix}_selected_config.md")) + .to_string_lossy() + .to_string(); + fs::write(out_file, selected_config).unwrap(); } // Converts a symbol name like From aa01e9cfdd997fe98b610d0330427a9a1658abce Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 12:16:55 +0100 Subject: [PATCH 15/22] changelogs and migration guides --- esp-config/README.md | 26 ++++++++++++++++++++++++++ esp-config/src/lib.rs | 1 + esp-hal/CHANGELOG.md | 2 ++ esp-hal/MIGRATING-0.20.md | 11 +++++++++++ esp-wifi/CHANGELOG.md | 1 + esp-wifi/MIGRATING-0.9.md | 11 +++++++++++ 6 files changed, 52 insertions(+) diff --git a/esp-config/README.md b/esp-config/README.md index 49a923ccbbe..d0f1f2553cc 100644 --- a/esp-config/README.md +++ b/esp-config/README.md @@ -8,6 +8,32 @@ ## [Documentation](https://docs.rs/crate/esp-config) +## Usage + +`esp-config` takes a prefix (usually the crate name) and a set of configuration keys and default values to produce a configuration system that supports: + +- Emitting rustc cfg's for boolean keys +- Emitting environment variables for numbers + - Along with decimal parsing, it supports Hex, Octal and Binary with the respective `0x`, `0o` and `0b` prefixes. +- Emitting environment variables string values + +### Viewing the configuration + +The possible configuration values are output as a markdown table in the crates `OUT_DIR` with the format `{prefix}_config_table.md`, this can then be included into the crates top level documentation. Here is an example of the output: + + +| Name | Description | Default value | +|------|-------------|---------------| +|**ESP_HAL_PLACE_SPI_DRIVER_IN_RAM**|Places the SPI driver in RAM for better performance|false| + +### Setting configuration options + +For any available configuration option, the environment variable or cfg is _always_ set based on the default value specified in the table. Users can override this by setting environment variables locally in their shell _or_ the preferred option is to utilize cargo's [`env` section](https://doc.rust-lang.org/cargo/reference/config.html#env). + +It's important to note that due to a [bug in cargo](https://github.com/rust-lang/cargo/issues/10358), any modifications to the environment, local or otherwise will only get picked up on a full clean build of the project. + +To see the final selected configuration another table is output to the `OUT_DIR` with the format `{prefix}_selected_config.md`. + ## Minimum Supported Rust Version (MSRV) This crate is guaranteed to compile on stable Rust 1.79 and up. It _might_ diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index 9c0224155d1..a8e2cced505 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] #[cfg(feature = "std")] mod generate; diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 7a4dffc22c5..7e9bd08a202 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed the parameters of `Spi::with_pins` to no longer be optional (#2133) - Renamed `DummyPin` to `NoPin` and removed all internal logic from it. (#2133) - The `NO_PIN` constant has been removed. (#2133) +- MSRV bump to 1.79 (#2156) ### Fixed @@ -70,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed `_with_default_pins` UART constructors (#2132) - Removed `uart::{DefaultRxPin, DefaultTxPin}` (#2132) - Removed `PcntSource` and `PcntInputConfig`. (#2134) +- Removed the `place-spi-driver-in-ram` feature, this is now enabled via [esp-config](https://docs.rs/esp-config) (#2156) ## [0.20.1] - 2024-08-30 diff --git a/esp-hal/MIGRATING-0.20.md b/esp-hal/MIGRATING-0.20.md index 866a74c97ca..4f19c3f4815 100644 --- a/esp-hal/MIGRATING-0.20.md +++ b/esp-hal/MIGRATING-0.20.md @@ -198,3 +198,14 @@ let mut i8080 = I8080::new(....); - i8080.send(0x12, 0, &[0, 1, 2, 3, 4]); + i8080.send(0x12u8, 0, &[0, 1, 2, 3, 4]); ``` + +### Placing drivers in RAM is now done via esp-config + +We've replaced some usage of features with [esp-config](https://docs.rs/esp-config). Please remove any reference to `place-spi-driver-in-ram` in your `Cargo.toml` and migrate to the `[env]` section of `.cargo/config.toml`. + +```diff +# feature in Cargo.toml +- esp-hal = { version = "0.20", features = ["place-spi-driver-in-ram"] } +# key in .cargo/config.toml [env] section ++ ESP_HAL_PLACE_SPI_DRIVER_IN_RAM=true +``` diff --git a/esp-wifi/CHANGELOG.md b/esp-wifi/CHANGELOG.md index 26d84fc3351..8c406f68f52 100644 --- a/esp-wifi/CHANGELOG.md +++ b/esp-wifi/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Removed the `clocks` parameter from `esp_wifi::initialize` (#1999) +- `cfg_toml` configuration system has been removed in favour of [esp-config](https://docs.rs/esp-config) (#2156) ## 0.9.1 - 2024-09-03 diff --git a/esp-wifi/MIGRATING-0.9.md b/esp-wifi/MIGRATING-0.9.md index 520ed67219f..d88da420af8 100644 --- a/esp-wifi/MIGRATING-0.9.md +++ b/esp-wifi/MIGRATING-0.9.md @@ -91,3 +91,14 @@ pub extern "C" fn esp_wifi_allocate_from_internal_ram(size: usize) -> *mut u8 { ``` It's important to allocate from internal memory (i.e. not PSRAM) + +### Tunable parameters are now set via esp-config + +We've replaced usage of `cfg_toml` with [esp-config](https://docs.rs/esp-config). Please remove any esp-wifi entries from `cfg.toml` and migrate the key value pairs to the `[env]` section of `.cargo/config.toml`. + +```diff +# key in cfg.toml +- rx_queue_size = 40 +# key in .cargo/config.toml [env] section ++ ESP_WIFI_RX_QUEUE_SIZE=40 +``` \ No newline at end of file From 22231a7584db7d9051cbc61de33a5b18e2cd3837 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 13:41:52 +0100 Subject: [PATCH 16/22] review feedback --- esp-config/Cargo.toml | 4 +++- esp-config/README.md | 10 ++++++++++ esp-config/src/generate.rs | 15 +++++++++------ esp-config/src/lib.rs | 9 ++++++--- esp-hal/src/lib.rs | 4 ++++ esp-wifi/src/lib.rs | 4 ++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index 91d1b799777..b8fc6c3e867 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" rust-version = "1.79.0" [dependencies] +document-features = "0.2.10" [features] -std = [] \ No newline at end of file +## Enable the generation and parsing of a config +build = [] \ No newline at end of file diff --git a/esp-config/README.md b/esp-config/README.md index d0f1f2553cc..e5881392c2d 100644 --- a/esp-config/README.md +++ b/esp-config/README.md @@ -34,6 +34,16 @@ It's important to note that due to a [bug in cargo](https://github.com/rust-lang To see the final selected configuration another table is output to the `OUT_DIR` with the format `{prefix}_selected_config.md`. +### Capturing configuration values in the downstream crate + +For all supported data types, there are helper macros that emit `const` code for parsing the configuration values. + +- Numbers - `esp_config_int!(integer_type, "ENV")` +- Strings - `esp_config_str!("ENV")` +- Bool - `esp_config_bool!("ENV")` + +In addition to environment variables, for boolean types rust `cfg`'s are emitted in snake case _without_ the prefix. + ## Minimum Supported Rust Version (MSRV) This crate is guaranteed to compile on stable Rust 1.79 and up. It _might_ diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 7874103eb2b..279f2ec5ec0 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -21,7 +21,7 @@ pub enum Value { } impl Value { - fn parse(&mut self, s: &str) -> Result<(), ParseError> { + fn parse_in_place(&mut self, s: &str) -> Result<(), ParseError> { *self = match self { Value::Bool(_) => match s { "false" | "no" | "n" => Value::Bool(false), @@ -51,13 +51,16 @@ impl Value { } } -/// This function will parse any screaming snake case environment variables that -/// match the given prefix. It will then attempt to parse the [`Value`]. Once -/// the config has been parsed, this function will emit snake case cfg's +/// Generate and parse config from a prefix, and array of key, default, +/// description tuples. +/// +/// This function will parse any `SCREAMING_SNAKE_CASE` environment variables +/// that match the given prefix. It will then attempt to parse the [`Value`]. +/// Once the config has been parsed, this function will emit `snake_case` cfg's /// _without_ the prefix which can be used in the dependant crate. After that, /// it will create a markdown table in the `OUT_DIR` under the name /// `{prefix}_config_table.md` where prefix has also been converted -/// to snake case. This can be included in crate documentation to outline the +/// to `snake_case`. This can be included in crate documentation to outline the /// available configuration options for the crate. /// /// The tuple ordering for the`config` array is key, default, description. @@ -99,7 +102,7 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { continue; }; - if let Err(e) = cfg.parse(&value) { + if let Err(e) = cfg.parse_in_place(&value) { failed.push(format!("{prefix}_{}: {e:?}", screaming_snake_case(&name))); } } diff --git a/esp-config/src/lib.rs b/esp-config/src/lib.rs index a8e2cced505..34ef2f3f3c5 100644 --- a/esp-config/src/lib.rs +++ b/esp-config/src/lib.rs @@ -1,9 +1,12 @@ -#![cfg_attr(not(feature = "std"), no_std)] #![doc = include_str!("../README.md")] +//! ## Feature Flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] +#![cfg_attr(not(feature = "build"), no_std)] -#[cfg(feature = "std")] +#[cfg(feature = "build")] mod generate; -#[cfg(feature = "std")] +#[cfg(feature = "build")] pub use generate::*; #[macro_export] diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs index c010207da67..a413c56db37 100644 --- a/esp-hal/src/lib.rs +++ b/esp-hal/src/lib.rs @@ -97,6 +97,10 @@ #![doc = ""] #![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_config_table.md"))] #![doc = ""] +//! It's important to note that due to a [bug in cargo](https://github.com/rust-lang/cargo/issues/10358), +//! any modifications to the environment, local or otherwise will only get +//! picked up on a full clean build of the project. +//! //! ## `Peripheral` Pattern //! //! Drivers take pins and peripherals as [peripheral::Peripheral] in most diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index 91aee3e2199..08e21c6a572 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -86,6 +86,10 @@ #![doc = ""] #![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_wifi_config_table.md"))] #![doc = ""] +//! It's important to note that due to a [bug in cargo](https://github.com/rust-lang/cargo/issues/10358), +//! any modifications to the environment, local or otherwise will only get +//! picked up on a full clean build of the project. + #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] #![no_std] #![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))] From 27152b0431eb978f72935fcb3ad59dbb4ba25a35 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Mon, 16 Sep 2024 14:06:32 +0100 Subject: [PATCH 17/22] avoid multiple case conversions where possible --- esp-config/src/generate.rs | 45 +++++++++++++++++++------------------- esp-hal/Cargo.toml | 2 +- esp-wifi/Cargo.toml | 2 +- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 279f2ec5ec0..1a7b55efc54 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -67,14 +67,10 @@ impl Value { /// /// Unknown keys with the supplied prefix will cause this function to panic. pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { + // ensure that the prefix is `SCREAMING_SNAKE_CASE` let mut prefix = screaming_snake_case(prefix); let mut doc_table = String::from(DOC_TABLE_HEADER); - for (key, default, desc) in config { - let key = screaming_snake_case(key); - let default = default.as_string(); - writeln!(doc_table, "|**{prefix}_{key}**|{desc}|{default}|").unwrap(); - } // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any // other file changed. @@ -82,8 +78,14 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { // Generate the template for the config let mut configs = HashMap::new(); - for (name, default, _) in config { - configs.insert(snake_case(name), default.clone()); + for (name, default, desc) in config { + let name = format!("{prefix}_{}", screaming_snake_case(&name)); + // insert into config + configs.insert(name.clone(), default.clone()); + + // write doc table line + let default = default.as_string(); + writeln!(doc_table, "|**{name}**|{desc}|{default}|").unwrap(); // Rebuild if config envvar changed. // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 @@ -98,12 +100,12 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { let name = snake_case(name); let Some(cfg) = configs.get_mut(&name) else { - unknown.push(format!("{prefix}_{}", screaming_snake_case(&name))); + unknown.push(var); continue; }; if let Err(e) = cfg.parse_in_place(&value) { - failed.push(format!("{prefix}_{}: {e:?}", screaming_snake_case(&name))); + failed.push(format!("{}: {e:?}", var)); } } } @@ -116,30 +118,27 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { panic!("Unknown configuration options detected: {:?}", unknown); } + let mut selected_config = String::from(CHOSEN_TABLE_HEADER); + // emit cfgs and set envs for (name, value) in configs.into_iter() { - println!("cargo:rustc-check-cfg=cfg({})", name); + let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_"))); + println!("cargo:rustc-check-cfg=cfg({cfg_name})"); match value { - Value::Bool(true) => println!("cargo:rustc-cfg={name}"), + Value::Bool(true) => { + println!("cargo:rustc-cfg={cfg_name}") + } _ => {} } + let value = value.as_string(); // values that haven't been seen will be output here with the default value - println!( - "cargo:rustc-env={}={}", - format!("{prefix}_{}", screaming_snake_case(&name)), - value.as_string() - ); - } + println!("cargo:rustc-env={}={}", name, value); - let mut selected_config = String::from(CHOSEN_TABLE_HEADER); - for (key, value, _) in config { - let key = screaming_snake_case(key); - let value = value.as_string(); - writeln!(selected_config, "|**{prefix}_{key}**|{value}|").unwrap(); + writeln!(selected_config, "|**{name}**|{value}|").unwrap(); } - // convert to snake case + // convert to snake case for the file name prefix.make_ascii_lowercase(); let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index cec8edd0a9b..cbde3477652 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -74,7 +74,7 @@ basic-toml = "0.1.9" cfg-if = "1.0.0" esp-build = { version = "0.1.0", path = "../esp-build" } esp-metadata = { version = "0.3.0", path = "../esp-metadata" } -esp-config = { version = "0.1.0", path = "../esp-config", features = ["std"] } +esp-config = { version = "0.1.0", path = "../esp-config", features = ["build"] } serde = { version = "1.0.209", features = ["derive"] } [features] diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index 1c3e8822032..402e7cbe323 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -57,7 +57,7 @@ esp-config = { version = "0.1.0", path = "../esp-config" } [build-dependencies] toml-cfg = "0.2.0" esp-build = { version = "0.1.0", path = "../esp-build" } -esp-config = { version = "0.1.0", path = "../esp-config", features = ["std"] } +esp-config = { version = "0.1.0", path = "../esp-config", features = ["build"] } esp-metadata = { version = "0.3.0", path = "../esp-metadata" } [features] From 71a8aa14f0215f0a9f82fcefa3b217f3209927e1 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Tue, 17 Sep 2024 10:51:32 +0100 Subject: [PATCH 18/22] refactor generate, fix bug, add full test --- esp-config/src/generate.rs | 159 +++++++++++++++++++++++++++---------- esp-hal/build.rs | 1 + esp-wifi/build.rs | 1 + 3 files changed, 117 insertions(+), 44 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 1a7b55efc54..972b92e2cb3 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -6,8 +6,8 @@ const DOC_TABLE_HEADER: &str = r#" |------|-------------|---------------| "#; const CHOSEN_TABLE_HEADER: &str = r#" -| Name | Chosen value | -|------|--------------| +| Name | Selected value | +|------|----------------| "#; #[derive(Clone, Debug, PartialEq, Eq)] @@ -63,43 +63,70 @@ impl Value { /// to `snake_case`. This can be included in crate documentation to outline the /// available configuration options for the crate. /// -/// The tuple ordering for the`config` array is key, default, description. +/// Passing a value of true for the `emit_md_tables` argument will create and +/// write markdown files of the available configuration and selected +/// configuration which can be included in documentation. /// /// Unknown keys with the supplied prefix will cause this function to panic. -pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { +pub fn generate_config( + prefix: &str, + config: &[(&str, Value, &str)], + emit_md_tables: bool, +) -> HashMap { + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + // ensure that the prefix is `SCREAMING_SNAKE_CASE` let mut prefix = screaming_snake_case(prefix); - let mut doc_table = String::from(DOC_TABLE_HEADER); + let mut selected_config = String::from(CHOSEN_TABLE_HEADER); - // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any - // other file changed. - println!("cargo:rerun-if-changed=build.rs"); + let mut configs = create_config(&prefix, config, &mut doc_table); + capture_from_env(&prefix, &mut configs); + emit_configuration(&prefix, &configs, &mut selected_config); - // Generate the template for the config - let mut configs = HashMap::new(); - for (name, default, desc) in config { - let name = format!("{prefix}_{}", screaming_snake_case(&name)); - // insert into config - configs.insert(name.clone(), default.clone()); + if emit_md_tables { + // convert to snake case for the file name + prefix.make_ascii_lowercase(); + write_config_tables(&prefix, doc_table, selected_config); + } - // write doc table line - let default = default.as_string(); - writeln!(doc_table, "|**{name}**|{desc}|{default}|").unwrap(); + configs +} - // Rebuild if config envvar changed. - // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 - println!("cargo:rerun-if-env-changed={name}"); +fn emit_configuration( + prefix: &str, + configs: &HashMap, + selected_config: &mut String, +) { + // emit cfgs and set envs + for (name, value) in configs.into_iter() { + let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_"))); + println!("cargo:rustc-check-cfg=cfg({cfg_name})"); + match value { + Value::Bool(true) => { + println!("cargo:rustc-cfg={cfg_name}") + } + _ => {} + } + + let value = value.as_string(); + // values that haven't been seen will be output here with the default value + println!("cargo:rustc-env={}={}", name, value); + + writeln!(selected_config, "|**{name}**|{value}|").unwrap(); } +} +fn capture_from_env(prefix: &str, configs: &mut HashMap) { let mut unknown = Vec::new(); let mut failed = Vec::new(); // Try and capture input from the environment for (var, value) in env::vars() { - if let Some(name) = var.strip_prefix(&format!("{prefix}_")) { - let name = snake_case(name); - let Some(cfg) = configs.get_mut(&name) else { + if let Some(_) = var.strip_prefix(&format!("{prefix}_")) { + let Some(cfg) = configs.get_mut(&var) else { unknown.push(var); continue; }; @@ -117,30 +144,33 @@ pub fn generate_config(prefix: &str, config: &[(&str, Value, &str)]) { if !unknown.is_empty() { panic!("Unknown configuration options detected: {:?}", unknown); } +} - let mut selected_config = String::from(CHOSEN_TABLE_HEADER); - - // emit cfgs and set envs - for (name, value) in configs.into_iter() { - let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_"))); - println!("cargo:rustc-check-cfg=cfg({cfg_name})"); - match value { - Value::Bool(true) => { - println!("cargo:rustc-cfg={cfg_name}") - } - _ => {} - } +fn create_config( + prefix: &str, + config: &[(&str, Value, &str)], + doc_table: &mut String, +) -> HashMap { + // Generate the template for the config + let mut configs = HashMap::new(); + for (name, default, desc) in config { + let name = format!("{prefix}_{}", screaming_snake_case(&name)); + // insert into config + configs.insert(name.clone(), default.clone()); - let value = value.as_string(); - // values that haven't been seen will be output here with the default value - println!("cargo:rustc-env={}={}", name, value); + // write doc table line + let default = default.as_string(); + writeln!(doc_table, "|**{name}**|{desc}|{default}|").unwrap(); - writeln!(selected_config, "|**{name}**|{value}|").unwrap(); + // Rebuild if config envvar changed. + // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 + println!("cargo:rerun-if-env-changed={name}"); } - // convert to snake case for the file name - prefix.make_ascii_lowercase(); + configs +} +fn write_config_tables(prefix: &str, doc_table: String, selected_config: String) { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir .join(format!("{prefix}_config_table.md")) @@ -186,7 +216,7 @@ mod test { let mut v = Value::Number(0); for input in INPUTS { - v.parse(input).unwrap(); + v.parse_in_place(input).unwrap(); // no matter the input format, the output format should be decimal assert_eq!(v.as_string(), "170"); } @@ -199,15 +229,56 @@ mod test { let mut v = Value::Bool(false); for input in TRUE_INPUTS { - v.parse(input).unwrap(); + v.parse_in_place(input).unwrap(); // no matter the input variant, the output format should be "true" assert_eq!(v.as_string(), "true"); } for input in FALSE_INPUTS { - v.parse(input).unwrap(); + v.parse_in_place(input).unwrap(); // no matter the input variant, the output format should be "false" assert_eq!(v.as_string(), "false"); } } + + #[test] + fn env_override() { + unsafe { + env::set_var("ESP_TEST_NUMBER", "0xaa"); + env::set_var("ESP_TEST_STRING", "Hello world!"); + env::set_var("ESP_TEST_BOOL", "true"); + } + + let configs = generate_config( + "esp-test", + &[ + ("number", Value::Number(999), "NA"), + ("string", Value::String("Demo".to_owned()), "NA"), + ("bool", Value::Bool(false), "NA"), + ], + false, + ); + + assert_eq!( + match configs.get("ESP_TEST_NUMBER").unwrap() { + Value::Number(num) => *num, + _ => unreachable!(), + }, + 0xaa + ); + assert_eq!( + match configs.get("ESP_TEST_STRING").unwrap() { + Value::String(val) => val, + _ => unreachable!(), + }, + "Hello world!" + ); + assert_eq!( + match configs.get("ESP_TEST_BOOL").unwrap() { + Value::Bool(val) => *val, + _ => unreachable!(), + }, + true + ); + } } diff --git a/esp-hal/build.rs b/esp-hal/build.rs index 96b9ce2152b..644b32c1f49 100644 --- a/esp-hal/build.rs +++ b/esp-hal/build.rs @@ -131,6 +131,7 @@ fn main() -> Result<(), Box> { Value::Bool(false), "Places the SPI driver in RAM for better performance", )], + true, ); Ok(()) diff --git a/esp-wifi/build.rs b/esp-wifi/build.rs index b640320c07b..cf0c723b74e 100644 --- a/esp-wifi/build.rs +++ b/esp-wifi/build.rs @@ -164,6 +164,7 @@ fn main() -> Result<(), Box> { ("failure_retry_cnt", Value::Number(1), "Number of connection retries station will do before moving to next AP. scan_method should be set as WIFI_ALL_CHANNEL_SCAN to use this config. Note: Enabling this may cause connection time to increase incase best AP doesn't behave properly. Defaults to 1"), ("scan_method", Value::Number(0), "0 = WIFI_FAST_SCAN, 1 = WIFI_ALL_CHANNEL_SCAN, defaults to 0"), ], + true ); Ok(()) From 07de843ba7b86e2dcfb1f27824b0253e96f2d130 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Tue, 17 Sep 2024 10:54:42 +0100 Subject: [PATCH 19/22] run host tests in CI --- .github/workflows/ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deb3ea5a4a1..79f6b994eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,3 +179,19 @@ jobs: # Check the formatting of all packages: - run: cargo xtask fmt-packages --check + + # -------------------------------------------------------------------------- + # host tests + + host-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + + # Check the formatting of all packages: + - run: cd esp-config && cargo test --features build From 03a5cf1e74ca51b8c25844b79c1c6e3196a71708 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Tue, 17 Sep 2024 11:51:53 +0100 Subject: [PATCH 20/22] add more esp-config tests --- esp-config/Cargo.toml | 3 + esp-config/src/generate.rs | 118 ++++++++++++++++++++++++++----------- 2 files changed, 88 insertions(+), 33 deletions(-) diff --git a/esp-config/Cargo.toml b/esp-config/Cargo.toml index b8fc6c3e867..43413ec6894 100644 --- a/esp-config/Cargo.toml +++ b/esp-config/Cargo.toml @@ -7,6 +7,9 @@ rust-version = "1.79.0" [dependencies] document-features = "0.2.10" +[dev-dependencies] +temp-env = "0.3.6" + [features] ## Enable the generation and parsing of a config build = [] \ No newline at end of file diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 972b92e2cb3..508ed10d532 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -243,42 +243,94 @@ mod test { #[test] fn env_override() { - unsafe { - env::set_var("ESP_TEST_NUMBER", "0xaa"); - env::set_var("ESP_TEST_STRING", "Hello world!"); - env::set_var("ESP_TEST_BOOL", "true"); - } - - let configs = generate_config( - "esp-test", - &[ - ("number", Value::Number(999), "NA"), - ("string", Value::String("Demo".to_owned()), "NA"), - ("bool", Value::Bool(false), "NA"), + temp_env::with_vars( + [ + ("ESP_TEST_NUMBER", Some("0xaa")), + ("ESP_TEST_STRING", Some("Hello world!")), + ("ESP_TEST_BOOL", Some("true")), ], - false, - ); - - assert_eq!( - match configs.get("ESP_TEST_NUMBER").unwrap() { - Value::Number(num) => *num, - _ => unreachable!(), + || { + let configs = generate_config( + "esp-test", + &[ + ("number", Value::Number(999), "NA"), + ("string", Value::String("Demo".to_owned()), "NA"), + ("bool", Value::Bool(false), "NA"), + ("number_default", Value::Number(999), "NA"), + ("string_default", Value::String("Demo".to_owned()), "NA"), + ("bool_default", Value::Bool(false), "NA"), + ], + false, + ); + + // some values have changed + assert_eq!( + match configs.get("ESP_TEST_NUMBER").unwrap() { + Value::Number(num) => *num, + _ => unreachable!(), + }, + 0xaa + ); + assert_eq!( + match configs.get("ESP_TEST_STRING").unwrap() { + Value::String(val) => val, + _ => unreachable!(), + }, + "Hello world!" + ); + assert_eq!( + match configs.get("ESP_TEST_BOOL").unwrap() { + Value::Bool(val) => *val, + _ => unreachable!(), + }, + true + ); + + // the rest are the defaults + assert_eq!( + match configs.get("ESP_TEST_NUMBER_DEFAULT").unwrap() { + Value::Number(num) => *num, + _ => unreachable!(), + }, + 999 + ); + assert_eq!( + match configs.get("ESP_TEST_STRING_DEFAULT").unwrap() { + Value::String(val) => val, + _ => unreachable!(), + }, + "Demo" + ); + assert_eq!( + match configs.get("ESP_TEST_BOOL_DEFAULT").unwrap() { + Value::Bool(val) => *val, + _ => unreachable!(), + }, + false + ); }, - 0xaa - ); - assert_eq!( - match configs.get("ESP_TEST_STRING").unwrap() { - Value::String(val) => val, - _ => unreachable!(), - }, - "Hello world!" - ); - assert_eq!( - match configs.get("ESP_TEST_BOOL").unwrap() { - Value::Bool(val) => *val, - _ => unreachable!(), + ) + } + + #[test] + #[should_panic] + fn env_unknown_bails() { + temp_env::with_vars( + [ + ("ESP_TEST_NUMBER", Some("0xaa")), + ("ESP_TEST_RANDOM_VARIABLE", Some("")), + ], + || { + generate_config("esp-test", &[("number", Value::Number(999), "NA")], false); }, - true ); } + + #[test] + #[should_panic] + fn env_invalid_values_bails() { + temp_env::with_vars([("ESP_TEST_NUMBER", Some("Hello world"))], || { + generate_config("esp-test", &[("number", Value::Number(999), "NA")], false); + }); + } } From 413b67b57582d1aa8d2d2e30c1ee0e84645a610d Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Wed, 18 Sep 2024 11:15:23 +0100 Subject: [PATCH 21/22] review comments --- esp-config/src/generate.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 508ed10d532..50394f4a632 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -78,7 +78,7 @@ pub fn generate_config( println!("cargo:rerun-if-changed=build.rs"); // ensure that the prefix is `SCREAMING_SNAKE_CASE` - let mut prefix = screaming_snake_case(prefix); + let prefix = format!("{}_", screaming_snake_case(prefix)); let mut doc_table = String::from(DOC_TABLE_HEADER); let mut selected_config = String::from(CHOSEN_TABLE_HEADER); @@ -87,9 +87,8 @@ pub fn generate_config( emit_configuration(&prefix, &configs, &mut selected_config); if emit_md_tables { - // convert to snake case for the file name - prefix.make_ascii_lowercase(); - write_config_tables(&prefix, doc_table, selected_config); + let file_name = snake_case(&prefix); + write_config_tables(&file_name, doc_table, selected_config); } configs @@ -102,7 +101,7 @@ fn emit_configuration( ) { // emit cfgs and set envs for (name, value) in configs.into_iter() { - let cfg_name = snake_case(name.trim_start_matches(&format!("{prefix}_"))); + let cfg_name = snake_case(name.trim_start_matches(prefix)); println!("cargo:rustc-check-cfg=cfg({cfg_name})"); match value { Value::Bool(true) => { @@ -125,7 +124,7 @@ fn capture_from_env(prefix: &str, configs: &mut HashMap) { // Try and capture input from the environment for (var, value) in env::vars() { - if let Some(_) = var.strip_prefix(&format!("{prefix}_")) { + if let Some(_) = var.strip_prefix(prefix) { let Some(cfg) = configs.get_mut(&var) else { unknown.push(var); continue; @@ -154,8 +153,7 @@ fn create_config( // Generate the template for the config let mut configs = HashMap::new(); for (name, default, desc) in config { - let name = format!("{prefix}_{}", screaming_snake_case(&name)); - // insert into config + let name = format!("{prefix}{}", screaming_snake_case(&name)); configs.insert(name.clone(), default.clone()); // write doc table line @@ -163,7 +161,6 @@ fn create_config( writeln!(doc_table, "|**{name}**|{desc}|{default}|").unwrap(); // Rebuild if config envvar changed. - // Note this is currently buggy and requires a clean build - PR is pending https://github.com/rust-lang/cargo/pull/14058 println!("cargo:rerun-if-env-changed={name}"); } @@ -173,14 +170,14 @@ fn create_config( fn write_config_tables(prefix: &str, doc_table: String, selected_config: String) { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir - .join(format!("{prefix}_config_table.md")) + .join(format!("{prefix}config_table.md")) .to_string_lossy() .to_string(); fs::write(out_file, doc_table).unwrap(); let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let out_file = out_dir - .join(format!("{prefix}_selected_config.md")) + .join(format!("{prefix}selected_config.md")) .to_string_lossy() .to_string(); fs::write(out_file, selected_config).unwrap(); From 239c3796f49ebc15b395804b48ebb4b3b25689bf Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Wed, 18 Sep 2024 11:56:21 +0100 Subject: [PATCH 22/22] add cargo env workaround --- esp-config/src/generate.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index 50394f4a632..a3257d4576a 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -76,6 +76,8 @@ pub fn generate_config( // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any // other file changed. println!("cargo:rerun-if-changed=build.rs"); + #[cfg(not(test))] + env_change_work_around(); // ensure that the prefix is `SCREAMING_SNAKE_CASE` let prefix = format!("{}_", screaming_snake_case(prefix)); @@ -94,6 +96,36 @@ pub fn generate_config( configs } +// A work-around for https://github.com/rust-lang/cargo/issues/10358 +// This can be removed when https://github.com/rust-lang/cargo/pull/14058 is merged. +// Unlikely to work on projects in workspaces +#[cfg(not(test))] +fn env_change_work_around() { + let mut out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + // We clean out_dir by removing all trailing directories, until it ends with + // target + while !out_dir.ends_with("target") { + if !out_dir.pop() { + // We ran out of directories... + return; + } + } + out_dir.pop(); + + let dotcargo = out_dir.join(".cargo/"); + if dotcargo.exists() { + println!( + "cargo:rerun-if-changed={}", + dotcargo.clone().join("config.toml").to_str().unwrap() + ); + println!( + "cargo:rerun-if-changed={}", + dotcargo.clone().join("config").to_str().unwrap() + ); + } +} + fn emit_configuration( prefix: &str, configs: &HashMap,