feat(ide): clangd/compile-DB foundations for fbuild ide (#1076 Phase 0) - #1197
Conversation
- 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>
📝 WalkthroughWalkthroughThe change generates per-tab Arduino ChangesINO preprocessing and compile database integration
clangd-config command
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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
crates/fbuild-build-engine/src/source_scanner.rs (1)
362-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
#line-directive-emission logic.The "emit
#line 1 "<path>"+ tab content + ensure trailing newline" logic is duplicated between the.ino.cppbody loop and the per-tab prelude loop. The two must stay byte-identical (pertest_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_dbnow takes 15 positional parameters.Several consecutive parameters share the same type (
&[PathBuf]forcore_sources/sketch_sources,&Pathfor the three*_build_dir/project_dirparams), 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 smallCompileDbInputs/CompileDbSourcesstruct 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
📒 Files selected for processing (17)
crates/fbuild-build-arm/src/stm32/orchestrator/arduino_mbed.rscrates/fbuild-build-engine/src/compile_database/clang.rscrates/fbuild-build-engine/src/compile_database/tests/clang.rscrates/fbuild-build-engine/src/pipeline/compile.rscrates/fbuild-build-engine/src/pipeline/sequential.rscrates/fbuild-build-engine/src/source_scanner.rscrates/fbuild-build-engine/src/source_scanner/tests.rscrates/fbuild-build-esp/src/esp32/orchestrator/build.rscrates/fbuild-cli/src/cli/args.rscrates/fbuild-cli/src/cli/clangd_config.rscrates/fbuild-cli/src/cli/clangd_config/README.mdcrates/fbuild-cli/src/cli/clangd_config/mod.rscrates/fbuild-cli/src/cli/clangd_config/vscode.rscrates/fbuild-cli/src/cli/clangd_config/zed.rscrates/fbuild-cli/src/cli/dispatch.rsdocs/DESIGN_DECISIONS.mddocs/reference/cli.md
💤 Files with no reviewable changes (1)
- crates/fbuild-cli/src/cli/clangd_config.rs
|
|
||
| /// 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(), | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ 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.
| /// 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.
| 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)?; |
There was a problem hiding this comment.
🎯 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.rsRepository: 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.rsRepository: 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.
| 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) | ||
| } |
There was a problem hiding this comment.
🎯 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 -C3Repository: 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 -C3Repository: 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 -C4Repository: 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))
PYRepository: 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.
| /// 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() | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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 | ||
| )) | ||
| })?; | ||
|
|
There was a problem hiding this comment.
🩺 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.
| 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() | ||
| }; |
There was a problem hiding this comment.
🎯 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
doneRepository: 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.rsRepository: 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:
- 1: https://code.visualstudio.com/docs/languages/json
- 2: https://github.com/microsoft/vscode-docs/blob/main/docs/languages/json.md
- 3: https://jsonlinter.dev/comments-in-json/
- 4: https://jsonc.org/
- 5: https://code.visualstudio.com/docs/configure/settings
🏁 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 5Repository: 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.
| 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) | ||
| } |
There was a problem hiding this comment.
🎯 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_configRepository: 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:
- 1: https://zed.dev/docs/languages/json
- 2: https://zedhub.dev/configuring-zed
- 3: Certain comments are not permitted in Zed's settings.json zed-industries/zed#12316
- 4: Zed errors out and fails to save local settings zed-industries/zed#45191
- 5: Add separate JSONC language zed-industries/zed#12655
- 6: Comments in JSON files zed-industries/zed#10921
- 7: https://zed.dev/docs/reference/all-settings
🌐 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:
- 1: https://code.visualstudio.com/docs/languages/json
- 2: https://github.com/microsoft/vscode-docs/blob/main/docs/languages/json.md
- 3: https://jsonc.org/
- 4: jsonc: Setting to allow trailing commas in jsonc microsoft/vscode#237258
- 5: Tolerate trailing commas in jsonc format microsoft/vscode#102061
- 6: Tolerate trailing commas in jsonc format (breakage) microsoft/vscode#177584
- 7: https://stackoverflow.com/questions/34192948/provide-json-schema-for-a-json-file-like-settings-json-in-visual-studio-code
- 8: https://stackoverflow.com/questions/30056721/how-do-i-configure-vs-code-to-enable-code-completion-on-json-files-jsonschema
- 9: Add JSONC support for .vscode configuration files microsoft/vscode#270933
- 10: Support naming settings.json as settings.jsonc (and others) microsoft/vscode#269824
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.
… 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>
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>
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.
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.
Lands Phase 0 of the corrected #1076 plan (the four independently-shippable foundations from the issue's implementation guide + the
.ino.prelude.hdirection update). Does not close the meta.What changed
#linedirectives — 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..inocompile entries — the converter writes<build_dir>/<stem>.ino.prelude.h(Arduino include + generated prototypes; multi-tab: + preceding tabs' text with their own#linedirectives), and the writtencompile_commands.jsonswaps the generated.ino.cppentry for one raw-.inoentry 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.inoentries.-isystembaking —translate_for_clangappends the cross-toolchain's GCC builtin include dirs (sorted, deduped, no-op when toolchains are absent) to every entry, replacing the--query-driverapproach.clangd-config— split intoclangd_config/{mod,vscode,zed}.rswith--editor vscode|zed(default vscode, existing behavior preserved) and--refresh(force DB regeneration); the degenerate query-driver path and the.clangdCompiler:pin (always bareclang++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 upcomingfbuild ideverb (Phase 1).Out of scope, tracked separately: prototype insertion position (#1196), the dead
BuildParams.generate_compiledbfield, thefbuild ideverb 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 passedsoldr cargo clippy --workspace --all-targets -- -D warnings→ exit 0;fmtcleanbash testrun before mergePart of #1076.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
.inosupport, improving navigation and diagnostics across multi-tab sketches.fbuild clangd-configwith VS Code and Zed support, plus optional refresh behavior..clangdand editor settings while preserving existing configuration.Documentation
Bug Fixes