From a6d5b04dab9238c5cd767a5511edcd4aaac76b7b Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 17 Apr 2026 08:38:37 +0200 Subject: [PATCH 01/12] feat(cmake): add IDF_BUILD_V2 shim for transparent cmakev2 delegation When IDF_BUILD_V2 evaluates to a CMake-truthy value, project.cmake delegates to Build system v2 (cmakev2). The shim wraps project(), forwards the app-declared COMPONENTS list via __SHIM_COMPONENTS, and publishes __V1_COMPAT_SHIM so Build system v2 internals can gate Build system v1 compatibility behavior. Existing app CMakeLists.txt files build unchanged. Co-authored-by: Frantisek Hrbata --- tools/cmake/project.cmake | 95 +++++++++++++++++++++++++++++++++++++ tools/cmakev2/project.cmake | 12 ++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake index aecb4dc6384..3be09b24d6e 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -1,6 +1,101 @@ # Designed to be included from an IDF app's CMakeLists.txt file cmake_minimum_required(VERSION 3.22) +# When the IDF_BUILD_V2 environment variable is set, transparently delegate +# to Build system v2 (cmakev2). This allows existing Build system v1 projects +# to be built with Build system v2 without modifying their CMakeLists.txt. +# The project() macro is wrapped to automatically call idf_project_default() +# after the real CMake project() call, bridging the API difference between +# Build system v1 and Build system v2. +# +# Build system v1 pattern (no changes needed): +# include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# idf_build_set_property(...) # optional, works with Build system v2 infra +# project(my_app) +# +# With IDF_BUILD_V2=y set in the environment, this becomes equivalent to: +# include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake) +# idf_build_set_property(...) +# project(my_app C CXX ASM) +# idf_project_default() +if($ENV{IDF_BUILD_V2}) + message(STATUS "IDF Build System V2 (cmakev2) activated via IDF_BUILD_V2 shim") + + # Include the Build system v2 (cmakev2) entry point. This sets up all + # Build system v2 infrastructure (build properties, target, toolchain, + # kconfig, etc.). idf.cmake's file-level __init_idf_path() call publishes + # ${idf_path} to this scope, so no further set() is needed here. + include(${CMAKE_CURRENT_LIST_DIR}/../cmakev2/idf.cmake) + + # Mark that we are running through the Build system v1 compatibility shim + # so that Build system v2 internals can enable backward-compatible build + # properties. + idf_build_set_property(__V1_COMPAT_SHIM 1) + + # Use the standard CMake trick to save the real project() command. + # After these two overrides, __project() calls the original CMake + # project(). See https://cmake.org/pipermail/cmake/2015-October/061751.html + function(project) + endfunction() + + function(_project) + endfunction() + + macro(project project_name) + # Call the real CMake project() with the languages required by cmakev2. + __project(${project_name} C CXX ASM) + + # Restore project() for sub-projects and third-party libraries that + # may call project() themselves (e.g. components/mbedtls calls + # add_subdirectory(mbedtls) which runs `project("Mbed TLS")`). + # + # The override is a function() (own variable scope), so PROJECT_* + # variables set by __project() must be explicitly propagated to + # PARENT_SCOPE — otherwise the caller (and any subsequent + # add_subdirectory() within that caller) sees the IDF top-level + # project's source/binary dirs instead of the third-party project's. + # See tools/cmake/project.cmake's mature override at the Build + # system v1 path for the full set of mirrored variables. + function(project) + set(project_ARGV ARGV) + __project(${${project_ARGV}}) + + set(PROJECT_NAME "${PROJECT_NAME}" PARENT_SCOPE) + set(PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}" PARENT_SCOPE) + set(PROJECT_SOURCE_DIR "${PROJECT_SOURCE_DIR}" PARENT_SCOPE) + set(PROJECT_VERSION "${PROJECT_VERSION}" PARENT_SCOPE) + set(PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}" PARENT_SCOPE) + set(PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR}" PARENT_SCOPE) + set(PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}" PARENT_SCOPE) + set(PROJECT_VERSION_TWEAK "${PROJECT_VERSION_TWEAK}" PARENT_SCOPE) + set(PROJECT_DESCRIPTION "${PROJECT_DESCRIPTION}" PARENT_SCOPE) + set(PROJECT_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}" PARENT_SCOPE) + + set(${PROJECT_NAME}_BINARY_DIR "${${PROJECT_NAME}_BINARY_DIR}" PARENT_SCOPE) + set(${PROJECT_NAME}_SOURCE_DIR "${${PROJECT_NAME}_SOURCE_DIR}" PARENT_SCOPE) + set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION}" PARENT_SCOPE) + set(${PROJECT_NAME}_VERSION_MAJOR "${${PROJECT_NAME}_VERSION_MAJOR}" PARENT_SCOPE) + set(${PROJECT_NAME}_VERSION_MINOR "${${PROJECT_NAME}_VERSION_MINOR}" PARENT_SCOPE) + set(${PROJECT_NAME}_VERSION_PATCH "${${PROJECT_NAME}_VERSION_PATCH}" PARENT_SCOPE) + set(${PROJECT_NAME}_VERSION_TWEAK "${${PROJECT_NAME}_VERSION_TWEAK}" PARENT_SCOPE) + set(${PROJECT_NAME}_DESCRIPTION "${${PROJECT_NAME}_DESCRIPTION}" PARENT_SCOPE) + set(${PROJECT_NAME}_HOMEPAGE_URL "${${PROJECT_NAME}_HOMEPAGE_URL}" PARENT_SCOPE) + endfunction() + + # If the app set COMPONENTS before project(), forward it to Build + # system v2 via a build property so __project_default() can use the + # correct component list instead of the hardcoded "main". + if(DEFINED COMPONENTS) + idf_build_set_property(__SHIM_COMPONENTS "${COMPONENTS}") + endif() + + # Initialize and build the project using Build system v2. + idf_project_default() + endmacro() + + return() +endif() + # Get the currently selected sdkconfig file early, so this doesn't # have to be done multiple times on different places. if(SDKCONFIG) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index 22189e962aa..bf63ddd2eaf 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -741,8 +741,18 @@ endfunction() function(__project_default) idf_build_get_property(build_dir BUILD_DIR) idf_build_get_property(executable PROJECT_NAME) + + # When the Build system v1 compatibility shim forwarded a COMPONENTS + # list, use it instead of default main. + idf_build_get_property(shim_components __SHIM_COMPONENTS) + if(shim_components) + set(root_components ${shim_components}) + else() + set(root_components main) + endif() + idf_build_executable("${executable}" - COMPONENTS main + COMPONENTS ${root_components} MAPFILE_TARGET "${executable}_mapfile") if(CONFIG_APP_BUILD_GENERATE_BINARIES AND TARGET idf::esptool_py) From 3b16b7a7fa53705e38c6b1cb95e1aa4bd0d04a46 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 17 Apr 2026 08:36:11 +0200 Subject: [PATCH 02/12] feat(cmakev2/compat): main auto-dep under Build system v1 shim Replicate Build system v1 behavior where the 'main' component implicitly depends on the discovered components (or the app-restricted __SHIM_COMPONENTS list) when no explicit REQUIRES/PRIV_REQUIRES is set. Gated on __V1_COMPAT_SHIM so native Build system v2 projects retain their explicit dependency contract. Co-authored-by: Frantisek Hrbata --- tools/cmakev2/compat.cmake | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index 9a25c0f6547..cfac2f8bdc6 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -642,6 +642,47 @@ function(idf_component_register) target_link_libraries("${COMPONENT_TARGET}" PRIVATE "${req_interface}") endforeach() + # Special treatment for 'main' component is only applied when the project + # was entered through the Build system v1 compatibility shim. Native + # Build system v2 projects with no REQUIRES on main should remain the + # author-declared dependency set rooted at main; forcing every discovered + # component into main's link graph in that case contradicts the Build + # system v2 contract that the dependency graph is what the author writes. + idf_build_get_property(v1_compat_shim __V1_COMPAT_SHIM) + if(v1_compat_shim AND COMPONENT_NAME STREQUAL "main" + AND NOT ARG_REQUIRES AND NOT ARG_PRIV_REQUIRES) + idf_component_get_property(component_source "${COMPONENT_NAME}" COMPONENT_SOURCE) + if(component_source STREQUAL "project_components") + idf_build_get_property(shim_components __SHIM_COMPONENTS) + if(shim_components) + # The project explicitly restricted COMPONENTS=. Honor + # the restriction literally — do not pull every discovered + # component into main's link graph, even when the user's list + # collapses to just "main". Apps that need additional + # components must enumerate them in COMPONENTS or main's + # REQUIRES. + set(all_components ${shim_components}) + else() + idf_build_get_property(all_components COMPONENTS_DISCOVERED) + endif() + list(REMOVE_ITEM all_components "main") + # Common components are already linked via link_libraries() + idf_build_get_property(common_components __COMPONENT_REQUIRES_COMMON) + if(common_components) + list(REMOVE_ITEM all_components ${common_components}) + endif() + foreach(req IN LISTS all_components) + idf_component_include("${req}") + idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE) + if(${component_type} STREQUAL LIBRARY) + target_link_libraries("${COMPONENT_TARGET}" PUBLIC "${req_interface}") + else() + target_link_libraries("${COMPONENT_TARGET}" INTERFACE "${req_interface}") + endif() + endforeach() + endif() + endif() + # Signal to idf_component_include that this component was included via the # backward compatible idf_component_register function. idf_component_set_property("${COMPONENT_NAME}" COMPONENT_FORMAT CMAKEV1) From 797d8deb5b8c36e393290d9a9f0fe7aed38d5e69 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 17 Apr 2026 08:38:37 +0200 Subject: [PATCH 03/12] feat(cmakev2): expose Build system v1 build properties under shim Under __V1_COMPAT_SHIM, populate EXECUTABLE, EXECUTABLE_NAME, and BUILD_COMPONENTS build properties in __project_default(), and propagate project_elf to the caller scope. Relax the BUILD_COMPONENTS query gate in build.cmake so component code that uses idf_build_get_property(... BUILD_COMPONENTS) keeps working. Co-authored-by: Frantisek Hrbata --- tools/cmakev2/build.cmake | 7 ++++++- tools/cmakev2/project.cmake | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index ab6d71ed0de..06c441a5db6 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -83,7 +83,12 @@ function(idf_build_get_property variable property) cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN}) if("${property}" STREQUAL BUILD_COMPONENTS) - idf_die("Build property 'BUILD_COMPONENTS' is not supported") + # BUILD_COMPONENTS is populated by the Build system v1 compatibility + # shim; reject only when running as a native Build system v2 project. + idf_build_get_property(_v1_compat __V1_COMPAT_SHIM) + if(NOT _v1_compat) + idf_die("Build property 'BUILD_COMPONENTS' is not supported") + endif() endif() set(genexpr) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index bf63ddd2eaf..ae7816b9ce5 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -755,6 +755,40 @@ function(__project_default) COMPONENTS ${root_components} MAPFILE_TARGET "${executable}_mapfile") + # In Build system v1, the ``project_elf`` variable (set to + # "${project_name}.elf") is used by several app CMakeLists.txt files to add + # linker flags after project(). In Build system v2 the executable target + # is just "${executable}" (the .elf suffix is an output property, not part + # of the target name). Propagate it to the caller's scope so existing + # code keeps working. + set(project_elf "${executable}" PARENT_SCOPE) + + # Provide Build system v1-compatible build properties so that existing + # test apps and project CMakeLists.txt files that query EXECUTABLE or + # BUILD_COMPONENTS continue to work when built through the shim. + idf_build_get_property(v1_compat __V1_COMPAT_SHIM) + if(v1_compat) + idf_build_set_property(EXECUTABLE "${executable}") + idf_build_set_property(EXECUTABLE_NAME "${executable}") + + # BUILD_COMPONENTS: in Build system v1 this is the set of components + # actually processed during the build (COMPONENTS + their transitive + # REQUIRES within the restricted scope). Use LIBRARY_COMPONENTS_LINKED + # from the Build system v2 library target which reflects the actual + # list of components linked, plus the architecture component which + # Build system v1 always includes but Build system v2 does not track + # as a linked library. + get_target_property(library ${executable} LIBRARY_INTERFACE) + if(library) + idf_library_get_property(linked_components "${library}" LIBRARY_COMPONENTS_LINKED) + idf_build_get_property(target_arch IDF_TARGET_ARCH) + if(target_arch AND NOT target_arch IN_LIST linked_components) + list(APPEND linked_components ${target_arch}) + endif() + idf_build_set_property(BUILD_COMPONENTS "${linked_components}") + endif() + endif() + if(CONFIG_APP_BUILD_GENERATE_BINARIES AND TARGET idf::esptool_py) # Is it possible to have a configuration where # CONFIG_APP_BUILD_GENERATE_BINARIES is not set? From e7dca1a3f86a7383aeac3de43b5909fe5e7a69f8 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 17 Apr 2026 08:36:11 +0200 Subject: [PATCH 04/12] fix(cmakev2): integrate manager-injected dependencies with idf_component_register Move managed-dependency injection and recursive inclusion to BEFORE the component's add_subdirectory() call, so its CMakeLists.txt can resolve managed-dep targets at register time. Inside idf_component_register, union the manager-injected REQUIRES/PRIV_REQUIRES with what the component author wrote instead of overwriting them. --- tools/cmakev2/compat.cmake | 26 +++++++++++++++++++++++-- tools/cmakev2/component.cmake | 36 ++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index cfac2f8bdc6..aad749ca9cb 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -702,8 +702,30 @@ function(idf_component_register) # Embedded files are managed in the idf_component_include function. idf_component_set_property("${COMPONENT_NAME}" EMBED_FILES "${ARG_EMBED_FILES}") idf_component_set_property("${COMPONENT_NAME}" EMBED_TXTFILES "${ARG_EMBED_TXTFILES}") - idf_component_set_property("${COMPONENT_NAME}" REQUIRES "${ARG_REQUIRES}") - idf_component_set_property("${COMPONENT_NAME}" PRIV_REQUIRES "${ARG_PRIV_REQUIRES}") + # The component manager's dependency injection runs before this component's + # CMakeLists.txt is evaluated (see idf_component_include() in + # component.cmake), and writes the manifest-derived names into both the + # MANAGED_* properties and the regular REQUIRES / PRIV_REQUIRES properties. + # The latter must not be overwritten by the component's own register call, + # else the manifest-derived transitive deps disappear from the build. Union + # the two so the persisted property is what the component author wrote + # PLUS what the manifest contributed. + idf_component_get_property(__existing_requires "${COMPONENT_NAME}" REQUIRES) + idf_component_get_property(__existing_priv_requires "${COMPONENT_NAME}" PRIV_REQUIRES) + set(__merged_requires "${ARG_REQUIRES}") + set(__merged_priv_requires "${ARG_PRIV_REQUIRES}") + foreach(__r IN LISTS __existing_requires) + if(__r AND NOT __r IN_LIST __merged_requires) + list(APPEND __merged_requires "${__r}") + endif() + endforeach() + foreach(__r IN LISTS __existing_priv_requires) + if(__r AND NOT __r IN_LIST __merged_priv_requires) + list(APPEND __merged_priv_requires "${__r}") + endif() + endforeach() + idf_component_set_property("${COMPONENT_NAME}" REQUIRES "${__merged_requires}") + idf_component_set_property("${COMPONENT_NAME}" PRIV_REQUIRES "${__merged_priv_requires}") idf_component_set_property("${COMPONENT_NAME}" REQUIRED_IDF_TARGETS "${ARG_REQUIRED_IDF_TARGETS}") idf_component_set_property("${COMPONENT_NAME}" COMPONENT_TYPE "${component_type}") endfunction() diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index 8a07d0a91d6..da557c55123 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -891,6 +891,29 @@ function(idf_component_include name) endif() list(APPEND __DEPENDENCY_CHAIN "${component_interface}") + + # Inject managed dependencies BEFORE add_subdirectory() evaluates the + # component's CMakeLists.txt — otherwise any component that queries a + # managed dependency at register time via + # idf_component_get_property(... COMPONENT_LIB) finds nothing because the + # target hasn't been created yet. + idf_build_get_property(idf_component_manager IDF_COMPONENT_MANAGER) + if(idf_component_manager EQUAL 1) + idf_component_get_property(_comp_dir "${component_name}" COMPONENT_DIR) + if(EXISTS "${_comp_dir}/idf_component.yml") + __inject_requirements_for_component_from_manager("${component_name}") + + idf_component_get_property(_managed_req "${component_name}" MANAGED_REQUIRES) + idf_component_get_property(_managed_priv_req "${component_name}" MANAGED_PRIV_REQUIRES) + + foreach(_dep IN LISTS _managed_req _managed_priv_req) + if(_dep) + idf_component_include("${_dep}") + endif() + endforeach() + endif() + endif() + # Evaluate the CMakeLists.txt file of the component. idf_component_get_property(component_build_dir "${component_name}" COMPONENT_BUILD_DIR) add_subdirectory("${component_directory}" "${component_build_dir}") @@ -977,25 +1000,16 @@ function(idf_component_include name) target_add_binary_data(${COMPONENT_TARGET} "${file}" "TEXT") endforeach() - # Inject managed dependencies if component manager is enabled + # Link managed dependencies for cmakev1 components (backward compat). + # The injection and inclusion already happened before add_subdirectory(). idf_build_get_property(idf_component_manager IDF_COMPONENT_MANAGER) idf_component_get_property(component_format "${component_name}" COMPONENT_FORMAT) if(idf_component_manager EQUAL 1) idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR) - # Check if component has manifest for managed dependency injection if(EXISTS "${component_dir}/idf_component.yml") - __inject_requirements_for_component_from_manager("${component_name}") - - # Include any managed dependencies idf_component_get_property(managed_requires "${component_name}" MANAGED_REQUIRES) idf_component_get_property(managed_priv_requires "${component_name}" MANAGED_PRIV_REQUIRES) - foreach(dep IN LISTS managed_requires managed_priv_requires) - if(dep) - idf_component_include("${dep}") - endif() - endforeach() - # For cmakev1 components, automatically link managed dependencies to maintain # backward compatibility. if("${component_format}" STREQUAL "CMAKEV1") From d2446422d473ade2139b2d77fcbbe004ace6ec53 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 17 Apr 2026 08:38:37 +0200 Subject: [PATCH 05/12] fix(cmakev2): respect explicit SET_COMPILER_OPTIMIZATION values Use a DEFINED check instead of a truthy check so explicit NO/OFF/FALSE values are honored. --- tools/cmakev2/project.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index ae7816b9ce5..432f4282661 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -190,7 +190,7 @@ function(__init_project_configuration) # 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) + if(NOT DEFINED set_compiler_optimization OR set_compiler_optimization STREQUAL "") set(set_compiler_optimization YES) endif() if(set_compiler_optimization) From 4825985844d9d1ab0fe006308e2384902be9d16d Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 15 Apr 2026 11:56:14 +0200 Subject: [PATCH 06/12] feat(cmakev2/compat): add idf_component_mock() for CMock-based test components --- tools/cmakev2/compat.cmake | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index aad749ca9cb..fdc8df19309 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -452,6 +452,89 @@ function(__init_common_components) idf_build_set_property(__COMMON_COMPONENTS_INITIALIZED YES) endfunction() +#[[api +.. cmakev2:function:: idf_component_mock + + .. code-block:: cmake + + idf_component_mock([INCLUDE_DIRS ...] + [MOCK_HEADER_FILES ...] + [REQUIRES ...] + [MOCK_SUBDIR ]) + + *INCLUDE_DIRS[in,opt]* + + Include directories for the header files provided in MOCK_HEADER_FILES. + + *MOCK_HEADER_FILES[in,opt]* + + Header files from which CMock generates mock implementations. + + *REQUIRES[in,opt]* + + Additional components required by the mock component. + + *MOCK_SUBDIR[in,opt]* + + Subdirectory under the mocks output where generated files are placed. + + Create a mock component using CMock and register it with the build system. + This is a compatibility shim matching the v1 ``idf_component_mock`` + function in ``tools/cmake/component.cmake``. It generates Mock*.c and + Mock*.h files from the given headers using Ruby + CMock, then delegates to + ``idf_component_register`` so the mock participates in normal dependency + resolution. +#]] +function(idf_component_mock) + set(options) + set(single_value MOCK_SUBDIR) + set(multi_value MOCK_HEADER_FILES INCLUDE_DIRS REQUIRES) + cmake_parse_arguments(_ "${options}" "${single_value}" "${multi_value}" ${ARGN}) + + list(APPEND __REQUIRES "cmock") + + set(MOCK_GENERATED_HEADERS "") + set(MOCK_GENERATED_SRCS "") + set(IDF_PATH $ENV{IDF_PATH}) + set(CMOCK_DIR "${IDF_PATH}/components/cmock/CMock") + set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}") + list(APPEND __INCLUDE_DIRS "${MOCK_GEN_DIR}/mocks") + + foreach(header_file ${__MOCK_HEADER_FILES}) + get_filename_component(file_without_dir ${header_file} NAME_WE) + if("${__MOCK_SUBDIR}" STREQUAL "") + list(APPEND MOCK_GENERATED_HEADERS "${MOCK_GEN_DIR}/mocks/Mock${file_without_dir}.h") + list(APPEND MOCK_GENERATED_SRCS "${MOCK_GEN_DIR}/mocks/Mock${file_without_dir}.c") + else() + list(APPEND MOCK_GENERATED_HEADERS "${MOCK_GEN_DIR}/mocks/${__MOCK_SUBDIR}/Mock${file_without_dir}.h") + list(APPEND MOCK_GENERATED_SRCS "${MOCK_GEN_DIR}/mocks/${__MOCK_SUBDIR}/Mock${file_without_dir}.c") + endif() + endforeach() + + file(MAKE_DIRECTORY "${MOCK_GEN_DIR}/mocks") + + idf_component_register(SRCS "${MOCK_GENERATED_SRCS}" + INCLUDE_DIRS ${__INCLUDE_DIRS} + REQUIRES ${__REQUIRES}) + + set(COMPONENT_LIB ${COMPONENT_LIB} PARENT_SCOPE) + add_custom_command( + OUTPUT ruby_found SYMBOLIC + COMMAND "ruby" "-v" + COMMENT "Try to find ruby. If this fails, you need to install ruby" + ) + + add_custom_command( + OUTPUT ${MOCK_GENERATED_SRCS} ${MOCK_GENERATED_HEADERS} + DEPENDS ruby_found + COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity" + ruby + ${CMOCK_DIR}/lib/cmock.rb + -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml + ${__MOCK_HEADER_FILES} + ) +endfunction() + #[[api .. cmakev2:function:: idf_component_register From e23b1f585f58f3401f76bec63d910dee062300f9 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 29 May 2026 17:10:17 +0200 Subject: [PATCH 07/12] fix(cmakev2): guard flash targets when esptool_py is excluded Add a COMMAND check on esptool_py_flash_target before invoking it in __init_project_flash_targets. Apps that restrict the build set without esptool_py would otherwise hit "Unknown CMake command". --- tools/cmakev2/project.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index 432f4282661..03e3fbcb351 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -533,7 +533,9 @@ endfunction() esptool_py component. #]] function(__init_project_flash_targets) - if(CONFIG_APP_BUILD_GENERATE_BINARIES) + # Skip if esptool_py is not in the build set; the flash-target function + # would otherwise be undefined. + if(CONFIG_APP_BUILD_GENERATE_BINARIES AND COMMAND esptool_py_flash_target) idf_component_get_property(main_args esptool_py FLASH_ARGS) idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) esptool_py_flash_target(flash "${main_args}" "${sub_args}") From 201b2c61ed3e24d8e8656029cd8e6b3936e6b07a Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 18 May 2026 13:38:26 +0200 Subject: [PATCH 08/12] fix(cmakev2): mirror sdkconfig CONFIG_* onto build properties Read sdkconfig.cmake's CONFIG_* values and re-publish them on the build-properties target so component CMakeLists.txt code that queries Kconfig via idf_build_get_property(var CONFIG_FOO) returns the expected value. --- tools/cmakev2/compat.cmake | 24 ++++++++++++++++++++++++ tools/cmakev2/kconfig.cmake | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index fdc8df19309..8b3e80f6638 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -922,3 +922,27 @@ function(idf_build_component component_dir) PREFIX "${component_prefix}" SOURCE "${component_source}") endfunction() + +#[[ + __import_sdkconfig_as_build_properties() + + Mirror every CONFIG_* symbol from the generated sdkconfig.cmake onto the + build-properties target so that the + `idf_build_get_property(var CONFIG_FOO)` idiom keeps working under the + compatibility shim. Called from kconfig.cmake::__generate_sdkconfig after + each kconfgen run. +#]] +function(__import_sdkconfig_as_build_properties) + idf_build_get_property(sdkconfig_cmake __SDKCONFIG_CMAKE) + if(NOT sdkconfig_cmake OR NOT EXISTS "${sdkconfig_cmake}") + return() + endif() + include("${sdkconfig_cmake}") + if(NOT DEFINED CONFIGS_LIST) + return() + endif() + idf_build_set_property(__CONFIG_VARIABLES "${CONFIGS_LIST}") + foreach(config IN LISTS CONFIGS_LIST) + idf_build_set_property("${config}" "${${config}}") + endforeach() +endfunction() diff --git a/tools/cmakev2/kconfig.cmake b/tools/cmakev2/kconfig.cmake index a5b219bbced..37b3fad896c 100644 --- a/tools/cmakev2/kconfig.cmake +++ b/tools/cmakev2/kconfig.cmake @@ -208,6 +208,10 @@ function(__generate_sdkconfig) # Generate Kconfig outputs __generate_kconfig_outputs() + # Mirror CONFIG_* values onto build properties so + # `idf_build_get_property(var CONFIG_FOO)` keeps working. + __import_sdkconfig_as_build_properties() + idf_msg("Generated sdkconfig configuration") endfunction() From 7d8b791fff76f6897803650babf86d54b2555722 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 18 May 2026 13:41:01 +0200 Subject: [PATCH 09/12] fix(cmakev2/compat): auto-append IDF_TARGET_ARCH to overridden common-requires When an app sets __COMPONENT_REQUIRES_COMMON before project(), the explicit value used to bypass the default branch that includes ${idf_target_arch}, dropping the arch component from the build. IDF_TARGET_ARCH isn't known pre-project(), so apps shouldn't have to hand-roll a target -> arch map. Append it after the explicit list during common-component initialization; empty on linux means no append. --- tools/cmakev2/compat.cmake | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/cmakev2/compat.cmake b/tools/cmakev2/compat.cmake index 8b3e80f6638..c6100ab9dfb 100644 --- a/tools/cmakev2/compat.cmake +++ b/tools/cmakev2/compat.cmake @@ -425,6 +425,14 @@ function(__init_common_components) # component. if(explicit_requires_common) set(requires_common "${explicit_requires_common}") + # Auto-append the target's arch component when the app overrides + # common-requires without it. Lets G0-style apps (which set the property + # before project() to a restricted list) avoid hand-rolling a + # target -> arch map pre-project, since IDF_TARGET_ARCH isn't known + # until after sdkconfig is included. Empty on linux -> no append. + if(idf_target_arch AND NOT idf_target_arch IN_LIST requires_common) + list(APPEND requires_common "${idf_target_arch}") + endif() elseif("${idf_target}" STREQUAL "linux") set(requires_common freertos esp_hw_support heap log soc hal esp_rom esp_common esp_system linux From 14e899410f3349eba5bf50cb8287c63dc71fe0c7 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 20 May 2026 20:42:44 +0200 Subject: [PATCH 10/12] fix(test_apps/rtc_custom_section): declare bootloader_support as a private requirement of main --- .../test_apps/rtc_custom_section/main/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/bootloader_support/test_apps/rtc_custom_section/main/CMakeLists.txt b/components/bootloader_support/test_apps/rtc_custom_section/main/CMakeLists.txt index 1df31fac804..b8928fdeb7b 100644 --- a/components/bootloader_support/test_apps/rtc_custom_section/main/CMakeLists.txt +++ b/components/bootloader_support/test_apps/rtc_custom_section/main/CMakeLists.txt @@ -1,2 +1,3 @@ idf_component_register(SRCS "test_main.c" - INCLUDE_DIRS ".") + INCLUDE_DIRS "." + PRIV_REQUIRES bootloader_support) From f55b23fd98148ea9d17bf510bbb7906cb476539f Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Fri, 29 May 2026 17:15:23 +0200 Subject: [PATCH 11/12] ci(bootloader): silence expected SECURE_BOOT_SIGNING_KEY CMake warning Reference SECURE_BOOT_SIGNING_KEY in the bootloader subproject so CMake does not warn about a manually-specified-but-unused variable when the sign-key consumers are in config-gated blocks. Ports the same fix the v1 subproject CMakeLists already has at line 119-120. --- components/bootloader/subproject/CMakeLists_v2.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/bootloader/subproject/CMakeLists_v2.txt b/components/bootloader/subproject/CMakeLists_v2.txt index 231f315579b..50bda3faa2c 100644 --- a/components/bootloader/subproject/CMakeLists_v2.txt +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -40,6 +40,9 @@ include("${IDF_PATH}/tools/cmakev2/idf.cmake") project(bootloader C CXX ASM) +# Suppress warning: "Manually-specified variables were not used by the project: SECURE_BOOT_SIGNING_KEY" +set(_ignore_signing_key "${SECURE_BOOT_SIGNING_KEY}") + # --------------------------------------------------------------------------- # Bootloader-specific build properties set before project initialization # --------------------------------------------------------------------------- From 19d69b61eb03bd6229dfed0a7c8895a3dfdaeb7b Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Tue, 2 Jun 2026 10:13:30 +0200 Subject: [PATCH 12/12] fix(cmakev2): wrap library archives with --start-group/--end-group Attach the flags as the first and last entries of the library's INTERFACE_LINK_LIBRARIES so the linker re-scans archives until all symbols resolve. --- tools/cmakev2/build.cmake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/cmakev2/build.cmake b/tools/cmakev2/build.cmake index 06c441a5db6..15ed609b487 100644 --- a/tools/cmakev2/build.cmake +++ b/tools/cmakev2/build.cmake @@ -361,11 +361,25 @@ function(idf_build_library library) # Include the requested components and link their interface targets to the # library. + # + # On the GNU linker, bracket the component interface list with + # --start-group/--end-group so the linker re-scans archives until all + # symbols resolve. Required when the archive defining a __wrap_* + # implementation is encountered after the archive consuming the wrapped + # symbol on the link line; without group iteration the wrap symbol is + # unresolved. + idf_build_get_property(linker_type LINKER_TYPE) + if("${linker_type}" STREQUAL "GNU") + target_link_libraries("${library}" INTERFACE "-Wl,--start-group") + endif() foreach(component_name IN LISTS ARG_COMPONENTS) idf_component_include("${component_name}") idf_component_get_property(component_interface "${component_name}" COMPONENT_INTERFACE) target_link_libraries("${library}" INTERFACE "${component_interface}") endforeach() + if("${linker_type}" STREQUAL "GNU") + target_link_libraries("${library}" INTERFACE "-Wl,--end-group") + endif() # Process optional requirements in DEFERRED mode only (no-op in IMMEDIATE or when unset). idf_build_get_property(opt_req_mode IDF_COMPONENT_OPTIONAL_REQUIRES_MODE)