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
59 changes: 59 additions & 0 deletions crates/fbuild-build/src/teensy/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ fn resolve_teensy_framework_library_sources_from_libraries(
}
}

let mut local_headers = HashSet::new();
for root in roots {
collect_header_names(root, &mut local_headers);
}

let mut pending = HashSet::new();
for root in roots {
collect_included_headers(root, &mut pending);
Expand All @@ -125,6 +130,9 @@ fn resolve_teensy_framework_library_sources_from_libraries(
let mut selected = HashSet::new();
let mut queue: Vec<String> = pending.iter().cloned().collect();
while let Some(header) = queue.pop() {
if local_headers.contains(&header) {
continue;
}
let Some(&library_idx) = header_to_library.get(&header) else {
continue;
};
Expand Down Expand Up @@ -737,4 +745,55 @@ mod tests {
vec![wrapper_dir.join("NeedsSpi.cpp"), spi_dir.join("SPI.cpp")]
);
}

#[test]
fn test_resolve_teensy_framework_libraries_prefers_local_fastled_over_framework() {
let tmp = tempfile::TempDir::new().unwrap();
let project_src = tmp.path().join("project").join("src");
let project_lib = tmp
.path()
.join("project")
.join("lib")
.join("FastLED")
.join("src");
std::fs::create_dir_all(&project_src).unwrap();
std::fs::create_dir_all(&project_lib).unwrap();
std::fs::write(project_src.join("main.cpp"), "#include <FastLED.h>\n").unwrap();
std::fs::write(project_lib.join("FastLED.h"), "#include <SPI.h>\n").unwrap();
std::fs::write(project_lib.join("FastLED.cpp"), "").unwrap();

let framework_fastled_dir = tmp
.path()
.join("framework")
.join("libraries")
.join("FastLED");
std::fs::create_dir_all(&framework_fastled_dir).unwrap();
std::fs::write(framework_fastled_dir.join("FastLED.h"), "").unwrap();
std::fs::write(framework_fastled_dir.join("FastLED.cpp"), "").unwrap();

let spi_dir = tmp.path().join("framework").join("libraries").join("SPI");
std::fs::create_dir_all(&spi_dir).unwrap();
std::fs::write(spi_dir.join("SPI.h"), "").unwrap();
std::fs::write(spi_dir.join("SPI.cpp"), "").unwrap();

let libraries = vec![
TeensyFrameworkLibrary {
name: "FastLED".to_string(),
dir: framework_fastled_dir.clone(),
include_dirs: vec![framework_fastled_dir.clone()],
source_files: vec![framework_fastled_dir.join("FastLED.cpp")],
},
TeensyFrameworkLibrary {
name: "SPI".to_string(),
dir: spi_dir.clone(),
include_dirs: vec![spi_dir.clone()],
source_files: vec![spi_dir.join("SPI.cpp")],
},
];

let roots = vec![project_src, project_lib];
let sources = resolve_teensy_framework_library_sources_from_libraries(&libraries, &roots);

assert_eq!(sources, vec![spi_dir.join("SPI.cpp")]);
}
}
141 changes: 140 additions & 1 deletion crates/fbuild-build/tests/teensy_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@
//! Run with: `uv run soldr cargo test -p fbuild-build --test teensy_build -- --ignored`

use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use fbuild_build::{BuildOrchestrator, BuildParams};
use fbuild_core::BuildProfile;

fn copy_dir_recursive(src: &Path, dst: &Path) {
fs::create_dir_all(dst).unwrap();
for entry in fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path);
} else {
fs::copy(&src_path, &dst_path).unwrap();
}
}
}

/// Build a self-contained Teensy 4.1 blink sketch.
///
/// This test requires Internet access (first run only, then cached).
Expand Down Expand Up @@ -212,3 +226,128 @@ fn build_teensy41_fixture() {

eprintln!("Build succeeded in {:.1}s", result.build_time_secs);
}

/// Build a Teensy 3.0 fixture where a project-local lib/FastLED shadows the bundled framework.
#[test]
#[ignore]
fn build_teensy30_fixture_prefers_local_fastled() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let fixture_dir = manifest_dir
.parent()
.unwrap()
.parent()
.unwrap()
.join("tests/platform/teensy30");

if !fixture_dir.exists() {
eprintln!("SKIP: {} does not exist", fixture_dir.display());
return;
}

let tmp = tempfile::TempDir::new().unwrap();
let project_dir = tmp.path().join("project");
copy_dir_recursive(&fixture_dir, &project_dir);

fs::create_dir_all(project_dir.join("lib/FastLED/src")).unwrap();
fs::write(
project_dir.join("lib/FastLED/src/FastLED.h"),
"\
#pragma once
#include <Arduino.h>

namespace fastled_fixture {
void begin();
}
",
)
.unwrap();
fs::write(
project_dir.join("lib/FastLED/src/FastLED.cpp"),
"\
#include <FastLED.h>

namespace fastled_fixture {
void begin() {
pinMode(LED_BUILTIN, OUTPUT);
}
}
",
)
.unwrap();
fs::write(
project_dir.join("src/main.ino"),
"\
#include <FastLED.h>

void setup() {
fastled_fixture::begin();
}

void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
",
)
.unwrap();

let params = BuildParams {
project_dir: project_dir.clone(),
env_name: "teensy30".to_string(),
clean: true,
profile: BuildProfile::Release,
build_dir: tmp.path().join(".fbuild/build"),
verbose: true,
jobs: None,
generate_compiledb: false,
compiledb_only: false,
log_sender: None,
symbol_analysis: false,
symbol_analysis_path: None,
no_timestamp: false,
src_dir: None,
pio_env: Default::default(),
extra_build_flags: Vec::new(),
watch_set_cache: None,
};

let orchestrator = fbuild_build::teensy::orchestrator::TeensyOrchestrator;
let result = orchestrator
.build(&params)
.expect("Teensy 3.0 local FastLED shadow build should succeed");

assert!(result.success);
let firmware_path = result.firmware_path.expect("should produce hex");
assert!(firmware_path.exists());
let build_dir = result
.elf_path
.as_ref()
.and_then(|path| path.parent())
.expect("elf path should live in the build output directory")
.to_path_buf();

let local_fastled_objects: Vec<_> = fs::read_dir(build_dir.join("lib").join("FastLED"))
.unwrap()
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().to_string())
.filter(|name| name.starts_with("FastLED_") && name.ends_with(".cpp.o"))
.collect();
assert!(
!local_fastled_objects.is_empty(),
"expected local lib/FastLED to compile"
);

let framework_fastled_objects: Vec<_> = fs::read_dir(build_dir.join("core"))
.unwrap()
.filter_map(|entry| entry.ok())
.map(|entry| entry.file_name().to_string_lossy().to_string())
.filter(|name| name.starts_with("FastLED_") && name.ends_with(".cpp.o"))
.collect();
assert!(
framework_fastled_objects.is_empty(),
"bundled Teensy framework FastLED should be shadowed by lib/FastLED, found {:?}",
framework_fastled_objects
);
}
Loading