diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index b7711a0f2f..fe0088ab12 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -37,6 +37,10 @@ function(idf_build_set_property property value) set(multi_value) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) + if("${property}" STREQUAL MINIMAL_BUILD) + idf_warn("Build property 'MINIMAL_BUILD' is obsolete and will be ignored") + endif() + set(append) if(ARG_APPEND) set(append APPEND) @@ -503,15 +507,27 @@ endfunction() Optional ``executable`` suffix. + *MAPFILE_TARGET[in,opt]* + + Name of the target for the map file. If provided, the link map file is + generated for the specified executable, and the ``MAPFILE_TARGET`` + target name is created for it. The ``MAPFILE_PATH`` property with the + link map file path is added to the ``MAPFILE_TARGET`` target. This can + be used for other targets that depend on the link map file. The link map file + is not generated on Darwin host, so the target ``MAPFILE_TARGET`` may not + be created if link map file is not generated. + Create a new executable target using the name specified in the ``executable`` argument, and link it to the library created with the component names provided in the ``COMPONENTS`` option. If the ``COMPONENTS`` option is not set, all discovered components are added to - the library. Optinaly set the executable name and suffix. + the library. Optionally set the executable name and suffix. The executable + library target name is added to the ``LIBRARY_INTERFACE`` executable + property. #]] function(idf_build_executable executable) set(options) - set(one_value NAME SUFFIX) + set(one_value NAME SUFFIX MAPFILE_TARGET) set(multi_value COMPONENTS) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) @@ -534,15 +550,29 @@ function(idf_build_executable executable) endif() add_executable(${executable} "${executable_src}") - if(ARG_NAME) - set_target_properties(${executable} PROPERTIES OUTPUT_NAME ${ARG_NAME}) - endif() + set_target_properties(${executable} PROPERTIES OUTPUT_NAME ${ARG_NAME}) if(ARG_SUFFIX) set_target_properties(${executable} PROPERTIES SUFFIX ${ARG_SUFFIX}) endif() target_link_libraries(${executable} PRIVATE ${library}) + + idf_build_get_property(linker_type LINKER_TYPE) + if(ARG_MAPFILE_TARGET AND "${linker_type}" STREQUAL "GNU") + set(mapfile "${CMAKE_BINARY_DIR}/${ARG_NAME}.map") + target_link_options(${executable} PRIVATE "LINKER:--Map=${mapfile}") + add_custom_command( + OUTPUT "${mapfile}" + DEPENDS ${executable} + ) + add_custom_target(${ARG_MAPFILE_TARGET} + DEPENDS "${mapfile}" + ) + set_target_properties(${ARG_MAPFILE_TARGET} PROPERTIES MAPFILE_PATH ${mapfile}) + endif() + + set_target_properties(${executable} PROPERTIES LIBRARY_INTERFACE ${library}) endfunction() #[[ @@ -647,28 +677,34 @@ endfunction() .. code-block:: cmake - idf_build_generate_metadata( + idf_build_generate_metadata( [FILE ]) - *executable[in]* + *binary[in]* - Executable target for which to generate a metadata file. + Binary target for which to generate a metadata file. *OUTPUT_FILE[in,opt]* Optional output file path for storing the metadata. If not provided, the default path ``/project_description.json`` is used. - Generate metadata for the specified ``executable`` and store it in the + Generate metadata for the specified ``binary`` and store it in the specified ``FILE``. If no ``FILE`` is provided, the default location ``/project_description.json`` will be used. #]] -function(idf_build_generate_metadata executable) +function(idf_build_generate_metadata binary) set(options) set(one_value OUTPUT_FILE) set(multi_value) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) + # The EXECUTABLE_TARGET property is set by the idf_build_binary or + # the idf_sign_binary function. + get_target_property(executable "${binary}" EXECUTABLE_TARGET) + if(NOT executable) + idf_die("Binary target '${binary}' is missing 'EXECUTABLE_TARGET' property.") + endif() __get_executable_library_or_die(TARGET "${executable}" OUTPUT library) idf_build_get_property(PROJECT_NAME PROJECT_NAME) @@ -679,9 +715,13 @@ function(idf_build_generate_metadata executable) idf_build_get_property(SDKCONFIG SDKCONFIG) idf_build_get_property(SDKCONFIG_DEFAULTS SDKCONFIG_DEFAULTS) set(PROJECT_EXECUTABLE "$") - # The PROJECT_BIN executable property must be set by the idf_build_binary - # function. - get_target_property(PROJECT_BIN "${executable}" EXECUTABLE_BINARY) + # The BINARY_PATH property is set by the idf_build_binary or + # the idf_sign_binary function. + get_target_property(binary_path ${binary} BINARY_PATH) + if(NOT binary_path) + idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.") + endif() + get_filename_component(PROJECT_BIN "${binary_path}" NAME) if(NOT PROJECT_BIN) set(PROJECT_BIN "") endif() @@ -767,8 +807,9 @@ endfunction() 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 - of the generated binary image will be also stored in the ``BINARY_PATH`` - property of the ``TARGET``. + of the generated binary image will be stored in the ``BINARY_PATH`` + property and the executable target in the ``EXECUTABLE_TARGET`` property of + the ``TARGET``. #]] function(idf_build_binary executable) set(options) @@ -828,13 +869,13 @@ function(idf_build_binary executable) # Create a custom target to generate the binary file add_custom_target(${ARG_TARGET} DEPENDS "${ARG_OUTPUT_FILE}") - # The EXECUTABLE_BINARY property is used by idf_build_generate_metadata to - # store the name of the binary image. - set_target_properties(${executable} PROPERTIES EXECUTABLE_BINARY ${binary_name}) - # 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. set_target_properties(${ARG_TARGET} PROPERTIES BINARY_PATH ${ARG_OUTPUT_FILE}) + + # Store executable target name in the EXECUTABLE_TARGET property. This is used + # by the idf_build_generate_metadata function. + set_target_properties(${ARG_TARGET} PROPERTIES EXECUTABLE_TARGET ${executable}) endfunction() #[[ @@ -870,9 +911,10 @@ endfunction() 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 - the signed binary image will be also stored in the ``BINARY_PATH`` property - of the ``TARGET``. + named ``TARGET`` will be created for the signed binary image. The path of + the signed binary image will be stored in the ``BINARY_PATH`` property and + the executable target in the ``EXECUTABLE_TARGET`` property of the + ``TARGET``. #]] function(idf_sign_binary binary) set(options) @@ -936,6 +978,14 @@ function(idf_sign_binary binary) # 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. set_target_properties(${ARG_TARGET} PROPERTIES BINARY_PATH ${ARG_OUTPUT_FILE}) + + # Store executable target name in the EXECUTABLE_TARGET property. This is used + # by the idf_build_generate_metadata function. + get_target_property(executable "${binary}" EXECUTABLE_TARGET) + if(NOT executable) + idf_die("Binary target '${binary}' is missing 'EXECUTABLE_TARGET' property.") + endif() + set_target_properties(${ARG_TARGET} PROPERTIES EXECUTABLE_TARGET ${executable}) endfunction() #[[ diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake index 50b4368e6a..6824a9296c 100644 --- a/tools/cmakev2/idf.cmake +++ b/tools/cmakev2/idf.cmake @@ -36,6 +36,7 @@ include(compat) include(ldgen) include(dfu) include(uf2) +include(size) include(GetGitRevisionDescription) # For backward compatibility, since externalproject_add is used by # project_include.cmake in the bootloader component. The ExternalProject diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index eaf4d6043a..f42712b516 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -90,6 +90,21 @@ function(__init_project_configuration) idf_build_get_property(project_dir PROJECT_DIR) idf_build_get_property(project_name PROJECT_NAME) + # Set the LINKER_TYPE build property. Different linkers may have varying + # options, so it's important to identify the linker type to configure the + # options correctly. Currently, LINKER_TYPE is used to set the appropriate + # linker options for linking the entire archive, which differs between the + # GNU and Apple linkers when building on the host. + if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + # Compiling for the host, and the host is macOS, so the linker is Darwin LD. + # Note, when adding support for Clang and LLD based toolchain this check will + # need to be modified. + set(linker_type "Darwin") + else() + set(linker_type "GNU") + endif() + idf_build_set_property(LINKER_TYPE "${linker_type}") + list(APPEND compile_definitions "_GLIBCXX_USE_POSIX_SEMAPHORE" # These two lines enable libstd++ to use "_GLIBCXX_HAVE_POSIX_SEMAPHORE" # posix-semaphores from components/pthread "_GNU_SOURCE") @@ -407,16 +422,13 @@ function(__init_project_configuration) list(APPEND link_options "-specs=picolibc.specs") endif() - if(CMAKE_C_COMPILER_ID MATCHES "GNU") - set(mapfile "${build_dir}/${project_name}.map") + if("${linker_type}" STREQUAL "GNU") set(target_upper "${idf_target}") string(TOUPPER ${target_upper} target_upper) # Add cross-reference table to the map file list(APPEND link_options "-Wl,--cref") # Add this symbol as a hint for esp_idf_size to guess the target name list(APPEND link_options "-Wl,--defsym=IDF_TARGET_${target_upper}=0") - # Enable map file output - list(APPEND link_options "-Wl,--Map=${mapfile}") # Check if linker supports --no-warn-rwx-segments execute_process(COMMAND ${CMAKE_LINKER} "--no-warn-rwx-segments" "--version" RESULT_VARIABLE result @@ -486,21 +498,6 @@ function(__init_project_configuration) idf_build_set_property(ASM_COMPILE_OPTIONS "${asm_compile_options}" APPEND) idf_build_set_property(COMPILE_DEFINITIONS "${compile_definitions}" APPEND) idf_build_set_property(LINK_OPTIONS "${link_options}" APPEND) - - # Set the LINKER_TYPE build property. Different linkers may have varying - # options, so it's important to identify the linker type to configure the - # options correctly. Currently, LINKER_TYPE is used to set the appropriate - # linker options for linking the entire archive, which differs between the - # GNU and Apple linkers when building on the host. - if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") - # Compiling for the host, and the host is macOS, so the linker is Darwin LD. - # Note, when adding support for Clang and LLD based toolchain this check will - # need to be modified. - set(linker_type "Darwin") - else() - set(linker_type "GNU") - endif() - idf_build_set_property(LINKER_TYPE "${linker_type}") endfunction() #[[ @@ -599,6 +596,7 @@ macro(idf_project_init) idf_die("sdkconfig.cmake file not found.") endif() include("${sdkconfig_cmake}") + unset(sdkconfig_cmake) # Initialize the target architecture based on the configuration # Ensure this is done after including the sdkconfig. @@ -639,6 +637,7 @@ macro(idf_project_init) idf_component_include("${component_name}") endforeach() endif() + unset(include_all_components) idf_build_set_property(__PROJECT_INITIALIZED YES) endif() @@ -694,26 +693,22 @@ function(idf_build_generate_flasher_args) INPUT "${build_dir}/flasher_args.json.in") endfunction() -#[[api -.. cmakev2:macro:: idf_project_default +#[[ +.. cmakev2:macro:: __project_default .. code-block:: cmake - idf_project_default() + __project_default() - Create a default project executable based on the main component and its - transitive dependencies. The executable name is derived from the - ``PROJECT_NAME`` variable, which by default uses the ``CMAKE_PROJECT_NAME`` - value specified in the CMake's ``project()`` call. - - Generate the binary image for the executable, signed or unsigned based on - the configuration, and add flash targets for it. + Helper function implementing the main idf_project_default macro + functionality, preventing global variable scope pollution. #]] -macro(idf_project_default) - idf_project_init() +function(__project_default) idf_build_get_property(build_dir BUILD_DIR) idf_build_get_property(executable PROJECT_NAME) - idf_build_executable("${executable}" COMPONENTS main SUFFIX ".elf") + idf_build_executable("${executable}" + COMPONENTS main + MAPFILE_TARGET "${executable}_mapfile") if(CONFIG_APP_BUILD_GENERATE_BINARIES) # Is it possible to have a configuration where @@ -734,6 +729,7 @@ macro(idf_project_default) TARGET app-flash NAME "app" FLASH) + idf_build_generate_metadata("${executable}_binary_signed") else() idf_build_binary("${executable}" OUTPUT_FILE "${build_dir}/${executable}.bin" @@ -750,16 +746,7 @@ macro(idf_project_default) idf_create_dfu("${executable}_binary" TARGET dfu) - endif() - - # FIXME: Dependencies should be specified within the components, not in the - # build system. - if(CONFIG_APP_BUILD_TYPE_APP_2NDBOOT) - add_dependencies(flash "partition_table_bin") - endif() - - if(CONFIG_APP_BUILD_BOOTLOADER) - add_dependencies(flash "bootloader") + idf_build_generate_metadata("${executable}_binary") endif() idf_build_generate_flasher_args() @@ -779,8 +766,33 @@ macro(idf_project_default) TARGET uf2-app APP_ONLY) - idf_build_generate_metadata("${executable}") + if(TARGET "${executable}_mapfile") + idf_create_size_report("${executable}_mapfile" + TARGET size) + endif() +endfunction() - unset(build_dir) - unset(executable) +#[[api +.. cmakev2:macro:: idf_project_default + + .. code-block:: cmake + + idf_project_default() + + Create a default project executable based on the main component and its + transitive dependencies. The executable name is derived from the + ``PROJECT_NAME`` variable, which by default uses the ``CMAKE_PROJECT_NAME`` + value specified in the CMake's ``project()`` call. + + Generate the binary image for the executable, signed or unsigned based on + the configuration, and add flash targets for it. +#]] +macro(idf_project_default) + idf_project_init() + # Only the idf_project_init macro needs be called within the global scope, + # as it includes the project_include.cmake files and the cmake version of + # the configuration. The remaining functionality of the idf_project_default + # macro is implemented in a __project_default helper function to avoid + # polluting the global variable space. + __project_default() endmacro() diff --git a/tools/cmakev2/size.cmake b/tools/cmakev2/size.cmake new file mode 100644 index 0000000000..17deff59c3 --- /dev/null +++ b/tools/cmakev2/size.cmake @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +#[[ +.. cmakev2:function:: idf_create_size_report + + .. code-block:: cmake + + idf_create_size_report( + TARGET ) + + *mapfile[in]* + + The mapfile target generated by the idf_build_executable function. + + *TARGET[in]* + + The base name for the size report targets to be created. In addition to + the default size report, which will be available under the target name + specified in the ``TARGET`` option, two more detailed targets, + "-files" and "-components", will also be created. + + Create size report targets for the specified ``mapfile`` target. The + ``TARGET`` option specifies the name of the default size report target, but + two more targets with detailed reports are also created. For example, if + ``TARGET`` is set to "size," three targets "size", "size-files", and + "size-components" will be created. +#]] +function(idf_create_size_report mapfile_target) + set(options) + set(one_value TARGET) + set(multi_value) + cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) + + if(NOT DEFINED ARG_TARGET) + idf_die("TARGET option is required") + endif() + + get_target_property(mapfile "${mapfile_target}" MAPFILE_PATH) + if(NOT mapfile) + idf_die("Mapfile target '${mapfile_target}' is missing 'MAPFILE_PATH' property.") + endif() + + idf_build_get_property(idf_path IDF_PATH) + idf_build_get_property(python PYTHON) + + set(idf_size ${python} -m esp_idf_size) + + add_custom_target(${ARG_TARGET} + COMMAND ${CMAKE_COMMAND} + -D "IDF_SIZE_TOOL=${idf_size}" + -D "MAP_FILE=${mapfile}" + -D "OUTPUT_JSON=${OUTPUT_JSON}" + -P "${idf_path}/tools/cmake/run_size_tool.cmake" + DEPENDS ${mapfile_target} + USES_TERMINAL + VERBATIM + ) + + add_custom_target(${ARG_TARGET}-files + COMMAND ${CMAKE_COMMAND} + -D "IDF_SIZE_TOOL=${idf_size}" + -D "IDF_SIZE_MODE=--files" + -D "MAP_FILE=${mapfile}" + -D "OUTPUT_JSON=${OUTPUT_JSON}" + -P "${idf_path}/tools/cmake/run_size_tool.cmake" + DEPENDS ${mapfile_target} + USES_TERMINAL + VERBATIM + ) + + add_custom_target(${ARG_TARGET}-components + COMMAND ${CMAKE_COMMAND} + -D "IDF_SIZE_TOOL=${idf_size}" + -D "IDF_SIZE_MODE=--archives" + -D "MAP_FILE=${mapfile}" + -D "OUTPUT_JSON=${OUTPUT_JSON}" + -P "${idf_path}/tools/cmake/run_size_tool.cmake" + DEPENDS ${mapfile_target} + USES_TERMINAL + VERBATIM + ) +endfunction() diff --git a/tools/cmakev2/test/CMakeLists.txt b/tools/cmakev2/test/CMakeLists.txt index 8332901d1b..bc0e3fa2e5 100644 --- a/tools/cmakev2/test/CMakeLists.txt +++ b/tools/cmakev2/test/CMakeLists.txt @@ -219,7 +219,8 @@ endfunction() # idf.py confserver-fatfs function(test_executable) idf_build_executable(fatfs_example - COMPONENTS fatfs_example) + COMPONENTS fatfs_example + MAPFILE_TARGET fatfs_example_mapfile) idf_build_binary(fatfs_example TARGET fatfs_example_bin OUTPUT_FILE fatfs_example.bin) @@ -229,14 +230,20 @@ function(test_executable) NAME fatfs_example) idf_create_menuconfig(fatfs_example TARGET menuconfig-fatfs) - idf_build_generate_metadata(fatfs_example + idf_build_generate_metadata(fatfs_example_bin OUTPUT_FILE project_description_fatfs.json) idf_create_confserver(fatfs_example TARGET confserver-fatfs) + if(TARGET fatfs_example_mapfile) + idf_create_size_report(fatfs_example_mapfile + TARGET fatfs-size) + endif() + idf_build_executable(hello_world_example - COMPONENTS hello_world_example) + COMPONENTS hello_world_example + MAPFILE_TARGET hello_world_example_mapfile) idf_build_binary(hello_world_example TARGET hello_world_example_bin OUTPUT_FILE hello_world_example.bin) @@ -246,10 +253,15 @@ function(test_executable) NAME hello_world_example) idf_create_menuconfig(hello_world_example TARGET menuconfig-hello_world) - idf_build_generate_metadata(hello_world_example + idf_build_generate_metadata(hello_world_example_bin OUTPUT_FILE project_description_hello_world.json) idf_create_confserver(hello_world_example TARGET confserver-hello_world) + + if(TARGET hello_world_example_mapfile) + idf_create_size_report(hello_world_example_mapfile + TARGET hello-size) + endif() endfunction() # Run tests diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index 69f94a8dfb..3671598640 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -981,11 +981,12 @@ endfunction() Output variable to store the library interface target linked to the executable. - Search for the library interface target created with the - idf_build_library() function and linked to the executable, examine the - LINK_LIBRARIES for the executable and the LIBRARY_INTERFACES build - property, which stores all library interface targets created by the - idf_build_library() function. + Search for the library interface target in the LIBRARY_INTERFACE executable + property. If not found, search for the library interface target created with + the idf_build_library() function and linked to the executable. Examine the + LINK_LIBRARIES for the executable and the LIBRARY_INTERFACES build property, + which stores all library interface targets created by the idf_build_library() + function. #]] function(__get_executable_library_or_die) set(options) @@ -1007,15 +1008,22 @@ function(__get_executable_library_or_die) "but an 'EXECUTABLE' target type is expected.") endif() - set(library NOTFOUND) - get_target_property(targets "${ARG_TARGET}" LINK_LIBRARIES) idf_build_get_property(libraries LIBRARY_INTERFACES) - foreach(target IN LISTS targets) - if("${target}" IN_LIST libraries) - set(library "${target}") - break() - endif() - endforeach() + get_target_property(library "${ARG_TARGET}" LIBRARY_INTERFACE) + if(NOT "${library}" IN_LIST libraries) + # The library interface is not stored in the LIBRARY_INTERFACE + # executable property, so the executable was not created by the + # idf_build_executable function. Try searching for the library + # interface in the LINK_LIBRARIES property. + set(library NOTFOUND) + get_target_property(targets "${ARG_TARGET}" LINK_LIBRARIES) + foreach(target IN LISTS targets) + if("${target}" IN_LIST libraries) + set(library "${target}") + break() + endif() + endforeach() + endif() if("${library}" STREQUAL "NOTFOUND") idf_die("No library interface target linked to the '${ARG_TARGET}' executable")