From 678b4955fd2b89833445480aba19f1994a74a7eb Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 23 Feb 2026 16:10:15 +0100 Subject: [PATCH 1/3] feat(cmakev2): Add build event callback framework for components Introduce a callback mechanism that lets components register CMake functions to be called at specific points in the build lifecycle. Currently, this framework only supports registering callbacks to be called after the executable target is created, i.e, the POST_ELF phase of the build but before the binary target is created. --- tools/cmakev2/build.cmake | 29 +++++++++++++++++ tools/cmakev2/component.cmake | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 7551640106..da11fd0aa7 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -112,6 +112,32 @@ function(__dump_build_properties) endforeach() endfunction() +#[[ + __idf_build_dispatch_build_event( ) + + *event[in]* + + Build event name. Currently only ``POST_ELF`` is supported. Other build + events may be extended when required. + + *target[in]* + + Name of the primary CMake target at this event point. Passed as the + sole argument to every registered callback so that callbacks can + operate on the correct target without querying build properties. + For ``POST_ELF`` this is the executable target name. + + Internal dispatcher called by the build system at well-defined lifecycle + points. Invokes every CMake function registered for + ``event`` via ``idf_component_register_build_event_callback``. +#]] +function(__idf_build_dispatch_build_event event target) + idf_build_get_property(callbacks "__BUILD_EVENT_CALLBACKS_${event}") + foreach(cb IN LISTS callbacks) + cmake_language(CALL "${cb}" "${target}") + endforeach() +endfunction() + #[[ __get_library_interface_or_die(LIBRARY OUTPUT ) @@ -608,6 +634,9 @@ function(idf_build_executable executable) endif() set_target_properties(${executable} PROPERTIES LIBRARY_INTERFACE ${library}) + + # Dispatch POST_ELF event once the executable target exists + __idf_build_dispatch_build_event(POST_ELF "${executable}") endfunction() #[[ diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index 08a9ca5c67..af05ed5989 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -1058,3 +1058,62 @@ function(idf_component_include name) __get_compile_options(OUTPUT compile_options) target_compile_options("${component_real_target}" BEFORE PRIVATE "${compile_options}") endfunction() + +#[[api +.. cmakev2:function:: idf_component_register_build_event_callback + + .. code-block:: cmake + + idf_component_register_build_event_callback(EVENT CALLBACK ) + + *EVENT[in]* + + Build lifecycle event at which the callback will be invoked. Currently + only ``POST_ELF`` is supported. + + *CALLBACK[in]* + + Name of a CMake function defined in the component's project_include.cmake file. + The build system calls this function with the primary CMake target as its argument + at the specified event point. For ``POST_ELF`` this is the executable target. + + Example:: + + # project_include.cmake + function(my_component_post_elf_hook target) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND my_tool "$") + endfunction() + + idf_component_register_build_event_callback( + EVENT POST_ELF + CALLBACK my_component_post_elf_hook) + +#]] +function(idf_component_register_build_event_callback) + set(options) + set(one_value EVENT CALLBACK) + set(multi_value) + cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) + + if(NOT DEFINED ARG_EVENT) + idf_die("idf_component_register_build_event_callback: EVENT option is required") + endif() + + if(NOT DEFINED ARG_CALLBACK) + idf_die("idf_component_register_build_event_callback: CALLBACK option is required") + endif() + + set(valid_events POST_ELF) + if(NOT "${ARG_EVENT}" IN_LIST valid_events) + idf_die("idf_component_register_build_event_callback: unknown event '${ARG_EVENT}'. " + "Valid events: ${valid_events}") + endif() + + if(NOT COMMAND "${ARG_CALLBACK}") + idf_die("idf_component_register_build_event_callback: callback '${ARG_CALLBACK}' " + "is not a known CMake function. Define it before calling this function.") + endif() + + idf_build_set_property("__BUILD_EVENT_CALLBACKS_${ARG_EVENT}" "${ARG_CALLBACK}" APPEND) +endfunction() From 19040b761bfb6a82adacf2bdeef88586e5bdbf01 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 23 Feb 2026 16:12:32 +0100 Subject: [PATCH 2/3] test(cmakev2): add POST_ELF build event callback test --- .../buildv2/test_build_event_callbacks.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tools/test_build_system/buildv2/test_build_event_callbacks.py diff --git a/tools/test_build_system/buildv2/test_build_event_callbacks.py b/tools/test_build_system/buildv2/test_build_event_callbacks.py new file mode 100644 index 0000000000..d03a2b20f7 --- /dev/null +++ b/tools/test_build_system/buildv2/test_build_event_callbacks.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +from pathlib import Path + +from test_build_system_helpers import IdfPyFunc +from test_build_system_helpers import get_snapshot + + +def _write_project_include(test_app_copy: Path, content: str) -> None: + """Write content to the main component's project_include.cmake file.""" + project_include = test_app_copy / 'main' / 'project_include.cmake' + project_include.write_text(content, encoding='utf-8') + + +def test_post_elf_callback_fires_before_binary(test_app_copy: Path, idf_py: IdfPyFunc) -> None: + """ + Verify a POST_ELF callback fires after the executable target exists but before + the binary (.bin) is generated. The callback attaches a POST_BUILD step to the + executable via add_custom_command(TARGET ... POST_BUILD). + """ + _write_project_include( + test_app_copy, + '\n'.join( + [ + 'function(__test_post_elf_cb target)', + ' add_custom_command(TARGET ${target} POST_BUILD', + ' COMMAND ${CMAKE_COMMAND} -E sleep 1', + ' COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_BINARY_DIR}/postelf_file")', + 'endfunction()', + 'idf_component_register_build_event_callback(EVENT POST_ELF CALLBACK __test_post_elf_cb)', + ] + ), + ) + + idf_py('build') + + elf_file = test_app_copy / 'build' / 'build_test_app.elf' + postelf_file = test_app_copy / 'build' / 'postelf_file' + bin_timestamp = test_app_copy / 'build' / 'build_test_app.bin' + + assert elf_file.exists(), 'ELF file must exist' + assert postelf_file.exists(), 'post-elf file must be created' + assert bin_timestamp.exists(), 'bin timestamp must exist' + + snap = get_snapshot([str(elf_file), str(postelf_file), str(bin_timestamp)]) + mtimes = dict(snap.info) + assert mtimes[str(postelf_file)] > mtimes[str(elf_file)], 'post-ELF file must be created after ELF file' + assert mtimes[str(bin_timestamp)] > mtimes[str(postelf_file)], ( + 'Binary generation must occur after post-ELF dependency' + ) From 9a86a46139a0d58d778c3f5e20419620d8cbbf16 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Tue, 24 Feb 2026 15:16:01 +0100 Subject: [PATCH 3/3] docs(cmakev2): Added a section about component callback feature --- docs/en/api-guides/build-system-v2.rst | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/en/api-guides/build-system-v2.rst b/docs/en/api-guides/build-system-v2.rst index 07af6fb50e..83a3096e15 100644 --- a/docs/en/api-guides/build-system-v2.rst +++ b/docs/en/api-guides/build-system-v2.rst @@ -274,6 +274,34 @@ LINKER_SCRIPTS: Also ensure that the ``esp_chip_info`` function is retained in the final binary even when section garbage collection, ``--gc-sections``, is enabled. This is required because ``esp_target_info.ld`` defines ``esp_target_chip_info`` as an alias for ``esp_chip_info``, and without forcing the linker to include it, the underlying ``esp_chip_info`` function could be discarded as unused. +.. _cmakev2-build-event-callbacks: + +Build Event Callback Framework +============================== + +The build system allows components to register callbacks that are invoked at specific points in the build lifecycle. This provides a generic way for components to run custom steps (for example, running a tool on the executable or adding dependencies) without relying on internal build targets or properties. + +Components register a callback in their ``project_include.cmake`` using :cmakev2:ref:`idf_component_register_build_event_callback`. The callback must be a CMake function defined in the same file. At the specified event, the build system invokes the callback and passes the relevant CMake target as the first argument (for example, the executable target for ``POST_ELF``). + +Currently supported events: + +- **POST_ELF** — Fired after the executable target is created and linked, but before the binary (``.bin``) image is generated. The callback receives the executable target name. Use this to perform actions on the ELF by attaching a ``POST_BUILD`` command to the executable with ``add_custom_command(TARGET ... POST_BUILD ...)``. + +Example: perform actions on the ELF after linking: + +.. code-block:: cmake + + # In project_include.cmake + function(my_post_elf_hook target) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND my_tool "$" + COMMENT "Running my_tool on the executable") + endfunction() + + idf_component_register_build_event_callback(EVENT POST_ELF CALLBACK my_post_elf_hook) + +Additional build events may be added in future when required. + .. _cmakev2-breaking-changes: Breaking Changes for v1 Components @@ -326,6 +354,13 @@ The hello_world example ``CMakeLists_v2.txt`` for v2. idf::spi_flash ) +``idf_build_add_post_elf_dependency`` and ``idf_build_get_post_elf_dependencies`` are Unavailable +------------------------------------------------------------------------------------------------- + +In v1, components that need to run a step after the executable is linked but before the binary image is generated use ``idf_build_add_post_elf_dependency`` to register a dependency and ``idf_build_get_post_elf_dependencies`` to retrieve the list of such dependencies (see the :doc:`build system ` API). These functions are **not available** in v2. + +In v2, use the :ref:`Build Event Callback Framework ` instead. Register a **POST_ELF** callback with :cmakev2:ref:`idf_component_register_build_event_callback` in your component's ``project_include.cmake``. The callback receives the executable target name; use it to attach a ``POST_BUILD`` command (e.g. with ``add_custom_command(TARGET ... POST_BUILD ...)``) or to add custom targets that depend on the executable. This achieves the same ordering (run after ELF, before binary) without relying on internal build properties. + The ``BUILD_COMPONENTS`` Build Property is Unavailable ------------------------------------------------------