Skip to content

fix(esp32): strip Windows UNC prefix + collect .S/.s assembly sources (FastLED #2507)#273

Merged
zackees merged 1 commit into
mainfrom
fix/esp32p4-include-paths
May 24, 2026
Merged

fix(esp32): strip Windows UNC prefix + collect .S/.s assembly sources (FastLED #2507)#273
zackees merged 1 commit into
mainfrom
fix/esp32p4-include-paths

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented May 24, 2026

Summary

Two bugs in the Windows compile path prevent FastLED's AutoResearch sketch from building for esp32p4 via fbuild. Both fixed here.

Cross-ref: FastLED/FastLED#2507 (meta issue), FastLED/FastLED#2493 (the PARLIO perf-trace work this unblocks).

Bug 1 — \?\ UNC prefix mangled into //?/ in response files

Path::canonicalize on Windows returns paths prefixed with the extended-length namespace marker \?\. That prefix only works with backslash separators, but fbuild-core::response_file::replace_path_backslashes rewrites all backslashes to forward slashes so gcc doesn't interpret them as escape sequences. The result was -I//?/C:/… on every include directory.

Some headers happened to resolve (gcc tolerates the prefix on some lookups) but chip-specific SoC headers like soc/soc_caps.h did not:

cores/esp32/USBMSC.h:17:10: fatal error: soc/soc_caps.h: No such file
   17 | #include "soc/soc_caps.h"

Fix: strip the \?\ prefix in canonicalize_existing_path (cfg(windows) only). New helper strip_unc_prefix removes both \?\ and the defensively-handled //?/ form, leaving C:\… which survives the backslash→slash rewrite as the standard C:/… form gcc accepts. path_arg_for_compile_cwd also strips from its cwd argument so strip_prefix matches when callers pass an un-stripped canonical path.

Trade-off: long-path support (>260 chars) is lost for stripped paths, but the cache root is <home>/.fbuild which is well under the Windows limit in practice.

Bug 2 — .S/.s assembly files silently dropped from library archives

library_info::is_source_file only matched .c/.cpp/.cc/.cxx, so hand-written assembly stubs were never compiled into library archives. On esp32p4 the FastLED RX ISR path (fl::GpioIsrRxMcpwm::beginsrc/platforms/esp/32/drivers/gpio_isr_rx/fast_isr.Sgpio_fast_edge_isr) failed at link:

ld.exe: libFastLED.a(platforms+_f948.o): in function
`fl::GpioIsrRxMcpwm::begin(fl::RxConfig const&)':
undefined reference to `gpio_fast_edge_isr'

Fix: extend is_source_file to include S and s extensions.

Tests

  • cargo test --release -p fbuild-build --lib zccache:: — 7/7 pass (5 existing + 2 new for the strip helper).
  • The existing compile_cwd_from_output_canonicalizes_existing_workspace test was updated to reflect the new contract (calls strip_unc_prefix on its expected value).

End-to-end verification

Before: bash compile esp32p4 --examples AutoResearch --no-filter failed during framework lib compile with soc/soc_caps.h: No such file.

After: succeeds — 1.7 MB firmware.bin produced in ~100 s.

build succeeded in 99.7s (flash: 1785050 bytes, ram: 1288852 bytes)
✅ Compilation succeeded (fbuild) [100.0s]
SUCCESS: AutoResearch

Test plan

  • cargo test --release -p fbuild-build --lib zccache::
  • End-to-end build of FastLED AutoResearch sketch for esp32p4 via fbuild
  • Sanity check on Linux/macOS that the cfg(windows) gate doesn't regress non-Windows compiles

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Resolved Windows path canonicalization issues affecting cache consistency and build reliability.
  • New Features

    • Added support for assembly source files (.S and .s extensions) in library builds.

Review Change Stack

…LED #2507)

Two bugs together prevented FastLED's AutoResearch sketch from building
for esp32p4 via fbuild on Windows.

## Bug 1 — \?\ UNC prefix mangled into //?/ in response files

`crates/fbuild-build/src/zccache.rs::canonicalize_existing_path` used
`Path::canonicalize`, which on Windows returns paths prefixed with
`\?\` (extended-length namespace marker). That prefix only works with
backslash separators, but the response-file writer
(`fbuild-core::response_file::replace_path_backslashes`) rewrites all
backslashes to forward slashes so gcc doesn't interpret them as escape
sequences. The result was `-I//?/C:/…` on every include directory.

Some headers happened to resolve (gcc seems to tolerate the prefix on
some lookups) but chip-specific SoC headers like `soc/soc_caps.h` did
not. On esp32p4 this surfaced as:

  cores/esp32/USBMSC.h:17:10: fatal error: soc/soc_caps.h: No such file
     17 | #include "soc/soc_caps.h"

Fix: strip the `\?\` prefix in `canonicalize_existing_path` (cfg(windows)
only). `strip_unc_prefix` removes both `\?\` and the defensively-handled
`//?/` form, leaving `C:\…` which survives the backslash→slash rewrite
as the standard `C:/…` form gcc accepts. `path_arg_for_compile_cwd`
also strips the prefix from its `cwd` argument so `strip_prefix` matches
when callers pass an un-stripped canonical path.

Trade-off: long-path support (>260 chars) is lost for stripped paths,
but the cache root is `<home>/.fbuild` which is well under the limit.

Two new unit tests cover the strip helper and the existing
`compile_cwd_from_output` test was updated to reflect the new contract.

## Bug 2 — .S/.s assembly files silently dropped from library archives

`crates/fbuild-packages/src/library/library_info.rs::is_source_file`
only matched `.c/.cpp/.cc/.cxx`, so hand-written assembly stubs were
never compiled into the library archive. On esp32p4 the FastLED RX
ISR path (`fl::GpioIsrRxMcpwm::begin` →
`src/platforms/esp/32/drivers/gpio_isr_rx/fast_isr.S` →
`gpio_fast_edge_isr`) failed at link with:

  ld.exe: libFastLED.a(platforms+_f948.o): in function
  `fl::GpioIsrRxMcpwm::begin(fl::RxConfig const&)':
  undefined reference to `gpio_fast_edge_isr'

Fix: extend `is_source_file` to include `S` and `s` extensions so the
project-as-library and per-library scanners pick them up.

## Verification

- `cargo test --release -p fbuild-build --lib zccache::` — 7/7 pass.
- End-to-end on the failing FastLED workload:
  `bash compile esp32p4 --examples AutoResearch --no-filter` now
  succeeds, producing a 1.7 MB firmware.bin in ~100 s.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

Windows zccache paths are normalized by stripping UNC extended-length prefixes before computing cache keys, ensuring consistency across different canonicalization behaviors. Additionally, assembly source files (.S/.s) are now recognized during library discovery to properly link hand-written interrupt and architecture-specific code.

Changes

Path normalization and source file discovery

Layer / File(s) Summary
UNC extended-length prefix normalization
crates/fbuild-build/src/zccache.rs
strip_unc_prefix removes Windows \\?\ and //?/ extended-length markers for GCC-compatible paths; returns input unchanged on non-Windows.
Path canonicalization with UNC stripping
crates/fbuild-build/src/zccache.rs
canonicalize_existing_path and path_arg_for_compile_cwd apply UNC prefix stripping to canonicalized paths to keep zccache keys stable on Windows.
Windows path canonicalization tests
crates/fbuild-build/src/zccache.rs
Test expectations updated to match strip_unc_prefix behavior, and new Windows-only tests verify extended-length marker removal and idempotency.
Assembly file source recognition
crates/fbuild-packages/src/library/library_info.rs
is_source_file now recognizes .S and .s assembly files so libraries with hand-written interrupt service routines and architecture stubs contribute symbols to linking.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Possibly related PRs

  • FastLED/fbuild#193: Both PRs modify zccache path normalization; #193 introduces path_arg_for_compile_cwd with canonicalization, and this PR extends it with UNC prefix stripping for stable Windows cache keys.

Poem

🐰 Paths on Windows twist and turn,
UNC prefixes we must unlearn,
Assembly files now join the crew,
Cache keys stable, symbols true! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the two main changes: Windows UNC prefix stripping and assembly source collection for esp32.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/esp32p4-include-paths

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/fbuild-packages/src/library/library_info.rs (2)

180-192: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add test coverage for assembly file recognition.

The change adds .S and .s support to fix real link failures, but test_source_files_basic only verifies C/C++ files. Given the importance of this fix (preventing undefined reference errors for hand-written ISRs), a test that explicitly creates and verifies assembly files are discovered would prevent regressions.

🧪 Suggested test
#[test]
fn test_source_files_includes_assembly() {
    let tmp = tempfile::TempDir::new().unwrap();
    let src = tmp.path().join("src");
    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("main.cpp"), "").unwrap();
    std::fs::write(src.join("isr.S"), "").unwrap();
    std::fs::write(src.join("lowlevel.s"), "").unwrap();

    let lib = InstalledLibrary::new(tmp.path(), "test");
    let files = lib.get_source_files();
    assert_eq!(files.len(), 3);
    assert!(files.iter().any(|f| f.file_name().unwrap() == "isr.S"));
    assert!(files.iter().any(|f| f.file_name().unwrap() == "lowlevel.s"));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fbuild-packages/src/library/library_info.rs` around lines 180 - 192,
Add a new unit test (e.g., test_source_files_includes_assembly) next to
test_source_files_basic that creates a temp src dir, writes files including
"isr.S" and "lowlevel.s" alongside a C/C++ file, constructs
InstalledLibrary::new(..., "test"), calls InstalledLibrary::get_source_files(),
and asserts the returned list length and that it contains entries with
file_name() equal to "isr.S" and "lowlevel.s" to ensure assembly (.S and .s)
files are discovered.

69-69: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the doc comment to reflect assembly file support.

The comment claims this method returns only .c, .cpp, .cc, .cxx files, but after the change at line 132, it also returns .S and .s assembly files.

📝 Proposed fix
-    /// Get all source files (.c, .cpp, .cc, .cxx) in the library.
+    /// Get all source files (.c, .cpp, .cc, .cxx, .S, .s) in the library.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fbuild-packages/src/library/library_info.rs` at line 69, Update the
doc comment above the method that "Get all source files (.c, .cpp, .cc, .cxx) in
the library." to include assembly file extensions (.S and .s) so it matches the
implementation change at line 132; edit the triple-slash Rust doc comment for
the library source files getter to list ".c, .cpp, .cc, .cxx, .S, .s" (or
equivalent phrasing) and ensure the comment remains concise and accurate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fbuild-build/src/zccache.rs`:
- Around line 447-451: The test function
strip_unc_prefix_removes_extended_length_marker has an assert_eq! that fails
rustfmt; run cargo fmt (or rustfmt) and reformat this test so the assert_eq! is
split across multiple lines per rustfmt rules (compare left and right operands
on separate lines), ensuring the call to strip_unc_prefix and the expected
PathBuf are formatted accordingly; locate the assert_eq! in
strip_unc_prefix_removes_extended_length_marker and apply cargo fmt to fix the
formatting.

---

Outside diff comments:
In `@crates/fbuild-packages/src/library/library_info.rs`:
- Around line 180-192: Add a new unit test (e.g.,
test_source_files_includes_assembly) next to test_source_files_basic that
creates a temp src dir, writes files including "isr.S" and "lowlevel.s"
alongside a C/C++ file, constructs InstalledLibrary::new(..., "test"), calls
InstalledLibrary::get_source_files(), and asserts the returned list length and
that it contains entries with file_name() equal to "isr.S" and "lowlevel.s" to
ensure assembly (.S and .s) files are discovered.
- Line 69: Update the doc comment above the method that "Get all source files
(.c, .cpp, .cc, .cxx) in the library." to include assembly file extensions (.S
and .s) so it matches the implementation change at line 132; edit the
triple-slash Rust doc comment for the library source files getter to list ".c,
.cpp, .cc, .cxx, .S, .s" (or equivalent phrasing) and ensure the comment remains
concise and accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 64abf862-1264-48f7-a131-07f69b6c5ca2

📥 Commits

Reviewing files that changed from the base of the PR and between 5fb20a5 and f7f4a22.

📒 Files selected for processing (2)
  • crates/fbuild-build/src/zccache.rs
  • crates/fbuild-packages/src/library/library_info.rs

Comment on lines +447 to +451
fn strip_unc_prefix_removes_extended_length_marker() {
let raw = std::path::PathBuf::from(r"\\?\C:\Users\test\.fbuild\cache");
let stripped = strip_unc_prefix(raw);
assert_eq!(stripped, std::path::PathBuf::from(r"C:\Users\test\.fbuild\cache"));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix rustfmt formatting violation.

The pipeline fails because cargo fmt --check expects the assert_eq! on line 450 to be formatted across multiple lines. Run cargo fmt to apply the correct formatting.

🎨 Suggested formatting fix
-    assert_eq!(stripped, std::path::PathBuf::from(r"C:\Users\test\.fbuild\cache"));
+    assert_eq!(
+        stripped,
+        std::path::PathBuf::from(r"C:\Users\test\.fbuild\cache")
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn strip_unc_prefix_removes_extended_length_marker() {
let raw = std::path::PathBuf::from(r"\\?\C:\Users\test\.fbuild\cache");
let stripped = strip_unc_prefix(raw);
assert_eq!(stripped, std::path::PathBuf::from(r"C:\Users\test\.fbuild\cache"));
}
fn strip_unc_prefix_removes_extended_length_marker() {
let raw = std::path::PathBuf::from(r"\\?\C:\Users\test\.fbuild\cache");
let stripped = strip_unc_prefix(raw);
assert_eq!(
stripped,
std::path::PathBuf::from(r"C:\Users\test\.fbuild\cache")
);
}
🧰 Tools
🪛 GitHub Actions: Formatting / 0_Formatting.txt

[error] 447-451: cargo fmt --check failed (rustfmt). Formatting diff detected in test strip_unc_prefix_removes_extended_length_marker: assert_eq! formatting expected multiline layout.

🪛 GitHub Actions: Formatting / Formatting

[error] 447-452: cargo fmt --all -- --check failed due to formatting differences in test assertion. The expected formatting did not match the checked-in code (Diff shows assert_eq formatting changed to multi-line).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fbuild-build/src/zccache.rs` around lines 447 - 451, The test function
strip_unc_prefix_removes_extended_length_marker has an assert_eq! that fails
rustfmt; run cargo fmt (or rustfmt) and reformat this test so the assert_eq! is
split across multiple lines per rustfmt rules (compare left and right operands
on separate lines), ensuring the call to strip_unc_prefix and the expected
PathBuf are formatted accordingly; locate the assert_eq! in
strip_unc_prefix_removes_extended_length_marker and apply cargo fmt to fix the
formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant