diff --git a/components/ulp/CMakeLists_v2.txt b/components/ulp/CMakeLists_v2.txt index e17023fc259..3bdd8fc778f 100644 --- a/components/ulp/CMakeLists_v2.txt +++ b/components/ulp/CMakeLists_v2.txt @@ -320,6 +320,16 @@ elseif(ULP_TYPE STREQUAL "lp_core") # via FLAGS. idf_component_get_property(esp_system_dir esp_system COMPONENT_DIR) set(lp_core_ld_flags "${ulp_ld_flags} -I\"${esp_system_dir}/ld\" -I\"${esp_system_dir}/ld/${target}\"") + # A user-supplied LINKER layout replaces the default one composed into + # lp_core_riscv.ld.in. It reaches this child project as LP_CORE_LINKER_SCRIPT + # (passed by __setup_ulp_project, same as the CMake v1 path). Hand it to the + # wrapper as the LP_CORE_LINKER_INCLUDE macro it #includes. The escaped quotes + # survive separate_arguments() in the linker-script preprocessor so the macro + # expands to a quoted header-name token ("/abs/path.ld"). + if(LP_CORE_LINKER_SCRIPT) + message(STATUS "Using custom LP-core linker layout: ${LP_CORE_LINKER_SCRIPT}") + string(APPEND lp_core_ld_flags " -DLP_CORE_LINKER_INCLUDE=\\\"${LP_CORE_LINKER_SCRIPT}\\\"") + endif() target_linker_script(${COMPONENT_LIB} INTERFACE "ld/lp_core_riscv.ld.in" MEMORY FLAGS "${lp_core_ld_flags}") diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index 5c447ffb925..ed082b1763f 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -32,9 +32,14 @@ function(__ulp_create_arg_file arguments output_file) endfunction() function(__ulp_add_preprocessed_linker_script ulp_app_name ld_template ld_script ld_script_target) - # Use the C preprocessor so sdkconfig and SoC constants can shape the - # linker template before the ULP executable is linked. - set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -o ${ld_script} ${ARGN} ${ld_template}) + # Use the C preprocessor so sdkconfig and SoC constants can shape the linker + # template before the ULP executable is linked. -MD -MF -MT records every file + # the preprocessor reads (sdkconfig.h, SoC headers, the base/layout/checks + # parts and a custom LINKER_LAYOUT with its own includes) into a depfile, so + # the script regenerates when any of them changes. + set(ld_depfile ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}.d) + set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -MD -MF ${ld_depfile} -MT ${ld_script} + -o ${ld_script} ${ARGN} ${ld_template}) set(compiler_arguments_file ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}_args.txt) __ulp_create_arg_file("${preprocessor_args}" "${compiler_arguments_file}") @@ -42,13 +47,21 @@ function(__ulp_add_preprocessed_linker_script ulp_app_name ld_template ld_script COMMAND ${CMAKE_C_COMPILER} @${compiler_arguments_file} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} MAIN_DEPENDENCY ${ld_template} - DEPENDS ${SDKCONFIG_HEADER} + # The response file is a dependency too: a change visible only + # in the preprocessor flags (e.g. a different LINKER layout + # path) must regenerate the script even though no file recorded + # in the depfile changed. __ulp_create_arg_file rewrites it + # only when its content actually changes, so this does not + # retrigger on every reconfigure. + DEPENDS ${compiler_arguments_file} + DEPFILE ${ld_depfile} COMMENT "Generating ${ld_script} linker script..." VERBATIM) add_custom_target(${ld_script_target} DEPENDS ${ld_script}) add_dependencies(${ulp_app_name} ${ld_script_target}) target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}) + set_property(TARGET ${ulp_app_name} APPEND PROPERTY LINK_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${ld_script}) endfunction() function(ulp_apply_default_options ulp_app_name) @@ -85,7 +98,10 @@ function(ulp_apply_default_sources ulp_app_name) target_include_directories(${ulp_app_name} PRIVATE ${COMPONENT_INCLUDES} ${sdkconfig_dir}) - # Pre-process the linker script + # Pre-process the linker script. The LP-core script is assembled from + # base + layout + checks. A user-supplied LINKER_LAYOUT (LP_CORE_LINKER_SCRIPT) + # replaces the layout, swapped in by the wrapper. This is supported for the + # LP-core type only. if(BUILD_RISCV) set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_riscv.ld.in) elseif(BUILD_LP_CORE) @@ -96,6 +112,18 @@ function(ulp_apply_default_sources ulp_app_name) message(FATAL_ERROR "Unable to determine ULP type. ") endif() + if(LP_CORE_LINKER_SCRIPT) + # The LINKER_LAYOUT-is-LP-core-only check is enforced once in + # ulp_embed_binary/ulp_add_project, so LP_CORE_LINKER_SCRIPT only ever + # reaches an LP-core child. + message(STATUS "Using custom LP-core linker layout: ${LP_CORE_LINKER_SCRIPT}") + # The inner escaped quotes are required: the wrapper does + # `#include LP_CORE_LINKER_INCLUDE`, so the macro must expand to a quoted + # header-name token ("/abs/path.ld"). __ulp_create_arg_file() additionally + # escapes spaces in the path, keeping it a single preprocessor token. + list(APPEND ULP_PREPRO_ARGS "-DLP_CORE_LINKER_INCLUDE=\\\"${LP_CORE_LINKER_SCRIPT}\\\"") + endif() + # Strip the .in suffix so the generated script keeps its .ld name. get_filename_component(ULP_LD_SCRIPT ${ULP_LD_TEMPLATE} NAME_WLE) __ulp_add_preprocessed_linker_script(${ulp_app_name} ${ULP_LD_TEMPLATE} ${ULP_LD_SCRIPT} diff --git a/components/ulp/project_include.cmake b/components/ulp/project_include.cmake index 20f184aeecf..7ef3b4d7ea4 100644 --- a/components/ulp/project_include.cmake +++ b/components/ulp/project_include.cmake @@ -7,7 +7,7 @@ endif() # Create ULP binary and embed into the application. function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name type binary_names - s_sources exp_dep_srcs) + s_sources exp_dep_srcs linker_script) if(NOT CMAKE_BUILD_EARLY_EXPANSION) set(ulp_cmake_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake") @@ -189,6 +189,9 @@ function(__setup_ulp_project app_name project_path prefix prefix_append_bin_name -DADD_PICOLIBC_SPECS=${CONFIG_LIBC_PICOLIBC} -DULP_VAR_PREFIX=${prefix} -DULP_TYPE=${type} + # Read by the LP-core child to swap in a custom layout; + # empty and ignored otherwise. + -DLP_CORE_LINKER_SCRIPT=${linker_script} ${ulp_project_args} -DIDF_TARGET=${idf_target} -DIDF_PATH=${idf_path} @@ -276,26 +279,52 @@ function(__ulp_resolve_type out_var type) set(${out_var} "${resolved_type}" PARENT_SCOPE) endfunction() +# Resolve a user-supplied LINKER_LAYOUT for a ULP of the given (already resolved) +# type: reject it for non-LP-core types, since only the LP-core linker script has +# a swappable layout part, then resolve the layout to an absolute path and verify +# it exists. The result is written back to the variable named by out_var. A +# relative path is resolved against CMAKE_CURRENT_LIST_DIR, the same base used for +# relative ULP source files in __setup_ulp_project, so that sources and the linker +# layout in one call are resolved consistently. Enforcing this here, at the public +# API, gives a single check that covers every ULP child build style. +function(__resolve_lp_core_linker linker type out_var) + if(linker) + if(NOT type STREQUAL "lp_core") + message(FATAL_ERROR "A custom LINKER_LAYOUT script is only supported for the LP-core ULP type.") + endif() + get_filename_component(linker "${linker}" ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR}) + if(NOT EXISTS "${linker}") + message(FATAL_ERROR "LINKER_LAYOUT linker script not found: ${linker}") + endif() + endif() + set(${out_var} "${linker}" PARENT_SCOPE) +endfunction() + function(ulp_embed_binary app_name s_sources exp_dep_srcs) - cmake_parse_arguments(ULP "" "PREFIX;TYPE" "" ${ARGN}) + cmake_parse_arguments(ULP "" "PREFIX;TYPE;LINKER_LAYOUT" "" ${ARGN}) if(NOT ULP_PREFIX) set(ULP_PREFIX "ulp_") endif() validate_ulp_type("${ULP_TYPE}") + # Resolve the effective ULP type (from TYPE or the project config) so the + # LINKER_LAYOUT guard is applied uniformly; the full-subproject child also + # consumes the resolved type. + __ulp_resolve_type(ulp_resolved_type "${ULP_TYPE}") if(IDF_BUILD_V2) - __ulp_resolve_type(ULP_TYPE "${ULP_TYPE}") + set(ULP_TYPE "${ulp_resolved_type}") endif() + __resolve_lp_core_linker("${ULP_LINKER_LAYOUT}" "${ulp_resolved_type}" LP_CORE_LINKER) set(ulp_cmake_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cmake") __setup_ulp_project("${app_name}" "${ulp_cmake_dir}" "${ULP_PREFIX}" FALSE "${ULP_TYPE}" "${app_name}" - "${s_sources}" "${exp_dep_srcs}") + "${s_sources}" "${exp_dep_srcs}" "${LP_CORE_LINKER}") endfunction() function(ulp_add_project app_name project_path) - cmake_parse_arguments(ULP "" "PREFIX;TYPE" "BINARIES" ${ARGN}) + cmake_parse_arguments(ULP "" "PREFIX;TYPE;LINKER_LAYOUT" "BINARIES" ${ARGN}) if(NOT ULP_BINARIES) set(ULP_BINARIES "${app_name}") endif() @@ -311,10 +340,15 @@ function(ulp_add_project app_name project_path) endif() validate_ulp_type("${ULP_TYPE}") + # Resolve the effective ULP type (from TYPE or the project config) so the + # LINKER_LAYOUT guard is applied uniformly; the full-subproject child also + # consumes the resolved type. + __ulp_resolve_type(ulp_resolved_type "${ULP_TYPE}") if(IDF_BUILD_V2) - __ulp_resolve_type(ULP_TYPE "${ULP_TYPE}") + set(ULP_TYPE "${ulp_resolved_type}") endif() + __resolve_lp_core_linker("${ULP_LINKER_LAYOUT}" "${ulp_resolved_type}" LP_CORE_LINKER) __setup_ulp_project("${app_name}" "${project_path}" "${ULP_PREFIX}" - "${ULP_PREFIX_APPEND_BIN_NAME}" "${ULP_TYPE}" "${ULP_BINARIES}" "" "") + "${ULP_PREFIX_APPEND_BIN_NAME}" "${ULP_TYPE}" "${ULP_BINARIES}" "" "" "${LP_CORE_LINKER}") endfunction() diff --git a/tools/cmakev2/utilities.cmake b/tools/cmakev2/utilities.cmake index e0b655da583..a077ccb5f0b 100644 --- a/tools/cmakev2/utilities.cmake +++ b/tools/cmakev2/utilities.cmake @@ -1279,16 +1279,25 @@ function(__preprocess_linker_script script_in script_out flags component_include set(linker_script_generator "${idf_path}/tools/cmake/linker_script_preprocessor.cmake") + # -MD -MF -MT makes the C preprocessor record every file it reads (the + # template plus everything it #includes, transitively) into a depfile, so + # the output is regenerated when any of them changes. MAIN_DEPENDENCY and + # DEPENDS only cover the top-level script and sdkconfig; the depfile closes + # the gap for includes the build system cannot enumerate up front. + set(depfile "${script_out}.d") + set(depfile_flags "-MD -MF \"${depfile}\" -MT \"${script_out}\"") + add_custom_command( OUTPUT ${script_out} COMMAND ${CMAKE_COMMAND} "-DCC=${CMAKE_C_COMPILER}" "-DSOURCE=${script_in}" "-DTARGET=${script_out}" - "-DCFLAGS=-I\"${config_dir}\" ${base_flags} ${component_includes}" + "-DCFLAGS=-I\"${config_dir}\" ${depfile_flags} ${base_flags} ${component_includes}" -P "${linker_script_generator}" MAIN_DEPENDENCY "${script_in}" DEPENDS "${sdkconfig_header}" + DEPFILE "${depfile}" COMMENT "Preprocessing linker script ${script_in} -> ${script_out}" VERBATIM) endfunction()