From 41cf842d5d9ca377c6338760f91bcb4e7755080f Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Thu, 2 Jul 2026 16:13:56 +0200 Subject: [PATCH] [zephyr][nrf52] Rebuild native build when config inputs change (#17318) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/nrf52/__init__.py | 20 ++++++++++--- esphome/components/zephyr/__init__.py | 41 +++++++++++++++++++++------ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 00271c97c7..64946e3cd1 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -58,7 +58,7 @@ from esphome.framework_helpers import ( get_project_link_flags, run_command_ok, ) -from esphome.helpers import write_file_if_changed +from esphome.helpers import rmtree, write_file_if_changed from esphome.storage_json import StorageJSON from esphome.types import ConfigType @@ -697,7 +697,8 @@ def process_stacktrace(config: ConfigType, line: str, backtrace_state: bool) -> return False -def _generate_cmake_lists() -> None: +def _generate_cmake_lists() -> bool: + """Write the project CMakeLists.txt, returning True if it changed.""" compile_flags = get_project_compile_flags() link_flags = get_project_link_flags() @@ -732,7 +733,7 @@ def _generate_cmake_lists() -> None: ")", ] - write_file_if_changed( + return write_file_if_changed( CORE.relative_build_path("zephyr", "CMakeLists.txt"), "\n".join(lines) + "\n", ) @@ -751,12 +752,23 @@ def run_compile(args, config: ConfigType) -> bool: paths = get_build_paths() env = get_build_env() - _generate_cmake_lists() + cmake_lists_changed = _generate_cmake_lists() board = zephyr_data()[KEY_BOARD] build_dir = CORE.relative_pioenvs_path(CORE.name) source_dir = CORE.relative_build_path("zephyr") + # A missing CMake cache (dropped by zephyr's copy_files() on config + # change) or a changed CMakeLists.txt requires a pristine build: Zephyr + # caches Kconfig/devicetree state that survives a plain cmake re-run. + # West can't do the wipe — its pristine modes only recognize a build dir + # by reading ZEPHYR_BASE from the very cache that was dropped. + if ( + cmake_lists_changed or not (build_dir / "CMakeCache.txt").is_file() + ) and build_dir.is_dir(): + _LOGGER.info("Build inputs changed, cleaning %s", build_dir) + rmtree(build_dir) + west_cmd = [ str(paths["python_executable"]), "-m", diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index bd5f01aa3a..cd077a142f 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -8,6 +8,7 @@ from esphome.const import CONF_BOARD, KEY_CORE, KEY_FRAMEWORK_VERSION from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.helpers import copy_file_if_changed, write_file_if_changed from esphome.types import ConfigType +from esphome.writer import clean_cmake_cache from .const import ( CONF_CDC_ACM, @@ -203,7 +204,20 @@ def zephyr_add_user(key, value): user[key] += [value] -def copy_files(): +def _write_file_if_changed_or_remove_when_empty(path: Path, content: str) -> bool: + """Write content to path, or remove a stale file when content is empty. + + Returns True if the file changed on disk. + """ + if content: + return write_file_if_changed(path, content) + if path.is_file(): + path.unlink() + return True + return False + + +def copy_files() -> None: user = zephyr_data()[KEY_USER] if user: entries = " ".join( @@ -219,6 +233,8 @@ def copy_files(): """ ) + changed = False + for image, want_opts in zephyr_data()[KEY_PRJ_CONF].items(): prj_conf = ( "\n".join( @@ -233,26 +249,25 @@ def copy_files(): else: path = CORE.relative_build_path("zephyr/prj.conf") - write_file_if_changed(CORE.relative_build_path(path), prj_conf) + changed |= write_file_if_changed(path, prj_conf) for image, content in zephyr_data()[KEY_OVERLAY].items(): if image: path = CORE.relative_build_path(f"sysbuild/{image}.overlay") else: path = CORE.relative_build_path("zephyr/app.overlay") - write_file_if_changed(path, content) + changed |= write_file_if_changed(path, content) for filename, path in zephyr_data()[KEY_EXTRA_BUILD_FILES].items(): - copy_file_if_changed( + changed |= copy_file_if_changed( path, CORE.relative_build_path(filename), ) pm_static = "\n".join(str(item) for item in zephyr_data()[KEY_PM_STATIC]) - if pm_static: - write_file_if_changed( - CORE.relative_build_path("zephyr/pm_static.yml"), pm_static - ) + changed |= _write_file_if_changed_or_remove_when_empty( + CORE.relative_build_path("zephyr/pm_static.yml"), pm_static + ) kconfig = zephyr_data()[KEY_KCONFIG] if kconfig: @@ -267,4 +282,12 @@ def copy_files(): + "\n" + kconfig ) - write_file_if_changed(CORE.relative_build_path("zephyr/Kconfig"), kconfig) + changed |= _write_file_if_changed_or_remove_when_empty( + CORE.relative_build_path("zephyr/Kconfig"), kconfig + ) + + if changed: + # A configure-time input changed; drop the CMake cache so the build + # can't reuse stale configure results (the native sdk-nrf toolchain + # rebuilds pristine when the cache is missing). + clean_cmake_cache()