257 lines
8.7 KiB
Rust
257 lines
8.7 KiB
Rust
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<String> = 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<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: 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
|
|
}
|