mirror of
https://github.com/esphome/esphome.git
synced 2026-07-06 06:14:57 +00:00
117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
"""Load clang-tidy idedata for the nrf52/Zephyr environment.
|
|
|
|
The compile commands come from a configure-only build of a minimal Zephyr
|
|
project using the native sdk-nrf toolchain (see
|
|
``esphome.components.nrf52.clang_tidy``); this module extracts the include
|
|
paths, defines and compiler flags clang-tidy needs from them.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
|
|
|
|
def load_idedata(environment, temp_folder, platformio_ini):
|
|
if explicit := os.environ.get("ESPHOME_ZEPHYR_COMPILE_COMMANDS"):
|
|
compile_commands_path = Path(explicit)
|
|
else:
|
|
from esphome.components.nrf52.clang_tidy import generate_compile_commands
|
|
|
|
work_dir = (Path(temp_folder) / f"zephyr-{environment}").resolve()
|
|
compile_commands_path = generate_compile_commands(
|
|
work_dir, Path(platformio_ini)
|
|
)
|
|
|
|
if not compile_commands_path.is_file():
|
|
raise RuntimeError(f"compile_commands.json not found: {compile_commands_path}")
|
|
|
|
def extract_include_paths(command):
|
|
include_paths = []
|
|
include_pattern = re.compile(r'("-I\s*[^"]+)|(-isystem\s*[^\s]+)|(-I\s*[^\s]+)')
|
|
for match in include_pattern.findall(command):
|
|
split_strings = re.split(
|
|
r"\s*-\s*(?:I|isystem)", list(filter(lambda x: x, match))[0]
|
|
)
|
|
include_paths.append(split_strings[1].strip())
|
|
return include_paths
|
|
|
|
def extract_defines(command):
|
|
define_pattern = re.compile(r"-D\s*([^\s]+)")
|
|
ignore_prefixes = ("_ASMLANGUAGE", "NRF_802154_ECB_PRIORITY=")
|
|
return [
|
|
match.replace("\\", "")
|
|
for match in define_pattern.findall(command)
|
|
if not any(match.startswith(prefix) for prefix in ignore_prefixes)
|
|
]
|
|
|
|
def get_builtin_include_paths(compiler):
|
|
result = subprocess.run(
|
|
[compiler, "-E", "-x", "c++", "-", "-v"],
|
|
input="",
|
|
text=True,
|
|
stderr=subprocess.PIPE,
|
|
stdout=subprocess.DEVNULL,
|
|
check=True,
|
|
)
|
|
include_paths = []
|
|
start_collecting = False
|
|
for line in result.stderr.splitlines():
|
|
if start_collecting:
|
|
if line.startswith(" "):
|
|
include_paths.append(line.strip())
|
|
else:
|
|
break
|
|
if "#include <...> search starts here:" in line:
|
|
start_collecting = True
|
|
return include_paths
|
|
|
|
def extract_cxx_flags(command):
|
|
# Extracts CXXFLAGS from the command string, excluding includes and
|
|
# defines. Anchored per token: a substring match would extract a bogus
|
|
# "-format-zero-length" from -Wno-format-zero-length.
|
|
flag_pattern = re.compile(
|
|
r"^(-O[0-3s]|-g|-std=.+|-Wall|-Wextra|-Werror|--.+|-f.+|-m.+|-imacros.+)$"
|
|
)
|
|
flags = []
|
|
tokens = shlex.split(command)
|
|
for i, token in enumerate(tokens):
|
|
if token == "-imacros" and i + 1 < len(tokens):
|
|
flags.append(f"-imacros{tokens[i + 1]}")
|
|
elif flag_pattern.match(token):
|
|
flags.append(token)
|
|
return flags
|
|
|
|
def transform_to_idedata_format(compile_commands):
|
|
# Use only the tidy app TU (main.cpp): as the app target, its compile
|
|
# command already carries the full Zephyr include set. Unioning every
|
|
# TU instead would drag in per-library internal include dirs (e.g. the
|
|
# Zephyr POSIX shim, whose signal.h redefines newlib's sigset_t) that
|
|
# no ESPHome source compiles against.
|
|
entry = next(
|
|
(e for e in compile_commands if e["file"].endswith("main.cpp")), None
|
|
)
|
|
if entry is None:
|
|
raise RuntimeError("tidy main.cpp not found in compile_commands.json")
|
|
command = entry["command"]
|
|
# Find the compiler by name: the command may be prefixed with a
|
|
# launcher (Zephyr auto-enables ccache when present).
|
|
cxx_path = next((t for t in shlex.split(command) if t.endswith("++")), None)
|
|
if cxx_path is None:
|
|
raise RuntimeError(f"no C++ compiler in compile command: {command}")
|
|
|
|
return {
|
|
"includes": {
|
|
"toolchain": get_builtin_include_paths(cxx_path),
|
|
"build": extract_include_paths(command),
|
|
},
|
|
"defines": extract_defines(command),
|
|
"cxx_path": cxx_path,
|
|
"cxx_flags": extract_cxx_flags(command),
|
|
}
|
|
|
|
compile_commands = json.loads(compile_commands_path.read_text(encoding="utf-8"))
|
|
return transform_to_idedata_format(compile_commands)
|