fn main() { generate_filter_snippet(); compile_custom_wokwi_components(); linker_be_nice(); // make sure linkall.x is the last linker script (otherwise might cause problems with flip-link) println!("cargo:rustc-link-arg=-Tlinkall.x"); } fn linker_be_nice() { let args: Vec = std::env::args().collect(); if args.len() > 1 { let kind = &args[1]; let what = &args[2]; match kind.as_str() { "undefined-symbol" => match what.as_str() { what if what.starts_with("_defmt_") => { eprintln!(); eprintln!( "💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`" ); eprintln!(); } "_stack_start" => { eprintln!(); eprintln!("💡 Is the linker script `linkall.x` missing?"); eprintln!(); } what if what.starts_with("esp_rtos_") => { eprintln!(); eprintln!( "💡 `esp-radio` has no scheduler enabled. Make sure you have initialized `esp-rtos` or provided an external scheduler." ); eprintln!(); } "embedded_test_linker_file_not_added_to_rustflags" => { eprintln!(); eprintln!( "💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests" ); eprintln!(); } "free" | "malloc" | "calloc" | "get_free_internal_heap_size" | "malloc_internal" | "realloc_internal" | "calloc_internal" | "free_internal" => { eprintln!(); eprintln!( "💡 Did you forget the `esp-alloc` dependency or didn't enable the `compat` feature on it?" ); eprintln!(); } _ => (), }, // we don't have anything helpful for "missing-lib" yet _ => { std::process::exit(1); } } std::process::exit(0); } println!( "cargo:rustc-link-arg=-Wl,--error-handling-script={}", std::env::current_exe().unwrap().display() ); } fn compile_custom_wokwi_components() { use std::process::Command; if std::env::var("CARGO_FEATURE_WOKWI").is_ok() { println!("cargo:rerun-if-changed=wokwi/ch1115.chip.c"); println!("cargo:rerun-if-changed=wokwi/ch1115.chip.json"); println!("cargo:rerun-if-changed=wokwi/Makefile"); let status = Command::new("make") .current_dir("wokwi") .status() .expect("Failed to execute make command. Is 'make' installed?"); if !status.success() { panic!( "Wokwi custom chip compilation failed with status: {}", status ); } } } // Taken over from esp_println because impossible to just extend their existing env logger. // https://github.com/esp-rs/esp-hal/blob/8fa6d7e985e724d6ad998e34307429b0920da370/esp-println/build.rs fn generate_filter_snippet() { use log::LevelFilter; use std::{env, path::Path}; 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::LevelFilter = log::LevelFilter::{max};" )); snippet .push_str("pub(crate) fn is_enabled(level: log::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(\"{name}\") {{ return level <= log::LevelFilter::{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::LevelFilter::{level}")); } else { snippet.push_str(" false"); } snippet.push('}'); snippet } } else { "pub(crate) const FILTER_MAX: log::LevelFilter = log::LevelFilter::Off; pub(crate) fn is_enabled(_level: log::Level, _target: &str) -> bool { true }".to_string() }; std::fs::write(&dest_path, &snippet).unwrap(); } #[derive(Default, Debug)] struct ParseResult { pub(crate) directives: Vec, pub(crate) errors: Vec, } 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, pub(crate) level: log::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 { use log::LevelFilter; 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 }