From d4d778182f6ed3c2be3bcf72cbb6b30cbc1c1e15 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 4 Mar 2026 16:32:01 +0100 Subject: [PATCH 01/16] fix(esp_partition/cmake): remove unused PRIV_INCLUDE_DIRS Currently, the bootloader version of the `esp_partition` component sets `PRIV_INCLUDE_DIRS` using the `private_include_dirs` variable. However, this variable is not properly initialized, which causes issues in cmakev2. In cmakev2, components are evaluated recursively, and a component may be evaluated in the context of another component, so components must initialize all variables before using them. Moreover, there are effectively no `PRIV_INCLUDE_DIRS` to set when the component is evaluated for the bootloader. Therefore, remove `PRIV_INCLUDE_DIRS` entirely for bootloader and test builds. Signed-off-by: Frantisek Hrbata --- components/esp_partition/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/esp_partition/CMakeLists.txt b/components/esp_partition/CMakeLists.txt index 3bd605b3ca..be80e77476 100644 --- a/components/esp_partition/CMakeLists.txt +++ b/components/esp_partition/CMakeLists.txt @@ -10,7 +10,6 @@ if(BOOTLOADER_BUILD) idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "include" - PRIV_INCLUDE_DIRS ${private_include_dirs} REQUIRES ${reqs} PRIV_REQUIRES ${priv_reqs}) @@ -22,7 +21,6 @@ elseif(esp_tee_build) idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "include" - PRIV_INCLUDE_DIRS ${private_include_dirs} REQUIRES ${reqs} PRIV_REQUIRES ${priv_reqs}) From 9204ed63e085c2700b35675126061e2398cad361 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 5 Mar 2026 17:07:22 +0100 Subject: [PATCH 02/16] fix(bootloader_support/cmake): initialize priv_include_dirs variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the bootloader build, the `priv_include_dirs` variable is uninitialized, but it is used in the common call to `idf_component_register`. Using uninitialized variables is not allowed in cmakev2 because a component may be evaluated in the context of another component’s variable scope. Signed-off-by: Frantisek Hrbata --- components/bootloader_support/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bootloader_support/CMakeLists.txt b/components/bootloader_support/CMakeLists.txt index 27ea2a5c48..512aab702a 100644 --- a/components/bootloader_support/CMakeLists.txt +++ b/components/bootloader_support/CMakeLists.txt @@ -70,6 +70,7 @@ if(CONFIG_ESP_ROM_REV0_HAS_NO_ECDSA_INTERFACE) endif() if(BOOTLOADER_BUILD OR CONFIG_APP_BUILD_TYPE_RAM) + set(priv_include_dirs) set(include_dirs "include" "bootloader_flash/include" "private_include") # micro-ecc is only needed for secure boot sources built under BOOTLOADER_BUILD. From 44a610cbf09f4639b4d47e600897c2f600eec0df Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 5 Mar 2026 17:24:13 +0100 Subject: [PATCH 03/16] feat(cmakev2/compat): allow to set commonly required components The commonly required components are configured solely for backward compatibility with cmakev1. Since the bootloader build explicitly sets commonly required components, we need to support this feature in cmakev2 as well. Ideally, there should be no commonly required components, and each component should specify its requirements explicitly. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/compat.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index 70fe67b3ea..8c6da91ade 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -419,10 +419,14 @@ function(__init_common_components) idf_build_get_property(idf_target IDF_TARGET) idf_build_get_property(idf_target_arch IDF_TARGET_ARCH) + idf_build_get_property(explicit_requires_common __COMPONENT_REQUIRES_COMMON) # Define common components that are included as dependencies for each # component. - if("${idf_target}" STREQUAL "linux") + if(explicit_requires_common) + set(requires_common "${explicit_requires_common}") + + elseif("${idf_target}" STREQUAL "linux") set(requires_common freertos esp_hw_support heap log soc hal esp_rom esp_common esp_system linux esp_stdio) else() From 413a0615bf92c6a65230da0917031c4af756d7da Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 5 Mar 2026 18:20:27 +0100 Subject: [PATCH 04/16] fix(bootloader): remove CMAKE_CURRENT_LIST_DIR from bootloader_extra_component_dirs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CMAKE_CURRENT_LIST_DIR` is actually `components/bootloader`, so it doesn’t need to be passed via `EXTRA_COMPONENT_DIRS`: the build already recognizes it as an esp-idf component. In **cmakev1**, this is silently ignored: if a component with the same name already exists, its directory is updated and the previous directory is stored in the `COMPONENT_OVERRIDEN_DIR` component property. In **cmakev2**, this is correctly detected and reported. CMake Warning at /home/fhrbata/work/esp-idf/tools/cmakev2/utilities.cmake:63 (message): IDF: Component 'bootloader' directory '/home/fhrbata/work/esp-idf/components/bootloader' with higher priority 'project_extra_components' will be used instead of component directory '/home/fhrbata/work/esp-idf/components/bootloader' with lower priority 'idf_components' Call Stack (most recent call first): /home/fhrbata/work/esp-idf/tools/cmakev2/component.cmake:625 (idf_warn) /home/fhrbata/work/esp-idf/tools/cmakev2/idf.cmake:411 (__init_component) /home/fhrbata/work/esp-idf/tools/cmakev2/project.cmake:580 (__init_components) CMakeLists_v2.txt:28 (idf_project_init) CMakeLists.txt:19 (include) Since it doesn’t make sense to explicitly add the bootloader as an extra component, remove it. Signed-off-by: Frantisek Hrbata --- components/bootloader/project_include.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index ef959eb5dd..2877f1e4e6 100644 --- a/components/bootloader/project_include.cmake +++ b/components/bootloader/project_include.cmake @@ -122,7 +122,6 @@ idf_build_get_property(extra_cmake_args EXTRA_CMAKE_ARGS) # BOOTLOADER_EXTRA_COMPONENT_DIRS may have been set by the `main` component, do not overwrite it idf_build_get_property(bootloader_extra_component_dirs BOOTLOADER_EXTRA_COMPONENT_DIRS) -list(APPEND bootloader_extra_component_dirs "${CMAKE_CURRENT_LIST_DIR}") # We cannot pass lists as a parameter to the external project without modifying the ';' separator string(REPLACE ";" "|" BOOTLOADER_IGNORE_EXTRA_COMPONENT "${BOOTLOADER_IGNORE_EXTRA_COMPONENT}") From 43d4fdd4b393a30dd1c6749d799f330d09581cc3 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Fri, 6 Mar 2026 17:05:51 +0100 Subject: [PATCH 05/16] fix(cmakev2/build): fix typo in idf_sign_binary keyfile variable The KEYFILE argument value was stored in a misspelled variable "keyfle" instead of "keyfile", causing custom keyfile paths to be silently ignored. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/build.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 5c1f3253ba..918beefab2 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -1067,7 +1067,7 @@ function(idf_sign_binary binary) endif() if(ARG_KEYFILE) - set(keyfle "${ARG_KEYFILE}") + set(keyfile "${ARG_KEYFILE}") else() idf_build_get_property(project_dir PROJECT_DIR) get_filename_component(keyfile "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}") From 6b8aa157e5c2de0a302123008618e11ef2bdeda9 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Fri, 6 Mar 2026 17:29:50 +0100 Subject: [PATCH 06/16] feat(cmakev2/build): add idf_check_bootloader_size helper Add a public API function to check that the bootloader binary does not overlap the partition table, mirroring the existing idf_check_binary_size pattern for application binaries. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/build.cmake | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 918beefab2..99d4c3af82 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -1227,6 +1227,35 @@ function(idf_check_binary_size binary) add_dependencies("${binary}" "${binary}_check_size") endfunction() +#[[ +.. cmakev2:function:: idf_check_bootloader_size + + .. code-block:: cmake + + idf_check_bootloader_size() + + *binary[in]* + + Binary image target to which to add a bootloader size check. The + ``binary`` target is created by the :cmakev2:ref:`idf_build_binary` or + :cmakev2:ref:`idf_sign_binary` function. + + Ensure that the bootloader binary image does not overlap the partition + table. The file path of the binary image should be stored in the + ``BINARY_PATH`` property of the ``binary`` target. +#]] +function(idf_check_bootloader_size binary) + get_target_property(binary_path ${binary} BINARY_PATH) + if(NOT binary_path) + idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.") + endif() + + partition_table_add_check_bootloader_size_target("${binary}_check_size" + DEPENDS "${binary_path}" + BOOTLOADER_BINARY_PATH "${binary_path}") + add_dependencies("${binary}" "${binary}_check_size") +endfunction() + #[[ .. cmakev2:function:: idf_check_binary_signed From ba4c6de807619a181d195908c475768f62900b9b Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Sat, 7 Mar 2026 13:33:49 +0100 Subject: [PATCH 07/16] feat(cmakev2/utilities): add idf_target_post_build_msg helper Add a helper function for printing multi-line post-build messages on a target. The message lines are joined with newlines, written to a file at configure time, and printed at build time using a single cmake -E cat command, replacing the need for repeated cmake -E echo invocations. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/utilities.cmake | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index ceabc67d1f..627cc2ea7a 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -683,6 +683,31 @@ function(add_prefix var prefix) set(${var} "${newlist}" PARENT_SCOPE) endfunction() +#[[ + idf_target_post_build_msg( ...) + + *target[in]* + + Target to attach the post-build message to. + + *line[in]* + + Message lines to print after the target is built. + + Add a post-build step to ```` that prints the given message lines. + The lines are joined with newlines, written to a file at configure time, + and printed at build time using a single ``cmake -E cat`` command. +#]] +function(idf_target_post_build_msg target) + string(MD5 hash "${ARGN}") + set(msg_file "${CMAKE_CURRENT_BINARY_DIR}/_post_build_msg_${target}_${hash}.txt") + list(JOIN ARGN "\n" msg) + file(WRITE "${msg_file}" "${msg}\n") + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E cat "${msg_file}" + VERBATIM) +endfunction() + #[[ fail_target( [...]) From 3a0f0bb13f1c59f48570844f4254424ce025dd68 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Sat, 7 Mar 2026 16:29:09 +0100 Subject: [PATCH 08/16] feat(cmakev2/build): add ALL option to idf_build_binary and idf_sign_binary Add an optional ALL parameter to idf_build_binary and idf_sign_binary functions. When specified, the created custom target is included in the default build target. Without ALL, custom targets created by these functions are excluded from the default build (add_custom_target behavior), meaning they won't be built unless explicitly requested or depended upon by another target in ALL. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/build.cmake | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 99d4c3af82..ab6d71ed0d 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -913,7 +913,8 @@ endfunction() idf_build_binary( TARGET - OUTPUT_FILE ) + OUTPUT_FILE + [ALL]) *executable[in]* @@ -928,6 +929,10 @@ endfunction() Output file path for storing the binary image file. + *ALL[in,opt]* + + If specified, the target will be added to the default build target. + Create a binary image for the specified ``executable`` target and save it in the file specified with the ``OUTPUT_FILE`` option. A custom target named ``TARGET`` will be created for the generated binary image. The path @@ -936,7 +941,7 @@ endfunction() the ``TARGET``. #]] function(idf_build_binary executable) - set(options) + set(options ALL) set(one_value OUTPUT_FILE TARGET) set(multi_value) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) @@ -990,8 +995,13 @@ function(idf_build_binary executable) COMMENT "Generating binary image '${binary_name}' from executable '${executable_name}'" ) - # Create a custom target to generate the binary file - add_custom_target(${ARG_TARGET} DEPENDS "${ARG_OUTPUT_FILE}") + # Create a custom target to generate the binary file. + # If ALL is specified, include the target in the default build. + set(all_arg) + if(ARG_ALL) + set(all_arg ALL) + endif() + add_custom_target(${ARG_TARGET} ${all_arg} DEPENDS "${ARG_OUTPUT_FILE}") # Store the path of the binary file in the BINARY_PATH property of the # custom binary target, which is used by the idf_flash_binary. @@ -1010,7 +1020,8 @@ endfunction() idf_sign_binary( TARGET OUTPUT_FILE - [KEYFILE ]) + [KEYFILE ] + [ALL]) *binary[in]* @@ -1033,6 +1044,10 @@ endfunction() provided, the key file specified by the ``CONFIG_SECURE_BOOT_SIGNING_KEY`` configuration option will be used. + *ALL[in,opt]* + + If specified, the target will be added to the default build target. + Sign binary image specified by ``binary`` target with ``KEYFILE`` and save it in the file specified with the `OUTPUT_FILE` option. A custom target named ``TARGET`` will be created for the signed binary image. The path of @@ -1041,7 +1056,7 @@ endfunction() ``TARGET``. #]] function(idf_sign_binary binary) - set(options) + set(options ALL) set(one_value OUTPUT_FILE TARGET KEYFILE) set(multi_value) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) @@ -1097,7 +1112,13 @@ function(idf_sign_binary binary) VERBATIM COMMENT "Signing '${binary_name}' with key '${key_name}' into '${signed_binary_name}'" ) - add_custom_target(${ARG_TARGET} DEPENDS "${ARG_OUTPUT_FILE}") + # Create a custom target for the signed binary file. + # If ALL is specified, include the target in the default build. + set(all_arg) + if(ARG_ALL) + set(all_arg ALL) + endif() + add_custom_target(${ARG_TARGET} ${all_arg} DEPENDS "${ARG_OUTPUT_FILE}") # Store the path of the binary file in the BINARY_PATH property of the # custom signed binary target, which is used by the idf_flash_binary. From c91597454d4a75d7674ce2f92dd4c0a172f5a80d Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Mon, 9 Mar 2026 12:07:49 +0100 Subject: [PATCH 09/16] feat(cmakev2/idf): add PROJECT_COMPONENTS_SOURCE build property Add the PROJECT_COMPONENTS_SOURCE build property to control how project components are categorised during component discovery. By default it is set to "project_components" (priority 3 - highest), preserving the existing behaviour. Setting it to "idf_components" (priority 0) before calling idf_project_init() makes the project's built-in components overridable by user-supplied components through EXTRA_COMPONENT_DIRS (priority 2). This is needed for sub-projects like the bootloader, whose built-in components (e.g. main) are provided by ESP-IDF and should be overridable by user-supplied components placed in bootloader_components/. The cmakev1 build system allowed this override because it used a last-one-wins strategy, but cmakev2 uses strict priority-based component resolution where project_components always win. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/idf.cmake | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake index 691c4c85ed..4287d8d4c9 100644 --- a/tools/cmakev2/idf.cmake +++ b/tools/cmakev2/idf.cmake @@ -362,12 +362,34 @@ endfunction() EXTRA_COMPONENT_EXCLUDE_DIRS List of paths to exclude from searching the component directories. + The ``PROJECT_COMPONENTS_SOURCE`` build property controls how the + project's own components are categorised -- i.e. those discovered + under ``CMAKE_CURRENT_SOURCE_DIR/main``, + ``CMAKE_CURRENT_SOURCE_DIR/components``, or the paths listed in + ``COMPONENT_DIRS``. It defaults to ``project_components`` + (priority 3 -- highest). Setting it to ``idf_components`` + (priority 0) makes them overridable by components supplied through + ``EXTRA_COMPONENT_DIRS`` (priority 2). This is useful for + sub-projects whose built-in components are provided by ESP-IDF and + should be overridable by user-supplied components. + Each component is initialized for every component directory found. #]] function(__init_components) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(prefix PREFIX) + idf_build_get_property(project_components_source PROJECT_COMPONENTS_SOURCE) + if(NOT project_components_source) + set(project_components_source "project_components") + endif() + + set(valid_sources "project_components" "idf_components") + if(NOT project_components_source IN_LIST valid_sources) + idf_die("Invalid PROJECT_COMPONENTS_SOURCE '${project_components_source}'." + " Must be one of: ${valid_sources}") + endif() + __get_component_paths(PATHS "${idf_path}/components" OUTPUT idf_components) if(COMPONENT_DIRS) @@ -404,7 +426,7 @@ function(__init_components) foreach(path IN LISTS project_components) __init_component(DIRECTORY "${path}" PREFIX "${prefix}" - SOURCE "project_components") + SOURCE "${project_components_source}") endforeach() foreach(path IN LISTS project_extra_components) From a2461ea32b60fff775ef54421ce9bd16a3b3baa6 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 10 Mar 2026 12:07:39 +0100 Subject: [PATCH 10/16] feat(cmakev2/kconfig): add GENERATE_SDKCONFIG build property Add a GENERATE_SDKCONFIG build property that controls whether kconfgen writes the sdkconfig file (--output config) during the configuration step. It defaults to 1 (enabled). When building the bootloader as a subproject, the set of components (and their Kconfig files) differs from the main project. Running kconfgen with --output config in this context rewrites the main project's sdkconfig. Even when the content is identical, the timestamp update causes ninja to detect sdkconfig as newer than build.ninja outputs (e.g. cmake_install.cmake), triggering an infinite CMake re-run loop. Setting GENERATE_SDKCONFIG to 0 in a subproject prevents this, matching the behaviour of cmakev1's __OUTPUT_SDKCONFIG property. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/kconfig.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/kconfig.cmake b/tools/cmakev2/kconfig.cmake index b4af854a54..856e542c8e 100644 --- a/tools/cmakev2/kconfig.cmake +++ b/tools/cmakev2/kconfig.cmake @@ -57,6 +57,7 @@ function(__init_kconfig) idf_build_set_property(SDKCONFIG "${sdkconfig}") idf_build_set_property(__SDKCONFIG_ORIG "${sdkconfig}") idf_build_set_property(SDKCONFIG_DEFAULTS "${sdkconfig_defaults_checked}") + idf_build_set_property(GENERATE_SDKCONFIG 1) # Setup ESP-IDF root Kconfig and sdkconfig.rename files. idf_build_set_property(__ROOT_KCONFIG "${idf_path}/Kconfig") @@ -643,9 +644,13 @@ function(__generate_kconfig_outputs) --output header "${sdkconfig_header}" --output cmake "${sdkconfig_cmake}" --output json "${sdkconfig_json}" - --output config "${sdkconfig}" ) + idf_build_get_property(generate_sdkconfig GENERATE_SDKCONFIG) + if(generate_sdkconfig) + list(APPEND kconfgen_outputs_cmd --output config "${sdkconfig}") + endif() + idf_build_set_property(__KCONFGEN_OUTPUTS_CMD "${kconfgen_outputs_cmd}") # Generate Kconfig outputs using kconfgen From 6416f75581a966cae6fb7c05498e9d9ab9da4549 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 10 Mar 2026 12:08:17 +0100 Subject: [PATCH 11/16] feat(bootloader): build bootloader using cmakev2 Add CMakeLists_v2.txt to the bootloader subproject, implementing the bootloader build using the new cmakev2 IDF build framework. The file covers the full bootloader build pipeline: - Sets PROJECT_COMPONENTS_SOURCE to "idf_components" so that the subproject's built-in components (main/, components/) are treated as IDF components (priority 0) rather than project components (priority 3). This preserves the cmakev1 behaviour where user-supplied components in bootloader_components/ can override the built-in ones. - Registers optional user-supplied bootloader components from the application project's bootloader_components/ directory, with support for selectively excluding individual components via IGNORE_EXTRA_COMPONENT. - Bootstraps the cmakev2 framework (idf.cmake) and initialises the project with BOOTLOADER_BUILD and NON_OS_BUILD properties, which are also exposed as C preprocessor definitions. - Sets GENERATE_SDKCONFIG to 0 to prevent the bootloader subproject from regenerating the main project's sdkconfig, as the bootloader has a different set of components and hence different Kconfig files. - Sets the common implicit component dependencies shared by every bootloader component (log, esp_rom, esp_common, esp_hw_support, esp_libc, arch-specific component). - Applies the compiler options specific for bootloader - Selects the correct target-specific linker script, including a separate script for ESP32-P4 silicon revisions < v3. - Links the bootloader ELF via idf_build_executable and then converts it to a flat binary via one of three paths depending on the secure boot configuration: * No secure boot: plain binary + size check + metadata. * Secure Boot V1 one-time-flash: plain binary with post-build instructions showing the esptool.py flash command. * Secure Boot V1 reflashable: derives the symmetric eFuse key from the ECDSA signing key, produces the reflash-digest image, and prints burn/flash instructions. * Secure Boot V2: produces an unsigned binary, optionally signs it with the configured signing key (RSA-PSS 3072, ECDSA P-256, or ECDSA P-384) via idf_sign_binary, and prints flash/multi-key signing instructions. - Adds comprehensive inline documentation explaining each section's purpose, the rationale behind individual flags, and the relationships between Kconfig symbols and generated artefacts. Signed-off-by: Frantisek Hrbata --- components/bootloader/project_include.cmake | 1 + .../bootloader/subproject/CMakeLists.txt | 5 + .../bootloader/subproject/CMakeLists_v2.txt | 351 ++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 components/bootloader/subproject/CMakeLists_v2.txt diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index 2877f1e4e6..aa9e368e43 100644 --- a/components/bootloader/project_include.cmake +++ b/components/bootloader/project_include.cmake @@ -138,6 +138,7 @@ externalproject_add(bootloader -DEXTRA_COMPONENT_DIRS=${bootloader_extra_component_dirs} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DIGNORE_EXTRA_COMPONENT=${BOOTLOADER_IGNORE_EXTRA_COMPONENT} + -DIDF_BUILD_V2=${IDF_BUILD_V2} ${sign_key_arg} ${ver_key_arg} ${extra_cmake_args} INSTALL_COMMAND "" diff --git a/components/bootloader/subproject/CMakeLists.txt b/components/bootloader/subproject/CMakeLists.txt index 8fb9509cee..48746c522f 100644 --- a/components/bootloader/subproject/CMakeLists.txt +++ b/components/bootloader/subproject/CMakeLists.txt @@ -15,6 +15,11 @@ if(NOT IDF_TARGET) "in by the parent build process.") endif() +if(IDF_BUILD_V2) + include(CMakeLists_v2.txt) + return() +endif() + # A number of these components are implemented as config-only when built in the bootloader set(COMPONENTS bootloader diff --git a/components/bootloader/subproject/CMakeLists_v2.txt b/components/bootloader/subproject/CMakeLists_v2.txt new file mode 100644 index 0000000000..bbfae626bb --- /dev/null +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -0,0 +1,351 @@ +cmake_minimum_required(VERSION 3.22) + +# --------------------------------------------------------------------------- +# Bootloader subproject CMakeLists (cmakev2 build system) +# +# This file drives the build of the ESP-IDF second-stage bootloader as a +# standalone CMake sub-project that is invoked by the top-level build when +# the project links against the `bootloader` component. +# --------------------------------------------------------------------------- + +# --------------------------------------------------------------------------- +# Extra component directories +# +# Users may supply custom bootloader components by placing them inside a +# `bootloader_components/` directory at the root of their application +# project. If the directory exists it is appended to EXTRA_COMPONENT_DIRS +# so that the IDF component discovery logic can find them. +# +# IGNORE_EXTRA_COMPONENT (list) – optional variable that names individual +# sub-directories of `bootloader_components/` to *exclude* from the build. +# Each entry is turned into an absolute path and collected in +# EXTRA_COMPONENT_EXCLUDE_DIRS which is consumed by the component resolver. +# --------------------------------------------------------------------------- +set(PROJECT_EXTRA_COMPONENTS "${PROJECT_SOURCE_DIR}/bootloader_components") +if(EXISTS ${PROJECT_EXTRA_COMPONENTS}) + list(APPEND EXTRA_COMPONENT_DIRS "${PROJECT_EXTRA_COMPONENTS}") +endif() + +if(IGNORE_EXTRA_COMPONENT) + # Prefix all entries of the list with ${PROJECT_EXTRA_COMPONENTS} absolute path + list(TRANSFORM IGNORE_EXTRA_COMPONENT + PREPEND "${PROJECT_EXTRA_COMPONENTS}/" + OUTPUT_VARIABLE EXTRA_COMPONENT_EXCLUDE_DIRS) +endif() + +# --------------------------------------------------------------------------- +# Include the cmakev2 IDF build framework +# --------------------------------------------------------------------------- +include("${IDF_PATH}/tools/cmakev2/idf.cmake") + +project(bootloader C CXX ASM) + +# --------------------------------------------------------------------------- +# Bootloader-specific build properties set before project initialization +# --------------------------------------------------------------------------- +set(BOOTLOADER_BUILD 1) +set(NON_OS_BUILD 1) +idf_build_set_property(BOOTLOADER_BUILD "${BOOTLOADER_BUILD}") +idf_build_set_property(NON_OS_BUILD "${NON_OS_BUILD}") +# The bootloader subproject has a different set of components and hence +# different Kconfig files. Do not regenerate the main project's sdkconfig. +idf_build_set_property(GENERATE_SDKCONFIG 0) +# Treat the bootloader subproject's own components (main/, components/) as +# IDF components (priority 0) instead of the default project components +# (priority 3). The bootloader's built-in components are provided by ESP-IDF +# and should be overridable by user-supplied components placed in the +# application's bootloader_components/ directory, which are discovered through +# EXTRA_COMPONENT_DIRS (priority 2). The cmakev1 build system allowed this +# override because it used a last-one-wins strategy. In cmakev2 with strict +# priority-based resolution, project_components would always win and prevent +# the override, so we downgrade them to idf_components priority. +idf_build_set_property(PROJECT_COMPONENTS_SOURCE "idf_components") +# Allow lazy optional component requirements evaluation for +# `idf_component_optional_requires`. The bootloader uses a single ESP-IDF +# library, and since it includes `esp_common`, this ensures that not all +# components listed in `esp_common` as optional requirements are pulled into +# the build. +idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED) + +# Perform internal IDF project initialisation +idf_project_init() + +# --------------------------------------------------------------------------- +# Common component requirements +# --------------------------------------------------------------------------- +idf_build_get_property(idf_target_arch IDF_TARGET_ARCH) +set(common_req log esp_rom esp_common esp_hw_support esp_libc ${idf_target_arch}) +idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${common_req}") + +# --------------------------------------------------------------------------- +# Compiler options and defines specific for bootloader +# --------------------------------------------------------------------------- +if(CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE) + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + idf_build_set_property(COMPILE_OPTIONS "-Oz" APPEND) + else() + idf_build_set_property(COMPILE_OPTIONS "-Os" APPEND) + endif() + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + idf_build_set_property(COMPILE_OPTIONS "-freorder-blocks" APPEND) + if(CONFIG_IDF_TARGET_ARCH_XTENSA) + idf_build_set_property(COMPILE_OPTIONS "-mno-target-align" APPEND) + endif() + endif() +elseif(CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG) + idf_build_set_property(COMPILE_OPTIONS "-Og" APPEND) + if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CONFIG_IDF_TARGET_LINUX) + # Disable shrink-wrapping to reduce binary size + idf_build_set_property(COMPILE_OPTIONS "-fno-shrink-wrap" APPEND) + endif() +elseif(CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF) + idf_build_set_property(COMPILE_OPTIONS "-O2" APPEND) +endif() + +idf_build_set_property(COMPILE_DEFINITIONS "BOOTLOADER_BUILD=1" APPEND) +idf_build_set_property(COMPILE_DEFINITIONS "NON_OS_BUILD=1" APPEND) +idf_build_set_property(COMPILE_OPTIONS "-fno-stack-protector" APPEND) + +# --------------------------------------------------------------------------- +# Bootloader Linker script +# --------------------------------------------------------------------------- +set(LD_DEFAULT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/main/ld/${IDF_TARGET}") +if(CONFIG_ESP32P4_SELECTS_REV_LESS_V3) + idf_build_set_property(BOOTLOADER_LINKER_SCRIPT "${LD_DEFAULT_PATH}/bootloader.rev0_2.ld.in" APPEND) +else() + idf_build_set_property(BOOTLOADER_LINKER_SCRIPT "${LD_DEFAULT_PATH}/bootloader.ld.in" APPEND) +endif() + +# --------------------------------------------------------------------------- +# Build the bootloader ELF +# --------------------------------------------------------------------------- +idf_build_executable(bootloader_elf + NAME bootloader + COMPONENTS main + MAPFILE_TARGET bootloader_mapfile) + +# --------------------------------------------------------------------------- +# Binary generation and post-build messages +# +# Collect tool command lists from the esptool_py component so that we can +# assemble human-readable flash / key-management commands for the developer. +# --------------------------------------------------------------------------- +# Build display strings for the post-build message +idf_component_get_property(esptool_py_cmd esptool_py ESPTOOLPY_CMD) +idf_component_get_property(main_args esptool_py FLASH_ARGS) +idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) +idf_component_get_property(espsecure_py_cmd esptool_py ESPSECUREPY_CMD) +idf_component_get_property(espefuse_py_cmd esptool_py ESPEFUSEPY_CMD) + +# String for printing flash command +string(REPLACE ";" " " esptoolpy_write_flash + "${esptool_py_cmd} --port=(PORT) --baud=(BAUD) ${main_args} write-flash ${sub_args}") + +string(REPLACE ";" " " espsecurepy "${espsecure_py_cmd}") +string(REPLACE ";" " " espefusepy "${espefuse_py_cmd}") + +# --------------------------------------------------------------------------- +# Guard: if binary generation is disabled or esptool_py is not part of the +# build, there is no binary to produce and no flash commands to print, so we +# stop here. +# --------------------------------------------------------------------------- +if(NOT CONFIG_APP_BUILD_GENERATE_BINARIES OR NOT TARGET idf::esptool_py) + # Binaries should not be generated; there's nothing else to do. + return() + +# --------------------------------------------------------------------------- +# No secure boot +# +# Convert the ELF directly to a flat binary. The size check target +# (idf_check_bootloader_size) verifies that the binary fits within the +# region reserved for the bootloader in the partition table / flash layout. +# idf_build_generate_metadata emits a JSON metadata file consumed by the +# top-level build and flash tooling. +# --------------------------------------------------------------------------- +elseif(NOT CONFIG_SECURE_BOOT) + # No bootloader signing is required + idf_build_binary(bootloader_elf + OUTPUT_FILE "${CMAKE_BINARY_DIR}/bootloader.bin" + TARGET bootloader_bin + ALL) + idf_check_bootloader_size(bootloader_bin) + idf_build_generate_metadata(EXECUTABLE bootloader_elf) + +# --------------------------------------------------------------------------- +# Secure Boot V1 +# +# Secure Boot V1 uses a symmetric HMAC-SHA256 key burned into eFuse. Two +# sub-modes are supported: +# +# ONE_TIME_FLASH – the bootloader is flashed once and can never be +# replaced on the same device. The developer is shown the exact +# esptool.py command to use for the initial flash. +# +# REFLASHABLE – a symmetric key is derived from the private signing key +# and burned into eFuse. Later reflashes are accepted only if the +# bootloader image is bundled with a digest computed using the same +# derived key. This mode requires generating the derived key file +# (secure-bootloader-key-.bin) from the project signing key, and a +# bootloader digest image (bootloader-reflash-digest.bin) that prepends +# the bootloader binary with the matching digest. +# --------------------------------------------------------------------------- +elseif(CONFIG_SECURE_BOOT_V1_ENABLED) + + idf_build_binary(bootloader_elf + OUTPUT_FILE "${CMAKE_BINARY_DIR}/bootloader.bin" + TARGET bootloader_bin + ALL) + idf_check_bootloader_size(bootloader_bin) + idf_build_generate_metadata(EXECUTABLE bootloader_elf) + + if(CONFIG_SECURE_BOOTLOADER_ONE_TIME_FLASH) + + idf_target_post_build_msg(bootloader_bin + "==============================================================================" + "Bootloader built. Secure boot enabled, so bootloader not flashed automatically." + "One-time flash command is:" + "\t${esptoolpy_write_flash} ${BOOTLOADER_OFFSET} ${CMAKE_BINARY_DIR}/bootloader.bin" + "* IMPORTANT: After first boot, BOOTLOADER CANNOT BE RE-FLASHED on same device" + ) + + elseif(CONFIG_SECURE_BOOTLOADER_REFLASHABLE) + + # Derived key length depends on encoding: 192-bit or 256-bit (default). + if(CONFIG_SECURE_BOOTLOADER_KEY_ENCODING_192BIT) + set(key_digest_len 192) + else() + set(key_digest_len 256) + endif() + + set(bootloader_digest_bin "${CMAKE_BINARY_DIR}/bootloader-reflash-digest.bin") + set(secure_bootloader_key "${CMAKE_BINARY_DIR}/secure-bootloader-key-${key_digest_len}.bin") + + # Derive the symmetric eFuse key from the ECDSA signing key. + # The resulting binary is burned into the SECURE_BOOT_V1 eFuse block. + add_custom_command(OUTPUT "${secure_bootloader_key}" + COMMAND ${espsecure_py_cmd} digest-private-key + --keylen "${key_digest_len}" + --keyfile "${SECURE_BOOT_SIGNING_KEY}" + "${secure_bootloader_key}" + DEPENDS "${SECURE_BOOT_SIGNING_KEY}" + VERBATIM) + + if(NOT CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES AND NOT EXISTS "${secure_bootloader_key}") + idf_die( + "No pre-generated key for a reflashable secure bootloader is available, " + "due to signing configuration." + "\nTo generate one, you can use this command:" + "\n\t${espsecurepy} generate-flash-encryption-key ${secure_bootloader_key}" + "\nIf a signing key is present, then instead use:" + "\n\t${espsecurepy} digest-private-key " + "--keylen (192/256) --keyfile KEYFILE " + "${secure_bootloader_key}") + endif() + + # Produce the reflash-digest image: the bootloader binary prefixed + # with a digest computed using the derived symmetric key. + add_custom_command(OUTPUT "${bootloader_digest_bin}" + COMMAND ${CMAKE_COMMAND} -E echo "DIGEST ${bootloader_digest_bin}" + COMMAND ${espsecure_py_cmd} digest-secure-bootloader --keyfile "${secure_bootloader_key}" + -o "${bootloader_digest_bin}" "${CMAKE_BINARY_DIR}/bootloader.bin" + DEPENDS "${secure_bootloader_key}" bootloader_bin + VERBATIM) + + add_custom_target(bootloader_digest_bin ALL DEPENDS "${bootloader_digest_bin}") + + idf_target_post_build_msg(bootloader_bin + "==============================================================================" + "Bootloader built and secure digest generated." + "Secure boot enabled, so bootloader not flashed automatically." + "Burn secure boot key to efuse using:" + "\t${espefusepy} burn-key secure_boot_v1 ${secure_bootloader_key}" + "First time flash command is:" + "\t${esptoolpy_write_flash} ${BOOTLOADER_OFFSET} ${CMAKE_BINARY_DIR}/bootloader.bin" + "==============================================================================" + "To reflash the bootloader after initial flash:" + "\t${esptoolpy_write_flash} 0x0 ${bootloader_digest_bin}" + "==============================================================================" + "* After first boot, only re-flashes of this kind (with same key) will be accepted." + "* Not recommended to reuse the same secure boot keyfile on multiple production devices." + ) + endif() + +# --------------------------------------------------------------------------- +# Secure Boot V2 +# +# Secure Boot V2 uses asymmetric signing. The bootloader ELF is first created +# as an *unsigned* flat binary; then, depending on +# CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES, it is either signed in-place +# (producing bootloader.bin) or left unsigned for the developer to sign +# externally. +# +# Multiple signing keys – devices with more than one eFuse key digest slot +# (CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS > 1) may have additional keys +# appended to the signature block after the build; instructions for doing so +# are printed in the post-build message. +# +# CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT – when set, the bootloader is +# flashed automatically by the normal flash target and no extra instructions +# are needed, so the post-build message block is skipped. +# --------------------------------------------------------------------------- +elseif(CONFIG_SECURE_BOOT_V2_ENABLED) + idf_build_binary(bootloader_elf + OUTPUT_FILE "${CMAKE_BINARY_DIR}/bootloader-unsigned.bin" + TARGET bootloader_unsigned_bin + ALL) + idf_check_bootloader_size(bootloader_unsigned_bin) + + if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES) + if(NOT EXISTS "${SECURE_BOOT_SIGNING_KEY}") + idf_die( + "Secure Boot Signing Key Not found." + "\nGenerate the Secure Boot V2 RSA-PSS 3072 Key." + "\nTo generate one, you can use this command:" + "\n\t${espsecurepy} generate-signing-key --version 2 your_key.pem" + ) + endif() + + # Sign the unsigned binary with the configured signing key and + # write the signed image to bootloader.bin. + idf_sign_binary(bootloader_unsigned_bin + OUTPUT_FILE "${CMAKE_BINARY_DIR}/bootloader.bin" + TARGET bootloader_signed_bin + KEYFILE "${SECURE_BOOT_SIGNING_KEY}" + ALL) + idf_check_bootloader_size(bootloader_signed_bin) + + set(post_build_target bootloader_signed_bin) + else() + # Signing will be performed externally; communicate this to the user. + set(post_build_target bootloader_unsigned_bin) + idf_target_post_build_msg(bootloader_unsigned_bin + "Bootloader generated but not signed" + ) + endif() + + # Emit post-build flash instructions. The message varies depending on + # whether the device has multiple eFuse key digest slots and whether the + # bootloader is flashed by default. + if(CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS GREATER 1 + AND NOT CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT) + idf_target_post_build_msg(${post_build_target} + "==============================================================================" + "Bootloader built. Secure boot enabled, so bootloader not flashed automatically." + "To sign the bootloader with additional private keys." + "\t${espsecurepy} sign-data -k secure_boot_signing_key2.pem -v 2 \ + --append-signatures -o signed_bootloader.bin build/bootloader/bootloader.bin" + "Secure boot enabled, so bootloader not flashed automatically." + "\t${esptoolpy_write_flash} ${BOOTLOADER_OFFSET} ${CMAKE_BINARY_DIR}/bootloader.bin" + "==============================================================================" + ) + elseif(NOT CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT) + idf_target_post_build_msg(${post_build_target} + "==============================================================================" + "Bootloader built. Secure boot enabled, so bootloader not flashed automatically." + "\t${esptoolpy_write_flash} ${BOOTLOADER_OFFSET} ${CMAKE_BINARY_DIR}/bootloader.bin" + "==============================================================================" + ) + endif() + + idf_build_generate_metadata(EXECUTABLE bootloader_elf) +endif() From ab90489ccb784b86f4f22fcb99722257fc29837d Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 10 Mar 2026 12:08:31 +0100 Subject: [PATCH 12/16] fix(cmakev2/utilities): resolve embed file path in idf_component_include Commit f62f45cf5cca changed target_add_binary_data to resolve the embedded file path relative to the component directory by using idf_component_get_property to obtain the COMPONENT_DIR. This fixed the path resolution when target_add_binary_data is called from idf_component_include, which runs outside the component's directory context. However, target_add_binary_data can also be called directly from a project's CMakeLists.txt with a non-component target, e.g. target_add_binary_data(app.elf ...) as done in examples/security/security_features_app. In this case, idf_component_get_property fails because the target is not a component. Fix this by moving the path resolution to idf_component_include, where the embed file paths from EMBED_FILES and EMBED_TXTFILES component properties are resolved to absolute paths relative to COMPONENT_DIR before being passed to target_add_binary_data. This way, target_add_binary_data receives already absolute paths from idf_component_include and can use plain get_filename_component(ABSOLUTE) for direct calls, which correctly resolves relative to CMAKE_CURRENT_SOURCE_DIR. Fixes: f62f45cf5cca ("fix(cmakev2/utilities): add a dependency target for the embedded file") Signed-off-by: Frantisek Hrbata --- tools/cmakev2/component.cmake | 4 ++++ tools/cmakev2/utilities.cmake | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index a0262a9ac8..8a07d0a91d 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -963,13 +963,17 @@ function(idf_component_include name) idf_die("Unsupported target type '${component_real_target_type}' in component '${component_name}'") endif() + idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR) + idf_component_get_property(embed_files "${component_name}" EMBED_FILES) foreach(file IN LISTS embed_files) + get_filename_component(file "${file}" ABSOLUTE BASE_DIR "${component_dir}") target_add_binary_data(${COMPONENT_TARGET} "${file}" "BINARY") endforeach() idf_component_get_property(embed_txtfiles "${component_name}" EMBED_TXTFILES) foreach(file IN LISTS embed_txtfiles) + get_filename_component(file "${file}" ABSOLUTE BASE_DIR "${component_dir}") target_add_binary_data(${COMPONENT_TARGET} "${file}" "TEXT") endforeach() diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index 627cc2ea7a..a280496418 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -837,12 +837,7 @@ function(target_add_binary_data target embed_file embed_type) idf_build_get_property(build_dir BUILD_DIR) idf_build_get_property(idf_path IDF_PATH) - # The target_add_binary_data function is also called within the - # idf_component_include function, which is not executed in the component - # directory context. Therefore, ensure that the absolute path of the - # embedded file is resolved relative to the component directory. - idf_component_get_property(component_directory "${target}" COMPONENT_DIR) - get_filename_component(embed_file "${embed_file}" ABSOLUTE BASE_DIR "${component_directory}") + get_filename_component(embed_file "${embed_file}" ABSOLUTE) get_filename_component(name "${embed_file}" NAME) set(embed_srcfile "${build_dir}/${name}.S") From 38edb84669cc7efa84616495d3d53f6c6fb552ce Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 11 Mar 2026 08:44:05 +0100 Subject: [PATCH 13/16] fix(esp_rom): exclude TLSF/multi_heap ROM patches from bootloader build The bootloader does not use the heap allocator or TLSF, so the ROM patch files esp_rom_tlsf.c and esp_rom_multi_heap.c are not needed in the bootloader build. In the cmakev1 build system this was never an issue because cmakev1 uses an early expansion phase where only Kconfig files for components listed in BUILD_COMPONENTS are processed. Since the heap component is not part of the bootloader's component list, its Kconfig options (CONFIG_HEAP_TLSF_USE_ROM_IMPL, etc.) were never defined and the conditional compilation of these source files was effectively skipped. In the cmakev2 build system, Kconfig options from all discovered components are visible regardless of whether the component is part of the build. Because the bootloader reuses the main project's sdkconfig (where CONFIG_HEAP_TLSF_USE_ROM_IMPL defaults to y on targets with ROM TLSF support like esp32c2), the TLSF patch sources were being compiled into the bootloader's esp_rom. This caused a build failure because esp_rom_tlsf.c includes tlsf_block_functions.h from the heap component, which is not a dependency of esp_rom and is not part of the bootloader build. Guard the TLSF and multi_heap ROM patch sources with NOT BOOTLOADER_BUILD to prevent them from being compiled in the bootloader context. This fix is compatible with both cmakev1 and cmakev2. Signed-off-by: Frantisek Hrbata --- components/esp_rom/CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/components/esp_rom/CMakeLists.txt b/components/esp_rom/CMakeLists.txt index 09b671b936..815d368453 100644 --- a/components/esp_rom/CMakeLists.txt +++ b/components/esp_rom/CMakeLists.txt @@ -24,14 +24,16 @@ else() "patches/esp_rom_efuse.c" "patches/esp_rom_gpio.c") - if(CONFIG_HEAP_TLSF_USE_ROM_IMPL AND CONFIG_ESP_ROM_TLSF_CHECK_PATCH) - # This file shall be included in the build if TLSF in ROM is activated - list(APPEND sources "patches/esp_rom_tlsf.c") - endif() + if(NOT BOOTLOADER_BUILD) + if(CONFIG_HEAP_TLSF_USE_ROM_IMPL AND CONFIG_ESP_ROM_TLSF_CHECK_PATCH) + # This file shall be included in the build if TLSF in ROM is activated + list(APPEND sources "patches/esp_rom_tlsf.c") + endif() - if(CONFIG_HEAP_TLSF_USE_ROM_IMPL AND CONFIG_ESP_ROM_MULTI_HEAP_WALK_PATCH) - # This file shall be included in the build if TLSF in ROM is activated - list(APPEND sources "patches/esp_rom_multi_heap.c") + if(CONFIG_HEAP_TLSF_USE_ROM_IMPL AND CONFIG_ESP_ROM_MULTI_HEAP_WALK_PATCH) + # This file shall be included in the build if TLSF in ROM is activated + list(APPEND sources "patches/esp_rom_multi_heap.c") + endif() endif() list(APPEND private_required_comp soc hal esp_hal_uart) From 9f8329c2dfd9f3fa7f6ec97f9a123ae3d0c3c2e8 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 18 Mar 2026 11:33:41 +0100 Subject: [PATCH 14/16] fix(cmakev2/project): initialize idf_path for macro prefix map The idf_path variable was used in -fmacro-prefix-map and -fdebug-prefix-map flags but never read from the IDF_PATH build property, resulting in an empty substitution. This caused full filesystem paths to leak into .rodata instead of being mapped to /IDF. Signed-off-by: Frantisek Hrbata --- tools/cmakev2/project.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index dddfaafa95..e99a46867e 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -84,6 +84,7 @@ function(__init_project_configuration) set(link_options) idf_build_get_property(idf_ver IDF_VER) + idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(idf_target IDF_TARGET) idf_build_get_property(component_interfaces COMPONENT_INTERFACES) idf_build_get_property(build_dir BUILD_DIR) From c6c1293d0c1c697255d84f823b23a4d0a766e016 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 18 Mar 2026 12:47:52 +0100 Subject: [PATCH 15/16] fix(cmakev2/project): guard compiler optimization flags with build property The __init_project_configuration() function in cmakev2's project.cmake unconditionally applied app-level compiler optimization flags based on CONFIG_COMPILER_OPTIMIZATION_* Kconfig options. When the bootloader subproject was built with cmakev2, these app-level flags leaked into the bootloader compile command alongside the correct bootloader-specific flags from CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_*. For example, with the default configuration (app: DEBUG, bootloader: SIZE), the bootloader received both "-Og -fno-shrink-wrap" (from app config) and "-Os -freorder-blocks" (from bootloader config). While GCC uses the last -O flag (-Os wins), the stray -fno-shrink-wrap persisted. Introduce a SET_COMPILER_OPTIMIZATION build property that defaults to YES when unset. Subprojects that manage their own optimization flags (like the bootloader) can set this to NO before calling idf_project_init() to prevent the default optimization flags from being applied. This keeps project.cmake generic without requiring it to know about specific subproject types. Signed-off-by: Frantisek Hrbata --- .../bootloader/subproject/CMakeLists_v2.txt | 4 ++ tools/cmakev2/project.cmake | 42 +++++++++++-------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/components/bootloader/subproject/CMakeLists_v2.txt b/components/bootloader/subproject/CMakeLists_v2.txt index bbfae626bb..7b6704a797 100644 --- a/components/bootloader/subproject/CMakeLists_v2.txt +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -66,6 +66,10 @@ idf_build_set_property(PROJECT_COMPONENTS_SOURCE "idf_components") # components listed in `esp_common` as optional requirements are pulled into # the build. idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED) +# The bootloader uses its own compiler optimization flags +# (CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_*) set below, not the app-level +# CONFIG_COMPILER_OPTIMIZATION_* defaults. +idf_build_set_property(SET_COMPILER_OPTIMIZATION NO) # Perform internal IDF project initialisation idf_project_init() diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index e99a46867e..715a122c63 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -187,24 +187,32 @@ function(__init_project_configuration) list(APPEND cxx_compile_options "-std=gnu++26") endif() - if(CONFIG_COMPILER_OPTIMIZATION_SIZE) - if(CMAKE_C_COMPILER_ID MATCHES "Clang") - list(APPEND compile_options "-Oz") - else() - list(APPEND compile_options "-Os") + # Subprojects that handle their own compiler optimization flags can set the + # SET_COMPILER_OPTIMIZATION build property to NO before idf_project_init(). + idf_build_get_property(set_compiler_optimization SET_COMPILER_OPTIMIZATION) + if(NOT set_compiler_optimization) + set(set_compiler_optimization YES) + endif() + if(set_compiler_optimization) + if(CONFIG_COMPILER_OPTIMIZATION_SIZE) + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND compile_options "-Oz") + else() + list(APPEND compile_options "-Os") + endif() + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + list(APPEND compile_options "-freorder-blocks") + endif() + elseif(CONFIG_COMPILER_OPTIMIZATION_DEBUG) + list(APPEND compile_options "-Og") + if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CONFIG_IDF_TARGET_LINUX) + list(APPEND compile_options "-fno-shrink-wrap") # Disable shrink-wrapping to reduce binary size + endif() + elseif(CONFIG_COMPILER_OPTIMIZATION_NONE) + list(APPEND compile_options "-O0") + elseif(CONFIG_COMPILER_OPTIMIZATION_PERF) + list(APPEND compile_options "-O2") endif() - if(CMAKE_C_COMPILER_ID MATCHES "GNU") - list(APPEND compile_options "-freorder-blocks") - endif() - elseif(CONFIG_COMPILER_OPTIMIZATION_DEBUG) - list(APPEND compile_options "-Og") - if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CONFIG_IDF_TARGET_LINUX) - list(APPEND compile_options "-fno-shrink-wrap") # Disable shrink-wrapping to reduce binary size - endif() - elseif(CONFIG_COMPILER_OPTIMIZATION_NONE) - list(APPEND compile_options "-O0") - elseif(CONFIG_COMPILER_OPTIMIZATION_PERF) - list(APPEND compile_options "-O2") endif() if(CONFIG_COMPILER_CXX_EXCEPTIONS) From 3ba07d507ed3e0db60dae1862eb2df159057733d Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 19 Mar 2026 06:48:49 +0100 Subject: [PATCH 16/16] fix(cmakev2/bootloader): use bootloader.bin when signing is not enabled When CONFIG_SECURE_BOOT_V2_ENABLED=y but CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES is not set, produce the binary directly as bootloader.bin instead of bootloader-unsigned.bin. This matches the v1 behavior where the intermediate binary name is conditional: bootloader-unsigned.bin only when build-time signing is enabled (so the signed output can be named bootloader.bin), otherwise the output is bootloader.bin directly. Signed-off-by: Frantisek Hrbata --- components/bootloader/subproject/CMakeLists_v2.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/bootloader/subproject/CMakeLists_v2.txt b/components/bootloader/subproject/CMakeLists_v2.txt index 7b6704a797..3dd1f51684 100644 --- a/components/bootloader/subproject/CMakeLists_v2.txt +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -293,8 +293,19 @@ elseif(CONFIG_SECURE_BOOT_V1_ENABLED) # are needed, so the post-build message block is skipped. # --------------------------------------------------------------------------- elseif(CONFIG_SECURE_BOOT_V2_ENABLED) + if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES) + # When signing during build, produce the raw binary as + # bootloader-unsigned.bin and then sign it into bootloader.bin. + set(bootloader_unsigned_bin "bootloader-unsigned.bin") + else() + # Without build-time signing, produce the binary directly as + # bootloader.bin (matching v1 behavior). The user is expected + # to sign it externally before flashing. + set(bootloader_unsigned_bin "bootloader.bin") + endif() + idf_build_binary(bootloader_elf - OUTPUT_FILE "${CMAKE_BINARY_DIR}/bootloader-unsigned.bin" + OUTPUT_FILE "${CMAKE_BINARY_DIR}/${bootloader_unsigned_bin}" TARGET bootloader_unsigned_bin ALL) idf_check_bootloader_size(bootloader_unsigned_bin)