refactor(ulp): make the ULP subproject directory explicit

components/ulp/cmake holds the project that builds a ULP program. Name it
subproject, as the bootloader does.
This commit is contained in:
Sudeep Mohanty
2026-09-02 09:31:07 +02:00
parent d6fee00af6
commit 1bec6e09d9
23 changed files with 31 additions and 26 deletions
+16
View File
@@ -0,0 +1,16 @@
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})
include(IDFULPProject)
ulp_apply_default_options(${ULP_APP_NAME})
ulp_apply_default_sources(${ULP_APP_NAME})
ulp_add_build_binary_targets(${ULP_APP_NAME} PREFIX ${ULP_VAR_PREFIX})
@@ -0,0 +1,69 @@
# 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)
# CMake v1 linked the ulp_embed_binary() call-site sources before the ULP runtime.
# In CMake v2 the runtime's WHOLE_ARCHIVE is emitted as a link option, before
# executable objects, so attaching these sources directly would reverse that
# order. Keep them in a separate archive that can be placed before the runtime,
# preserving legacy code placement and strong-over-weak symbol resolution.
function(__ulp_embed_binary_add_legacy_sources executable)
set(legacy_sources_lib "${executable}_legacy_sources")
add_library(${legacy_sources_lib} STATIC ${ULP_S_SOURCES})
target_link_libraries(${legacy_sources_lib} PRIVATE "library_${executable}")
if(ADD_PICOLIBC_SPECS)
target_compile_options(${legacy_sources_lib} PRIVATE $<$<COMPILE_LANG_AND_ID:C,GNU>:-specs=picolibc.specs>)
target_compile_options(${legacy_sources_lib} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-specs=picolibcpp.specs>)
endif()
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(${legacy_sources_lib} BEFORE PRIVATE "${parent_sdkconfig_dir}")
endif()
if(COMPONENT_INCLUDES)
target_include_directories(${legacy_sources_lib} 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(${legacy_sources_lib} PRIVATE ${COMPONENT_DIR})
endif()
__idf_build_link_whole_archive(${executable} PRIVATE ${legacy_sources_lib} BEFORE)
endfunction()
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})
__ulp_embed_binary_add_legacy_sources(${ULP_APP_NAME})
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}")
@@ -0,0 +1,265 @@
# This is the CMake v1 (legacy) ULP child entry point, used by ulp_embed_binary.
# CMake v2 full-subproject builds include components/ulp/subproject/ulp_project.cmake
# instead.
#
# Legacy ULP child projects are plain CMake projects, so the parent-provided
# sdkconfig.cmake has to be loaded here before the common ULP helpers inspect
# CONFIG_* values. CMake v1 callers pass this path explicitly.
include(${SDKCONFIG_CMAKE})
include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake)
enable_language(C ASM)
set(CMAKE_EXECUTABLE_SUFFIX ".elf")
ulp_detect_build_type()
ulp_apply_build_type_options()
# Check the supported assembler version
if(BUILD_FSM)
check_expected_tool_version("esp32ulp-elf" ${CMAKE_ASM_COMPILER})
endif()
# CMake v1-only helpers that preprocess the ULP memory-layout linker template
# with the C preprocessor. The CMake v2 path attaches the template through
# target_linker_script(... FLAGS) and does not use these.
function(__ulp_create_arg_file arguments output_file)
# Escape all spaces
list(TRANSFORM arguments REPLACE " " "\\\\ ")
# Create a single string with all args separated by space
list(JOIN arguments " " arguments)
# Generate the response file late enough for target generator expressions
# in include directories to resolve to their final build-system values.
file(GENERATE OUTPUT "${output_file}" CONTENT "${arguments}")
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. -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}")
add_custom_command(OUTPUT ${ld_script}
COMMAND ${CMAKE_C_COMPILER} @${compiler_arguments_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${ld_template}
# 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)
if(BUILD_RISCV)
target_link_options(${ulp_app_name} PRIVATE "-nostartfiles")
target_link_options(${ulp_app_name} PRIVATE -Wl,--gc-sections)
target_link_options(${ulp_app_name} PRIVATE "-Wl,--no-warn-rwx-segments")
target_link_options(${ulp_app_name} PRIVATE -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.map)
elseif(BUILD_LP_CORE)
target_link_options(${ulp_app_name} PRIVATE "-nostartfiles")
target_link_options(${ulp_app_name} PRIVATE "-Wl,--no-warn-rwx-segments")
target_link_options(${ulp_app_name} PRIVATE -Wl,--gc-sections)
target_link_options(${ulp_app_name} PRIVATE -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.map)
elseif(BUILD_FSM)
target_link_options(${ulp_app_name} PRIVATE -Map=${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.map)
endif()
endfunction()
function(ulp_apply_default_sources ulp_app_name)
message(STATUS "Building ULP app ${ulp_app_name}")
get_filename_component(sdkconfig_dir ${SDKCONFIG_HEADER} DIRECTORY)
foreach(include ${COMPONENT_INCLUDES})
list(APPEND component_includes -I${include})
endforeach()
list(REMOVE_DUPLICATES component_includes)
list(APPEND ULP_PREPRO_ARGS ${component_includes})
list(APPEND ULP_PREPRO_ARGS -I${COMPONENT_DIR})
list(APPEND ULP_PREPRO_ARGS -I${sdkconfig_dir})
list(APPEND ULP_PREPRO_ARGS -I${IDF_PATH}/components/esp_system/ld)
list(APPEND ULP_PREPRO_ARGS -I${IDF_PATH}/components/esp_system/ld/${IDF_TARGET})
target_include_directories(${ulp_app_name} PRIVATE ${COMPONENT_INCLUDES} ${sdkconfig_dir})
# 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)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/lp_core_riscv.ld.in)
elseif(BUILD_FSM)
set(ULP_LD_TEMPLATE ${IDF_PATH}/components/ulp/ld/ulp_fsm.ld.in)
else()
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}
ld_script ${ULP_PREPRO_ARGS})
# To avoid warning "Manually-specified variables were not used by the project"
set(bypassWarning "${IDF_TARGET}")
set(bypassWarning "${ULP_VAR_PREFIX}")
set(bypassWarning "${ULP_TYPE}")
# Save user sources before adding core sources
set(ULP_USER_SOURCES ${ULP_S_SOURCES})
# Clear ULP_S_SOURCES and rebuild it with only the sources we need
set(ULP_S_SOURCES ${ULP_USER_SOURCES})
if(BUILD_RISCV)
#risc-v ulp uses extra files for building:
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/start.S"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_touch.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_gpio.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c")
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
#Makes the csr utillies for riscv visible:
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/include"
"${IDF_PATH}/components/ulp/ulp_riscv/shared/include"
"${IDF_PATH}/components/riscv/include")
target_link_options(${ulp_app_name} PRIVATE SHELL:-T ${IDF_PATH}/components/ulp/ld/${IDF_TARGET}.peripherals.ld)
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
target_compile_definitions(${ulp_app_name} PRIVATE ULP_RISCV_REGISTER_OPS)
elseif(BUILD_FSM)
foreach(ulp_s_source ${ULP_S_SOURCES})
get_filename_component(ulp_ps_source ${ulp_s_source} NAME_WE)
set(ulp_ps_output ${CMAKE_CURRENT_BINARY_DIR}/${ulp_ps_source}.ulp.S)
# Put all arguments to the list
set(preprocessor_args -D__ASSEMBLER__ -E -P -xc -o ${ulp_ps_output} ${ULP_PREPRO_ARGS} ${ulp_s_source})
set(compiler_arguments_file ${CMAKE_CURRENT_BINARY_DIR}/${ulp_ps_source}_args.txt)
__ulp_create_arg_file("${preprocessor_args}" "${compiler_arguments_file}")
# Generate preprocessed assembly files.
add_custom_command(OUTPUT ${ulp_ps_output}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_C_COMPILER} @${compiler_arguments_file}
DEPENDS ${ulp_s_source}
VERBATIM)
# During assembly file compilation, output listing files as well.
set_source_files_properties(${ulp_ps_output}
PROPERTIES COMPILE_FLAGS
"-al=${CMAKE_CURRENT_BINARY_DIR}/${ulp_ps_source}.lst")
list(APPEND ULP_PS_SOURCES ${ulp_ps_output})
endforeach()
target_sources(${ulp_app_name} PRIVATE ${ULP_PS_SOURCES})
elseif(BUILD_LP_CORE)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/start.S"
"${IDF_PATH}/components/ulp/lp_core/lp_core/vector.S"
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/${IDF_TARGET}/vector_table.S"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_startup.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_pmp.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_print.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_panic.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_interrupt.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_mailbox.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c")
if(CONFIG_SOC_LP_CORE_SUPPORT_I2C)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c")
endif()
if(CONFIG_SOC_LP_SPI_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c")
endif()
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_uart_shared.c"
"${IDF_PATH}/components/esp_driver_uart/src/uart_wakeup.c"
"${IDF_PATH}/components/esp_hal_uart/uart_hal_iram.c"
"${IDF_PATH}/components/esp_hal_uart/uart_hal.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_uart.c")
endif()
if(CONFIG_SOC_LP_MAILBOX_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_hw.c")
else()
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/port/lp_core_mailbox_impl_sw.c")
endif()
if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_touch.c")
endif()
set(target_folder ${IDF_TARGET})
target_link_options(${ulp_app_name}
PRIVATE SHELL:-T ${IDF_PATH}/components/soc/${target_folder}/ld/${IDF_TARGET}.peripherals.ld)
if(CONFIG_ESP_ROM_HAS_LP_ROM)
target_link_options(${ulp_app_name}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.ld)
target_link_options(${ulp_app_name}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.newlib.ld)
target_link_options(${ulp_app_name}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.version.ld)
target_link_options(${ULP_APP_NAME}
PRIVATE SHELL:-T ${IDF_PATH}/components/esp_rom/${IDF_TARGET}/ld/${IDF_TARGET}lp.rom.api.ld)
endif()
target_sources(${ulp_app_name} PRIVATE ${ULP_S_SOURCES})
target_include_directories(${ulp_app_name} PRIVATE "${IDF_PATH}/components/ulp/lp_core/lp_core/include"
"${IDF_PATH}/components/ulp/lp_core/shared/include")
target_compile_definitions(${ulp_app_name} PRIVATE IS_ULP_COCPU)
endif()
endfunction()
@@ -0,0 +1,101 @@
# The directory of this file, captured while it is being processed:
# CMAKE_CURRENT_LIST_DIR inside a function resolves at call time, which is the
# caller's directory, not this one.
set(__ULP_SUBPROJECT_DIR "${CMAKE_CURRENT_LIST_DIR}")
macro(ulp_detect_build_type)
# Logic to determine ULP type and set reusable flags
set(BUILD_RISCV OFF)
set(BUILD_FSM OFF)
set(BUILD_LP_CORE OFF)
# If ULP_TYPE is explicitly set, use it; otherwise fall back to CONFIG checks
if(ULP_TYPE)
string(TOLOWER "${ULP_TYPE}" ulp_type_lower)
if(ulp_type_lower STREQUAL "riscv")
set(BUILD_RISCV ON)
elseif(ulp_type_lower STREQUAL "fsm")
set(BUILD_FSM ON)
elseif(ulp_type_lower STREQUAL "lp_core")
set(BUILD_LP_CORE ON)
endif()
elseif(CONFIG_ULP_COPROC_TYPE_RISCV)
set(BUILD_RISCV ON)
elseif(CONFIG_ULP_COPROC_TYPE_LP_CORE)
set(BUILD_LP_CORE ON)
elseif(CONFIG_ULP_COPROC_TYPE_FSM)
set(BUILD_FSM ON)
endif()
endmacro()
macro(ulp_apply_build_type_options)
# CMake initializes GNU ASM definitions as --defsym during enable_language().
# Native full-subproject FSM sources are preprocessed by the toolchain rule,
# so use C preprocessor definitions for generated ASM rules.
if(BUILD_FSM AND ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN)
set(CMAKE_ASM_DEFINE_FLAG "-D")
endif()
endmacro()
function(ulp_add_build_binary_targets ulp_app_name)
cmake_parse_arguments(ULP "" "PREFIX" "" ${ARGN})
if(NOT ULP_PREFIX)
set(ULP_PREFIX "ulp_")
endif()
if(ADD_PICOLIBC_SPECS)
target_compile_options(${ulp_app_name} PRIVATE $<$<COMPILE_LANG_AND_ID:C,GNU>:-specs=picolibc.specs>)
target_compile_options(${ulp_app_name} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-specs=picolibcpp.specs>)
endif()
if(BUILD_LP_CORE)
set(ULP_BASE_ADDR "0x0")
else()
set(ULP_BASE_ADDR "0x50000000")
endif()
set(ULP_MAP_GEN ${PYTHON} ${__ULP_SUBPROJECT_DIR}/esp32ulp_mapgen.py)
# Dump the list of global symbols in a convenient format
add_custom_command(OUTPUT ${ulp_app_name}.sym
COMMAND ${CMAKE_READELF} -sW $<TARGET_FILE:${ulp_app_name}> > ${ulp_app_name}.sym
DEPENDS ${ulp_app_name}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
# Dump the binary for inclusion into the project
if(BUILD_LP_CORE AND CONFIG_ULP_COPROC_RUN_FROM_HP_MEM)
# LP-core images running from HP memory use the normal ESP image format;
# RTC-memory ULP binaries are raw object-copy output.
add_custom_command(OUTPUT ${ulp_app_name}.bin
COMMAND ${PYTHON} -m esptool --chip ${IDF_TARGET} elf2image
--output ${ulp_app_name}.bin $<TARGET_FILE:${ulp_app_name}>
DEPENDS ${ulp_app_name}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
else()
add_custom_command(OUTPUT ${ulp_app_name}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${ulp_app_name}> ${ulp_app_name}.bin
DEPENDS ${ulp_app_name}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
add_custom_command(OUTPUT ${ulp_app_name}.ld ${ulp_app_name}.h
COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name}
--base ${ULP_BASE_ADDR} --prefix ${ULP_PREFIX}
--target ${IDF_TARGET}
DEPENDS ${ulp_app_name}.sym
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_custom_target(${ulp_app_name}_binary_artifacts
DEPENDS ${ulp_app_name} ${ulp_app_name}.bin ${ulp_app_name}.sym
${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.ld
${CMAKE_CURRENT_BINARY_DIR}/${ulp_app_name}.h
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
# Building the component separately from the project should result in all
# ULP files being built. Multiple ULP executables in one child project all
# contribute their artifact targets here.
if(NOT TARGET build)
add_custom_target(build)
endif()
add_dependencies(build ${ulp_app_name}_binary_artifacts)
endfunction()
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# esp32ulp_mapgen utility converts a symbol list provided by nm into an export script
# for the linker and a header file.
import argparse
import os
import re
import textwrap
import typing
UTIL = os.path.basename(__file__)
def name_mangling(name: str) -> str:
# Simple and dumb name mangling for namespaced name following GCC algorithm
ns, n = name.split('::')
return f'_ZN{len(ns)}{ns}{len(n)}{n}E'
# On ESP32-P4 the LP CPU accesses HP SRAM via the L2 cache at the same
# address as the HP CPU cacheable alias. The HP application should access
# LP Core variables through the non-cacheable alias to avoid coherency
# issues with the LP CPU. The non-cacheable alias is obtained by adding
# the offset 0x40000000 to addresses below 0x50000000.
_P4_NON_CACHEABLE_OFFSET = 0x40000000
_P4_LP_RAM_BASE = 0x50000000
def map_to_hp_cpu_addr(addr: int, target: str | None) -> int:
"""Translate an LP-CPU VMA to the address the HP CPU should use to access it."""
if target == 'esp32p4' and addr < _P4_LP_RAM_BASE:
return addr + _P4_NON_CACHEABLE_OFFSET
return addr
def gen_ld_h_from_sym(
f_sym: typing.TextIO,
f_ld: typing.TextIO,
f_h: typing.TextIO,
base_addr: int,
prefix: str,
target: str | None = None,
) -> None:
f_ld.write(
textwrap.dedent(
f"""
/* ULP variable definitions for the linker.
* This file is generated automatically by {UTIL} utility.
*/
""" # noqa: E222
)
)
cpp_mode = False
var_prefix = prefix
namespace = ''
if '::' in prefix:
# C++ mode, let's avoid the extern "C" type and instead use namespace
f_h.write(
textwrap.dedent(
f"""
/* ULP variable definitions for the compiler.
* This file is generated automatically by {UTIL} utility.
*/
#pragma once
""" # noqa: E222
)
)
tmp = prefix.split('::')
namespace = tmp[0]
var_prefix = '_'.join(tmp[1:]) # Limit to a single namespace here to avoid complex mangling rules
f_h.write(f'namespace {namespace} {{\n')
cpp_mode = True
else:
f_h.write(
textwrap.dedent(
f"""
/* ULP variable definitions for the compiler.
* This file is generated automatically by {UTIL} utility.
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {{
#endif\n
""" # noqa: E222
)
)
# Format the regular expression to match the readelf output
expr = re.compile(
r'^.*(?P<address>[a-f0-9]{8})\s+(?P<size>\d+) (OBJECT|NOTYPE)\s+GLOBAL\s+DEFAULT\s+[^ ]+ (?P<name>.*)$'
)
for line in f_sym:
# readelf format output has the following structure:
# Num: Value Size Type Bind Vis Ndx Name
# So match the line with a regular expression to parse it first
groups = expr.match(line)
# Ignore lines that do not match the expected format such as non global symbols
if groups is None:
continue
# Extract the symbol information
addr = map_to_hp_cpu_addr(int(groups.group('address'), 16) + base_addr, target)
size = int(groups.group('size'))
sym_name = groups.group('name')
# Add the variable prefix if provided
var_name = var_prefix + sym_name
# Get the dimension of the variable if it is an array
var_dimension = f'[{size // 4}]' if size > 4 else '' # Use // to automatically perform an integer division
# Write the variable definition to the header file and the address to the linker script
f_h.write(f'extern uint32_t {var_name}{var_dimension};\n')
name_in_ld = name_mangling(namespace + '::' + var_name) if cpp_mode else var_name
f_ld.write(f'{name_in_ld} = 0x{addr:08x};\n')
if cpp_mode:
f_h.write('}\n')
else:
f_h.write(
textwrap.dedent(
"""
#ifdef __cplusplus
}
#endif
"""
)
)
def main() -> None:
description = (
'This application generates .h and .ld files for symbols defined in input file. '
'The input symbols file can be generated using readelf utility like this: '
'<PREFIX>readelf -sW <elf_file> > <symbols_file>'
)
parser = argparse.ArgumentParser(description=description)
parser.add_argument(
'-s', '--symfile', required=True, help='symbols file name', metavar='SYMFILE', type=argparse.FileType('r')
)
parser.add_argument(
'-o', '--outputfile', required=True, help='destination .h and .ld files name prefix', metavar='OUTFILE'
)
parser.add_argument('--base-addr', required=True, help='base address of the ULP memory, to be added to each symbol')
parser.add_argument('-p', '--prefix', required=False, help='prefix for generated header file', default='ulp_')
parser.add_argument(
'--target',
required=False,
help='IDF target chip name (e.g. esp32p4), used for address translation',
default=None,
)
args = parser.parse_args()
with open(args.outputfile + '.h', 'w') as f_h, open(args.outputfile + '.ld', 'w') as f_ld:
gen_ld_h_from_sym(args.symfile, f_ld, f_h, int(args.base_addr, 0), args.prefix, args.target)
if __name__ == '__main__':
main()
@@ -0,0 +1,49 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
foreach(required_var ULP_FSM_PREPROCESSOR ULP_FSM_ASSEMBLER ULP_FSM_TARGET ULP_FSM_SOURCE ULP_FSM_OBJECT)
if(NOT DEFINED ${required_var} OR "${${required_var}}" STREQUAL "")
message(FATAL_ERROR "${required_var} is required")
endif()
endforeach()
set(extra_args)
set(extra_args_started OFF)
math(EXPR last_arg_index "${CMAKE_ARGC} - 1")
foreach(arg_index RANGE 0 ${last_arg_index})
if("${CMAKE_ARGV${arg_index}}" STREQUAL "--")
set(extra_args_started ON)
continue()
endif()
if(extra_args_started)
list(APPEND extra_args "${CMAKE_ARGV${arg_index}}")
endif()
endforeach()
set(preprocessed_source "${ULP_FSM_OBJECT}.ulp.S")
set(listing_file "${ULP_FSM_OBJECT}.lst")
execute_process(
COMMAND "${ULP_FSM_PREPROCESSOR}"
-D__ASSEMBLER__ -E -P -x c
${extra_args}
-o "${preprocessed_source}"
"${ULP_FSM_SOURCE}"
RESULT_VARIABLE preprocess_result
)
if(NOT preprocess_result EQUAL 0)
message(FATAL_ERROR "Failed to preprocess ${ULP_FSM_SOURCE}")
endif()
execute_process(
COMMAND "${ULP_FSM_ASSEMBLER}"
"--mcpu=${ULP_FSM_TARGET}"
"-al=${listing_file}"
-o "${ULP_FSM_OBJECT}"
-c "${preprocessed_source}"
RESULT_VARIABLE assemble_result
)
if(NOT assemble_result EQUAL 0)
message(FATAL_ERROR "Failed to assemble ${preprocessed_source}")
endif()
@@ -0,0 +1,36 @@
# CMake toolchain file for ULP
set(CMAKE_SYSTEM_NAME Generic)
# Compiler is only used for preprocessing
set(CMAKE_C_COMPILER "xtensa-esp32-elf-gcc")
set(CMAKE_CXX_COMPILER "xtensa-esp32-elf-g++")
set(CMAKE_ASM_COMPILER "esp32ulp-elf-as")
set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy")
set(CMAKE_LINKER "esp32ulp-elf-ld")
# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(),
# making idf_project_init visible when this toolchain is loaded. Legacy child
# projects, even when launched by a CMake v2 parent, include the old IDF CMake
# entrypoint and keep the historical explicit preprocessing path.
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF)
if(IDF_BUILD_V2 AND COMMAND idf_project_init)
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON)
endif()
if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN)
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT
"${CMAKE_COMMAND} \
-DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \
-DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
-DULP_FSM_TARGET=esp32 \
-DULP_FSM_SOURCE=<SOURCE> \
-DULP_FSM_OBJECT=<OBJECT> \
-P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- <DEFINES> <INCLUDES>")
else()
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
--mcpu=esp32 <DEFINES> <INCLUDES> -o <OBJECT> -c <SOURCE>")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32ulp -nostdlib" CACHE STRING "ULP Linker Base Flags")
set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} <FLAGS> <CMAKE_ASM_LINK_FLAGS> \
<LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
@@ -0,0 +1,36 @@
# CMake toolchain file for ULP
set(CMAKE_SYSTEM_NAME Generic)
# Compiler is only used for preprocessing
set(CMAKE_C_COMPILER "xtensa-esp32s2-elf-gcc")
set(CMAKE_CXX_COMPILER "xtensa-esp32s2-elf-g++")
set(CMAKE_ASM_COMPILER "esp32ulp-elf-as")
set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy")
set(CMAKE_LINKER "esp32ulp-elf-ld")
# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(),
# making idf_project_init visible when this toolchain is loaded. Legacy child
# projects, even when launched by a CMake v2 parent, include the old IDF CMake
# entrypoint and keep the historical explicit preprocessing path.
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF)
if(IDF_BUILD_V2 AND COMMAND idf_project_init)
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON)
endif()
if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN)
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT
"${CMAKE_COMMAND} \
-DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \
-DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
-DULP_FSM_TARGET=esp32s2 \
-DULP_FSM_SOURCE=<SOURCE> \
-DULP_FSM_OBJECT=<OBJECT> \
-P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- <DEFINES> <INCLUDES>")
else()
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
--mcpu=esp32s2 <DEFINES> <INCLUDES> -o <OBJECT> -c <SOURCE>")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s2ulp -nostdlib" CACHE STRING "ULP Linker Base Flags")
set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} <FLAGS> <CMAKE_ASM_LINK_FLAGS> \
<LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
@@ -0,0 +1,36 @@
# CMake toolchain file for ULP
set(CMAKE_SYSTEM_NAME Generic)
# Compiler is only used for preprocessing
set(CMAKE_C_COMPILER "xtensa-esp32s3-elf-gcc")
set(CMAKE_CXX_COMPILER "xtensa-esp32s3-elf-g++")
set(CMAKE_ASM_COMPILER "esp32ulp-elf-as")
set(CMAKE_OBJCOPY "esp32ulp-elf-objcopy")
set(CMAKE_LINKER "esp32ulp-elf-ld")
# Native CMake v2 ULP projects include cmakev2/idf.cmake before project(),
# making idf_project_init visible when this toolchain is loaded. Legacy child
# projects, even when launched by a CMake v2 parent, include the old IDF CMake
# entrypoint and keep the historical explicit preprocessing path.
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN OFF)
if(IDF_BUILD_V2 AND COMMAND idf_project_init)
set(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN ON)
endif()
if(ULP_PREPROCESS_FSM_ASM_IN_TOOLCHAIN)
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT
"${CMAKE_COMMAND} \
-DULP_FSM_PREPROCESSOR=${CMAKE_C_COMPILER} \
-DULP_FSM_ASSEMBLER=${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
-DULP_FSM_TARGET=esp32s3 \
-DULP_FSM_SOURCE=<SOURCE> \
-DULP_FSM_OBJECT=<OBJECT> \
-P ${CMAKE_CURRENT_LIST_DIR}/preprocess_fsm_asm.cmake -- <DEFINES> <INCLUDES>")
else()
set(CMAKE_ASM${ASM_DIALECT}_COMPILE_OBJECT "${CMAKE_ASM${ASM_DIALECT}_COMPILER} \
--mcpu=esp32s3 <DEFINES> <INCLUDES> -o <OBJECT> -c <SOURCE>")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-A elf32-esp32s3ulp -nostdlib" CACHE STRING "ULP Linker Base Flags")
set(CMAKE_ASM_LINK_EXECUTABLE "${CMAKE_LINKER} <FLAGS> <CMAKE_ASM_LINK_FLAGS> \
<LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
@@ -0,0 +1,15 @@
# CMake toolchain file for ULP LP core
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER "riscv32-esp-elf-gcc")
set(CMAKE_CXX_COMPILER "riscv32-esp-elf-g++")
set(CMAKE_ASM_COMPILER "riscv32-esp-elf-gcc")
set(CMAKE_C_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei_zaamo_zalrsc -mdiv -fdata-sections -ffunction-sections"
CACHE STRING "C Compiler Base Flags")
set(CMAKE_CXX_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei_zaamo_zalrsc -mdiv -fdata-sections -ffunction-sections"
CACHE STRING "C++ Compiler Base Flags")
set(CMAKE_ASM_FLAGS "-Os -ggdb -march=rv32imac_zicsr_zifencei_zaamo_zalrsc -x assembler-with-cpp"
CACHE STRING "Assembler Base Flags")
set(CMAKE_EXE_LINKER_FLAGS "-march=rv32imac_zicsr_zifencei_zaamo_zalrsc --specs=nano.specs --specs=nosys.specs"
CACHE STRING "Linker Base Flags")
@@ -0,0 +1,15 @@
# CMake toolchain file for ULP-RISC-V
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER "riscv32-esp-elf-gcc")
set(CMAKE_CXX_COMPILER "riscv32-esp-elf-g++")
set(CMAKE_ASM_COMPILER "riscv32-esp-elf-gcc")
set(CMAKE_C_FLAGS "-Os -march=rv32imc_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections"
CACHE STRING "C Compiler Base Flags")
set(CMAKE_CXX_FLAGS "-Os -march=rv32imc_zicsr_zifencei -mdiv -fdata-sections -ffunction-sections"
CACHE STRING "C++ Compiler Base Flags")
set(CMAKE_ASM_FLAGS "-march=rv32imc -x assembler-with-cpp"
CACHE STRING "Assembler Base Flags")
set(CMAKE_EXE_LINKER_FLAGS "-march=rv32imc_zicsr_zifencei --specs=nano.specs --specs=nosys.specs"
CACHE STRING "Linker Base Flags")
+196
View File
@@ -0,0 +1,196 @@
# ULP subproject entry point for CMake v2 full-subproject builds.
#
# A ULP child project includes this file instead of tools/cmakev2/idf.cmake
# directly. It pulls in the cmakev2 build system and layers the ULP subproject
# API on top: ulp_project_init, ulp_build_executable, ulp_build_binary and
# ulp_project_default, the ULP analogs of idf_project_init,
# idf_build_executable, idf_build_binary and idf_project_default.
include(${IDF_PATH}/tools/cmakev2/idf.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/IDFULPProjectCommon.cmake)
# __ULP_BUILDV2 is only set by the IDF_BUILD_V2 ULP child path for now.
if(__ULP_BUILDV2)
set(NON_OS_BUILD 1)
# Clearing common components below only prevents automatic app-component closure.
# Explicitly included ULP dependencies still read NON_OS_BUILD to select their no-OS shape.
idf_build_set_property(NON_OS_BUILD 1)
endif()
# Reset the compile/link state inherited from the parent app build and select
# ULP-specific build behavior. Idempotent within a child configure.
function(__ulp_prepare_build)
idf_build_get_property(ulp_build_prepared __ULP_BUILD_PREPARED)
if(ulp_build_prepared)
return()
endif()
if(BUILD_FSM)
check_expected_tool_version("esp32ulp-elf" ${CMAKE_ASM_COMPILER})
# FSM links with esp32ulp-elf-ld directly, not through GCC, so avoid
# GCC-driver link grouping flags from the generic executable helper.
idf_build_set_property(LINKER_TYPE ULP_FSM)
endif()
# ULP projects use their own toolchain files and component-provided options,
# so discard compile/link options added for normal app builds.
foreach(property IN ITEMS
COMPILE_OPTIONS
C_COMPILE_OPTIONS
CXX_COMPILE_OPTIONS
ASM_COMPILE_OPTIONS
COMPILE_DEFINITIONS
LINK_OPTIONS)
idf_build_set_property(${property} "")
endforeach()
# Do not apply the default app component closure to ULP child projects.
# ULP components should contribute dependencies explicitly.
idf_build_set_property(__COMPONENT_REQUIRES_COMMON "")
idf_build_set_property(__COMMON_COMPONENT_INTERFACES "")
idf_build_set_property(__COMMON_COMPONENTS_INITIALIZED YES)
idf_build_set_property(__ULP_BUILD_PREPARED YES)
endfunction()
#[[api
.. cmakev2:macro:: ulp_project_init
.. code-block:: cmake
ulp_project_init()
Initialize a ULP full-subproject build. Calls :cmakev2:ref:`idf_project_init`
and then applies the ULP-specific setup: it detects the ULP type (RISC-V, LP
core or FSM) from the ``ULP_TYPE`` variable or the project configuration and
resets the compile and link options inherited from the parent application
build, so the ULP executable uses only its own toolchain and
component-provided options.
This is the ULP analog of :cmakev2:ref:`idf_project_init` and is the entry
point for projects that build more than one ULP executable, followed by
:cmakev2:ref:`ulp_build_executable` and :cmakev2:ref:`ulp_build_binary`
calls. For the common single-executable case use
:cmakev2:ref:`ulp_project_default` instead.
#]]
macro(ulp_project_init)
idf_project_init()
ulp_detect_build_type()
ulp_apply_build_type_options()
__ulp_prepare_build()
endmacro()
#[[api
.. cmakev2:function:: ulp_build_executable
.. code-block:: cmake
ulp_build_executable(<executable>
[COMPONENTS <component>...]
[MAPFILE_TARGET <target>])
*executable[in]*
Name of the ULP executable target to create.
Build a ULP executable. This is the ULP analog of
:cmakev2:ref:`idf_build_executable` and forwards its arguments to
:cmakev2:ref:`idf_build_executable`. Call :cmakev2:ref:`ulp_build_binary`
for each executable that should produce embeddable ULP artifacts.
#]]
function(ulp_build_executable executable)
if("PREFIX" IN_LIST ARGN)
message(FATAL_ERROR "Use ulp_build_binary(${executable} PREFIX ...) to set ULP symbol prefixes.")
endif()
idf_build_executable(${executable} ${ARGN})
endfunction()
#[[api
.. cmakev2:function:: ulp_build_binary
.. code-block:: cmake
ulp_build_binary(<executable>
[PREFIX <prefix>])
*executable[in]*
ULP executable target for which to generate embeddable artifacts.
*PREFIX[in,opt]*
Symbol name prefix for the generated symbol header and linker export
files. Defaults to ``ulp_``. Use a distinct prefix per executable when a
project builds several ULP binaries.
Generate the ``.bin`` payload and symbol header/linker export files for a
ULP executable. This is the ULP analog of :cmakev2:ref:`idf_build_binary`.
#]]
function(ulp_build_binary executable)
cmake_parse_arguments(_ULP "" "PREFIX" "" ${ARGN})
if(_ULP_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unexpected arguments to ulp_build_binary(): ${_ULP_UNPARSED_ARGUMENTS}")
endif()
if(DEFINED _ULP_PREFIX)
ulp_add_build_binary_targets(${executable} PREFIX "${_ULP_PREFIX}")
elseif(ULP_PREFIX_APPEND_BIN_NAME)
set(prefix "ulp_")
if(DEFINED ULP_VAR_PREFIX)
set(prefix "${ULP_VAR_PREFIX}")
endif()
string(MAKE_C_IDENTIFIER "${prefix}${executable}_" prefix)
ulp_add_build_binary_targets(${executable} PREFIX "${prefix}")
else()
ulp_add_build_binary_targets("${executable}")
endif()
endfunction()
# Build the single default ULP executable from the main component.
function(__ulp_project_default)
set(ulp_app_name "${PROJECT_NAME}")
set(executable_args COMPONENTS main)
set(binary_args)
if(NOT BUILD_FSM)
list(APPEND executable_args MAPFILE_TARGET ${ulp_app_name}_mapfile)
endif()
if(DEFINED ULP_VAR_PREFIX AND NOT "${ULP_VAR_PREFIX}" STREQUAL "")
list(APPEND binary_args PREFIX ${ULP_VAR_PREFIX})
endif()
ulp_build_executable(${ulp_app_name} ${executable_args})
ulp_build_binary(${ulp_app_name} ${binary_args})
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}")
endfunction()
#[[api
.. cmakev2:macro:: ulp_project_default
.. code-block:: cmake
ulp_project_default()
Build a single ULP executable from the ``main`` component and its transitive
dependencies, the ULP analog of :cmakev2:ref:`idf_project_default`. Calls
:cmakev2:ref:`ulp_project_init`, then builds the executable (named after
``PROJECT_NAME``) with :cmakev2:ref:`ulp_build_executable` and generates its
embeddable artifacts with :cmakev2:ref:`ulp_build_binary`.
#]]
macro(ulp_project_default)
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)
__ulp_project_default()
endmacro()