Files
firmware/build.rs
T
swaits 1c98786ce6 feat(build): per-board memory.x override with MCU-family fallback
build.rs resolves the linker layout in two steps: per-board override
`ld/{board}-memory.x` first, MCU-family default second. Lets a board
deviate from the family default without a per-board build matrix.

Adds `ld/wio_tracker_l1-memory.x` with `ORIGIN = 0x1000` (no-SD
layout). The Wio Tracker L1 ships from Seeed with s140 SoftDevice, but
our earlier `ORIGIN = 0x1000, LENGTH = 0xE9000` linker silently
overwrote it across many UF2 flashes; the bootloader now reports
`SoftDevice: not found`. Until s140 is restored via SWD, the WIO
needs the no-SD layout to match the bootloader's actual boot chain.

Delete the per-board file once SD is restored -- the build will then
fall back to the SD-aware `ld/nrf52840-memory.x` and the WIO matches
every other nRF52840 board.
2026-04-28 11:03:46 -06:00

139 lines
5.5 KiB
Rust

use minijinja::{context, Environment};
use owo_colors::OwoColorize;
use std::collections::HashMap;
use std::fs;
fn main() {
// Protocol tests run on the host without a board feature.
if std::env::var_os("DONGLORA_HOST_TEST").is_some() {
return;
}
let board_dir = "src/board";
// ── Discover boards vs helpers by content ───────────────────────
//
// A .rs file in src/board/ that implements LoRaBoard is a board.
// Everything else (traits.rs, esp32s3.rs, etc.) is a helper module.
// No exclusion lists — detection is based on what the file contains.
let mut boards: Vec<String> = Vec::new();
for entry in fs::read_dir(board_dir).expect("failed to read src/board directory") {
let p = match entry {
Ok(e) => e.path(),
Err(_) => continue,
};
if !p.is_file() || p.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let stem = match p.file_stem().and_then(|s| s.to_str()) {
Some("mod") => continue, // Skip generated file
Some(s) => s.to_string(),
None => continue,
};
let content = match fs::read_to_string(&p) {
Ok(c) => c,
Err(_) => continue,
};
if content.contains("LoRaBoard for Board") {
boards.push(stem);
}
}
boards.sort(); // Deterministic ordering
// ── Validate exactly one board feature is enabled ───────────────
let enabled: Vec<_> = std::env::vars()
.filter(|(k, _)| k.starts_with("CARGO_FEATURE_"))
.map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase())
.filter(|f| boards.contains(f))
.collect();
match enabled.len() {
0 => panic!(
"\n\n{}\n",
format!("No board selected! Found: {:?}", boards)
.red()
.bold()
),
1 => println!("cargo:rustc-cfg=board_selected"),
_ => panic!(
"\n\n{}\n",
format!("Conflict: Multiple boards enabled {:?}", enabled)
.yellow()
.bold()
),
}
// ── Auto-discover helper modules used by board files ────────────
//
// Scan each board file for `use super::<name>` to find which helper
// modules they depend on. Generate cfg gates so helpers compile only
// when a dependent board is selected.
let mut helper_boards: HashMap<String, Vec<String>> = HashMap::new();
for board in &boards {
let path = format!("{}/{}.rs", board_dir, board);
if let Ok(content) = fs::read_to_string(&path) {
for line in content.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("use super::") {
let helper = rest
.split(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.unwrap_or("");
if !helper.is_empty() && helper != "traits" {
helper_boards
.entry(helper.to_string())
.or_default()
.push(board.clone());
}
}
}
}
}
// ── Render board module template ────────────────────────────────
let mut env = Environment::new();
let template = fs::read_to_string("src/board/mod.rs.j2").expect("missing src/board/mod.rs.j2");
env.add_template("mod", &template)
.expect("failed to parse board template");
let rendered = env
.get_template("mod")
.expect("template not found")
.render(context!(boards => boards, helpers => helper_boards))
.expect("failed to render board module");
fs::write("src/board/mod.rs", rendered).expect("failed to write src/board/mod.rs");
println!("cargo:rerun-if-changed=src/board/mod.rs.j2");
println!("cargo:rerun-if-changed={}", board_dir);
// cortex-m-rt's link.x does `INCLUDE memory.x`. Resolve the layout
// in two steps:
// 1. Per-board override: `ld/{board}-memory.x` if it exists.
// 2. MCU-family default by target triple.
// The override path is for boards that deviate from the family
// default (e.g. WIO Tracker L1 currently has no SoftDevice in flash
// and needs ORIGIN=0x1000 instead of the SD-aware 0x26000). Drop
// the per-board file once the layout matches the family default.
let target = std::env::var("TARGET").unwrap_or_default();
let board_specific = format!("ld/{}-memory.x", enabled[0]);
let memory_x: Option<String> = if std::path::Path::new(&board_specific).exists() {
Some(board_specific.clone())
} else {
match target.as_str() {
"thumbv7em-none-eabihf" => Some("ld/nrf52840-memory.x".to_string()),
"thumbv6m-none-eabi" => Some("ld/rp2040-memory.x".to_string()),
_ => None,
}
};
if let Some(src) = memory_x {
let out_dir = std::env::var("OUT_DIR").unwrap();
fs::copy(&src, format!("{out_dir}/memory.x")).expect("failed to copy memory.x");
println!("cargo:rustc-link-search={out_dir}");
}
println!("cargo:rerun-if-changed=ld/nrf52840-memory.x");
println!("cargo:rerun-if-changed=ld/rp2040-memory.x");
println!("cargo:rerun-if-changed={}", board_specific);
}