CMake Architecture and Usage

CMake Architecture

The project’s CMakeLists.txt directory structure and invocation relationships are shown below:

../../_images/build_cmake_project_structure.svg
  • SOC Project: Each SOC project root directory contains a CMakeLists.txt as the build entry point. All MCU projects under it are added here. Corresponding to amebaxxx_gcc_project/CMakeLists.txt.

  • MCU Project: Each MCU project uses unified toolchain and configuration parameters (some configurations may vary due to firmware or ROM). Different MCU projects may use different toolchains. An MCU project includes builds of one or more firmwares. Corresponding to amebaxxx_gcc_project/project_xxx/CMakeLists.txt

  • Firmware: Mainly includes image1, image2, image3. Each firmware will generate a corresponding axf file after build done. The axf files are then processed to generate the final bin files that can be downloaded to the chip. The differences are as follows:

    • image1: Defines the common infrastructure for bootloader and system flash layout in MCU systems, providing a secure bootloader with convenient software upgrade support. Corresponding to amebaxxx_gcc_project/project_xxx/asdk/make/image1/CMakeLists.txt.

    • image2: The main application firmware of the SOC, typically containing a real-time OS (FreeRTOS) and application tasks. Corresponding to amebaxxx_gcc_project/project_xxx/asdk|vsdk/make/image2/CMakeLists.txt.

    • image3: Highly configurable, allowing users to include only required security services. Protected by Read Protection (RDP), decrypted in the secure bootloader, and loaded into TrustZone-protected SRAM. Corresponding to amebaxxx_gcc_project/project_xxx/asdk/make/image3/CMakeLists.txt.

  • Component: Components are organized under the component directory at the same level as SOC projects. Each subdirectory represents a functional module with its own CMakeLists.txt defining compilation and linking configurations. For example, component/wifi/CMakeLists.txt. All required components are added by each firmware.

Attention

In the diagram above, the CMakeLists.txt in image1, image2, and image3 directories can independently add components and define generation rules for their respective firmwares.

Component CMakeLists.txt Structure

The CMakeLists.txt consists file for compiling a component consists of public part and private part:

Public Part

Appends the following to the Global Compilation Configuration:

  • Include directories: Added to global if other components need headers from this component.

  • Definitions: Global preprocessor macros added to global when this component is added (rarely used).

  • Link libraries: Required if the component is provided as a prebuilt library (not source).

Configuration variables: public_includes, public_definitions, public_libraries. Update these in the highlighted section of code block below. See examples in cmake/CMakeLists-template.cmake and APIs in List Operations.

set(public_includes)                #public include directories, NOTE: relative path is OK
set(public_definitions)             #public definitions
set(public_libraries)               #public libraries(files), NOTE: linked with whole-archive options

#------------------------------------------------------------------#
# Component public part, user config begin(DO NOT remove this line)

# set public_includes, public_definitions, public_libraries in this section

# Component public part, user config end(DO NOT remove this line)
#------------------------------------------------------------------#

ameba_global_include(${public_includes})
ameba_global_define(${public_defines})
ameba_global_library(${public_libraries}) #default: whole-achived

Note

If there’s no need to update the three types of configurations mentioned above for the current component, the highlighted code section can be left blank.

Private Part

Defines the component library build:

  • Source files: Source files for current component

  • Private includes: Only for current component

  • Private definitions: Only for current component

  • Private compile options: Only for current component

Configuration variables: private_sources, private_includes, private_definitions, private_compile_options. Update these in the highlighted section of code block below. See examples in cmake/CMakeLists-template.cmake and APIs in List Operations.

Attention

The final compilation configuration of a component (particularly include directories, definitions and compile options) is composed of both private part and public part, with the former taking higher priority for include directories and compile options . For example, the actual header search paths for a component include:

  • Private include directories added by the current component.

  • Global include directories from Global Compilation Configuration, which includes paths appended by both the current component and other components.

Therefore, configurations already added in public part should not be duplicated in private part.

General recommendations:

  • Prefer placing include directories in the private part to avoid header file pollution.

  • Exceptions: For generic or low-level components used by many others, placing includes in public part is preferred to improve reusability.

set(private_sources)                 #private source files, NOTE: relative path is OK
set(private_includes)                #private include directories, NOTE: relative path is OK
set(private_definitions)             #private definitions
set(private_compile_options)         #private compile_options

#------------------------------#
# Component private part, user config begin

 # set private_sources, private_includes, private_definitions, private_compile_options in this section

# Component private part, user config end
#------------------------------#

ameba_add_internal_library(foo
    p_SOURCES
        ${private_sources}
    p_INCLUDES
        ${private_includes}
    p_DEFINITIONS
        ${private_definitions}
    p_COMPILE_OPTIONS
        ${private_compile_options}
)

Note

Quick Guide

Modifying Existing Component Build Config

Refer to the instructions in CMakeLists.txt Structure, utilize Useful Types for Complex Logic Processing, and note that APIs from List Operations can be called multiple times. Below are some examples:

  • Add public header file paths. It is recommended to use relative paths:

    ameba_list_append(public_includes
        ${CMAKE_CURRENT_SOURCE_DIR}        # Access current dir by CMAKE_CURRENT_SOURCE_DIR, same as .
        ${CMAKE_CURRENT_SOURCE_DIR}/foo    # Access sub dir by CMAKE_CURRENT_SOURCE_DIR, same as ./foo
        foo                                # Access sub dir directly
    )
    
  • Add the link library paths. For the variables used here, you can refer to MCU Project-related Constants:

    ameba_list_append(public_libraries
        ${c_SDK_LIB_APP_DIR}/lib_foo.a
    )
    
  • Add private sources. It is recommended to use relative paths:

    ameba_list_append(private_sources
        foo/foo.c
        bar/bar.c
    )
    
  • Add private header file paths. It is recommended to use relative paths:

    ameba_list_append(public_includes
        ../common  # Access parent dir
        foo
        bar
    )
    
  • Add definitions:

    ameba_list_append(private_definitions
        __RTOS__
        MBEDTLS_CONFIG_FILE="mbedtls/config.h"
    )
    
  • Add compile options:

    ameba_list_append(private_compile_options
        -Wno-unused-parameter
    )
    

Integrating Code with Standalone Build System

For third-party code with independent build systems (e.g., CMake/Makefile-based components), non-intrusive integration solutions can be implemented to link them into the firmware. Specific adaptation strategies should be selected based on their build system types:

  1. Create a wrapper CMakeLists.txt in some directory using the template cmake/CMakeLists-template.cmake.

  2. Refer to Modifying Existing Component Build Config to config public part

  3. In private part, add the original CMake project via ameba_add_subdirectory() and use ameba_port_standalone_internal_library to link the target:

    ameba_add_subdirectory(path/to/your/cmakelists) # Add the real CMakeLists.txt dir of the wrapped component
    ameba_port_standalone_internal_library(foo)     # Add the real target of the wrapped component to link
    
  4. Refer to CMakeLists.txt Invocation Relationships to add the component directory in the firmware’s CMakeLists.txt:

    ameba_add_subdirectory(path/to/your/wrap/cmakelists)
    
  5. Compile and test

Commonly Used CMake Interfaces and Preset Constants

List Operations

ameba_list_append

ameba_list_append(<list_name> [<value> ...])

Appends elements to a list, supports appending multiple elements. Parameter description:

list_name:

List variable name

value:

Value(s) to append

ameba_list_append_if

ameba_list_append_if(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])

Appends elements to a list conditionally, supports appending multiple elements. Parameter description:

condition:

Variable name representing the condition

list_name:

List variable name

value:

Value(s) to append when condition is true

p_ELSE:

Optional keyword parameter, followed by value(s) to append when condition is false

else value:

Value(s) to append when condition is false

Attention

If the variable represented by condition is undefined or defined with a bool value of FALSE, it will be considered as false.

ameba_list_append_ifnot

ameba_list_append_ifnot(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])

Appends elements to a list conditionally (opposite functionality to ameba_list_append_if()). Parameter description:

condition:

Variable name representing the condition

list_name:

List variable name

value:

Value(s) to append when condition is false

p_ELSE:

Optional keyword parameter, followed by value(s) to append when condition is true

else value:

Value(s) to append when condition is true

Attention

If the variable represented by condition is undefined or defined with a bool value of FALSE, it will be considered as false.

ameba_list_append_ifdef

ameba_list_append_ifdef(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])

Appends elements to a list based on variable definition status. Parameter description:

condition:

Variable name representing the condition

list_name:

List variable name

value:

Value(s) to append when condition is defined (Note: condition can be FALSE)

p_ELSE:

Optional keyword parameter, followed by value(s) to append when condition is undefined

else value:

Value(s) to append when condition is undefined

Add Library

The following APIs are used to compile code into static libraries or to perform further processing on existing targets.

Note

These APIs have the following characteristics:

  • The firmware that links the static library depends on which firmware’s CMakeLists.txt it is added in. For example, if added in image2/CMakeLists.txt, the static library will be linked to firmware image2.

  • The actual target name generated by the name parameter in these APIs is combined with other information such as c_MCU_PROJECT_NAME and c_CURRENT_IMAGE. Users can obtain the real target name using the variable c_CURRENT_TARGET_NAME after API calls.

  • These APIs internally apply Global Compilation Configuration to compile the current target.

ameba_add_internal_library

ameba_add_internal_library(<name>
                           [p_SOURCES <sources> ...]
                           [p_INCLUDES <include dirs> ...]
                           [p_COMPILE_OPTIONS <compile options> ...]
                           [p_DEFINITIONS <definitions> ...]
                           [p_DEPENDENCIES <dependencies> ...]
)

Adds a static library, compiles it, and automatically links the target to the current firmware. The library file is generated under the build directory. Parameter descriptions:

name:

Target name. The actual library file is lib_${name}.a.

p_SOURCES:

Source files for the target.

p_INCLUDES:

Include directories for the target.

p_COMPILE_OPTIONS:

Compile options for the target.

p_DEFINITIONS:

Preprocessor definitions for the target.

p_DEPENDENCIES:

Dependencies for the target.

Attention

The library file generated by this API resides in the build directory. It does not check CONFIG_AMEBA_RLS internally and is always active.

ameba_port_standalone_internal_library

ameba_port_standalone_internal_library(<name>)

Links an existing static library target to the current firmware:

name:

Target name.

Tip

Particularly useful for non-intrusive adaptation of third-party library CMakeLists.txt refer to Integrating Code with Standalone Build System.

Attention

This API does not check CONFIG_AMEBA_RLS internally and is always active.

Add Subdirectory

ameba_add_subdirectory

ameba_add_subdirectory(<dir>)

Add a directory for compilation. Refer to add_subdirectory. When dir is an external path, the last-level directory name will be used as binary_dir. Parameter description:

dir:

Directory to be added for compilation

ameba_add_subdirectory_if

ameba_add_subdirectory_if(<condition> <dir>)

Add a directory for compilation based on specified condition. Other behaviors are same as ameba_add_subdirectory. Parameter description:

condition:

Variable name representing the condition

dir:

Directory to add for compilation when condition is met

ameba_add_subdirectory_ifnot

ameba_add_subdirectory_ifnot(<condition> <dir>)

Add a directory for compilation based on specified condition. Other behaviors are same as ameba_add_subdirectory. Parameter description:

condition:

Variable name representing the condition

dir:

Directory to add for compilation when condition is NOT met

Attention

Variables represented by condition that are undefined or defined with a bool value of FALSE will be considered unmet

ameba_add_subdirectory_if_exist

ameba_add_subdirectory_if_exist(<dir>)

When path or path/CMakeLists.txt doesn’t exist, silently return. When path/CMakeLists.txt exists, behaves same as ameba_add_subdirectory. Parameter description:

dir:

Directory to add for compilation when path/CMakeLists.txt exists

Constants Definition

The CMake script cmake/global_define.cmake defines several constants that can be directly used in script writing. Some examples are listed below:

Constant type

Variable name

Value

General Constants

c_BASEDIR

Root dir of the repo

c_CMAKE_FILES_DIR

${c_BASEDIR}/cmake

c_COMPONENT_DIR

${c_BASEDIR}/component

SoC Project-related Constants

c_SOC_TYPE

One of [“amebadplus”, “amebalite”, “amebasmart”]

c_SOC_PROJECT_DIR

${c_BASEDIR}/${c_SOC_TYPE}_gcc_project

MCU Project-related Constants

c_MCU_TYPE

One of [“km0”, “km4”, “kr4”, “ca32”, …]

c_MCU_PROJECT_NAME

If MCU project folder name is project_xxx, value is xxx

c_CURRENT_IMAGE

Current image target name, value like: target_img2_km4

c_MCU_KCONFIG_FILE

Kconfig file path for current MCU project

c_MCU_PROJECT_DIR

${c_SOC_PROJECT_DIR}/project_${c_MCU_TYPE}

c_SDK_LIB_APP_DIR

${c_MCU_PROJECT_DIR}/[asdk|vsdk]/lib/application

c_SDK_LIB_SOC_DIR

${c_MCU_PROJECT_DIR}/[asdk|vsdk]/lib/soc

Component-related Constants

c_CMPT_AT_CMD_DIR

${c_COMPONENT_DIR}/at_cmd

c_CMPT_EXAMPLE_DIR

${c_COMPONENT_DIR}/example

c_CMPT_OS_DIR

${c_COMPONENT_DIR}/os

c_CMPT_WIFI_DIR

${c_COMPONENT_DIR}/wifi

c_CMPT_CRASHDUMP_DIR

${c_COMPONENT_DIR}/soc/common/crashdump

c_CMPT_SOC_DIR

${c_COMPONENT_DIR}/soc/${c_SOC_TYPE}

Advanced Learning Readings

Directory structure of cmake:

cmake
├── flags                              # Global compile and link options
│   ├── ca32
│   │   ├── compile_options.cmake
│   │   └── link_options.cmake
│   ├── common
│   │   ├── compile_options.cmake
│   │   └── link_options.cmake
│   ├── km0
│   │   ├── compile_options.cmake
│   │   └── link_options.cmake
│   ├── km4
│   │   ├── compile_options.cmake
│   │   └── link_options.cmake
│   └── kr4
│       ├── compile_options.cmake
│       └── link_options.cmake
├── CMakeLists-template.cmake        # CMakeLists.txt template for component
├── common.cmake                     # Project related APIs
├── global_define.cmake              # Global defined parameters
├── utility_base.cmake               # Utility APIs (lower level)
├── utility.cmake                    # Utility APIs (upper level)
└── toolchain                        # Toolchain defines
    ├── ameba-toolchain-asdk-10.3.1.cmake
    ├── ameba-toolchain-asdk-12.3.1.cmake
    ├── ameba-toolchain-check.cmake
    ├── ameba-toolchain-common.cmake
    └── ameba-toolchain-vsdk-10.3.1.cmake

Global Compilation Configuration

Refers to compilation settings that are ​**shared by all components** within a specified scope.

Compilation Configuration Contents

  • Sources

  • Include directories

  • Compile options

  • Definitions

  • Link options

  • Link libraries

Note

  • Sources, include directories, compile options, and definitions are used for component compilation.

  • Link options and link libraries are used for firmware linking.

Global Compilation Configuration Sources

Primarily consists of two sources:

  • Settings in cmake/flags

  • Additional content appended by each component’s public part

Attention

  • The order of components’ appended configurations in the final global compilation settings (e.g., the order of header file paths) depends on the sequence in which components are added to the firmware

  • This ordering is unreliable. For order-sensitive configurations (like header file paths), it’s recommended to handle them through alternative approaches. For details see private part

Scope of Global Compilation Configuration

The global compilation configuration applies to each MCU project. For example, RTL8721Dx has separate configurations for km0 and km4, while RTL8730E has configurations for km0, km4, and ca32. These configurations are isolated. Components under the same MCU project use the same global compilation configuration. See the following diagram:

../../_images/build_cmake_project_structure_with_config.svg

Key points:

  • Each MCU project contains an independent compilation scope.

  • Different firmwares (image1, image2, image3) under the same MCU project share the same compilation config.

  • The same component is compiled with different global configurations across MCU projects (e.g., at_cmd in project_mcu1 vs. project_mcu2).

View Detailed Compilation Parameters of a Source File

Add the following code at the beginning of the target source file or introduce syntax errors, then compile. The compiler will report errors at the corresponding source file location and print detailed compilation parameters.

#error debug

Useful Types for Complex Logic Processing

SOC Type

Identify SOC types (e.g., amebadplus, amebalite, amebasmart) using variables: CONFIG_AMEBADPLUS, CONFIG_AMEBALITE, CONFIG_AMEBASMART

if(CONFIG_AMEBADPLUS)
    # Add code for amebadplus here
elseif(CONFIG_AMEBALITE)
    # Add code for amebalite here
elseif(CONFIG_AMEBASMART)
    # Add code for amebasmart here
endif()

Attention

  1. SOC type distinctions should be minimized. Prefer feature-based configuration switches instead.

  2. If such usage is mandatory, adhere to the principle of upward compatibility to ensure future SOC type additions require no modification of this logic (e.g., avoid adding new elseif clauses). Example implementation in component/rtk_coex/CMakeLists.txt:

    if(NOT CONFIG_AMEBAD)
        if (CONFIG_COEXIST_HOST)
            include(rtk_coex_api.cmake)
        endif()
    endif()
    

MCU Type

Identify MCU types (e.g., km0, km4, kr4, ca32) using string variable c_MCU_TYPE:

if(${c_MCU_TYPE} STREQUAL "km0")
    # Add code for km0 here
elseif(${c_MCU_TYPE} STREQUAL "km4")
    # Add code for km4 here
elseif(${c_SOC_TYPE} STREUQAL "ca32")
    # Add code for ca32 here
endif()

Common CMake Debugging Methods

Logging

Use CMake’s built-in message() or more readable alternatives: ameba_debug(), ameba_info(), ameba_warning(), ameba_fatal().

Example usage to halt execution for analysis:

message(FATAL_ERROR "stop here")

Troubleshooting Common CMake Errors

Tip

Always check the first error in terminal output when troubleshooting CMake issues

Undefined Reference Error

This error typically occurs during the linking stage when generating axf files. Common causes include:

  • Cause 1: The library containing the symbol is not linked

  • Cause 2: The source file containing the symbol is not compiled

  • Cause 3: The code block containing the symbol is not compiled

The following uses RTL8721Dx as an example to demonstrate troubleshooting steps for the above causes. Below is a sample link error output (partial information retained for demonstration):

[5/42] Linking C executable project_km4/asdk/make/image2/target_img2_km4.axf
FAILED: project_km4/asdk/make/image2/target_img2_km4.axf
ccache /opt/rtk-toolchain/asdk-10.3.1-4354/linux/newlib/bin/arm-none-eabi-gcc
-O2
-o project_km4/asdk/make/image2/target_img2_km4.axf

-Wl,--whole-archive
project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
project_km4/asdk/make/image2/swlib/lib_swlib.a
project_km4/asdk/make/image2/file_system/fatfs/lib_fatfs.a
project_km4/asdk/make/image2/file_system/littlefs/lib_littlefs.a
project_km4/asdk/make/image2/file_system/kv/lib_kv.a
project_km4/asdk/make/image2/file_system/vfs/lib_vfs.a
project_km4/asdk/make/image2/fwlib/lib_fwlib.a
project_km4/asdk/make/image2/hal/lib_hal.a
project_km4/asdk/make/image2/misc/lib_misc.a
project_km4/asdk/make/image2/lwip/lib_lwip.a

-Wl,--no-whole-archive

-lm
-O2
-o project_km4/asdk/make/image2/target_img2_km4.axf

-Wl,--whole-archive
project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
project_km4/asdk/make/image2/cmsis-dsp/lib_cmsis_dsp.a

-Wl,--no-whole-archive

-lm
-lstdc++
ld: amebadplus_gcc_project/project_km4/asdk/lib/soc/lib_chipinfo.a(ameba_rom_patch.o): in function `io_assert_failed':
(.text.io_assert_failed+0x12): undefined reference to `rtk_log_write_nano'

Key information from highlighted lines 1, 2, 33, 34:

  • MCU Project: km4

  • Firmware: image2, File: target_img2_km4.axf

  • Undefined symbol: rtk_log_write_nano, called in function io_assert_failed()

  • Source file: log.c

  • Component: swlib

  • Library: lib_swlib.a

Troubleshooting steps:

The library containing the symbol is not linked

  1. Verify if link parameters contain lib_swlib.a

  2. Line 9 shows project_km4/asdk/make/image2/swlib/lib_swlib.a is linked. If missing, check:

    • Whether swlib component is added in image2’s CMakeLists.txt

External Command Parameter Error

Typically caused by incorrect parameters in COMMAND sections of add_custom_target() or add_custom_command(), often due to empty CMake variables. Sample error output showing CMake’s -E usage:

FAILED: build/lib_atcmd.a
/usr/bin/cmake -E copy build/lib_atcmd.a
CMake Error: cmake version 3.30.2
Usage: /usr/bin/cmake -E <command> [arguments...]
Available commands:
capabilities              - Report capabilities built into cmake in JSON format
cat [--] <files>...       - concat the files and print them to the standard output
chdir dir cmd [args...]   - run command in a given directory
...

Corresponding CMake code example:

add_custom_command(
    OUTPUT lib/lib_atcmd.a
    COMMAND ${CMAKE_COMMAND} -E copy build/lib_atcmd.a ${output_path}
    DEPENDS build/lib_atcmd.a
)

This error occurs when ${output_path} is empty, resulting in incomplete copy command as shown in line 2.

Copied and replaced a source file but did not trigger recompilation

The CMake build system uses incremental compilation by default, meaning it only recompiles when source code or header files change. Its mechanism for detecting changes is ​**based on whether the file’s modification timestamp is newer than the previous build**.

When replacing a file with an older version (via copy-paste) while keeping its original modification timestamp (default behavior in Windows Explorer), the incremental compilation ​**will not be triggered** because the timestamp remains unchanged.

Recommended solutions for Windows environments: 1. ​**Create a new file** and copy the content into it (generates a new timestamp) 2. Use the build.py -c command to clean the build directory

Suppress Compilation Warnings as Errors

The global compilation configuration in cmake/flags/common/compile_options.cmake enables -Werror by default, which ​treats all compilation warnings as errors. To disable this behavior, use one of the following methods:

  • ​Method 1: Component-Specific Disable. Add compilation options in the target component’s CMakeLists.txt:

    ameba_list_append(private_compile_options
        -Wno-error  # Disable warning-as-error for this component
    )
    
  • ​Method 2: Global Disable. Modify the global configuration file cmake/flags/common/compile_options.cmake and ​comment out the -Werror line

Linking a static library with no-whole-archive option

Most static libraries processed by APIs are linked into the firmware using the whole-archive method by default. If you need to force a static library to use no-whole-archive, here are the solutions for different scenarios:

  • Scenario 1: Static libraries compiled using ameba_add_internal_library

    Add the parameter p_NO_WHOLE_ARCHIVE to the ameba_add_internal_library() function:

    ameba_add_internal_library(at_cmd
        p_NO_WHOLE_ARCHIVE
        p_SOURCES
            ${private_sources}
        p_INCLUDES
            ${private_includes}
        p_DEFINITIONS
            ${private_definitions}
        p_COMPILE_OPTIONS
            ${private_compile_options}
        p_DEPENDENCIES
            ${c_BUILD_INFO}
    )
    
  • Scenario 2: Static libraries added to linking via public_libraries

    Add the parameter p_NO_WHOLE_ARCHIVE when calling ameba_global_library():

    ameba_global_library(${public_libraries} p_NO_WHOLE_ARCHIVE)