diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index ef959eb5dd..aa9e368e43 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}") @@ -139,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..3dd1f51684 --- /dev/null +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -0,0 +1,366 @@ +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) +# 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() + +# --------------------------------------------------------------------------- +# 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) + 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}" + 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() 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. 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}) 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) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 5c1f3253ba..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}) @@ -1067,7 +1082,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}") @@ -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. @@ -1227,6 +1248,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 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() 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/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) 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 diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index dddfaafa95..715a122c63 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) @@ -186,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) diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index ceabc67d1f..a280496418 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( [...]) @@ -812,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")