mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 12:20:56 +00:00

* Move macros to new crate * Generate a single file * Pre-generate esp-metadata-generated * Move saving code to xtask * Format with both rustfmt and prettyplease * Fix doc build * Unhide macros * Fix doc string * Update semver-check baseline
211 lines
7.0 KiB
Rust
211 lines
7.0 KiB
Rust
use std::{env, path::Path};
|
|
|
|
use log_04::LevelFilter;
|
|
|
|
#[macro_export]
|
|
macro_rules! assert_unique_used_features {
|
|
($($feature:literal),+ $(,)?) => {
|
|
assert!(
|
|
(0 $(+ cfg!(feature = $feature) as usize)+ ) == 1,
|
|
"Exactly one of the following features must be enabled: {}",
|
|
[$($feature),+].join(", ")
|
|
);
|
|
};
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Ensure that only a single communication method is specified
|
|
assert_unique_used_features!("jtag-serial", "uart", "auto");
|
|
|
|
let chip = esp_metadata_generated::Chip::from_cargo_feature()?;
|
|
// Ensure that, if the `jtag-serial` communication method feature is enabled,
|
|
// a compatible chip feature is also enabled.
|
|
let has_jtag_serial = chip.contains("soc_has_usb_device");
|
|
|
|
if cfg!(feature = "jtag-serial") && !has_jtag_serial {
|
|
panic!(
|
|
"The `jtag-serial` feature is only supported by the ESP32-C3, ESP32-C6, ESP32-H2, ESP32-P4, and ESP32-S3"
|
|
);
|
|
}
|
|
|
|
// Ensure that, if the `colors` is used with `log-04`.
|
|
if cfg!(feature = "colors") && !cfg!(feature = "log-04") {
|
|
println!(
|
|
"cargo:warning=The `colors` feature is only effective when using the `log-04` feature"
|
|
);
|
|
}
|
|
|
|
if std::env::var("ESP_LOGLEVEL").is_ok() || std::env::var("ESP_LOGFILTER").is_ok() {
|
|
panic!(
|
|
"`ESP_LOGLEVEL` and `ESP_LOGFILTER` is not supported anymore. Please use `ESP_LOG` instead."
|
|
);
|
|
}
|
|
|
|
generate_filter_snippet();
|
|
|
|
#[cfg(target_os = "windows")]
|
|
println!("cargo:rustc-cfg=host_is_windows");
|
|
|
|
println!("cargo:rerun-if-env-changed=ESP_LOG");
|
|
println!("cargo:rustc-check-cfg=cfg(host_is_windows)");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_filter_snippet() {
|
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
|
let dest_path = Path::new(&out_dir).join("log_filter.rs");
|
|
|
|
let filter = env::var("ESP_LOG");
|
|
let snippet = if let Ok(filter) = filter {
|
|
let res = parse_spec(&filter);
|
|
|
|
if !res.errors.is_empty() {
|
|
panic!("Error parsing `ESP_LOG`: {:?}", res.errors);
|
|
} else {
|
|
let max = res
|
|
.directives
|
|
.iter()
|
|
.map(|v| v.level)
|
|
.max()
|
|
.unwrap_or(LevelFilter::Off);
|
|
let max = match max {
|
|
LevelFilter::Off => "Off",
|
|
LevelFilter::Error => "Error",
|
|
LevelFilter::Warn => "Warn",
|
|
LevelFilter::Info => "Info",
|
|
LevelFilter::Debug => "Debug",
|
|
LevelFilter::Trace => "Trace",
|
|
};
|
|
|
|
let mut snippet = String::new();
|
|
|
|
snippet.push_str(&format!(
|
|
"pub(crate) const FILTER_MAX: log_04::LevelFilter = log_04::LevelFilter::{max};"
|
|
));
|
|
|
|
snippet.push_str(
|
|
"pub(crate) fn is_enabled(level: log_04::Level, _target: &str) -> bool {",
|
|
);
|
|
|
|
let mut global_level = None;
|
|
for directive in res.directives {
|
|
let level = match directive.level {
|
|
LevelFilter::Off => "Off",
|
|
LevelFilter::Error => "Error",
|
|
LevelFilter::Warn => "Warn",
|
|
LevelFilter::Info => "Info",
|
|
LevelFilter::Debug => "Debug",
|
|
LevelFilter::Trace => "Trace",
|
|
};
|
|
|
|
if let Some(name) = directive.name {
|
|
// If a prefix matches, don't continue to the next directive
|
|
snippet.push_str(&format!(
|
|
"if _target.starts_with(\"{}\") {{ return level <= log_04::LevelFilter::{}; }}",
|
|
&name, level
|
|
));
|
|
} else {
|
|
if global_level.is_some() {
|
|
panic!("Multiple global log levels specified in `ESP_LOG`");
|
|
}
|
|
global_level = Some(level);
|
|
}
|
|
}
|
|
|
|
// Place the fallback rule at the end
|
|
if let Some(level) = global_level {
|
|
snippet.push_str(&format!("level <= log_04::LevelFilter::{level}"));
|
|
} else {
|
|
snippet.push_str(" false");
|
|
}
|
|
snippet.push('}');
|
|
snippet
|
|
}
|
|
} else {
|
|
"pub(crate) const FILTER_MAX: log_04::LevelFilter = log_04::LevelFilter::Off; pub(crate) fn is_enabled(_level: log_04::Level, _target: &str) -> bool { true }".to_string()
|
|
};
|
|
|
|
std::fs::write(&dest_path, &snippet).unwrap();
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
struct ParseResult {
|
|
pub(crate) directives: Vec<Directive>,
|
|
pub(crate) errors: Vec<String>,
|
|
}
|
|
|
|
impl ParseResult {
|
|
fn add_directive(&mut self, directive: Directive) {
|
|
self.directives.push(directive);
|
|
}
|
|
|
|
fn add_error(&mut self, message: String) {
|
|
self.errors.push(message);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Directive {
|
|
pub(crate) name: Option<String>,
|
|
pub(crate) level: LevelFilter,
|
|
}
|
|
|
|
/// Parse a logging specification string (e.g:
|
|
/// `crate1,crate2::mod3,crate3::x=error/foo`) and return a vector with log
|
|
/// directives.
|
|
fn parse_spec(spec: &str) -> ParseResult {
|
|
let mut result = ParseResult::default();
|
|
|
|
let mut parts = spec.split('/');
|
|
let mods = parts.next();
|
|
|
|
if let Some(m) = mods {
|
|
for s in m.split(',').map(|ss| ss.trim()) {
|
|
if s.is_empty() {
|
|
continue;
|
|
}
|
|
let mut parts = s.split('=');
|
|
let (log_level, name) =
|
|
match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
|
|
(Some(part0), None, None) => {
|
|
// if the single argument is a log-level string or number,
|
|
// treat that as a global fallback
|
|
match part0.parse() {
|
|
Ok(num) => (num, None),
|
|
Err(_) => (LevelFilter::max(), Some(part0)),
|
|
}
|
|
}
|
|
(Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
|
|
(Some(part0), Some(part1), None) => {
|
|
if let Ok(num) = part1.parse() {
|
|
(num, Some(part0))
|
|
} else {
|
|
result.add_error(format!("invalid logging spec '{part1}'"));
|
|
continue;
|
|
}
|
|
}
|
|
_ => {
|
|
result.add_error(format!("invalid logging spec '{s}'"));
|
|
continue;
|
|
}
|
|
};
|
|
|
|
result.add_directive(Directive {
|
|
name: name.map(|s| s.to_owned()),
|
|
level: log_level,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Sort by length so that the most specific prefixes come first
|
|
result
|
|
.directives
|
|
.sort_by(|a, b| match (a.name.as_ref(), b.name.as_ref()) {
|
|
(Some(a), Some(b)) => b.len().cmp(&a.len()),
|
|
_ => std::cmp::Ordering::Equal,
|
|
});
|
|
|
|
result
|
|
}
|