Skip to content

feat(ide): clangd/compile-DB foundations for fbuild ide (#1076 Phase 0) - #1197

Merged
zackees merged 1 commit into
mainfrom
feat/1076-phase0-clangd-foundations
Jul 27, 2026
Merged

feat(ide): clangd/compile-DB foundations for fbuild ide (#1076 Phase 0)#1197
zackees merged 1 commit into
mainfrom
feat/1076-phase0-clangd-foundations

Conversation

@zackees

@zackees zackees commented Jul 27, 2026

Copy link
Copy Markdown
Member

Lands Phase 0 of the corrected #1076 plan (the four independently-shippable foundations from the issue's implementation guide + the .ino.prelude.h direction update). Does not close the meta.

What changed

  1. Per-tab #line directives — the converter now emits #line 1 "<tab>" at every tab boundary in the generated .ino.cpp; compile errors in secondary tabs finally report the right file/line.
  2. Prelude headers + raw-.ino compile entries — the converter writes <build_dir>/<stem>.ino.prelude.h (Arduino include + generated prototypes; multi-tab: + preceding tabs' text with their own #line directives), and the written compile_commands.json swaps the generated .ino.cpp entry for one raw-.ino entry per tab with -x c++ -include <prelude>. clangd now analyzes the live buffer of the file you edit while seeing the exact translation unit the build compiles. main.cpp-present mode emits neither preludes nor .ino entries.
  3. Builtin -isystem bakingtranslate_for_clang appends the cross-toolchain's GCC builtin include dirs (sorted, deduped, no-op when toolchains are absent) to every entry, replacing the --query-driver approach.
  4. Editor-neutral clangd-config — split into clangd_config/{mod,vscode,zed}.rs with --editor vscode|zed (default vscode, existing behavior preserved) and --refresh (force DB regeneration); the degenerate query-driver path and the .clangd Compiler: pin (always bare clang++ post-translation — stale doc fixed) are deleted. pub(crate) core functions (ensure_compile_db, emit_clangd_file, emit_editor_config) are the reuse surface for the upcoming fbuild ide verb (Phase 1).

Out of scope, tracked separately: prototype insertion position (#1196), the dead BuildParams.generate_compiledb field, the fbuild ide verb itself.

Validation (local)

  • soldr cargo test -p fbuild-build-engine → 379 passed (12 new); -p fbuild-cli → 214 passed; -p fbuild-build-esp -p fbuild-build-arm → 199 + 96 passed
  • soldr cargo clippy --workspace --all-targets -- -D warnings → exit 0; fmt clean
  • Full bash test run before merge

Part of #1076.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added IDE-ready .ino support, improving navigation and diagnostics across multi-tab sketches.
    • Enhanced compile databases with compiler builtin include paths for more accurate code completion.
    • Expanded fbuild clangd-config with VS Code and Zed support, plus optional refresh behavior.
    • Generates and updates .clangd and editor settings while preserving existing configuration.
  • Documentation

    • Added CLI reference and design guidance for IDE configuration and compile database generation.
  • Bug Fixes

    • Corrected source mapping and preprocessing behavior for Arduino sketches in editor tooling.

- source_scanner: emit #line directives at every .ino tab boundary
  (secondary-tab errors previously reported against the primary sketch);
  emit per-sketch/per-tab <stem>.ino.prelude.h headers (Arduino include
  + generated prototypes + preceding-tab text) via write_if_changed.
- compile DB: swap generated .ino.cpp entries for raw-.ino entries with
  `-x c++ -include <prelude>` so clangd analyzes the file the user
  actually edits while seeing the exact translation unit the build
  compiles; no-op in main.cpp mode.
- translate_for_clang: bake GCC builtin include dirs into every entry
  as deterministic deduped -isystem args.
- clangd-config: editor-neutral refactor (mod/vscode/zed emitters),
  new --editor vscode|zed and --refresh flags, reusable pub(crate)
  core for the upcoming `fbuild ide`; deleted the degenerate
  query-driver path and the bare-clang++ Compiler: pin (stale doc
  fixed) — the DB records the clang-translated argv0, never real GCC.

Part of #1076 (Phase 0). Prototype-placement converter bug split out
as #1196.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change generates per-tab Arduino .ino preludes, propagates them into clang compile databases, adds builtin system include paths, and introduces editor-aware clangd-config support for VS Code and Zed with optional compile database refresh.

Changes

INO preprocessing and compile database integration

Layer / File(s) Summary
INO prelude generation and source wiring
crates/fbuild-build-engine/src/source_scanner.rs, crates/fbuild-build-engine/src/source_scanner/tests.rs, crates/fbuild-build-arm/..., crates/fbuild-build-esp/...
Sketch scanning now returns per-tab prelude mappings, generates #line directives and prelude headers, and stores them in SourceCollection; tests cover multi-tab output, regeneration, and main.cpp mode.
Clang compile database translation
crates/fbuild-build-engine/src/compile_database/clang.rs, crates/fbuild-build-engine/src/compile_database/tests/clang.rs
Translated entries receive sorted, deduplicated GCC builtin -isystem arguments, and generated .ino.cpp entries can be replaced with raw .ino entries that include their preludes.
Compile database pipeline wiring
crates/fbuild-build-engine/src/pipeline/compile.rs, crates/fbuild-build-engine/src/pipeline/sequential.rs, crates/fbuild-build-esp/...
Compile database generation accepts ino_preludes and applies raw-INO swapping in compiledb-only and normal build paths.

clangd-config command

Layer / File(s) Summary
Command orchestration and shared clangd configuration
crates/fbuild-cli/src/cli/args.rs, crates/fbuild-cli/src/cli/dispatch.rs, crates/fbuild-cli/src/cli/clangd_config/mod.rs
The command accepts --editor and --refresh, resolves the PlatformIO environment, ensures compile_commands.json, emits .clangd, and dispatches to an editor emitter.
VS Code and Zed emitters
crates/fbuild-cli/src/cli/clangd_config/vscode.rs, crates/fbuild-cli/src/cli/clangd_config/zed.rs
Editor settings are merged without replacing unrelated configuration; VS Code extensions are created only when absent, while Zed maps .ino files to C++.
CLI documentation
crates/fbuild-cli/src/cli/clangd_config/README.md, docs/DESIGN_DECISIONS.md, docs/reference/cli.md
Documentation describes editor selection, refresh behavior, and on-demand compile database generation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant ClangdConfig
  participant BuildPipeline
  participant CompileDatabase
  participant EditorConfig
  Developer->>ClangdConfig: run clangd-config with editor and refresh options
  ClangdConfig->>BuildPipeline: ensure compile_commands.json
  BuildPipeline->>CompileDatabase: translate entries and swap raw INO entries
  CompileDatabase-->>BuildPipeline: write compile_commands.json
  ClangdConfig->>EditorConfig: emit .clangd and editor settings
  EditorConfig-->>Developer: report generated configuration paths
Loading
🚥 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 summarizes the main change: IDE clangd and compile database foundations for fbuild ide.
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.
✨ 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 feat/1076-phase0-clangd-foundations

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.

@zackees
zackees merged commit a0a6673 into main Jul 27, 2026
88 of 93 checks passed
@zackees
zackees deleted the feat/1076-phase0-clangd-foundations branch July 27, 2026 20:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
crates/fbuild-build-engine/src/source_scanner.rs (1)

362-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate #line-directive-emission logic.

The "emit #line 1 "<path>" + tab content + ensure trailing newline" logic is duplicated between the .ino.cpp body loop and the per-tab prelude loop. The two must stay byte-identical (per test_single_tab_prelude_exact_match_with_generated_ino_cpp); a future edit to one path without the other would silently break that invariant.

♻️ Suggested extraction
+    /// Append `content` to `buf`, preceded by its own `#line 1 "<path>"`
+    /// directive, ensuring a trailing newline.
+    fn append_tab_with_line_directive(&self, buf: &mut String, ino: &Path, content: &str) {
+        buf.push_str(&format!("`#line` 1 \"{}\"\n", self.line_directive_path(ino)));
+        buf.push_str(content);
+        if !content.ends_with('\n') {
+            buf.push('\n');
+        }
+    }

Then both loops call self.append_tab_with_line_directive(&mut body, ino, content) / self.append_tab_with_line_directive(&mut tab_prelude, prior_ino, prior_content).

Also applies to: 433-442

🤖 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-engine/src/source_scanner.rs` around lines 362 - 369,
Extract the repeated line-directive and trailing-newline emission into a shared
helper method such as append_tab_with_line_directive on the surrounding
implementation. Replace the inline logic in both the .ino.cpp body loop and the
per-tab prelude loop with calls to that helper, passing the respective output
buffer, ino path, and content so both outputs remain byte-identical.
crates/fbuild-build-engine/src/pipeline/compile.rs (1)

127-143: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

generate_compile_db now takes 15 positional parameters.

Several consecutive parameters share the same type (&[PathBuf] for core_sources/sketch_sources, &Path for the three *_build_dir/project_dir params), and this PR adds one more slot to an already long list across 3 call sites. A single positional-order mistake at any call site would compile but silently misroute sources into the wrong build phase. Consider bundling into a small CompileDbInputs/CompileDbSources struct with named fields to make call sites self-documenting and immune to reordering mistakes.

🤖 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-engine/src/pipeline/compile.rs` around lines 127 - 143,
Refactor generate_compile_db to accept a named- field input struct, such as
CompileDbInputs or CompileDbSources, grouping the source lists, flags, paths,
and architecture currently passed positionally. Update all three call sites to
construct this struct with explicit field names, preserving the existing
compilation behavior while preventing argument-order mistakes.
🤖 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-engine/src/compile_database/clang.rs`:
- Around line 263-300: Update raw_ino_entry_from_template so each generated raw
.ino entry uses a unique object output path derived from raw_ino instead of
retaining template.output and its -o argument. Replace the template’s
output-path argument consistently in arguments and set CompileEntry.output to
the corresponding raw .ino object path, while preserving the existing flag
insertion and source-argument substitution.

In `@crates/fbuild-build-engine/src/source_scanner.rs`:
- Around line 444-446: The prelude naming in write_ino_preludes must distinguish
same-stem .ino files from different subdirectories. Derive each tab’s parent
path relative to src_dir, normalize that subdirectory component, and include it
in the generated prelude filename when the parent is not the source root;
preserve the existing stem-only name for root-level tabs and ensure the returned
raw-entry-to-prelude mapping uses each unique path.

In `@crates/fbuild-cli/src/cli/clangd_config/mod.rs`:
- Around line 206-220: Update the generated header in render_clangd_yaml to
state that .clangd is regenerated on every run and manual edits will be lost.
Keep emit_clangd_file’s overwrite behavior unchanged; do not add
merge-preserving logic.
- Around line 155-192: Update ensure_compile_db to accept the build-generated
compile database path for library projects when the project-root file is not
produced, returning that path instead of reporting failure. Ensure clangd-config
uses the returned database path, and update the CLI build summary and top-level
output summary to reference the same path that clangd uses.

In `@crates/fbuild-cli/src/cli/clangd_config/vscode.rs`:
- Around line 14-23: The settings.json update in the vscode configuration flow
should use fbuild_core::fs::write_atomic_sync instead of std::fs::write,
matching the atomic-write behavior used for extensions.json. Preserve the
existing path, merged_settings content, and FbuildError context while adapting
the error handling to the atomic-write API.
- Around line 55-78: Update the JSON parsing in merge_vscode_settings to use the
project's JSONC/trailing-comma tolerant parser instead of serde_json::from_str,
while preserving the existing empty/whitespace handling and error context for
settings_path. Ensure valid VS Code comments and trailing commas are accepted
before merging settings.

In `@crates/fbuild-cli/src/cli/clangd_config/zed.rs`:
- Around line 41-73: Update merge_zed_settings to parse non-empty editor
settings with the repository’s JSONC-aware parser instead of
serde_json::from_str, while preserving the existing empty-file handling and
error context. Ensure comments and trailing commas in user settings are accepted
before merge_ino_file_type and merge_clangd_lsp_config run.

---

Nitpick comments:
In `@crates/fbuild-build-engine/src/pipeline/compile.rs`:
- Around line 127-143: Refactor generate_compile_db to accept a named- field
input struct, such as CompileDbInputs or CompileDbSources, grouping the source
lists, flags, paths, and architecture currently passed positionally. Update all
three call sites to construct this struct with explicit field names, preserving
the existing compilation behavior while preventing argument-order mistakes.

In `@crates/fbuild-build-engine/src/source_scanner.rs`:
- Around line 362-369: Extract the repeated line-directive and trailing-newline
emission into a shared helper method such as append_tab_with_line_directive on
the surrounding implementation. Replace the inline logic in both the .ino.cpp
body loop and the per-tab prelude loop with calls to that helper, passing the
respective output buffer, ino path, and content so both outputs remain
byte-identical.
🪄 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 Plus

Run ID: 0c898a9c-5d85-4e36-bac2-b2cc4d11eea5

📥 Commits

Reviewing files that changed from the base of the PR and between d63d43b and e3b9e16.

📒 Files selected for processing (17)
  • crates/fbuild-build-arm/src/stm32/orchestrator/arduino_mbed.rs
  • crates/fbuild-build-engine/src/compile_database/clang.rs
  • crates/fbuild-build-engine/src/compile_database/tests/clang.rs
  • crates/fbuild-build-engine/src/pipeline/compile.rs
  • crates/fbuild-build-engine/src/pipeline/sequential.rs
  • crates/fbuild-build-engine/src/source_scanner.rs
  • crates/fbuild-build-engine/src/source_scanner/tests.rs
  • crates/fbuild-build-esp/src/esp32/orchestrator/build.rs
  • crates/fbuild-cli/src/cli/args.rs
  • crates/fbuild-cli/src/cli/clangd_config.rs
  • crates/fbuild-cli/src/cli/clangd_config/README.md
  • crates/fbuild-cli/src/cli/clangd_config/mod.rs
  • crates/fbuild-cli/src/cli/clangd_config/vscode.rs
  • crates/fbuild-cli/src/cli/clangd_config/zed.rs
  • crates/fbuild-cli/src/cli/dispatch.rs
  • docs/DESIGN_DECISIONS.md
  • docs/reference/cli.md
💤 Files with no reviewable changes (1)
  • crates/fbuild-cli/src/cli/clangd_config.rs

Comment on lines +263 to +300

/// Build one raw-`.ino` compile entry from the generated `.ino.cpp` entry's
/// (already clang-translated) flags: insert `-x c++ -include <prelude>`
/// right before `-c`, and swap every argument that names the generated file
/// (the `-c <file>` argument) for the raw `.ino` path.
fn raw_ino_entry_from_template(
template: &CompileEntry,
raw_ino: &Path,
prelude: &Path,
) -> CompileEntry {
let raw_ino_str = raw_ino.to_string_lossy().to_string();
let prelude_str = prelude.to_string_lossy().to_string();

let mut arguments = Vec::with_capacity(template.arguments.len() + 4);
let mut inserted_flavor = false;
for arg in &template.arguments {
if !inserted_flavor && arg == "-c" {
arguments.push("-x".to_string());
arguments.push("c++".to_string());
arguments.push("-include".to_string());
arguments.push(prelude_str.clone());
inserted_flavor = true;
}

if *arg == template.file {
arguments.push(raw_ino_str.clone());
} else {
arguments.push(arg.clone());
}
}

CompileEntry {
arguments,
directory: template.directory.clone(),
file: raw_ino_str,
output: template.output.clone(),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Raw .ino entries share the original .ino.cpp object output path.

Every raw .ino entry produced from a multi-tab sketch keeps the template's -o <path> argument and output field unchanged, so all tabs point at the same generated .ino.cpp.o path. Harmless for clangd (which never executes -c/-o), but incorrect metadata that could cause collisions if this compile DB is ever consumed by tooling that actually invokes the commands (e.g. the prepare_for_iwyu path in this same file).

🛠️ Suggested fix
     let mut arguments = Vec::with_capacity(template.arguments.len() + 4);
     let mut inserted_flavor = false;
     for arg in &template.arguments {
         if !inserted_flavor && arg == "-c" {
             arguments.push("-x".to_string());
             arguments.push("c++".to_string());
             arguments.push("-include".to_string());
             arguments.push(prelude_str.clone());
             inserted_flavor = true;
         }

-        if *arg == template.file {
+        if let Some(out) = &template.output {
+            if arg == out {
+                arguments.push(format!("{raw_ino_str}.o"));
+                continue;
+            }
+        }
+        if *arg == template.file {
             arguments.push(raw_ino_str.clone());
         } else {
             arguments.push(arg.clone());
         }
     }

     CompileEntry {
         arguments,
         directory: template.directory.clone(),
         file: raw_ino_str,
-        output: template.output.clone(),
+        output: template.output.as_ref().map(|_| format!("{raw_ino_str}.o")),
     }
📝 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
/// Build one raw-`.ino` compile entry from the generated `.ino.cpp` entry's
/// (already clang-translated) flags: insert `-x c++ -include <prelude>`
/// right before `-c`, and swap every argument that names the generated file
/// (the `-c <file>` argument) for the raw `.ino` path.
fn raw_ino_entry_from_template(
template: &CompileEntry,
raw_ino: &Path,
prelude: &Path,
) -> CompileEntry {
let raw_ino_str = raw_ino.to_string_lossy().to_string();
let prelude_str = prelude.to_string_lossy().to_string();
let mut arguments = Vec::with_capacity(template.arguments.len() + 4);
let mut inserted_flavor = false;
for arg in &template.arguments {
if !inserted_flavor && arg == "-c" {
arguments.push("-x".to_string());
arguments.push("c++".to_string());
arguments.push("-include".to_string());
arguments.push(prelude_str.clone());
inserted_flavor = true;
}
if *arg == template.file {
arguments.push(raw_ino_str.clone());
} else {
arguments.push(arg.clone());
}
}
CompileEntry {
arguments,
directory: template.directory.clone(),
file: raw_ino_str,
output: template.output.clone(),
}
}
/// Build one raw-`.ino` compile entry from the generated `.ino.cpp` entry's
/// (already clang-translated) flags: insert `-x c++ -include <prelude>`
/// right before `-c`, and swap every argument that names the generated file
/// (the `-c <file>` argument) for the raw `.ino` path.
fn raw_ino_entry_from_template(
template: &CompileEntry,
raw_ino: &Path,
prelude: &Path,
) -> CompileEntry {
let raw_ino_str = raw_ino.to_string_lossy().to_string();
let prelude_str = prelude.to_string_lossy().to_string();
let mut arguments = Vec::with_capacity(template.arguments.len() + 4);
let mut inserted_flavor = false;
for arg in &template.arguments {
if !inserted_flavor && arg == "-c" {
arguments.push("-x".to_string());
arguments.push("c++".to_string());
arguments.push("-include".to_string());
arguments.push(prelude_str.clone());
inserted_flavor = true;
}
if let Some(out) = &template.output {
if arg == out {
arguments.push(format!("{raw_ino_str}.o"));
continue;
}
}
if *arg == template.file {
arguments.push(raw_ino_str.clone());
} else {
arguments.push(arg.clone());
}
}
CompileEntry {
arguments,
directory: template.directory.clone(),
file: raw_ino_str,
output: template.output.as_ref().map(|_| format!("{raw_ino_str}.o")),
}
}
🤖 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-engine/src/compile_database/clang.rs` around lines 263 -
300, Update raw_ino_entry_from_template so each generated raw .ino entry uses a
unique object output path derived from raw_ino instead of retaining
template.output and its -o argument. Replace the template’s output-path argument
consistently in arguments and set CompileEntry.output to the corresponding raw
.ino object path, while preserving the existing flag insertion and
source-argument substitution.

Comment on lines +444 to +446
let tab_stem = ino.file_stem().unwrap_or_default().to_string_lossy();
let prelude_path = self.build_dir.join(format!("{}.ino.prelude.h", tab_stem));
write_if_changed(&prelude_path, &tab_prelude)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether nested .ino tabs (same stem, different subdirectory) are
# actually reachable given how walk_sources / order_ino_files scan the tree.
rg -n 'fn walk_sources' -A 20 crates/fbuild-build-engine/src/source_scanner.rs
rg -n 'fn order_ino_files' -A 20 crates/fbuild-build-engine/src/source_scanner.rs

Repository: FastLED/fbuild

Length of output: 1791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant parts of source_scanner.rs around prelude generation
# and source collection helper definitions.
wc -l crates/fbuild-build-engine/src/source_scanner.rs
sed -n '400,470p' crates/fbuild-build-engine/src/source_scanner.rs
sed -n '470,770p' crates/fbuild-build-engine/src/source_scanner.rs

# Search for helper definitions used by prelude generation.
rg -n 'file_stem_eq_ignore_ascii_case|compare_ino_paths|primary_ino_stems|fn ino_preludes|struct SourceScanner|ino_preludes|prelude_path' crates/fbuild-build-engine/src/source_scanner.rs

Repository: FastLED/fbuild

Length of output: 13391


Avoid prelude filename collisions for same-stem .ino tabs in different subdirectories.

walk_sources() recursively collects .ino files from subdirectories, but write_ino_preludes() names every prelude only by ino.file_stem(), creating collisions like src/foo/a/A.ino and src/foo/b/A.ino. The later prelude overwrites the earlier one and both raw .ino entries in the returned map point to the same final prelude content, so the second tab is compiled/analyzed against the previous tab’s concatenation. Include a normalized subdirectory component in the prelude filename for tabs whose parent directory under src_dir is not the source tree root.

🤖 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-engine/src/source_scanner.rs` around lines 444 - 446, The
prelude naming in write_ino_preludes must distinguish same-stem .ino files from
different subdirectories. Derive each tab’s parent path relative to src_dir,
normalize that subdirectory component, and include it in the generated prelude
filename when the parent is not the source root; preserve the existing stem-only
name for root-level tabs and ensure the returned raw-entry-to-prelude mapping
uses each unique path.

Comment on lines +155 to +192
pub(crate) async fn ensure_compile_db(
project_dir: &str,
project_path: &std::path::Path,
env_name: &str,
verbose: bool,
refresh: bool,
) -> fbuild_core::Result<std::path::PathBuf> {
let db_path = project_path.join("compile_commands.json");
if !refresh && db_path.exists() {
output::progress("Using existing compile_commands.json");
return Ok(db_path);
}

output::progress("Generating compile_commands.json...");
run_build(
project_dir.to_string(),
Some(env_name.to_string()),
false, // clean
false, // clean_all
verbose,
None, // jobs
false, // quick
false, // release
false, // dry_run
Some("compiledb".to_string()),
None,
true, // no_timestamp
None,
false, // bloat_analysis
)
.await?;
if !db_path.exists() {
return Err(fbuild_core::FbuildError::Other(
"compile_commands.json was not generated".into(),
));
}
Ok(db_path)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the project-as-library suppression logic referenced by DD-008 to confirm
# whether it exposes a build-dir fallback path that ensure_compile_db could use.
rg -n "library.json" crates/fbuild-build-engine/src --type rust -C3
rg -n "project-root" crates/fbuild-build-engine/src --type rust -C3

Repository: FastLED/fbuild

Length of output: 21068


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== clangd config file outline =="
ast-grep outline crates/fbuild-cli/src/cli/clangd_config/mod.rs || true

echo "== clangd config relevant sections =="
sed -n '1,260p' crates/fbuild-cli/src/cli/clangd_config/mod.rs | cat -n

echo "== compile_database module files =="
ls -la crates/fbuild-build-engine/src/compile_database
sed -n '1,140p' crates/fbuild-build-engine/src/compile_database/database.rs | cat -n
sed -n '1,240p' crates/fbuild-build-engine/src/compile_database/tests/serialization_and_write.rs | cat -n

echo "== search for run_build implementation and compile_commands path handling =="
rg -n "async fn run_build|fn run_build|compile_commands\.json|expected_output_path|write_and_copy|CompilationDatabase|\\.fbuild/build" crates --type rust -C3

Repository: FastLED/fbuild

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate build command and compiledb path =="
rg -n "async fn run_build|compiledb_only|generate_compiledb|compile_database_path|write_and_copy|expected_output_path|CompileDatabase::" crates --type rust -C3 | head -n 220

echo "== inspect candidate build source files =="
for f in $(git ls-files 'crates/fbuild-cli/src/**' | rg '(build|compil|compiledb|clangd_config)' | head -n 40); do
  echo "--- $f ($(wc -l < "$f") lines) ---"
  ast-grep outline "$f" || true
done

echo "== focused runs =="
rg -n "compiledb_only|generate_compiledb|CompileDatabase::write_and_copy|CompileDatabase::expected_output_path|compile_database_path" crates/fbuild-cli crates/fbuild-build-engine/src --type rust -C4

Repository: FastLED/fbuild

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== crates/fbuild-cli/src/cli/build.rs =="
cat -n crates/fbuild-cli/src/cli/build.rs

echo "== crates/fbuild-build-esp/src/esp32/orchestrator/build.rs compiledb section =="
sed -n '550,605p' crates/fbuild-build-esp/src/esp32/orchestrator/build.rs | cat -n

echo "== crates/fbuild-build-engine/src/compile_database/mod.rs =="
cat -n crates/fbuild-build-engine/src/compile_database/mod.rs

echo "== crates/fbuild-build-engine/src/esp32/pipeline.rs or generate_compile_db locations =="
rg -n "fn generate_compile_db|generate_compile_db|CompileDatabase::write_and_copy|fs::remove_file\\(.*compile_commands|unlink\\(.*compile_commands" crates/fbuild-build-esp crates/fbuild-build-engine crates/fbuild-core --type rust -C4

echo "== deterministic behavior: library-project write_and_copy vs current checks =="
python3 - <<'PY'
from pathlib import Path
import tempfile, json

# A behavior model extracted from crates/fbuild-build-engine/src/compile_database/database.rs:
#   - write_and_copy writes to build_dir, then skips project copy if library.json exists and returns build_path.
#   - CompileDatabase::expected_output_path returns build_dir/path for library projects.
def effective_compile_db_for_clangd_should_look_like(build_dir, project_dir_has_library_json):
    # This mirrors the build-engine contract for the path returned by write_and_copy and expected_output_path.
    if project_dir_has_library_json:
        return str(Path(build_dir) / "compile_commands.json")
    return str(Path(project_dir) / "compile_commands.json")

# Existing ensure_compile_db only checks project_path.join("compile_commands.json"); no build_dir return.
def ensure_compile_db_returns_path(project_dir, project_dir_has_library_json, build_dir):
    root_path = Path(project_dir) / "compile_commands.json"
    if root_path.exists():
        return str(root_path)
    # Simulate build writes only into build_dir for a fresh library project.
    return None  # errors with "compile_commands.json was not generated"

for lib in (True, False):
    with tempfile.TemporaryDirectory() as tmp:
        project_dir = Path(tmp) / "project"
        build_dir = Path(tmp) / ".fbuild" / "build" / "esp32"
        project_dir.mkdir()
        build_dir.mkdir(parents=True)
        if lib:
            (project_dir / "library.json").write_text('{"name":"test"}')
        db = {"directory": "/", "file": "src/main.cpp", "arguments": ["g++"]}
        with open(build_dir / "compile_commands.json", "w") as f:
            json.dump([db], f)

        print(f"library_project={lib}")
        print("  expected_output_path_or_write_and_copy_result =", effective_compile_db_for_clangd_should_look_like(build_dir, lib))
        print("  current_ensure_compile_db_returns =", ensure_compile_db_returns_path(project_dir, lib, build_dir))
PY

Repository: FastLED/fbuild

Length of output: 17744


Propagate the build-dir compile database into clangd-config.

ensure_compile_db treats the project-root compile_commands.json as the only success case, but for library projects the build writes compile_commands.json to the build dir and skips the root copy to avoid clobbering existing tool-generated databases. Return/use that build-dir path in .clangd for library projects instead of failing with compile_commands.json was not generated. Also update the CLI build summary path and the top-level output summary so they match the database clangd uses.

🤖 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-cli/src/cli/clangd_config/mod.rs` around lines 155 - 192,
Update ensure_compile_db to accept the build-generated compile database path for
library projects when the project-root file is not produced, returning that path
instead of reporting failure. Ensure clangd-config uses the returned database
path, and update the CLI build summary and top-level output summary to reference
the same path that clangd uses.

Comment on lines +206 to +220
/// Render the `.clangd` YAML, pinning the compilation database to the
/// project root. No longer pins a `Compiler:` path or requests
/// `--query-driver` (FastLED/fbuild#1076 Phase 0 — the toolchain's builtin
/// include dirs are baked into `compile_commands.json` as `-isystem`
/// instead; the query-driver path had already silently degenerated to a
/// no-op glob).
fn render_clangd_yaml() -> String {
"# Generated by `fbuild clangd-config` — safe to edit, regenerate to refresh.\n\
CompileFlags:\n\
\x20\x20CompilationDatabase: .\n\
Diagnostics:\n\
\x20\x20# Many embedded toolchains emit flags clangd cannot parse cleanly.\n\
\x20\x20Suppress: [drv_unknown_argument, unknown-warning-option]\n"
.to_string()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Misleading "safe to edit" claim in the generated .clangd header.

emit_clangd_file fully overwrites .clangd on every invocation (no merge, unlike the VS Code/Zed emitters), so the embedded comment's implication that hand edits are safe is misleading — a user's manual edits will be silently discarded the next time clangd-config runs (which per DD-008/README is meant to be a cheap, repeatable operation). Either adjust the wording (e.g. "regenerated on every run — manual edits will be lost") or add merge-preserving behavior similar to the editor emitters.

🤖 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-cli/src/cli/clangd_config/mod.rs` around lines 206 - 220,
Update the generated header in render_clangd_yaml to state that .clangd is
regenerated on every run and manual edits will be lost. Keep emit_clangd_file’s
overwrite behavior unchanged; do not add merge-preserving logic.

Comment on lines +14 to +23
let settings_path = vscode_dir.join("settings.json");
let merged_settings = merge_vscode_settings(&settings_path)?;
std::fs::write(&settings_path, merged_settings).map_err(|e| {
fbuild_core::FbuildError::Other(format!(
"failed to write {}: {}",
settings_path.display(),
e
))
})?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Non-atomic write for settings.json while extensions.json uses atomic write.

extensions.json is deliberately written via fbuild_core::fs::write_atomic_sync for crash-safety, but the arguably more important, pre-existing settings.json is written with a plain std::fs::write. See the consolidated comment for the shared fix across vscode.rs and zed.rs.

🤖 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-cli/src/cli/clangd_config/vscode.rs` around lines 14 - 23, The
settings.json update in the vscode configuration flow should use
fbuild_core::fs::write_atomic_sync instead of std::fs::write, matching the
atomic-write behavior used for extensions.json. Preserve the existing path,
merged_settings content, and FbuildError context while adapting the error
handling to the atomic-write API.

Comment on lines +55 to +78
fn merge_vscode_settings(settings_path: &Path) -> fbuild_core::Result<String> {
let mut root: serde_json::Map<String, serde_json::Value> = if settings_path.exists() {
let content = std::fs::read_to_string(settings_path).map_err(|e| {
fbuild_core::FbuildError::Other(format!(
"failed to read {}: {}",
settings_path.display(),
e
))
})?;
// Tolerate an empty/whitespace file as an empty object.
if content.trim().is_empty() {
serde_json::Map::new()
} else {
serde_json::from_str(&content).map_err(|e| {
fbuild_core::FbuildError::Other(format!(
"failed to parse {} as JSON: {}",
settings_path.display(),
e
))
})?
}
} else {
serde_json::Map::new()
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate vscode.rs and related files =="
fd -a 'vscode.rs|zed.rs' . | sed 's#^\./##'

echo
echo "== vscode.rs outline =="
ast-grep outline crates/fbuild-cli/src/cli/clangd_config/vscode.rs --view expanded || true

echo
echo "== vscode.rs relevant content =="
cat -n crates/fbuild-cli/src/cli/clangd_config/vscode.rs | sed -n '1,140p'

echo
echo "== Search for jsonc/json-with-comments usage in CLI/features =="
rg -n "jsonc|JSON with Comments|serde_json::from_str|settings.json|merge_vscode_settings|merge_zed_settings|from_str" crates/fbuild-cli crates/fbuild-core 2>/dev/null || true

echo
echo "== Check dependency on serde_json and jsonc parsers =="
fd -a 'Cargo.toml' . | while read -r f; do
  if rg -q 'serde_json|jsonc|json-comment|serde_json5' "$f"; then
    echo "--- $f"
    rg -n 'serde_json|jsonc|json-comment|serde_json5|serde' "$f"
  fi
done

Repository: FastLED/fbuild

Length of output: 20564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe for serde_json behavior against trailing comma and line comments.
# Run against system cargo resolver's index is unavailable here; the point is semantic behavior of serde_json::from_str.
python3 - <<'PY'
try:
    import subprocess, sys
except Exception as exc:
    print("Could not import subprocess:", exc)
    sys.exit(0)

cases = [
    ("valid_object", '{"c": true}'),
    ("line_comment", '{"c": true}//comment'),
    ("block_comment", "/* comment */{\"c\": true}"),
    ("trailing_comma", '{"c": true,}'),
    ("line_comment_trailing_comma", '{"c": true}//comment\n,}'),
]
for name, s in cases:
    try:
        print(f"{name}: serde_json::from_str expected syntax error for JSONC/trailing-comma inputs")
    except Exception as exc:
        print(f"{name}: {exc}")
PY
sed -n '1,80p' crates/fbuild-cli/src/cli/clangd_config/vscode.rs

Repository: FastLED/fbuild

Length of output: 3547


🌐 Web query:

VS Code settings.json JSON with Comments official documentation

💡 Result:

Visual Studio Code uses a format called JSON with Comments (JSONC) for its configuration files, including settings.json, tasks.json, and launch.json [1][2]. Key characteristics of JSONC as used in VS Code include: Support for Comments: Unlike standard JSON, which does not allow comments, JSONC permits both single-line comments (//) and block comments (/* */), similar to JavaScript syntax [1][3]. Trailing Commas: The format accepts trailing commas in objects and arrays [1][4]. However, while they are permitted, they are discouraged and the editor may display a warning for their use in certain contexts [1][2]. Official Support: VS Code provides native support for this format, including full IntelliSense, schema validation, and error highlighting specifically designed for settings.json [5]. You can view the official documentation regarding this format in the VS Code "Editing JSON" guide [1]. Users can also explicitly associate other files with the JSONC mode using the files.associations setting in VS Code if they wish to enable comment support for other JSON files [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== clangd_config shared/README =="
cat -n crates/fbuild-cli/src/cli/clangd_config/README.md 2>/dev/null || true

echo
echo "== zed.rs relevant content =="
cat -n crates/fbuild-cli/src/cli/clangd_config/zed.rs | sed -n '1,100p'

echo
echo "== atomic file write implementation =="
rg -n "fn write_atomic_sync|write_atomic" crates/fbuild-core/src -A 20 -B 5

Repository: FastLED/fbuild

Length of output: 26926


Parse VS Code settings as JSONC before merging.

.vscode/settings.json files are JSON-with-Comments and may include ////* */ comments and trailing commas; serde_json::from_str rejects those valid VS Code edits, so fbuild clangd-config can fail entirely instead of preserving unrelated settings. Use a JSONC/trailing-comma tolerant parser here, not bare serde_json.

🤖 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-cli/src/cli/clangd_config/vscode.rs` around lines 55 - 78,
Update the JSON parsing in merge_vscode_settings to use the project's
JSONC/trailing-comma tolerant parser instead of serde_json::from_str, while
preserving the existing empty/whitespace handling and error context for
settings_path. Ensure valid VS Code comments and trailing commas are accepted
before merging settings.

Comment on lines +41 to +73
fn merge_zed_settings(settings_path: &Path) -> fbuild_core::Result<String> {
let mut root: serde_json::Map<String, serde_json::Value> = if settings_path.exists() {
let content = std::fs::read_to_string(settings_path).map_err(|e| {
fbuild_core::FbuildError::Other(format!(
"failed to read {}: {}",
settings_path.display(),
e
))
})?;
if content.trim().is_empty() {
serde_json::Map::new()
} else {
serde_json::from_str(&content).map_err(|e| {
fbuild_core::FbuildError::Other(format!(
"failed to parse {} as JSON: {}",
settings_path.display(),
e
))
})?
}
} else {
serde_json::Map::new()
};

merge_ino_file_type(&mut root);
merge_clangd_lsp_config(&mut root);

let mut out = serde_json::to_string_pretty(&serde_json::Value::Object(root)).map_err(|e| {
fbuild_core::FbuildError::Other(format!("failed to serialize settings.json: {}", e))
})?;
out.push('\n');
Ok(out)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline crates/fbuild-cli/src/cli/clangd_config/zed.rs --view expanded || true

echo
echo "== relevant file contents =="
cat -n crates/fbuild-cli/src/cli/clangd_config/zed.rs | sed -n '1,180p'

echo
echo "== vscode.rs relevant contents =="
cat -n crates/fbuild-cli/src/cli/clangd_config/vscode.rs | sed -n '1,180p'

echo
echo "== clangd_config files =="
fd -a . crates/fbuild-cli/src/cli/clangd_config | sed 's#^\./##'

echo
echo "== read/write settings paths in clangd_config =="
rg -n "settings|atomic|write|read_to_string|serde_json::from_str|json_to_settings" crates/fbuild-cli/src/cli/clangd_config

Repository: FastLED/fbuild

Length of output: 26286


🌐 Web query:

Zed settings.json comments JSONC format documentation

💡 Result:

Zed uses a superset of the standard JSON format known as JSONC (JSON with Comments) for its settings.json files [1][2]. This format allows users to include single-line comments using the // syntax [1][2]. Key technical details regarding Zed's use of JSONC include: Format Support: Zed explicitly supports JSONC, which enables the use of comments within configuration files to help document or disable specific settings [1][3]. You can toggle comments on the current line or selection in these files using the standard shortcut (cmd-/ on macOS or ctrl-/ on Linux) [1]. Distinction from Standard JSON: While standard.json files follow strict JSON specifications, Zed settings files (and other designated files) are treated as JSONC [1][4]. This distinction is important because some language servers or formatters may report errors if they attempt to parse files containing comments as standard, strict JSON [5][6]. Configuration and File Associations: Zed now includes a distinct JSONC language identifier [5]. You can control which files are treated as JSONC by using the file_types setting in your configuration [7][5]. For example, you can explicitly map additional file patterns to JSONC if needed: { "file_types": { "JSONC": ["*.json"] } } When editing your settings.json, Zed handles it as JSONC by default, meaning it accepts comments and may also support trailing commas, which are commonly allowed in JSONC but disallowed in strict JSON [3][4].

Citations:


🌐 Web query:

VS Code IntelliSense settings.json comments trailing commas JSONC json.schemas settings.json

💡 Result:

VS Code uses a format called JSON with Comments (JSONC) for its primary configuration files (e.g., settings.json, launch.json, tasks.json) [1][2][3]. This format allows standard JSON syntax with the addition of single-line (//) and block (/* */) comments [1][2]. Trailing Commas and Warnings While JSONC mode supports trailing commas, the editor typically discourages them and will display a warning [1][2][3]. This warning can be suppressed or permitted for specific files by associating a JSON schema with those files that includes the non-standard properties "allowTrailingCommas": true and "allowComments": true [4][5][6]. VS Code's built-in configuration files (like settings.json) do not trigger these warnings because they are internally configured with schemas that explicitly allow these features [5][3]. Configuring JSON Schemas in settings.json You can enable IntelliSense, validation, and specific JSONC features for your own custom JSON files by defining them in your user or workspace settings.json using the json.schemas property [2][7][8]. To apply these settings, add an entry to the json.schemas array in your settings.json file: { "json.schemas": [ { "fileMatch": [ "/your-file-pattern.jsonc" ], "schema": { "allowComments": true, "allowTrailingCommas": true } } ] } If a file has a $schema property defined internally, VS Code may prioritize that schema over the one defined in your settings.json [4]. In such cases, removing the $schema property from the file or adjusting the schema mapping in settings is often necessary to apply your custom rules [4]. Recent Updates As of late 2025, VS Code has introduced formal support for.jsonc file extensions for configuration files (e.g., settings.jsonc, tasks.jsonc), providing a standard way to distinguish these files from strict JSON [9][10]. The editor will attempt to resolve these files in addition to the standard.json files [9].

Citations:


Parse the editor settings file with a JSONC-aware parser.

Both .vscode/settings.json and .zed/settings.json support comments/trailing commas via VS Code/Zed, but serde_json::from_str rejects them. A comment or trailing comma in a user-created settings file makes fbuild ide fail while trying to merge clangd config; parse with JSONC support instead of strict JSON.

🤖 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-cli/src/cli/clangd_config/zed.rs` around lines 41 - 73, Update
merge_zed_settings to parse non-empty editor settings with the repository’s
JSONC-aware parser instead of serde_json::from_str, while preserving the
existing empty-file handling and error context. Ensure comments and trailing
commas in user settings are accepted before merge_ino_file_type and
merge_clangd_lsp_config run.

@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Jul 28, 2026
zackees added a commit that referenced this pull request Jul 31, 2026
… unused_mut, rustdoc links

main is red on six checks. They are unrelated to each other and accumulated
because the workflows that run them are `pull_request`-triggered, so nothing
re-ran them on main after the merges that broke them (#1197, #1206, #1207).
Found while verifying an unrelated dependency bump (#1215); every failure
there reproduced identically on main.

Dylint: drop `crates/fbuild-cli/src/cli/clangd_config.rs` from
ban_unrooted_tempdir's allowlist -- the file was deleted in #1197.

Lint subprocess spawns: annotate three deliberate direct spawns with
`allow-direct-spawn:` markers (clangd test driver, interactive gdb that must
inherit terminal stdio, detached editor launch that must outlive the CLI).
The scanner only inspects the hit line and the one line directly above it, so
the marker must be a single line -- a wrapped comment silently fails to
register. This also fixes Check (ubuntu-latest), which runs the same script.

Check (macos-latest): `ports.rs` bound `let mut ports` unconditionally while
only `cfg(target_os = "linux")` mutates it, so every other unix target tripped
`-D unused-mut`. The `mut` now exists only inside the Linux cfg via a
shadowing block, rather than an `#[allow]`.

Documentation: six rustdoc errors across three crates. URL templates such as
`?query=<optional filter>` parsed as unclosed HTML tags; several intra-doc
links pointed at `cfg(target_os = "linux")` items that do not exist to resolve
against when docs build on a non-Linux host; three more pointed at private
items. Unresolvable links become code spans, each with a short note on why it
is deliberately not a link.

CH32V006 (`build / build`) is also red on main but is a vendor-C compile error
in the openwch Arduino core, not CI hygiene, and is left for a targeted fix.

Verified locally on Windows, all exit 0: check_dylint_allowlists.py,
find_direct_subprocess.py --fail, cargo doc with RUSTDOCFLAGS=-D warnings,
clippy -D warnings, cargo fmt --check, and `bash test` (77 suites, 0 failed).

The macOS fix could not be verified locally: cross-compiling fbuild-serial to
x86_64-apple-darwin fails on ring's build script needing a macOS `cc`. The
cfg-shadowing pattern was verified in an isolated cargo project (clean under
-D warnings with the cfg both on and off); Check (macos-latest) on this PR is
the real proof.

Co-Authored-By: Claude <noreply@anthropic.com>
zackees added a commit that referenced this pull request Jul 31, 2026
Pinning dylint back to =6.0.1 made the driver build again, so the lints
actually ran for the first time in a while and reported 34 violations across
three crates. None are new: they accumulated in #1197, #1206 and #1207 while
Dylint was failing at earlier steps (a stale allowlist path, then the 6.0.2
driver) and never reached the lint pass.

Handled by category rather than uniformly.

ban_std_pathbuf (30) -- migrated to `fbuild_core::path::NormalizedPath` in
`fbuild-cli` (`ide.rs`, `debug.rs`, `ide_debug.rs`, `clangd_config/{mod,
vscode,zed}.rs`), `fbuild-daemon` (`handlers/libraries.rs`) and the
`sysfs_usb.rs` test fixtures. Not allowlisted: that allowlist's header states
"New files MUST NOT be added here. The target state is zero entries", and
exempting brand-new files would hollow out the gate that was just repaired.
`NormalizedPath` derefs to `Path`, so call sites taking `&Path` are unchanged;
`to_path_buf()` appears only where an external API (`BuildLayout::new`)
demands an owned `PathBuf`.

ban_std_fs_in_async (2) -- `handlers/libraries.rs::installed_dir_names` now
uses async `fbuild_core::fs::read_dir` (the sanctioned re-export of
`tokio::fs`), which makes it and `build_library_entries` async along with
their three unit tests. It also switches the directory test from
`Path::is_dir()` to `entry.file_type().await`: `is_dir()` is a blocking
`stat` on the async worker, so keeping it would have satisfied the lint's
letter while preserving exactly the stall the lint exists to prevent
(#844).

ban_raw_subprocess (2) -- allowlisted with justifications, which is what the
lint's own diagnostic offers for genuinely-justified sites. Both are correct
as raw spawns: `fbuild debug` hands the terminal to gdb, so the child must
inherit this process's stdio rather than be captured; and `fbuild ide`
launches the editor detached so it OUTLIVES the CLI, which is the opposite of
what a containment group guarantees.

Verified: `cargo clippy --workspace --all-targets -- -D warnings`,
`cargo fmt --all --check`, and `bash test` (77 suites, 0 failed) all green.

Dylint itself could not be run locally to pre-verify -- the pinned nightly and
`cargo-dylint` are present, but `dylint-link` fails to link the lint cdylibs
on this Windows host. CI is the proof for that gate.

Co-Authored-By: Claude <noreply@anthropic.com>
zackees added a commit that referenced this pull request Jul 31, 2026
Pinning dylint back to =6.0.1 made the driver build again, so the lints
actually ran for the first time in a while and reported 34 violations across
three crates. None are new: they accumulated in #1197, #1206 and #1207 while
Dylint was failing at earlier steps (a stale allowlist path, then the 6.0.2
driver) and never reached the lint pass.

Handled by category rather than uniformly.

ban_std_pathbuf (30) -- migrated to `fbuild_core::path::NormalizedPath` in
`fbuild-cli` (`ide.rs`, `debug.rs`, `ide_debug.rs`, `clangd_config/{mod,
vscode,zed}.rs`), `fbuild-daemon` (`handlers/libraries.rs`) and the
`sysfs_usb.rs` test fixtures. Not allowlisted: that allowlist's header states
"New files MUST NOT be added here. The target state is zero entries", and
exempting brand-new files would hollow out the gate that was just repaired.
`NormalizedPath` derefs to `Path`, so call sites taking `&Path` are unchanged;
`to_path_buf()` appears only where an external API (`BuildLayout::new`)
demands an owned `PathBuf`.

ban_std_fs_in_async (2) -- `handlers/libraries.rs::installed_dir_names` now
uses async `fbuild_core::fs::read_dir` (the sanctioned re-export of
`tokio::fs`), which makes it and `build_library_entries` async along with
their three unit tests. It also switches the directory test from
`Path::is_dir()` to `entry.file_type().await`: `is_dir()` is a blocking
`stat` on the async worker, so keeping it would have satisfied the lint's
letter while preserving exactly the stall the lint exists to prevent
(#844).

ban_raw_subprocess (2) -- allowlisted with justifications, which is what the
lint's own diagnostic offers for genuinely-justified sites. Both are correct
as raw spawns: `fbuild debug` hands the terminal to gdb, so the child must
inherit this process's stdio rather than be captured; and `fbuild ide`
launches the editor detached so it OUTLIVES the CLI, which is the opposite of
what a containment group guarantees.

Verified: `cargo clippy --workspace --all-targets -- -D warnings`,
`cargo fmt --all --check`, and `bash test` (77 suites, 0 failed) all green.

Dylint itself could not be run locally to pre-verify -- the pinned nightly and
`cargo-dylint` are present, but `dylint-link` fails to link the lint cdylibs
on this Windows host. CI is the proof for that gate.

Co-Authored-By: Claude <noreply@anthropic.com>

Also widens the cargo-cache .gitignore entries to `**/`-prefixed forms. The
existing patterns contain a slash, so git anchors them to the repo root and
they never covered nested crates; running any cargo command inside
`dylints/<lint>/` (each is its own workspace) materializes a full registry
cache there that a `git add -A` will stage.
zackees added a commit that referenced this pull request Jul 31, 2026
Pinning dylint back to =6.0.1 made the driver build again, so the lints
actually ran for the first time in a while and reported 34 violations across
three crates. None are new: they accumulated in #1197, #1206 and #1207 while
Dylint was failing at earlier steps (a stale allowlist path, then the 6.0.2
driver) and never reached the lint pass.

Handled by category rather than uniformly.

ban_std_pathbuf (30) -- migrated to `fbuild_core::path::NormalizedPath` in
`fbuild-cli` (`ide.rs`, `debug.rs`, `ide_debug.rs`, `clangd_config/{mod,
vscode,zed}.rs`), `fbuild-daemon` (`handlers/libraries.rs`) and the
`sysfs_usb.rs` test fixtures. Not allowlisted: that allowlist's header states
"New files MUST NOT be added here. The target state is zero entries", and
exempting brand-new files would hollow out the gate that was just repaired.
`NormalizedPath` derefs to `Path`, so call sites taking `&Path` are unchanged;
`to_path_buf()` appears only where an external API (`BuildLayout::new`)
demands an owned `PathBuf`.

ban_std_fs_in_async (2) -- `handlers/libraries.rs::installed_dir_names` now
uses async `fbuild_core::fs::read_dir` (the sanctioned re-export of
`tokio::fs`), which makes it and `build_library_entries` async along with
their three unit tests. It also switches the directory test from
`Path::is_dir()` to `entry.file_type().await`: `is_dir()` is a blocking
`stat` on the async worker, so keeping it would have satisfied the lint's
letter while preserving exactly the stall the lint exists to prevent
(#844).

ban_raw_subprocess (2) -- allowlisted with justifications, which is what the
lint's own diagnostic offers for genuinely-justified sites. Both are correct
as raw spawns: `fbuild debug` hands the terminal to gdb, so the child must
inherit this process's stdio rather than be captured; and `fbuild ide`
launches the editor detached so it OUTLIVES the CLI, which is the opposite of
what a containment group guarantees.

Verified: `cargo clippy --workspace --all-targets -- -D warnings`,
`cargo fmt --all --check`, and `bash test` (77 suites, 0 failed) all green.

Dylint itself could not be run locally to pre-verify -- the pinned nightly and
`cargo-dylint` are present, but `dylint-link` fails to link the lint cdylibs
on this Windows host. CI is the proof for that gate.

Co-Authored-By: Claude <noreply@anthropic.com>

Also widens the cargo-cache .gitignore entries to `**/`-prefixed forms. The
existing patterns contain a slash, so git anchors them to the repo root and
they never covered nested crates; running any cargo command inside
`dylints/<lint>/` (each is its own workspace) materializes a full registry
cache there that a `git add -A` will stage.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant