refactor: build system auto-discovery, updated docs
- build.rs discovers boards by content (implements LoRaBoard), not exclusion lists - build.rs auto-discovers helper modules by scanning board files for use super::<helper> - Jinja template dynamically generates cfg gates for helpers - Updated PORTING.md for new architecture (hal/, driver/, BoardParts, associated types) - Zero manual template editing needed when adding boards to existing MCU families
This commit is contained in:
1 parent
ecc451affe
commit
18b509344c
3 files changed
+94
-46
No files matched your search
@@ -1,32 +1,48 @@
|
|||||||
use minijinja::{context, Environment};
|
use minijinja::{context, Environment};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Protocol tests run on the host without a board feature. Cargo doesn't
|
// Protocol tests run on the host without a board feature.
|
||||||
// expose `cfg(test)` to build scripts, so we rely on an env var set by
|
|
||||||
// the justfile `test` recipe. See: `just test`
|
|
||||||
if std::env::var_os("DONGLORA_HOST_TEST").is_some() {
|
if std::env::var_os("DONGLORA_HOST_TEST").is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let board_dir = "src/board";
|
let board_dir = "src/board";
|
||||||
let boards: Vec<String> = fs::read_dir(board_dir)
|
|
||||||
.expect("failed to read src/board directory")
|
|
||||||
.filter_map(|e| {
|
|
||||||
let p = e.ok()?.path();
|
|
||||||
let stem = p.file_stem()?.to_str()?;
|
|
||||||
// Exclude non-board files: mod.rs, traits.rs, and platform helpers (esp32s3.rs).
|
|
||||||
if p.is_file()
|
|
||||||
&& p.extension()? == "rs"
|
|
||||||
&& !matches!(stem, "mod" | "traits" | "esp32s3")
|
|
||||||
{
|
|
||||||
return Some(p.file_stem()?.to_str()?.to_string());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
|
// ── 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()
|
let enabled: Vec<_> = std::env::vars()
|
||||||
.filter(|(k, _)| k.starts_with("CARGO_FEATURE_"))
|
.filter(|(k, _)| k.starts_with("CARGO_FEATURE_"))
|
||||||
.map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase())
|
.map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase())
|
||||||
@@ -49,6 +65,35 @@ fn main() {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 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 mut env = Environment::new();
|
||||||
let template = fs::read_to_string("src/board/mod.rs.j2").expect("missing src/board/mod.rs.j2");
|
let template = fs::read_to_string("src/board/mod.rs.j2").expect("missing src/board/mod.rs.j2");
|
||||||
env.add_template("mod", &template)
|
env.add_template("mod", &template)
|
||||||
@@ -56,7 +101,7 @@ fn main() {
|
|||||||
let rendered = env
|
let rendered = env
|
||||||
.get_template("mod")
|
.get_template("mod")
|
||||||
.expect("template not found")
|
.expect("template not found")
|
||||||
.render(context!(boards => boards))
|
.render(context!(boards => boards, helpers => helper_boards))
|
||||||
.expect("failed to render board module");
|
.expect("failed to render board module");
|
||||||
fs::write("src/board/mod.rs", rendered).expect("failed to write src/board/mod.rs");
|
fs::write("src/board/mod.rs", rendered).expect("failed to write src/board/mod.rs");
|
||||||
|
|
||||||
@@ -64,9 +109,7 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed={}", board_dir);
|
println!("cargo:rerun-if-changed={}", board_dir);
|
||||||
|
|
||||||
// cortex-m-rt's link.x does `INCLUDE memory.x`. Copy our nRF layout into
|
// cortex-m-rt's link.x does `INCLUDE memory.x`. Copy our nRF layout into
|
||||||
// OUT_DIR (which cortex-m-rt already adds to the linker search path) so it's
|
// OUT_DIR so it's only visible to ARM builds.
|
||||||
// only visible to ARM builds. Keeping it out of the project root prevents
|
|
||||||
// the linker from shadowing esp-hal's generated memory.x on Xtensa builds.
|
|
||||||
let target = std::env::var("TARGET").unwrap_or_default();
|
let target = std::env::var("TARGET").unwrap_or_default();
|
||||||
if !target.starts_with("xtensa") {
|
if !target.starts_with("xtensa") {
|
||||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||||
|
|||||||
+25
-21
@@ -5,14 +5,14 @@
|
|||||||
### 1. Create the board file
|
### 1. Create the board file
|
||||||
|
|
||||||
Create `src/board/your_board.rs`. Use an existing board as a template:
|
Create `src/board/your_board.rs`. Use an existing board as a template:
|
||||||
- ESP32-S3 boards: copy `heltec_v4.rs` (uses shared helpers from `esp32s3.rs`)
|
- ESP32-S3 boards: copy `heltec_v4.rs` (uses shared helpers from `board/esp32s3.rs` and `hal/esp32s3.rs`)
|
||||||
- nRF52840 boards: copy `rak_wisblock_4631.rs`
|
- nRF52840 boards: copy `rak_wisblock_4631.rs` (uses shared helpers from `hal/nrf52840.rs`)
|
||||||
|
- New MCU family: create a new `hal/<mcu>.rs` with MCU primitives first
|
||||||
|
|
||||||
ESP32-S3 boards share common type aliases, peripheral bundles, and init helpers
|
`build.rs` auto-discovers your board file — any `.rs` file in `src/board/` that
|
||||||
via `src/board/esp32s3.rs`. Your board file only needs the trait impl and
|
implements `LoRaBoard for Board` is automatically detected as a board. Helper
|
||||||
board-specific init (Vext GPIO, display reset). If you add a new ESP32-S3 board,
|
modules (like `esp32s3.rs`) are auto-discovered via `use super::<helper>` imports
|
||||||
add its feature to the `#[cfg(any(...))]` gate on `mod esp32s3` in
|
in board files. No template editing or exclusion lists needed.
|
||||||
`src/board/mod.rs.j2`.
|
|
||||||
|
|
||||||
### 2. Define concrete types
|
### 2. Define concrete types
|
||||||
|
|
||||||
@@ -21,7 +21,9 @@ Your board must export these type aliases:
|
|||||||
```rust
|
```rust
|
||||||
pub type RadioDriver = ...; // Must work with lora_phy::LoRa<RadioDriver, Delay>
|
pub type RadioDriver = ...; // Must work with lora_phy::LoRa<RadioDriver, Delay>
|
||||||
pub type UsbDriver = ...; // Must implement embassy_usb_driver::Driver<'static>
|
pub type UsbDriver = ...; // Must implement embassy_usb_driver::Driver<'static>
|
||||||
|
// (or UartDriver for UART boards)
|
||||||
pub type DisplayI2c = ...; // Must implement embedded_hal_async::i2c::I2c
|
pub type DisplayI2c = ...; // Must implement embedded_hal_async::i2c::I2c
|
||||||
|
pub type DisplayDriver = ...; // Must implement DrawTarget<Color = BinaryColor>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Define peripheral bundles
|
### 3. Define peripheral bundles
|
||||||
@@ -32,39 +34,43 @@ pub struct RadioParts {
|
|||||||
pub delay: embassy_time::Delay,
|
pub delay: embassy_time::Delay,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UsbParts {
|
pub struct UsbParts { // or UartParts for UART boards
|
||||||
pub driver: UsbDriver,
|
pub driver: UsbDriver,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DisplayParts {
|
pub struct DisplayParts {
|
||||||
pub i2c: DisplayI2c,
|
pub i2c: DisplayI2c,
|
||||||
pub mac: [u8; 6],
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Implement the LoRaBoard trait
|
### 4. Implement the LoRaBoard trait
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use super::traits::LoRaBoard;
|
use super::traits::{BoardParts, LoRaBoard};
|
||||||
|
|
||||||
impl LoRaBoard for Board {
|
impl LoRaBoard for Board {
|
||||||
const NAME: &'static str = "Your Board Name";
|
const NAME: &'static str = "Your Board Name";
|
||||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22); // check your radio + PA
|
const TX_POWER_RANGE: (i8, i8) = (-9, 22); // check your radio + PA
|
||||||
|
|
||||||
|
type RadioParts = RadioParts;
|
||||||
|
type CommParts = UsbParts; // or UartParts
|
||||||
|
type DisplayParts = DisplayParts;
|
||||||
|
type DisplayDriver = DisplayDriver;
|
||||||
|
|
||||||
fn init() -> Self { ... }
|
fn init() -> Self { ... }
|
||||||
fn mac_address() -> [u8; 6] { ... } // read from efuse, FICR, etc.
|
fn mac_address() -> [u8; 6] { ... } // read from efuse, FICR, etc.
|
||||||
|
fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
|
||||||
|
// Initialize buses via hal::, construct drivers, return BoardParts
|
||||||
|
BoardParts { radio, host, display: Some(display_parts), mac: Self::mac_address() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The compiler will refuse to build if any of these are missing.
|
### 5. Add display init
|
||||||
|
|
||||||
### 5. Implement `Board::into_parts()`
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Board {
|
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
// Construct and initialize your display driver (SSD1306, SH1106, etc.)
|
||||||
// Initialize SPI, DMA, radio, USB, I2C
|
|
||||||
// Return peripheral bundles
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,7 +85,7 @@ your_board = ["dep:your-hal", ...]
|
|||||||
|
|
||||||
### 7. Add to justfile
|
### 7. Add to justfile
|
||||||
|
|
||||||
Add your board's feature/target/chip to the `boards` list at the top of `justfile`.
|
Add your board's feature/target/chip to the board definitions at the top of `justfile`.
|
||||||
|
|
||||||
### 8. Build and test
|
### 8. Build and test
|
||||||
|
|
||||||
@@ -88,5 +94,3 @@ just check your_board # Must compile
|
|||||||
just clippy your_board # Must be clean
|
just clippy your_board # Must be clean
|
||||||
just build your_board # Produces firmware
|
just build your_board # Produces firmware
|
||||||
```
|
```
|
||||||
|
|
||||||
`build.rs` auto-discovers your `.rs` file — no template editing needed.
|
|
||||||
+4
-3
@@ -2,9 +2,10 @@
|
|||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub use traits::*;
|
pub use traits::*;
|
||||||
|
|
||||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))]
|
{% for helper, helper_features in helpers|items -%}
|
||||||
mod esp32s3;
|
#[cfg(any({% for f in helper_features %}feature = "{{ f }}"{% if not loop.last %}, {% endif %}{% endfor %}))]
|
||||||
|
mod {{ helper }};
|
||||||
|
{% endfor %}
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
{%- for board in boards %}
|
{%- for board in boards %}
|
||||||
{% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] {
|
{% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] {
|
||||||
|
|||||||
Reference in new issue
Block a user