Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
[![Documentation](https://github.com/fastled/fbuild/actions/workflows/docs.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/docs.yml)
[![Validate Boards](https://github.com/fastled/fbuild/actions/workflows/validate-boards.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/validate-boards.yml)
[![Build Native Binaries](https://github.com/fastled/fbuild/actions/workflows/build.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/build.yml)
[![Library Selection Acceptance (#205)](https://github.com/fastled/fbuild/actions/workflows/acceptance-205.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/acceptance-205.yml)
[![Library Selection Perf (#205)](https://github.com/fastled/fbuild/actions/workflows/bench-205.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/bench-205.yml)

<details>
<summary><strong>Per-platform board build badges</strong> (click to expand)</summary>
Expand Down
27 changes: 22 additions & 5 deletions crates/fbuild-build/tests/teensylc_acceptance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ fn teensylc_blink_meets_205_acceptance_criteria() {

let params = BuildParams {
project_dir: project_dir.clone(),
env_name: "teensyLC".to_string(),
// WHY: env names are case-sensitive and must match the
// [env:teensylc] key in tests/platform/teensylc/platformio.ini.
// Same root-cause family as #220 / #221 in measure_baseline_205.py.
env_name: "teensylc".to_string(),
clean: true,
profile: BuildProfile::Release,
build_dir: build_dir.path().to_path_buf(),
Expand Down Expand Up @@ -71,15 +74,29 @@ fn teensylc_blink_meets_205_acceptance_criteria() {
#204 regression"
);
}
for required in ["setup", "loop"] {
// WHY: setup/loop are extern "C" via Arduino.h's prototype, so
// ideally appear unmangled. But Teensyduino's main calls them via
// the framework's main.cpp and toolchain LTO can leave only the
// mangled C++ symbols (`_Z5setupv` / `_Z4loopv`) when the .ino is
// compiled as C++ without the extern "C" prototype reaching the
// definition. Accept either form — the contract is "the user's
// setup/loop landed in the firmware", not "they kept their C
// linkage". The earlier `has_symbol_containing` was rejected in
// PR #209 review for matching `Stream::setupXxx`-style false
// positives; the explicit-mangled fallback below is targeted and
// doesn't share that problem.
for (required, mangled) in [("setup", "_Z5setupv"), ("loop", "_Z4loopv")] {
let unmangled_present = probe.has_symbol(required).expect("symbol query");
let mangled_present = probe.has_symbol(mangled).expect("symbol query");
assert!(
probe.has_symbol(required).expect("symbol query"),
"A-11: required symbol '{required}' missing from ELF"
unmangled_present || mangled_present,
"A-11: required symbol '{required}' missing from ELF \
(also looked for mangled '{mangled}')"
);
}

// ── compile_commands.json probes (AC#1, A-20..A-22) ─────────────────
let compdb_path = locate_compile_commands(build_dir.path(), "teensyLC")
let compdb_path = locate_compile_commands(build_dir.path(), "teensylc")
.expect("compile_commands.json should land in build dir");
let db = CompileDb::from_path(&compdb_path).expect("parse compile_commands.json");
assert!(
Expand Down
42 changes: 42 additions & 0 deletions crates/fbuild-library-select/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,48 @@ mod tests {
);
}

#[test]
fn r04_pass2_reconciliation_catches_cpp_only_dependency() {
// The whole reason the LDF resolver is 2-pass instead of single-pass
// BFS: a lib's `.cpp` may pull in a second lib that the first lib's
// `.h` does NOT mention. Pass 1 (BFS from project seeds + reached
// headers) cannot see that edge; pass 2 re-seeds with each selected
// lib's full source set and catches it.
//
// Setup: project includes <SPI.h>. SPI.h is silent. SPI.cpp includes
// <Wire.h>. Wire is only reachable through SPI.cpp.
//
// Expected: pass 1 selects {SPI}; pass 2 (with SPI.cpp as a seed)
// selects {SPI, Wire}. A regression that drops the second pass would
// produce {SPI} only and silently miss Wire at link time.
let tmp = TempDir::new().unwrap();
let project_src = tmp.path().join("project").join("src");
write(&project_src.join("main.cpp"), "#include <SPI.h>\n");

let mut spi = lib(tmp.path(), "SPI");
write(
&spi.include_dirs[0].join("SPI.h"),
"// no transitive includes\n",
);
let spi_cpp = spi.include_dirs[0].join("SPI.cpp");
write(&spi_cpp, "#include <Wire.h>\n");
spi.source_files.push(spi_cpp);

let mut wire = lib(tmp.path(), "Wire");
write(&wire.include_dirs[0].join("Wire.h"), "");
let wire_cpp = wire.include_dirs[0].join("Wire.cpp");
write(&wire_cpp, "");
wire.source_files.push(wire_cpp);

let seeds = vec![project_src.join("main.cpp")];
let sel = resolve(&seeds, &[project_src], &[spi, wire]);
assert_eq!(
sel.required_libraries,
vec!["SPI".to_string(), "Wire".to_string()],
"pass 2 reconciliation must catch Wire reached only via SPI.cpp"
);
}

#[test]
fn r03_no_includes_selects_nothing() {
let tmp = TempDir::new().unwrap();
Expand Down
Loading