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
6 changes: 3 additions & 3 deletions ci/platform_boundary_research.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 565 attr_cfg #[cfg(not(windows
crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 570 attr_cfg #[cfg(not(windows))] host_executable host_mechanic
crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 728 attr_cfg #[cfg(windows)] host_executable host_mechanic
crates/fbuild-toolchain/src/toolchain/esp_qemu.rs 755 attr_cfg #[cfg(windows)] host_executable host_mechanic
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 224 attr_cfg #[cfg_attr(not(target_os=),allow(dead_code))] host_executable host_artifact_policy
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 266 attr_cfg #[cfg(not(target_os=))] host_executable host_artifact_policy
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 272 attr_cfg #[cfg(target_os=)] host_executable host_artifact_policy
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 253 attr_cfg #[cfg_attr(not(target_os=),allow(dead_code))] host_executable host_artifact_policy
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 295 attr_cfg #[cfg(not(target_os=))] host_executable host_artifact_policy
crates/fbuild-toolchain/src/toolchain/esp_qemu_runtime.rs 301 attr_cfg #[cfg(target_os=)] host_executable host_artifact_policy
crates/fbuild-toolchain/tests/qemu_linux_runtime.rs 18 attr_cfg #![cfg(target_os=)] host_executable host_artifact_policy
20 changes: 15 additions & 5 deletions crates/fbuild-build-engine/src/framework_libs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,29 +95,39 @@ pub fn resolve_framework_library_selection_active_declared(
)
}

/// Active framework selection with additional translation-unit seeds and
/// include roots supplied by externally declared libraries.
/// Active framework selection with additional translation-unit seeds and the
/// compiler's complete include path.
///
/// An external library can include a framework header from one of its own
/// `.cpp` files. The compiler sees that dependency, so the LDF must see it as
/// well or the selected framework archive is omitted from the final link.
/// The complete include path also lets the scanner resolve SDK headers that
/// define capability macros guarding later framework-library includes (for
/// example `soc/soc_caps.h` guarding ESP32's `LittleFS.h`).
pub fn resolve_framework_library_selection_active_declared_with_extra(
libraries: &[FrameworkLibrary],
project_dir: &Path,
src_dir: &Path,
defines: &HashMap<String, String>,
declared: &[String],
extra_source_files: &[PathBuf],
extra_include_dirs: &[PathBuf],
compiler_include_dirs: &[PathBuf],
) -> fbuild_library_select::Selection {
let roots = framework_include_scan_roots(project_dir, src_dir);
let filtered = filter_framework_libs_shadowed_by_project(libraries, &roots);
let mut seeds = collect_project_seeds(&roots);
seeds.extend_from_slice(extra_source_files);
let mut search_paths = project_search_paths(&roots);
for include_dir in extra_include_dirs {
// Preserve the compiler's observable include order. In particular, ESP32
// searches core/variant/SDK headers before project headers; reversing that
// order can make the LDF inspect a shadowing header the compiler never
// sees and derive the wrong capability set.
let mut search_paths = Vec::new();
for include_dir in compiler_include_dirs {
push_existing_unique(&mut search_paths, include_dir.clone());
}
for project_path in project_search_paths(&roots) {
push_existing_unique(&mut search_paths, project_path);
}
fbuild_library_select::resolve_with_stats_active_declared(
&seeds,
&search_paths,
Expand Down
100 changes: 100 additions & 0 deletions crates/fbuild-build-engine/src/framework_libs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,103 @@ fn external_library_source_selects_framework_dependency() {

assert_eq!(selection.required_libraries, vec!["WiFi"]);
}

/// An SDK header on the compiler include path can define a capability macro
/// that guards a later framework-library include. The selection scan must see
/// the same include path as the compiler or it incorrectly prunes that branch.
#[test]
fn sdk_capability_header_keeps_framework_library_include_reachable() {
let tmp = tempfile::TempDir::new().unwrap();
let project_dir = tmp.path().join("project");
let src_dir = project_dir.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
std::fs::write(
src_dir.join("main.cpp"),
"#include <soc/soc_caps.h>\n\
#if defined(SOC_WIFI_SUPPORTED) && SOC_WIFI_SUPPORTED\n\
#include <LittleFS.h>\n\
#endif\n",
)
.unwrap();

let sdk_dir = tmp.path().join("sdk");
std::fs::create_dir_all(sdk_dir.join("soc")).unwrap();
std::fs::write(
sdk_dir.join("soc").join("soc_caps.h"),
"#define SOC_WIFI_SUPPORTED 1\n",
)
.unwrap();

let littlefs_dir = tmp
.path()
.join("framework")
.join("libraries")
.join("LittleFS");
std::fs::create_dir_all(&littlefs_dir).unwrap();
std::fs::write(littlefs_dir.join("LittleFS.h"), "").unwrap();
std::fs::write(littlefs_dir.join("LittleFS.cpp"), "int littlefs;\n").unwrap();

let selection = resolve_framework_library_selection_active_declared_with_extra(
&[FrameworkLibrary {
name: "LittleFS".to_string(),
dir: littlefs_dir.clone(),
include_dirs: vec![littlefs_dir.clone()],
source_files: vec![littlefs_dir.join("LittleFS.cpp")],
}],
&project_dir,
&src_dir,
&HashMap::new(),
&[],
&[],
&[sdk_dir],
);

assert_eq!(selection.required_libraries, vec!["LittleFS"]);
}

#[test]
fn compiler_include_order_wins_over_shadowing_project_header() {
let tmp = tempfile::TempDir::new().unwrap();
let project_dir = tmp.path().join("project");
let src_dir = project_dir.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
std::fs::write(src_dir.join("main.cpp"), "#include <soc/capability.h>\n").unwrap();
std::fs::create_dir_all(src_dir.join("soc")).unwrap();
std::fs::write(
src_dir.join("soc").join("capability.h"),
"#define PROJECT_SHADOW 1\n",
)
.unwrap();

let sdk_dir = tmp.path().join("sdk");
std::fs::create_dir_all(sdk_dir.join("soc")).unwrap();
let sdk_header = sdk_dir.join("soc").join("capability.h");
std::fs::write(&sdk_header, "#define SDK_HEADER 1\n").unwrap();

let selection = resolve_framework_library_selection_active_declared_with_extra(
&[],
&project_dir,
&src_dir,
&HashMap::new(),
&[],
&[],
&[sdk_dir],
);

assert!(
selection
.included_files
.contains(&sdk_header.canonicalize().unwrap()),
"selection must resolve headers using compiler include order"
);
assert!(
!selection.included_files.contains(
&src_dir
.join("soc")
.join("capability.h")
.canonicalize()
.unwrap()
),
"a project shadow searched after the SDK must not replace the compiler-selected header"
);
}
6 changes: 2 additions & 4 deletions crates/fbuild-build-esp/src/esp32/orchestrator/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ impl BuildOrchestrator for Esp32Orchestrator {
.flat_map(|library| library.include_dirs.iter().cloned()),
);
let mut external_library_sources = Vec::new();
let mut external_library_include_dirs = Vec::new();

if !lib_deps.is_empty() {
let libs_dir = build_dir.join("libs");
Expand Down Expand Up @@ -397,8 +396,7 @@ impl BuildOrchestrator for Esp32Orchestrator {
// cross-project cache hits. Library includes are same-tier, so a
// stable sort is safe for include resolution.
external_library_sources = lib_result.source_files;
external_library_include_dirs = lib_result.include_dirs;
let mut lib_include_dirs = external_library_include_dirs.clone();
let mut lib_include_dirs = lib_result.include_dirs;
lib_include_dirs.sort();
include_dirs.extend(lib_include_dirs);
library_archives = lib_result.archives;
Expand Down Expand Up @@ -429,7 +427,7 @@ impl BuildOrchestrator for Esp32Orchestrator {
&library_selection_defines,
&declared_lib_deps,
&external_library_sources,
&external_library_include_dirs,
&include_dirs,
);
let selected_framework_libraries: Vec<_> = framework_libraries
.iter()
Expand Down
103 changes: 102 additions & 1 deletion crates/fbuild-build/tests/esp32_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ async fn build_esp32c6_blink() {

fs::write(
project_dir.join("platformio.ini"),
"[env:esp32c6]\nplatform = espressif32\nboard = esp32-c6\nframework = arduino\n",
"[env:esp32c6]\nplatform = espressif32\nboard = esp32-c6-devkitc-1\nframework = arduino\n",
)
.unwrap();

Expand Down Expand Up @@ -242,6 +242,107 @@ void loop() {
);
}

/// Regression gate for #1452: SDK capability headers participate in library
/// discovery, so a guarded LittleFS include selects both LittleFS and its FS
/// dependency without a manual `lib_deps` declaration.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
#[ignore = "downloads ESP32 toolchain (~hundreds of MB)"]
async fn build_esp32c6_discovers_guarded_littlefs() {
install_test_compile_backend().await;
let tmp = tempfile::TempDir::new().unwrap();
let project_dir = tmp.path();

fs::write(
project_dir.join("platformio.ini"),
"[env:esp32c6]\nplatform = espressif32\nboard = esp32-c6-devkitc-1\nframework = arduino\n",
)
.unwrap();

let src_dir = project_dir.join("src");
fs::create_dir_all(&src_dir).unwrap();
fs::write(
src_dir.join("platform_flags.h"),
"#define FL_IS_ESP32 1\n#define FL_PLATFORM_HAS_LARGE_MEMORY 1\n",
)
.unwrap();
fs::write(
src_dir.join("main.cpp"),
"\
#include <Arduino.h>
#include \"platform_flags.h\"
#include <soc/soc_caps.h>

#if !defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && !FL_PLATFORM_HAS_LARGE_MEMORY
#define FASTLED_AUTORESEARCH_LOW_MEMORY 1
#endif

#if !(defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && FASTLED_AUTORESEARCH_LOW_MEMORY)
#if defined(FL_IS_ESP32) && defined(SOC_WIFI_SUPPORTED) && SOC_WIFI_SUPPORTED
#include <LittleFS.h>
#endif
#endif

void setup() {
#if !(defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && FASTLED_AUTORESEARCH_LOW_MEMORY)
#if defined(FL_IS_ESP32) && defined(SOC_WIFI_SUPPORTED) && SOC_WIFI_SUPPORTED
LittleFS.begin(true);
File file = LittleFS.open(\"/probe\", FILE_WRITE);
file.print(\"ok\");
file.close();
#endif
#endif
}

void loop() {}
",
)
.unwrap();

let build_dir = project_dir.join(format!(
"{}/{}/esp32c6/release",
fbuild_paths::FBUILD_DIR_NAME,
fbuild_paths::BUILD_DIR_NAME
));
let params = BuildParams {
project_dir: project_dir.to_path_buf(),
env_name: "esp32c6".to_string(),
clean_all: false,
clean: true,
clean_only: false,
profile: BuildProfile::Release,
build_dir: build_dir.clone(),
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,
bloat_analysis: false,
caller_path: None,
};

let orchestrator = fbuild_build::esp32::orchestrator::Esp32Orchestrator;
let result = under_test_timeout(orchestrator.build(&params))
.await
.expect("ESP32-C6 LittleFS build should succeed without lib_deps");

assert!(result.success);
assert!(
build_dir.join("fw_libs/liblittlefs.a").is_file(),
"LittleFS must be auto-discovered from the guarded sketch include"
);
assert!(
build_dir.join("fw_libs/libfs.a").is_file(),
"FS must be discovered transitively from LittleFS.h"
);
}

/// Build a self-contained ESP32-C3 blink sketch (RISC-V).
///
/// ESP32-C3 uses the rv32imc RISC-V ISA. This test validates the full build
Expand Down
4 changes: 2 additions & 2 deletions crates/fbuild-header-scan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ mod scanner;
mod walker;

pub use scanner::{
IncludeKind, IncludeRef, Span, active_defines, defined_macro_names, scan, scan_active,
scan_active_with_known,
IncludeKind, IncludeRef, Span, active_defines, active_defines_with_known, defined_macro_names,
scan, scan_active, scan_active_with_known,
};
pub use walker::{
WalkResult, WalkState, collect_defined_macro_names, walk, walk_active, walk_with_state,
Expand Down
12 changes: 11 additions & 1 deletion crates/fbuild-header-scan/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,18 @@ pub fn defined_macro_names(src: &str) -> Vec<String> {
/// The LDF uses this for sketch translation units so a sketch-local feature
/// define remains visible when its included headers are scanned.
pub fn active_defines(src: &str, defines: &HashMap<String, String>) -> HashMap<String, String> {
active_defines_with_known(src, defines, &HashSet::new())
}

/// Return active source-local defines while treating macros defined elsewhere
/// in the reachable corpus as undecidable.
pub fn active_defines_with_known(
src: &str,
defines: &HashMap<String, String>,
defined_somewhere: &HashSet<String>,
) -> HashMap<String, String> {
let mut macros = defines.clone();
let _ = active_source(src, &mut macros, &HashSet::new());
let _ = active_source(src, &mut macros, defined_somewhere);
macros
}

Expand Down
30 changes: 30 additions & 0 deletions crates/fbuild-header-scan/src/scanner_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,33 @@ fn a_true_operand_settles_or_despite_an_undecidable_one() {
let paths: Vec<&str> = refs.iter().map(|r| r.path.as_str()).collect();
assert_eq!(paths, vec!["taken.h"], "{paths:?}");
}

#[test]
fn nested_unknown_fastled_guards_remain_visible() {
let known: HashSet<String> = [
"FASTLED_AUTORESEARCH_LOW_MEMORY",
"FL_PLATFORM_HAS_LARGE_MEMORY",
"FL_IS_ESP32",
"SOC_WIFI_SUPPORTED",
]
.into_iter()
.map(str::to_string)
.collect();
let refs = scan_active_with_known(
"#if !defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && !FL_PLATFORM_HAS_LARGE_MEMORY\n\
#define FASTLED_AUTORESEARCH_LOW_MEMORY 1\n\
#endif\n\
#if !(defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && FASTLED_AUTORESEARCH_LOW_MEMORY)\n\
#if defined(FL_IS_ESP32)\n\
#include <soc/soc_caps.h>\n\
#endif\n\
#if defined(FL_IS_ESP32) && defined(SOC_WIFI_SUPPORTED) && SOC_WIFI_SUPPORTED\n\
#include <LittleFS.h>\n\
#endif\n\
#endif\n",
&HashMap::new(),
&known,
);
let paths: Vec<&str> = refs.iter().map(|r| r.path.as_str()).collect();
assert!(paths.contains(&"LittleFS.h"), "{paths:?}");
}
2 changes: 1 addition & 1 deletion crates/fbuild-library-select/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub const SCANNER_VERSION: u32 = 3;

/// Bump when the resolver's 2-pass LDF semantics change (seed expansion,
/// attribution, convergence rule, etc.).
pub const LDF_MODE_VERSION: u32 = 6;
pub const LDF_MODE_VERSION: u32 = 7;

/// Namespace for the library-selection file cache.
pub const NAMESPACE: &str = "library-selection";
Expand Down
Loading
Loading