Summary
Add a fbuild command (e.g. fbuild clangd-config or a flag like fbuild build --emit-clangd-config) that, given a project directory with a platformio.ini, resolves the default environment and emits a working clangd/VS Code configuration into the project so that "Go to Definition", header hover, and include resolution work in VS Code without any extra user setup.
Concretely, after running the command the user should get (at the project root):
compile_commands.json — already produced by fbuild build -t compiledb today
.clangd — a YAML config that pins CompileFlags.CompilationDatabase: . and exposes the cross-compiler via --query-driver
.vscode/settings.json — disables the built-in MS C/C++ IntelliSense provider for this folder, points clangd.arguments at the correct --compile-commands-dir and --query-driver glob, and recommends the llvm-vs-code-extensions.vscode-clangd extension
The goal: a fresh clone of a FastLED/PlatformIO sketch, plus fbuild clangd-config, plus the clangd VS Code extension, gives the user working symbol navigation into the env's framework headers (Arduino core, ESP-IDF, AVR libc, Teensy core, etc.) on the first open.
Sibling issue for the WASM path: zackees/fastled-wasm#177.
Current state
fbuild already has most of the moving parts but does not stitch them into an "IDE-ready" configuration:
fbuild build -t compiledb exists (crates/fbuild-cli/src/cli/args.rs lines ~220–222, parser restricted to "compiledb"), and it is used internally by run_iwyu and run_clang_tool in crates/fbuild-cli/src/cli/clang_tools.rs to materialize compile_commands.json.
compile_commands.json is written to both the build dir and the project root by crates/fbuild-build/src/compile_database/database.rs, which is exactly what clangd auto-discovers.
crates/fbuild-build/src/compile_database/generate.rs::generate_entries already records the real GCC/G++ binary paths (not cache wrappers) as arguments[0] — these are the paths clangd needs for --query-driver.
- The default environment is already resolved in
crates/fbuild-config/src/ini_parser/mod.rs::get_default_environment (priority: PLATFORMIO_DEFAULT_ENVS env override → [platformio] default_envs → first env in file order).
- Toolchain include dirs are computed via
fbuild_packages::toolchain::clang::find_gcc_builtin_include_dirs() (used in run_iwyu).
What is missing: a thin "emit IDE config" step on top of compiledb that writes .clangd and .vscode/settings.json so that:
- clangd does not need any manual setup,
- VS Code's MS C/C++ extension does not race against clangd, and
- clangd extracts the cross-compiler's builtin headers via
--query-driver so symbols defined in toolchain headers (e.g. <Arduino.h>, ESP-IDF <esp_log.h>) resolve.
Today, even after fbuild build -t compiledb succeeds, a fresh checkout opened in VS Code still shows red squiggles on framework includes for most users because clangd is either not enabled, not pointed at a query driver, or fighting the MS extension.
Proposed implementation
New CLI surface
Two options, both fine:
- Preferred: new subcommand
fbuild clangd-config [project_dir] [-e <env>] in crates/fbuild-cli/src/cli/args.rs and dispatched from crates/fbuild-cli/src/cli/dispatch.rs. Implementation lives in a new module crates/fbuild-cli/src/cli/clangd_config.rs.
- Alternative: new flag
--emit-clangd-config on fbuild build that runs after a successful build (or after -t compiledb).
Mechanism
-
Resolve the default env name:
- If
-e <env> is given, use it.
- Else call
IniParser::get_default_environment() from fbuild-config.
-
Ensure compile_commands.json exists at the project root: if absent, internally invoke the same code path as fbuild build -t compiledb -e <env> (run_build(..., target = Some("compiledb"), ...), same call used by run_iwyu).
-
Read the generated compile_commands.json, take the first entry, and pull arguments[0] — that is the absolute path to the env's real cross-compiler (e.g. xtensa-esp32-elf-gcc, avr-g++, arm-none-eabi-g++). Compute its directory (the toolchain bin/) for the --query-driver glob. Also collect any -isysroot / --sysroot / --target= flags present in arguments.
-
Write <project>/.clangd:
# Generated by `fbuild clangd-config` — safe to edit, regenerate to refresh.
CompileFlags:
CompilationDatabase: .
# Trust the build's compiler. Avoid clangd's default driver guess.
Compiler: <abs path to env's g++>
Diagnostics:
# Many embedded toolchains emit warnings clangd cannot parse cleanly.
Suppress: [drv_unknown_argument, unknown-warning-option]
-
Write <project>/.vscode/settings.json (merge if it exists; only update the clangd-related keys and a couple of intellisense disable keys):
On Windows the --query-driver glob must use forward slashes and end with * (or *.exe); document this in the generator.
-
Write <project>/.vscode/extensions.json recommending llvm-vs-code-extensions.vscode-clangd if no extensions.json exists. Do not overwrite an existing one.
-
Print a short summary: env name, compiler used, files written, and a one-line hint to run clangd: Restart language server in VS Code.
Where to hook
- New module:
crates/fbuild-cli/src/cli/clangd_config.rs (mirrors the small, self-contained shape of clang_tools.rs).
- Reuse
super::build::{normalize_path, run_build} and fbuild_config::ini_parser for env resolution.
- Reuse
fbuild_packages::toolchain::clang::find_gcc_builtin_include_dirs() as a fallback when compile_commands.json arguments do not contain a usable compiler path (defensive).
Alternatives considered
.clangd only, no settings.json. Works for users who have clangd already configured, but the most common failure mode is that the MS C/C++ extension is active and clangd is silent. Emitting settings.json solves the dominant complaint.
- Skip
--query-driver, rely on -isystem in compile_commands.json. Works in some envs but fails in others (ESP-IDF, AVR libc) because clangd still chooses its own -resource-dir. --query-driver is the recommended path for cross-toolchains and matches what run_iwyu effectively does today.
- Push this into the VS Code extension (
vscode-fbuild/). Reasonable, but the CLI command works for users on Cursor, Neovim+clangd, Helix, Zed, and CI lint jobs too. The extension can later expose a button that just calls fbuild clangd-config.
- Generate
c_cpp_properties.json for the MS extension. Explicitly avoided — the MS extension cannot follow the cross-toolchain reliably and conflicts with clangd. The whole point is to make clangd the source of truth.
Acceptance criteria
Test plan
- Unit tests in
crates/fbuild-cli (or a small helper crate) for the file-merge logic: given a pre-existing .vscode/settings.json with unrelated keys, the generator preserves them and only updates the clangd/MS-extension keys.
- Unit test that extraction of the compiler/toolchain-bin directory from a real
compile_commands.json entry (one ESP32, one AVR, one Teensy) yields a sensible --query-driver glob.
- Integration test under
tests/ that runs fbuild clangd-config against a fixture project with [platformio] default_envs = uno and asserts the three files exist and that .clangd mentions the compiler from the compile DB.
- Cross-platform CI: extend the existing
check-windows.yml / check-macos.yml / check-ubuntu.yml jobs to run the new command against one fixture and assert exit 0 plus file presence.
- Manual: smoke test on a real sketch in VS Code on Windows (path separators are the historical foot-gun).
Notes
- This is a tooling/IDE-integration feature and does not touch the build pipeline; it sits cleanly on top of the existing
compile_database machinery.
- The same generator can later be reused by the
vscode-fbuild extension as a one-click "Configure clangd" action.
Summary
Add a fbuild command (e.g.
fbuild clangd-configor a flag likefbuild build --emit-clangd-config) that, given a project directory with aplatformio.ini, resolves the default environment and emits a working clangd/VS Code configuration into the project so that "Go to Definition", header hover, and include resolution work in VS Code without any extra user setup.Concretely, after running the command the user should get (at the project root):
compile_commands.json— already produced byfbuild build -t compiledbtoday.clangd— a YAML config that pinsCompileFlags.CompilationDatabase: .and exposes the cross-compiler via--query-driver.vscode/settings.json— disables the built-in MS C/C++ IntelliSense provider for this folder, pointsclangd.argumentsat the correct--compile-commands-dirand--query-driverglob, and recommends thellvm-vs-code-extensions.vscode-clangdextensionThe goal: a fresh clone of a FastLED/PlatformIO sketch, plus
fbuild clangd-config, plus the clangd VS Code extension, gives the user working symbol navigation into the env's framework headers (Arduino core, ESP-IDF, AVR libc, Teensy core, etc.) on the first open.Sibling issue for the WASM path: zackees/fastled-wasm#177.
Current state
fbuild already has most of the moving parts but does not stitch them into an "IDE-ready" configuration:
fbuild build -t compiledbexists (crates/fbuild-cli/src/cli/args.rslines ~220–222, parser restricted to"compiledb"), and it is used internally byrun_iwyuandrun_clang_toolincrates/fbuild-cli/src/cli/clang_tools.rsto materializecompile_commands.json.compile_commands.jsonis written to both the build dir and the project root bycrates/fbuild-build/src/compile_database/database.rs, which is exactly what clangd auto-discovers.crates/fbuild-build/src/compile_database/generate.rs::generate_entriesalready records the real GCC/G++ binary paths (not cache wrappers) asarguments[0]— these are the paths clangd needs for--query-driver.crates/fbuild-config/src/ini_parser/mod.rs::get_default_environment(priority:PLATFORMIO_DEFAULT_ENVSenv override →[platformio] default_envs→ first env in file order).fbuild_packages::toolchain::clang::find_gcc_builtin_include_dirs()(used inrun_iwyu).What is missing: a thin "emit IDE config" step on top of
compiledbthat writes.clangdand.vscode/settings.jsonso that:--query-driverso symbols defined in toolchain headers (e.g.<Arduino.h>, ESP-IDF<esp_log.h>) resolve.Today, even after
fbuild build -t compiledbsucceeds, a fresh checkout opened in VS Code still shows red squiggles on framework includes for most users because clangd is either not enabled, not pointed at a query driver, or fighting the MS extension.Proposed implementation
New CLI surface
Two options, both fine:
fbuild clangd-config [project_dir] [-e <env>]incrates/fbuild-cli/src/cli/args.rsand dispatched fromcrates/fbuild-cli/src/cli/dispatch.rs. Implementation lives in a new modulecrates/fbuild-cli/src/cli/clangd_config.rs.--emit-clangd-configonfbuild buildthat runs after a successful build (or after-t compiledb).Mechanism
Resolve the default env name:
-e <env>is given, use it.IniParser::get_default_environment()fromfbuild-config.Ensure
compile_commands.jsonexists at the project root: if absent, internally invoke the same code path asfbuild build -t compiledb -e <env>(run_build(..., target = Some("compiledb"), ...), same call used byrun_iwyu).Read the generated
compile_commands.json, take the first entry, and pullarguments[0]— that is the absolute path to the env's real cross-compiler (e.g.xtensa-esp32-elf-gcc,avr-g++,arm-none-eabi-g++). Compute its directory (the toolchainbin/) for the--query-driverglob. Also collect any-isysroot/--sysroot/--target=flags present inarguments.Write
<project>/.clangd:Write
<project>/.vscode/settings.json(merge if it exists; only update the clangd-related keys and a couple of intellisense disable keys):{ "C_Cpp.intelliSenseEngine": "disabled", "C_Cpp.autoAddFileAssociations": false, "clangd.arguments": [ "--compile-commands-dir=${workspaceFolder}", "--query-driver=<toolchain bin glob, e.g. C:/Users/<u>/.platformio/packages/toolchain-xtensa-esp32/bin/*>", "--background-index", "--clang-tidy", "--header-insertion=never", "--completion-style=detailed" ] }On Windows the
--query-driverglob must use forward slashes and end with*(or*.exe); document this in the generator.Write
<project>/.vscode/extensions.jsonrecommendingllvm-vs-code-extensions.vscode-clangdif noextensions.jsonexists. Do not overwrite an existing one.Print a short summary: env name, compiler used, files written, and a one-line hint to run
clangd: Restart language serverin VS Code.Where to hook
crates/fbuild-cli/src/cli/clangd_config.rs(mirrors the small, self-contained shape ofclang_tools.rs).super::build::{normalize_path, run_build}andfbuild_config::ini_parserfor env resolution.fbuild_packages::toolchain::clang::find_gcc_builtin_include_dirs()as a fallback whencompile_commands.jsonarguments do not contain a usable compiler path (defensive).Alternatives considered
.clangdonly, no settings.json. Works for users who have clangd already configured, but the most common failure mode is that the MS C/C++ extension is active and clangd is silent. Emittingsettings.jsonsolves the dominant complaint.--query-driver, rely on-isystemincompile_commands.json. Works in some envs but fails in others (ESP-IDF, AVR libc) because clangd still chooses its own-resource-dir.--query-driveris the recommended path for cross-toolchains and matches whatrun_iwyueffectively does today.vscode-fbuild/). Reasonable, but the CLI command works for users on Cursor, Neovim+clangd, Helix, Zed, and CI lint jobs too. The extension can later expose a button that just callsfbuild clangd-config.c_cpp_properties.jsonfor the MS extension. Explicitly avoided — the MS extension cannot follow the cross-toolchain reliably and conflicts with clangd. The whole point is to make clangd the source of truth.Acceptance criteria
fbuild clangd-config(or equivalent flag) exists, is documented in--help, and accepts an optional[project_dir]and-e/--environment.IniParser::get_default_environment(PLATFORMIO_DEFAULT_ENVS →[platformio].default_envs→ first env).compile_commands.json,.clangd,.vscode/settings.json..vscode/extensions.jsonis created only if absent..clangdreferences the project root as the compilation database directory..vscode/settings.jsondisables the MS C/C++ IntelliSense engine for this folder and setsclangd.argumentswith--compile-commands-dirand--query-driverpointing at the env's toolchainbin/directory..vscode/settings.json..vscode/settings.jsonuse forward slashes (clangd-friendly); the--query-driverglob ends with*and matches the toolchain executables.src/*.cppin VS Code (with the clangd extension installed and the MS extension also installed) shows zero red squiggles on framework includes and "Go to Definition" jumps into the toolchain headers.Test plan
crates/fbuild-cli(or a small helper crate) for the file-merge logic: given a pre-existing.vscode/settings.jsonwith unrelated keys, the generator preserves them and only updates the clangd/MS-extension keys.compile_commands.jsonentry (one ESP32, one AVR, one Teensy) yields a sensible--query-driverglob.tests/that runsfbuild clangd-configagainst a fixture project with[platformio] default_envs = unoand asserts the three files exist and that.clangdmentions the compiler from the compile DB.check-windows.yml/check-macos.yml/check-ubuntu.ymljobs to run the new command against one fixture and assert exit 0 plus file presence.Notes
compile_databasemachinery.vscode-fbuildextension as a one-click "Configure clangd" action.