From 83cdcc1da308e64348256a4292396add742f7d8a Mon Sep 17 00:00:00 2001 From: Renz Bagaporo Date: Tue, 21 Jul 2026 10:37:50 +0900 Subject: [PATCH] fix(ulp): use builtin v2 project for legacy embed Route ulp_embed_binary() through a built-in CMake v2 default ULP project instead of generating a compatibility project with a synthetic main component. Attach legacy ULP sources directly to the ULP executable, pass the parent component include view through the existing ULP_S_SOURCES/COMPONENT_* channel, and remove the generated legacy project templates plus the component path ownership validation bypass they required. Update the build-system coverage to assert that the builtin child project builds and rebuilds when parent-owned ULP sources change. --- components/ulp/cmake/CMakeLists.txt | 5 + components/ulp/cmake/CMakeLists_v2.txt | 52 ++++++++ .../cmake/legacy_project/CMakeLists.txt.in | 10 -- .../legacy_project/main/CMakeLists.txt.in | 17 --- .../main/ulp_legacy_inputs.cmake.in | 16 --- components/ulp/project_include.cmake | 123 +++--------------- tools/cmakev2/component_validation.cmake | 5 - tools/test_build_system/buildv2/test_ulp.py | 31 +++-- 8 files changed, 94 insertions(+), 165 deletions(-) create mode 100644 components/ulp/cmake/CMakeLists_v2.txt delete mode 100644 components/ulp/cmake/legacy_project/CMakeLists.txt.in delete mode 100644 components/ulp/cmake/legacy_project/main/CMakeLists.txt.in delete mode 100644 components/ulp/cmake/legacy_project/main/ulp_legacy_inputs.cmake.in diff --git a/components/ulp/cmake/CMakeLists.txt b/components/ulp/cmake/CMakeLists.txt index b89b2dfbb8a..34d60cf1a73 100644 --- a/components/ulp/cmake/CMakeLists.txt +++ b/components/ulp/cmake/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.22) +if(IDF_BUILD_V2) + include(${CMAKE_CURRENT_LIST_DIR}/CMakeLists_v2.txt) + return() +endif() + include(${IDF_PATH}/tools/cmake/idf.cmake) project(${ULP_APP_NAME}) add_executable(${ULP_APP_NAME}) diff --git a/components/ulp/cmake/CMakeLists_v2.txt b/components/ulp/cmake/CMakeLists_v2.txt new file mode 100644 index 00000000000..81fbe209f83 --- /dev/null +++ b/components/ulp/cmake/CMakeLists_v2.txt @@ -0,0 +1,52 @@ +# CMake v2 default ULP project for ulp_embed_binary() callers. The ULP runtime +# is built from the ulp component graph, and the legacy source list is attached +# directly to the executable, matching the CMake v1 link semantics. +include(${CMAKE_CURRENT_LIST_DIR}/ulp_project.cmake) + +if("${ULP_TYPE}" STREQUAL "fsm") + project(${ULP_APP_NAME} ASM) +else() + project(${ULP_APP_NAME} C CXX ASM) +endif() + +ulp_project_init() + +# A single executable is built, so DEFERRED optional-requires resolution is +# safe and keeps the linked component set minimal. +idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED) + +set(executable_args COMPONENTS ulp) +if(NOT BUILD_FSM) + list(APPEND executable_args MAPFILE_TARGET ${ULP_APP_NAME}_mapfile) +endif() +ulp_build_executable(${ULP_APP_NAME} ${executable_args}) + +target_sources(${ULP_APP_NAME} PRIVATE ${ULP_S_SOURCES}) +if(ULP_PARENT_SDKCONFIG_HEADER) + # Legacy ulp_embed_binary() sources are authored against the parent app's + # sdkconfig, including project-local Kconfig.projbuild symbols. + get_filename_component(parent_sdkconfig_dir "${ULP_PARENT_SDKCONFIG_HEADER}" DIRECTORY) + target_include_directories(${ULP_APP_NAME} BEFORE PRIVATE "${parent_sdkconfig_dir}") +endif() +if(COMPONENT_INCLUDES) + target_include_directories(${ULP_APP_NAME} PRIVATE ${COMPONENT_INCLUDES}) +endif() +# CMake v1 exposed COMPONENT_DIR only to the FSM assembly preprocessing, not to +# the compilation of ULP C sources. The v2 FSM toolchain preprocesses inside +# the compile rule, which takes its include flags from the target, so keep the +# directory on the target for FSM builds only. +if(BUILD_FSM AND COMPONENT_DIR) + target_include_directories(${ULP_APP_NAME} PRIVATE ${COMPONENT_DIR}) +endif() + +if(DEFINED ULP_VAR_PREFIX AND NOT "${ULP_VAR_PREFIX}" STREQUAL "") + ulp_build_binary(${ULP_APP_NAME} PREFIX ${ULP_VAR_PREFIX}) +else() + ulp_build_binary(${ULP_APP_NAME}) +endif() + +idf_build_generate_metadata(EXECUTABLE ${ULP_APP_NAME}) +if(TARGET "${ULP_APP_NAME}_mapfile") + idf_create_size_report("${ULP_APP_NAME}_mapfile" TARGET size) +endif() +idf_build_generate_depgraph("${ULP_APP_NAME}") diff --git a/components/ulp/cmake/legacy_project/CMakeLists.txt.in b/components/ulp/cmake/legacy_project/CMakeLists.txt.in deleted file mode 100644 index 5513a51459f..00000000000 --- a/components/ulp/cmake/legacy_project/CMakeLists.txt.in +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.22) - -include(${IDF_PATH}/components/ulp/cmake/ulp_project.cmake) -if("${ULP_TYPE}" STREQUAL "fsm") - project(${IDF_DEFAULT_PROJECT_NAME} ASM) -else() - project(${IDF_DEFAULT_PROJECT_NAME} C CXX ASM) -endif() - -ulp_project_default() diff --git a/components/ulp/cmake/legacy_project/main/CMakeLists.txt.in b/components/ulp/cmake/legacy_project/main/CMakeLists.txt.in deleted file mode 100644 index 07df636601b..00000000000 --- a/components/ulp/cmake/legacy_project/main/CMakeLists.txt.in +++ /dev/null @@ -1,17 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/ulp_legacy_inputs.cmake) - -if(ULP_LEGACY_PARENT_SDKCONFIG_HEADER) - add_compile_options(-include "${ULP_LEGACY_PARENT_SDKCONFIG_HEADER}") -endif() - -idf_component_register(SRCS ${ULP_LEGACY_SOURCES} - INCLUDE_DIRS ${ULP_LEGACY_INCLUDE_DIRS} - REQUIRES ulp - # Match CMake v1-compatible idf_component_register(WHOLE_ARCHIVE) - # semantics used by existing ULP test/example components. - WHOLE_ARCHIVE) -# This generated compatibility component intentionally uses parent app sources -# and include directories. Skip __component_validation_check_sources() and -# __component_validation_check_include_dirs(), which validate native CMake v2 -# component ownership in tools/cmakev2/component_validation.cmake. -idf_component_set_property("${COMPONENT_NAME}" __SKIP_COMPONENT_PATH_OWNERSHIP_VALIDATION YES) diff --git a/components/ulp/cmake/legacy_project/main/ulp_legacy_inputs.cmake.in b/components/ulp/cmake/legacy_project/main/ulp_legacy_inputs.cmake.in deleted file mode 100644 index 646532d88a4..00000000000 --- a/components/ulp/cmake/legacy_project/main/ulp_legacy_inputs.cmake.in +++ /dev/null @@ -1,16 +0,0 @@ -set(ULP_LEGACY_SOURCES -@ULP_LEGACY_SOURCES@ -) - -set(ULP_LEGACY_INCLUDE_DIR_CANDIDATES -@ULP_LEGACY_INCLUDE_DIR_CANDIDATES@ -) - -set(ULP_LEGACY_PARENT_SDKCONFIG_HEADER [=[@ULP_LEGACY_PARENT_SDKCONFIG_HEADER@]=]) - -set(ULP_LEGACY_INCLUDE_DIRS) -foreach(include IN LISTS ULP_LEGACY_INCLUDE_DIR_CANDIDATES) - if(IS_DIRECTORY "${include}") - list(APPEND ULP_LEGACY_INCLUDE_DIRS "${include}") - endif() -endforeach() diff --git a/components/ulp/project_include.cmake b/components/ulp/project_include.cmake index 4d2b0612111..4a0b02a0566 100644 --- a/components/ulp/project_include.cmake +++ b/components/ulp/project_include.cmake @@ -6,88 +6,8 @@ endif() # # Create ULP binary and embed into the application. -# Generate a minimal CMake v2 ULP project in the parent build directory for -# legacy ulp_embed_binary() callers. This lets the v2 ULP component graph build -# legacy source lists without writing into the application source tree. -function(__ulp_write_legacy_project project_dir sources component_dir) - set(template_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake/legacy_project") - set(main_dir "${project_dir}/main") - file(MAKE_DIRECTORY "${main_dir}") - - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS - "${template_dir}/CMakeLists.txt.in" - "${template_dir}/main/CMakeLists.txt.in" - "${template_dir}/main/ulp_legacy_inputs.cmake.in") - - file(GENERATE OUTPUT "${project_dir}/CMakeLists.txt" - INPUT "${template_dir}/CMakeLists.txt.in") - file(GENERATE OUTPUT "${main_dir}/CMakeLists.txt" - INPUT "${template_dir}/main/CMakeLists.txt.in") - - set(ULP_LEGACY_SOURCES) - foreach(source IN LISTS sources) - string(APPEND ULP_LEGACY_SOURCES " [=[${source}]=]\n") - endforeach() - - set(ULP_LEGACY_INCLUDE_DIR_CANDIDATES) - if(DEFINED COMPONENT_NAME) - set(legacy_include_components "${COMPONENT_NAME}") - foreach(require_property IN ITEMS REQUIRES PRIV_REQUIRES) - idf_component_get_property(component_requires "${COMPONENT_NAME}" ${require_property}) - list(APPEND legacy_include_components ${component_requires}) - endforeach() - list(REMOVE_DUPLICATES legacy_include_components) - - foreach(component IN LISTS legacy_include_components) - if(component STREQUAL "ulp") - continue() - endif() - idf_component_get_property(include_component_dir "${component}" COMPONENT_DIR) - set(include_properties INCLUDE_DIRS) - if(component STREQUAL COMPONENT_NAME) - list(APPEND include_properties PRIV_INCLUDE_DIRS) - endif() - foreach(include_property IN LISTS include_properties) - idf_component_get_property(component_includes "${component}" ${include_property}) - foreach(include IN LISTS component_includes) - if(IS_ABSOLUTE "${include}") - string(APPEND ULP_LEGACY_INCLUDE_DIR_CANDIDATES " [=[${include}]=]\n") - elseif(include_component_dir) - string(APPEND ULP_LEGACY_INCLUDE_DIR_CANDIDATES - " [=[${include_component_dir}/${include}]=]\n") - endif() - endforeach() - endforeach() - endforeach() - endif() - if(component_dir) - string(APPEND ULP_LEGACY_INCLUDE_DIR_CANDIDATES " [=[${component_dir}]=]\n") - endif() - - set(ULP_LEGACY_PARENT_SDKCONFIG_HEADER) - if(SDKCONFIG_HEADER AND EXISTS "${SDKCONFIG_HEADER}") - set(ULP_LEGACY_PARENT_SDKCONFIG_HEADER "${main_dir}/ulp_parent_sdkconfig.h") - file(STRINGS "${SDKCONFIG_HEADER}" parent_sdkconfig_lines) - set(parent_sdkconfig_content - "/* Generated from the parent project sdkconfig for legacy ULP sources. */\n") - foreach(parent_sdkconfig_line IN LISTS parent_sdkconfig_lines) - if(parent_sdkconfig_line MATCHES "^#define CONFIG_IDF_TOOLCHAIN(_[A-Z]+)?[ \t]") - continue() - endif() - string(APPEND parent_sdkconfig_content "${parent_sdkconfig_line}\n") - endforeach() - file(GENERATE OUTPUT "${ULP_LEGACY_PARENT_SDKCONFIG_HEADER}" - CONTENT "${parent_sdkconfig_content}") - endif() - - file(READ "${template_dir}/main/ulp_legacy_inputs.cmake.in" inputs_template) - string(CONFIGURE "${inputs_template}" inputs_content @ONLY) - file(GENERATE OUTPUT "${main_dir}/ulp_legacy_inputs.cmake" - CONTENT "${inputs_content}") -endfunction() - function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name type binary_names - legacy_project s_sources exp_dep_srcs) + s_sources exp_dep_srcs) if(NOT CMAKE_BUILD_EARLY_EXPANSION) set(ulp_cmake_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake") @@ -218,6 +138,7 @@ function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name -D__ULP_BUILD=1 -DIDF_PARENT_BUILD_DIR=${build_dir} -DULP_PREFIX_APPEND_BIN_NAME=${prefix_append_bin_name} + -DULP_PARENT_SDKCONFIG_HEADER=${SDKCONFIG_HEADER} -DSDKCONFIG=${sdkconfig} -DSDKCONFIG_HEADER=${SDKCONFIG_HEADER} -DSDKCONFIG_CMAKE=${SDKCONFIG_CMAKE} @@ -229,27 +150,21 @@ function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name # share more of the normal IDF app toolchain response-file # setup, but keep them under IDF_CUSTOM_TOOLCHAIN for now. -DIDF_CUSTOM_TOOLCHAIN=1) - if(legacy_project) - set(generated_project_dir "${ulp_binary_dir}/legacy_project") - __ulp_write_legacy_project("${generated_project_dir}" "${sources}" "${COMPONENT_DIR}") - set(project_path "${generated_project_dir}") - elseif(s_sources) + if(s_sources) list(APPEND ulp_project_args -DULP_S_SOURCES=$) endif() - if(NOT legacy_project) - if(DEFINED COMPONENT_TARGET AND TARGET ${COMPONENT_TARGET}) - # Backward compatibility for existing ULP subprojects that - # include headers from the parent component. New full - # subprojects should declare their own component include - # directories and dependencies instead. - list(APPEND ulp_project_args - -DCOMPONENT_INCLUDES=$) - endif() - if(COMPONENT_DIR) - list(APPEND ulp_project_args - -DCOMPONENT_DIR=${COMPONENT_DIR}) - endif() + if(DEFINED COMPONENT_TARGET AND TARGET ${COMPONENT_TARGET}) + # Backward compatibility for existing ULP subprojects that + # include headers from the parent component. New full + # subprojects should declare their own component include + # directories and dependencies instead. + list(APPEND ulp_project_args + -DCOMPONENT_INCLUDES=$) + endif() + if(COMPONENT_DIR) + list(APPEND ulp_project_args + -DCOMPONENT_DIR=${COMPONENT_DIR}) endif() else() list(APPEND ulp_project_args @@ -300,10 +215,8 @@ function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name endforeach() endif() - # Only the CMake v1 path reads this property when passing ULP_S_SOURCES - # to the child project. Leaving it set for v2 is harmless and keeps the - # post-ExternalProject artifact wiring common. Can be dropped when - # v1 support is dropped. + # Both build system versions pass ULP_S_SOURCES to the child project + # through this property; see the generator expression above. set_property(TARGET ${app_name} PROPERTY ULP_SOURCES "${sources}") spaces2list(exp_dep_srcs) @@ -377,7 +290,7 @@ function(ulp_embed_binary app_name s_sources exp_dep_srcs) __setup_ulp_project("${app_name}" "${ulp_cmake_dir}" "${ULP_PREFIX}" FALSE "${ULP_TYPE}" "${app_name}" - TRUE "${s_sources}" "${exp_dep_srcs}") + "${s_sources}" "${exp_dep_srcs}") endfunction() function(ulp_add_project app_name project_path) @@ -402,5 +315,5 @@ function(ulp_add_project app_name project_path) endif() __setup_ulp_project("${app_name}" "${project_path}" "${ULP_PREFIX}" - "${ULP_PREFIX_APPEND_BIN_NAME}" "${ULP_TYPE}" "${ULP_BINARIES}" FALSE "" "") + "${ULP_PREFIX_APPEND_BIN_NAME}" "${ULP_TYPE}" "${ULP_BINARIES}" "" "") endfunction() diff --git a/tools/cmakev2/component_validation.cmake b/tools/cmakev2/component_validation.cmake index b98d9b4b3e2..436be2e9ec7 100644 --- a/tools/cmakev2/component_validation.cmake +++ b/tools/cmakev2/component_validation.cmake @@ -170,11 +170,6 @@ function(__component_validation_run_checks) # Run validation checks for each component foreach(component_interface ${component_interfaces}) - __idf_component_get_property_unchecked(skip_path_ownership_validation ${component_interface} - __SKIP_COMPONENT_PATH_OWNERSHIP_VALIDATION) - if(skip_path_ownership_validation) - continue() - endif() __component_validation_check_sources(${component_interface}) __component_validation_check_include_dirs(${component_interface}) endforeach() diff --git a/tools/test_build_system/buildv2/test_ulp.py b/tools/test_build_system/buildv2/test_ulp.py index 6d01da2e583..d63e44047b9 100644 --- a/tools/test_build_system/buildv2/test_ulp.py +++ b/tools/test_build_system/buildv2/test_ulp.py @@ -108,9 +108,9 @@ def test_ulp_cmake_api_availability( expected_available: tuple[str, ...], expected_unavailable: tuple[str, ...], ) -> None: - # Full ULP subprojects and generated legacy ULP child projects intentionally - # expose different CMake API surfaces. Configure a minimal child project - # through each entry point and verify that only the expected commands exist. + # Full ULP subprojects and legacy ULP child projects intentionally expose + # different CMake API surfaces. Configure a minimal child project through + # each entry point and verify that only the expected commands exist. idf_path = Path(os.environ['IDF_PATH']) project_dir = tmp_path / 'project' build_dir = tmp_path / 'build' @@ -156,22 +156,19 @@ def test_ulp_cmake_api_availability( @pytest.mark.test_app_copy('components/ulp/test_apps/ulp_fsm') -def test_ulp_embed_binary_uses_generated_legacy_project(idf_py: IdfPyFunc, test_app_copy: Path) -> None: - # Legacy ulp_embed_binary() callers should be built through a generated - # CMake v2 child project. The generated project references parent-owned ULP +def test_ulp_embed_binary_builds_v2_child_project(idf_py: IdfPyFunc, test_app_copy: Path) -> None: + # Legacy ulp_embed_binary() callers are built through the builtin ULP child + # project as a CMake v2 subproject. The child references parent-owned ULP # sources by path, so editing those sources must still rebuild the child # artifact on the next parent build. assert test_app_copy.exists() idf_py('-DIDF_TARGET=esp32', '-DSDKCONFIG=build/sdkconfig', 'build') - generated_projects = sorted(Path('build/subprojects').glob('*/legacy_project')) - assert len(generated_projects) == 1 + ulp_binary_dirs = sorted(p for p in Path('build/subprojects').glob('*') if p.is_dir()) + assert len(ulp_binary_dirs) == 1 - generated_project = generated_projects[0] - ulp_binary_dir = generated_project.parent - assert (generated_project / 'CMakeLists.txt').exists() - assert (generated_project / 'main/ulp_legacy_inputs.cmake').exists() + ulp_binary_dir = ulp_binary_dirs[0] ulp_binary = ulp_binary_dir / f'{ulp_binary_dir.name}.bin' assert ulp_binary.exists() original_ulp_binary_hash = _sha256(ulp_binary) @@ -189,6 +186,16 @@ def test_ulp_embed_binary_uses_generated_legacy_project(idf_py: IdfPyFunc, test_ assert _sha256(ulp_binary) != original_ulp_binary_hash +@pytest.mark.test_app_copy('examples/system/ulp/ulp_riscv/interrupts') +def test_ulp_embed_binary_uses_parent_project_config(idf_py: IdfPyFunc, test_app_copy: Path) -> None: + # Legacy ulp_embed_binary() sources are compiled as part of the builtin ULP + # child project, but still rely on project-local CONFIG_* symbols from the + # parent app's Kconfig.projbuild. + assert test_app_copy.exists() + + idf_py('-DIDF_TARGET=esp32s2', '-DSDKCONFIG=build/sdkconfig', 'build') + + @pytest.mark.test_app_copy('tools/test_apps/system/ulp/full_subproject/lp_core') def test_ulp_full_project_regenerates_config_from_parent_sdkconfig(idf_py: IdfPyFunc, test_app_copy: Path) -> None: # Full ULP subprojects use the parent sdkconfig as their source of truth.