|
# SPDX-License-Identifier: CC0-1.0 |
|
# pyright: reportMissingImports=false, reportUndefinedVariable=false |
|
# flake8: noqa |
|
""" |
|
Utility build hook to generate and fix a compile_commands.json suitable for |
|
clangd. Prepares the compilation database and propagates -I flags to library |
|
entries under `lib/`. |
|
Additionally, in environments with multiple toolchains installed, this script restricts |
|
include paths to a single specified toolchain to ensure deterministic include |
|
resolution. |
|
|
|
Usage (must be run as a pair): |
|
pio run -t compiledb && pio run -t fix_compiledb |
|
|
|
Run `compiledb` first, then `fix_compiledb`. |
|
|
|
Version Info (pio --version) |
|
PlatformIO Core, version 6.1.18 |
|
""" |
|
|
|
import glob |
|
import json |
|
import os |
|
import sys |
|
import shlex |
|
|
|
Import("env") # type: ignore |
|
|
|
|
|
def pre_compiledb_actions(toolchain, framework_libs=None): |
|
""" |
|
Prepare compile_commands.json for clangd: |
|
- restrict CPPPATH to the specified toolchain and Arduino framework libs |
|
- set COMPILATIONDB_PATH to the build directory |
|
|
|
framework_libs_subdirs: optional list of sub-path patterns under PROJECT_PACKAGES_DIR |
|
e.g. [os.path.join("arduinoespressif32", "libraries")] |
|
""" |
|
print("start pre_compiledb_actions") |
|
# turn off default toolchain includes |
|
env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=False) |
|
# restrict CPPPATH to xtensa-esp32s3-elf include paths to avoid mixing toolchains. |
|
toolchain_paths = [ |
|
p for p in env.DumpIntegrationIncludes().get("toolchain", []) if toolchain in p |
|
] |
|
# add path(s) to Arduino framework libraries (support multiple framework dirs) |
|
packages_dir = env.get("PROJECT_PACKAGES_DIR") |
|
framework_libs = framework_libs or [] |
|
framework_libs_dirs = [] |
|
for sub in framework_libs: |
|
# join packages_dir with the provided sub-path pattern and glob |
|
pattern = os.path.join(packages_dir, sub) |
|
framework_libs_dirs.extend(glob.glob(pattern)) |
|
|
|
src_paths = [] |
|
for libraries_dir in framework_libs_dirs: |
|
src_paths.extend(glob.glob(os.path.join(libraries_dir, "*", "src"))) |
|
libs_path = list(set(src_paths)) |
|
# replace CPPPATH for compiledb only |
|
env.Replace(CPPPATH=toolchain_paths + libs_path) |
|
# create compile_commands.json |
|
env.Replace(COMPILATIONDB_PATH=os.path.join("$BUILD_DIR", "compile_commands.json")) |
|
|
|
print("completed pre_compiledb_actions") |
|
|
|
|
|
def fix_compiledb_action(*args, **kwargs): |
|
""" |
|
Post-process compile_commands.json: |
|
- find the entry for the given project-relative source (args[1]) |
|
- extract its -I include flags and append missing ones to entries under 'lib/' |
|
- write the file back only if modifications were made |
|
""" |
|
print("start fix_compiledb_action") |
|
|
|
compile_commands_dir = os.path.normpath(args[0]) |
|
src_main = os.path.normpath(args[1]) |
|
|
|
compile_commands_path = os.path.join(compile_commands_dir, "compile_commands.json") |
|
if not os.path.isfile(compile_commands_path): |
|
print( |
|
f"fix_compiledb_action: compile_commands.json not found at {compile_commands_path}", |
|
file=sys.stderr, |
|
) |
|
return |
|
|
|
compile_commands = [] |
|
with open(compile_commands_path, "r", encoding="utf-8") as fp: |
|
compile_commands = json.load(fp) |
|
|
|
match = next( |
|
( |
|
entry |
|
for entry in compile_commands |
|
if os.path.normpath(entry.get("file") or "") == src_main |
|
), |
|
None, |
|
) |
|
if not match: |
|
print(f"fix_compiledb_action: no match {src_main}", file=sys.stderr) |
|
return |
|
cmd = match.get("command") or " ".join(match.get("arguments", [])) |
|
tokens = shlex.split(cmd) |
|
includes = [t for t in tokens if t.startswith("-I")] |
|
|
|
# propagate -I flags to entries under 'lib/' |
|
lib_count = 0 |
|
if includes: |
|
for entry in compile_commands: |
|
rel = os.path.normpath(entry.get("file")).replace(os.sep, "/") |
|
if rel.startswith("lib/"): |
|
original_cmd = entry.get("command") or "" |
|
existing_tokens = shlex.split(original_cmd) |
|
# only append tokens that are not already present to avoid duplication |
|
to_append = [inc for inc in includes if inc not in existing_tokens] |
|
if to_append: |
|
entry["command"] = original_cmd + (" " + " ".join(to_append)) |
|
lib_count += 1 |
|
if lib_count: |
|
with open(compile_commands_path, "w", encoding="utf-8") as fp: |
|
json.dump(compile_commands, fp, indent=2) |
|
print( |
|
f"Appended includes to {lib_count} lib entries in {compile_commands_path}" |
|
) |
|
|
|
print("completed fix_compiledb_action") |
|
|
|
|
|
# pio run -t compiledb |
|
if "compiledb" in COMMAND_LINE_TARGETS: |
|
# Running `pio run -t compiledb` alone can aggregate include paths from all |
|
# installed toolchains; to avoid that we explicitly restrict to a single |
|
# toolchain here and collect includes only for it. |
|
pre_compiledb_actions( |
|
# ls -laF ~/.platformio/packages | grep toolchain |
|
# drwx------ 6 hiromasa hiromasa 4096 10月 13 2024 toolchain-esp32ulp/ |
|
# drwx------ 7 hiromasa hiromasa 4096 9月 16 2024 toolchain-riscv32-esp/ |
|
# drwx------ 7 hiromasa hiromasa 4096 9月 16 2024 toolchain-xtensa-esp32s3/ |
|
# specify exactly one toolchain identifier, e.g. "xtensa-esp32s3-elf" |
|
"xtensa-esp32s3-elf", |
|
# ls -laF ~/.platformio/packages | grep frame |
|
# drwx------ 6 hiromasa hiromasa 4096 10月 14 2023 framework-arduino-gd32v/ |
|
# drwx------ 6 hiromasa hiromasa 4096 9月 16 2024 framework-arduinoespressif32/ |
|
# drwx------ 8 hiromasa hiromasa 4096 10月 13 2024 framework-espidf/ |
|
# framework library subdirs under PROJECT_PACKAGES_DIR (joined with packages_dir inside function) |
|
[ |
|
os.path.join("framework-arduinoespressif32", "libraries"), |
|
], |
|
) |
|
|
|
|
|
# pio run -t fix_compiledb |
|
env.AddCustomTarget( |
|
"fix_compiledb", |
|
None, |
|
fix_compiledb_action( |
|
# build directory where compile_commands.json is written (COMPILATIONDB_PATH) |
|
# .pio/build/esp32-s3-devkitc-1 |
|
os.path.join(env.get("PROJECT_BUILD_DIR"), env.get("PIOENV")), |
|
# source file whose compile_commands entry is used to extract -I include flags |
|
# src/main.cpp |
|
os.path.join("src", "main.cpp"), |
|
), |
|
) |