diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77fe79fd1d..8d2ec68010 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -292,6 +292,11 @@ jobs: name: Run script/clang-tidy for ESP32 IDF options: --environment esp32-idf-tidy --grep USE_ESP_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: - name: Check out code from GitHub @@ -331,13 +336,13 @@ jobs: - name: Run clang-tidy run: | . venv/bin/activate - script/clang-tidy --all-headers --fix ${{ matrix.options }} + script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} env: # Also cache libdeps, store them in a ~/.platformio subfolder PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps - name: Suggested changes - run: script/ci-suggest-changes + run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }} # yamllint disable-line rule:line-length if: always() diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 378d95e7b9..7cdffafdb5 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,5 +1,4 @@ #pragma once - #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -28,6 +27,11 @@ using SPIInterface = spi_host_device_t; #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. */ diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 9f4099e67f..8bc554d5f4 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -20,11 +20,6 @@ // Feature flags #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_BUTTON #define USE_CLIMATE @@ -79,20 +74,10 @@ #define USE_LVGL_TEXTAREA #define USE_LVGL_TILEVIEW #define USE_LVGL_TOUCHSCREEN -#define USE_MD5 #define USE_MDNS #define USE_MEDIA_PLAYER -#define USE_MQTT -#define USE_NETWORK #define USE_NEXTION_TFT_UPLOAD #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_POWER_SUPPLY #define USE_QR_CODE @@ -107,9 +92,28 @@ #define USE_UART_DEBUGGER #define USE_UPDATE #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_AP #define USE_WIREGUARD +#endif // Arduino-specific feature flags #ifdef USE_ARDUINO diff --git a/platformio.ini b/platformio.ini index 61b7f8d746..ccfd52c3ca 100644 --- a/platformio.ini +++ b/platformio.ini @@ -194,6 +194,26 @@ build_flags = -DUSE_LIBRETINY 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. ;;;;;;;; ESP8266 ;;;;;;;; @@ -440,3 +460,19 @@ build_flags = ${common.build_flags} -DUSE_HOST -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} diff --git a/script/ci-custom.py b/script/ci-custom.py index dda5410778..a3a31b2259 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -558,6 +558,7 @@ def lint_relative_py_import(fname): "esphome/components/rp2040/core.cpp", "esphome/components/libretiny/core.cpp", "esphome/components/host/core.cpp", + "esphome/components/zephyr/core.cpp", "esphome/components/http_request/httplib.h", ], ) diff --git a/script/clang-tidy b/script/clang-tidy index a857274b01..5baaaf6b3a 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -40,12 +40,37 @@ def clang_options(idedata): else: 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 cmd.extend( [ # disable built-in include directories from the host "-nostdinc", - "-nostdinc++", # replace pgmspace.h, as it uses GNU extensions clang doesn't support # https://github.com/earlephilhower/newlib-xtensa/pull/18 "-D_PGMSPACE_H_", @@ -70,22 +95,7 @@ def clang_options(idedata): ) # copy compiler flags, except those clang doesn't understand. - cmd.extend( - 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", - ) - ) + cmd.extend(flag for flag in idedata["cxx_flags"] if flag not in omit_flags) # 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 for directory in list(idedata["includes"]["build"]): # skip our own directories, we add those later - if not directory.startswith(f"{root_path}") or directory.startswith( - ( - f"{root_path}/.pio", - f"{root_path}/.platformio", - f"{root_path}/.temp", - f"{root_path}/managed_components", + if ( + not directory.startswith(f"{root_path}") + or directory.startswith( + ( + f"{root_path}/.platformio", + f"{root_path}/.temp", + f"{root_path}/managed_components", + ) ) + or (directory.startswith(f"{root_path}") and "/.pio/" in directory) ): cmd.extend(["-isystem", directory]) diff --git a/script/helpers.py b/script/helpers.py index 6148371e32..3c1b0c0ddd 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -5,6 +5,7 @@ import re import subprocess import colorama +import helpers_zephyr root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) 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 Path(temp_folder).mkdir(exist_ok=True) - stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment]) - match = re.search(r'{\s*".*}', stdout.decode("utf-8")) - data = json.loads(match.group()) - + if "nrf" in environment: + data = helpers_zephyr.load_idedata(environment, temp_folder, platformio_ini) + 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") return data diff --git a/script/helpers_zephyr.py b/script/helpers_zephyr.py new file mode 100644 index 0000000000..c3ba149005 --- /dev/null +++ b/script/helpers_zephyr.py @@ -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 +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)