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: 1 addition & 1 deletion crates/fbuild-build/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ fn command_hash_path(object: &Path) -> PathBuf {
/// enforced by `clippy.toml` (1.75). Does not canonicalize symlinks or `..`.
/// Falls back to the original path if `current_dir()` fails (e.g. cwd was
/// deleted) — callers should treat that as the path they originally got.
fn absolute_from_cwd(path: &Path) -> PathBuf {
pub fn absolute_from_cwd(path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
Expand Down
84 changes: 84 additions & 0 deletions crates/fbuild-build/src/pipeline/project_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,21 @@ use std::path::{Path, PathBuf};
/// Add the project's `include/` directory and `lib/` subdirectories to include paths.
///
/// PlatformIO automatically adds these — replicate that behavior.
///
/// All emitted paths are absolute. When `project_dir` is relative, it is first
/// resolved against the current working directory (see `absolute_from_cwd`).
/// This is load-bearing for the compiler step: the zccache path normalizer
/// (`zccache::path_arg_for_compile_cwd`) only strips the compile-cwd prefix
/// from *absolute* include paths and passes relative ones through unchanged.
/// Compiles run with `cwd = <project>/`, so a relative include like
/// `.build/pio/esp32dev/lib/FastLED` would be re-resolved against the
/// already-project-rooted cwd and yield a doubled path that GCC then fails to
/// open (`fatal error: FastLED.h: No such file or directory`). Promoting
/// `project_dir` to absolute up front keeps the include paths stable through
/// the normalize→exec chain. See FastLED/fbuild#303.
pub fn discover_project_includes(project_dir: &Path, include_dirs: &mut Vec<PathBuf>) {
let project_dir = crate::compiler::absolute_from_cwd(project_dir);

// PlatformIO automatically includes the project's include/ directory
let include_dir = project_dir.join("include");
if include_dir.is_dir() {
Expand Down Expand Up @@ -68,3 +82,73 @@ pub fn is_platform_project(
}
false
}

#[cfg(test)]
mod tests {
use super::*;

/// FastLED/fbuild#303: every emitted include dir must be absolute, even
/// when `project_dir` is passed in relative form (which is what
/// `fbuild test-emu .build/pio/<env>` produces). A relative include path
/// survives `zccache::path_arg_for_compile_cwd` unchanged and then gets
/// re-resolved against `cwd = <project>/`, producing a doubled-prefix
/// non-existent path. Promoting to absolute up front breaks the cycle.
#[test]
fn includes_are_absolute_for_absolute_project_dir() {
let tmp = tempfile::tempdir().unwrap();
let project = tmp.path();
std::fs::create_dir_all(project.join("lib").join("FastLED")).unwrap();
std::fs::write(project.join("lib").join("FastLED").join("FastLED.h"), b"").unwrap();
std::fs::create_dir_all(project.join("include")).unwrap();

let mut dirs = Vec::new();
discover_project_includes(project, &mut dirs);

assert!(!dirs.is_empty(), "should discover include + lib paths");
for d in &dirs {
assert!(
d.is_absolute(),
"include dir must be absolute, got: {}",
d.display()
);
}
// The lib's root must be in the list — that's where FastLED.h lives.
assert!(
dirs.iter().any(|d| d.ends_with("FastLED")),
"lib/FastLED root missing from {:?}",
dirs
);
}

/// Same property, but with a relative `project_dir` — mirrors what the
/// daemon receives from `fbuild test-emu .build/pio/esp32dev` (the
/// `PathBuf` is built directly from the request string and never
/// canonicalized in the handler).
#[test]
fn includes_are_absolute_for_relative_project_dir() {
let tmp = tempfile::tempdir().unwrap();
let project = tmp.path();
std::fs::create_dir_all(project.join("lib").join("FastLED")).unwrap();

// Build a relative path against the current process cwd that points
// at our tempdir. If the tempdir isn't under cwd (varies by platform
// and TMPDIR), fall back to the absolute path — the absolutization
// invariant must still hold either way.
let cwd = std::env::current_dir().unwrap();
let relative = match project.strip_prefix(&cwd) {
Ok(rel) => PathBuf::from(".").join(rel),
Err(_) => project.to_path_buf(),
};

let mut dirs = Vec::new();
discover_project_includes(&relative, &mut dirs);

for d in &dirs {
assert!(
d.is_absolute(),
"include dir must be absolute even when project_dir is relative; got: {}",
d.display()
);
}
}
}
Loading