[clang] clang tidy support with zephyr (#8352)

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
tomaszduda23 2025-05-13 01:36:34 +02:00 committed by GitHub
parent f4eb75e4e0
commit 7c0546c9f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 237 additions and 45 deletions

View File

@ -292,6 +292,11 @@ jobs:
name: Run script/clang-tidy for ESP32 IDF name: Run script/clang-tidy for ESP32 IDF
options: --environment esp32-idf-tidy --grep USE_ESP_IDF options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf pio_cache_key: tidyesp32-idf
- id: clang-tidy
name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR
pio_cache_key: tidy-zephyr
ignore_errors: true
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -331,13 +336,13 @@ jobs:
- name: Run clang-tidy - name: Run clang-tidy
run: | run: |
. venv/bin/activate . venv/bin/activate
script/clang-tidy --all-headers --fix ${{ matrix.options }} script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
env: env:
# Also cache libdeps, store them in a ~/.platformio subfolder # Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
- name: Suggested changes - name: Suggested changes
run: script/ci-suggest-changes run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
if: always() if: always()

View File

@ -1,5 +1,4 @@
#pragma once #pragma once
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
@ -28,6 +27,11 @@ using SPIInterface = spi_host_device_t;
#endif // USE_ESP_IDF #endif // USE_ESP_IDF
#ifdef USE_ZEPHYR
// TODO supprse clang-tidy. Remove after SPI driver for nrf52 is added.
using SPIInterface = void *;
#endif
/** /**
* Implementation of SPI Controller mode. * Implementation of SPI Controller mode.
*/ */

View File

@ -20,11 +20,6 @@
// Feature flags // Feature flags
#define USE_ALARM_CONTROL_PANEL #define USE_ALARM_CONTROL_PANEL
#define USE_AUDIO_FLAC_SUPPORT
#define USE_AUDIO_MP3_SUPPORT
#define USE_API
#define USE_API_NOISE
#define USE_API_PLAINTEXT
#define USE_BINARY_SENSOR #define USE_BINARY_SENSOR
#define USE_BUTTON #define USE_BUTTON
#define USE_CLIMATE #define USE_CLIMATE
@ -79,20 +74,10 @@
#define USE_LVGL_TEXTAREA #define USE_LVGL_TEXTAREA
#define USE_LVGL_TILEVIEW #define USE_LVGL_TILEVIEW
#define USE_LVGL_TOUCHSCREEN #define USE_LVGL_TOUCHSCREEN
#define USE_MD5
#define USE_MDNS #define USE_MDNS
#define USE_MEDIA_PLAYER #define USE_MEDIA_PLAYER
#define USE_MQTT
#define USE_NETWORK
#define USE_NEXTION_TFT_UPLOAD #define USE_NEXTION_TFT_UPLOAD
#define USE_NUMBER #define USE_NUMBER
#define USE_ONLINE_IMAGE_BMP_SUPPORT
#define USE_ONLINE_IMAGE_PNG_SUPPORT
#define USE_ONLINE_IMAGE_JPEG_SUPPORT
#define USE_OTA
#define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK
#define USE_OTA_VERSION 2
#define USE_OUTPUT #define USE_OUTPUT
#define USE_POWER_SUPPLY #define USE_POWER_SUPPLY
#define USE_QR_CODE #define USE_QR_CODE
@ -107,9 +92,28 @@
#define USE_UART_DEBUGGER #define USE_UART_DEBUGGER
#define USE_UPDATE #define USE_UPDATE
#define USE_VALVE #define USE_VALVE
// Feature flags which do not work for zephyr
#ifndef USE_ZEPHYR
#define USE_AUDIO_FLAC_SUPPORT
#define USE_AUDIO_MP3_SUPPORT
#define USE_API
#define USE_API_NOISE
#define USE_API_PLAINTEXT
#define USE_MD5
#define USE_MQTT
#define USE_NETWORK
#define USE_ONLINE_IMAGE_BMP_SUPPORT
#define USE_ONLINE_IMAGE_PNG_SUPPORT
#define USE_ONLINE_IMAGE_JPEG_SUPPORT
#define USE_OTA
#define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK
#define USE_OTA_VERSION 2
#define USE_WIFI #define USE_WIFI
#define USE_WIFI_AP #define USE_WIFI_AP
#define USE_WIREGUARD #define USE_WIREGUARD
#endif
// Arduino-specific feature flags // Arduino-specific feature flags
#ifdef USE_ARDUINO #ifdef USE_ARDUINO

View File

@ -194,6 +194,26 @@ build_flags =
-DUSE_LIBRETINY -DUSE_LIBRETINY
build_src_flags = -include Arduino.h build_src_flags = -include Arduino.h
; This is the common settings for the nRF52 using Zephyr.
[common:nrf52-zephyr]
extends = common
platform = https://github.com/tomaszduda23/platform-nordicnrf52/archive/refs/tags/v10.3.0-1.zip
framework = zephyr
platform_packages =
platformio/framework-zephyr @ https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-4.zip
platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.16.1-1.zip
build_flags =
${common.build_flags}
-DUSE_ZEPHYR
-DUSE_NRF52
lib_deps =
bblanchon/ArduinoJson@7.0.0 ; json
wjtje/qr-code-generator-library@1.7.0 ; qr_code
pavlodn/HaierProtocol@0.9.31 ; haier
functionpointer/arduino-MLX90393@1.0.2 ; mlx90393
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
lvgl/lvgl@8.4.0 ; lvgl
; All the actual environments are defined below. ; All the actual environments are defined below.
;;;;;;;; ESP8266 ;;;;;;;; ;;;;;;;; ESP8266 ;;;;;;;;
@ -440,3 +460,19 @@ build_flags =
${common.build_flags} ${common.build_flags}
-DUSE_HOST -DUSE_HOST
-std=c++17 -std=c++17
;;;;;;;; nRF52 ;;;;;;;;
[env:nrf52]
extends = common:nrf52-zephyr
board = adafruit_feather_nrf52840
build_flags =
${common:nrf52-zephyr.build_flags}
${flags:runtime.build_flags}
[env:nrf52-tidy]
extends = common:nrf52-zephyr
board = adafruit_feather_nrf52840
build_flags =
${common:nrf52-zephyr.build_flags}
${flags:clangtidy.build_flags}

View File

@ -558,6 +558,7 @@ def lint_relative_py_import(fname):
"esphome/components/rp2040/core.cpp", "esphome/components/rp2040/core.cpp",
"esphome/components/libretiny/core.cpp", "esphome/components/libretiny/core.cpp",
"esphome/components/host/core.cpp", "esphome/components/host/core.cpp",
"esphome/components/zephyr/core.cpp",
"esphome/components/http_request/httplib.h", "esphome/components/http_request/httplib.h",
], ],
) )

View File

@ -40,12 +40,37 @@ def clang_options(idedata):
else: else:
cmd.append(f"--target={triplet}") cmd.append(f"--target={triplet}")
omit_flags = (
"-free",
"-fipa-pta",
"-fstrict-volatile-bitfields",
"-mlongcalls",
"-mtext-section-literals",
"-mdisable-hardware-atomics",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
"-fno-tree-switch-conversion",
)
if "zephyr" in triplet:
omit_flags += (
"-fno-reorder-functions",
"-mfp16-format=ieee",
"--param=min-pagesize=0",
)
else:
cmd.extend(
[
# disable built-in include directories from the host
"-nostdinc++",
]
)
# set flags # set flags
cmd.extend( cmd.extend(
[ [
# disable built-in include directories from the host # disable built-in include directories from the host
"-nostdinc", "-nostdinc",
"-nostdinc++",
# replace pgmspace.h, as it uses GNU extensions clang doesn't support # replace pgmspace.h, as it uses GNU extensions clang doesn't support
# https://github.com/earlephilhower/newlib-xtensa/pull/18 # https://github.com/earlephilhower/newlib-xtensa/pull/18
"-D_PGMSPACE_H_", "-D_PGMSPACE_H_",
@ -70,22 +95,7 @@ def clang_options(idedata):
) )
# copy compiler flags, except those clang doesn't understand. # copy compiler flags, except those clang doesn't understand.
cmd.extend( cmd.extend(flag for flag in idedata["cxx_flags"] if flag not in omit_flags)
flag
for flag in idedata["cxx_flags"]
if flag
not in (
"-free",
"-fipa-pta",
"-fstrict-volatile-bitfields",
"-mlongcalls",
"-mtext-section-literals",
"-mdisable-hardware-atomics",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
"-fno-tree-switch-conversion",
)
)
# defines # defines
cmd.extend(f"-D{define}" for define in idedata["defines"]) cmd.extend(f"-D{define}" for define in idedata["defines"])
@ -100,13 +110,16 @@ def clang_options(idedata):
# add library include directories using -isystem to suppress their errors # add library include directories using -isystem to suppress their errors
for directory in list(idedata["includes"]["build"]): for directory in list(idedata["includes"]["build"]):
# skip our own directories, we add those later # skip our own directories, we add those later
if not directory.startswith(f"{root_path}") or directory.startswith( if (
( not directory.startswith(f"{root_path}")
f"{root_path}/.pio", or directory.startswith(
f"{root_path}/.platformio", (
f"{root_path}/.temp", f"{root_path}/.platformio",
f"{root_path}/managed_components", f"{root_path}/.temp",
f"{root_path}/managed_components",
)
) )
or (directory.startswith(f"{root_path}") and "/.pio/" in directory)
): ):
cmd.extend(["-isystem", directory]) cmd.extend(["-isystem", directory])

View File

@ -5,6 +5,7 @@ import re
import subprocess import subprocess
import colorama import colorama
import helpers_zephyr
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", "..")))
basepath = os.path.join(root_path, "esphome") basepath = os.path.join(root_path, "esphome")
@ -147,10 +148,14 @@ def load_idedata(environment):
# ensure temp directory exists before running pio, as it writes sdkconfig to it # ensure temp directory exists before running pio, as it writes sdkconfig to it
Path(temp_folder).mkdir(exist_ok=True) Path(temp_folder).mkdir(exist_ok=True)
stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment]) if "nrf" in environment:
match = re.search(r'{\s*".*}', stdout.decode("utf-8")) data = helpers_zephyr.load_idedata(environment, temp_folder, platformio_ini)
data = json.loads(match.group()) else:
stdout = subprocess.check_output(
["pio", "run", "-t", "idedata", "-e", environment]
)
match = re.search(r'{\s*".*}', stdout.decode("utf-8"))
data = json.loads(match.group())
temp_idedata.write_text(json.dumps(data, indent=2) + "\n") temp_idedata.write_text(json.dumps(data, indent=2) + "\n")
return data return data

124
script/helpers_zephyr.py Normal file
View File

@ -0,0 +1,124 @@
import json
from pathlib import Path
import re
import subprocess
def load_idedata(environment, temp_folder, platformio_ini):
build_environment = environment.replace("-tidy", "")
build_dir = Path(temp_folder) / f"build-{build_environment}"
Path(build_dir).mkdir(exist_ok=True)
Path(build_dir / "platformio.ini").write_text(
Path(platformio_ini).read_text(encoding="utf-8"), encoding="utf-8"
)
esphome_dir = Path(build_dir / "esphome")
esphome_dir.mkdir(exist_ok=True)
Path(esphome_dir / "main.cpp").write_text(
"""
#include <zephyr/kernel.h>
int main() { return 0;}
""",
encoding="utf-8",
)
zephyr_dir = Path(build_dir / "zephyr")
zephyr_dir.mkdir(exist_ok=True)
Path(zephyr_dir / "prj.conf").write_text(
"""
CONFIG_NEWLIB_LIBC=y
""",
encoding="utf-8",
)
subprocess.run(["pio", "run", "-e", build_environment, "-d", build_dir], check=True)
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])
return include_paths
def extract_defines(command):
defines = []
define_pattern = re.compile(r"-D\s*([^\s]+)")
for match in define_pattern.findall(command):
if match not in ("_ASMLANGUAGE"):
defines.append(match)
return defines
def find_cxx_path(commands):
for entry in commands:
command = entry["command"]
cxx_path = command.split()[0]
if not cxx_path.endswith("++"):
continue
return cxx_path
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):
flags = []
# Extracts CXXFLAGS from the command string, excluding includes and defines.
flag_pattern = re.compile(
r"(-O[0-3s]|-g|-std=[^\s]+|-Wall|-Wextra|-Werror|--[^\s]+|-f[^\s]+|-m[^\s]+|-imacros\s*[^\s]+)"
)
for match in flag_pattern.findall(command):
flags.append(match.replace("-imacros ", "-imacros"))
return flags
def transform_to_idedata_format(compile_commands):
cxx_path = find_cxx_path(compile_commands)
idedata = {
"includes": {
"toolchain": get_builtin_include_paths(cxx_path),
"build": set(),
},
"defines": set(),
"cxx_path": cxx_path,
"cxx_flags": set(),
}
for entry in compile_commands:
command = entry["command"]
exec = command.split()[0]
if exec != cxx_path:
continue
idedata["includes"]["build"].update(extract_include_paths(command))
idedata["defines"].update(extract_defines(command))
idedata["cxx_flags"].update(extract_cxx_flags(command))
# Convert sets to lists for JSON serialization
idedata["includes"]["build"] = list(idedata["includes"]["build"])
idedata["defines"] = list(idedata["defines"])
idedata["cxx_flags"] = list(idedata["cxx_flags"])
return idedata
compile_commands = json.loads(
Path(
build_dir / ".pio" / "build" / build_environment / "compile_commands.json"
).read_text(encoding="utf-8")
)
return transform_to_idedata_format(compile_commands)