IC:

概述

概念

  • SOC项目:每个SOC项目根目录都包含一个 CMakeLists.txt 作为构建入口,其下所有MCU项目都是在这里添加的。

  • MCU项目:每个MCU项目使用统一的工具链和配置参数(个别配置会因固件或ROM有差异),不同MCU项目可能使用不同的工具链,MCU项目下又包括了一个或多个固件的构建。

  • 固件:主要包括 image1image2image3,三者区别如下:

    • image1: 定义了MCU系统中引导加载程序(bootloader)和系统闪存布局的通用基础架构,并提供了一个安全引导加载程序,支持便捷的软件升级。

    • image2: 是该SOC的主应用固件,通常包含实时操作系统(FreeRTOS)和应用程序任务。

    • image3: 具有高度可配置性,允许用户仅包含所需的安全服务与功能。它受到读保护(RDP)的保护,将在安全引导加载程序中解密,并被加载至由TrustZone技术保护的安全静态随机存储器(SRAM)中。

  • 组件:组件是在和SOC项目同级的 component 目录下,按照不同的功能划分为不同的子目录,每个组件都有相应的 CMakeLists.txt 定义其编译及被链接等配置。

  • 固件构建:在各自MCU项目下 [asdk|vsdk]/make/[image1|image2|image3]/CMakeLists.txt 中根据需要添加所需组件并定义编译规则。

SDK CMake结构图

整个项目 CMakeLists.txt 目录结构和调用关系如下图所示:

../../_images/build_cmake_project_structure.svg

注意

在上图中, image1image2image3 目录中的 CMakeLists.txt 中可以为 各自固件添加相应的组件,他们是相互独立的

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

全局编译配置

全局编译配置指在一定范围内,所有的组件共享的编译配置内容

编译配置主要内容

  • 源文件 (sources)

  • 头文件路径 (include directories)

  • 编译选项 (compile options)

  • 预定义 (definitions)

  • 链接选项 (link options)

  • 链接库 (link libraries)

备注

  • 源文件,头文件路径,编译选项,预定义 用于组件编译。

  • 链接选项链接库 用于固件链接。

全局编译配置来源

主要由两部分来源:

注意

  • 最终全局编译配置中各组件追加部分的顺序(如头文件路径的顺序)取决于组件被固件add的顺序

  • 这个顺序是不可靠的,在顺序敏感的配置中(如头文件路径)最好通过别的方式处理,详见 组件private部分

全局编译配置作用域

全局编译配置 的作用域为各MCU项目,如 RTL8721Dxkm0km4 各有一套全局编译配置, RTL8730Ekm0km4ca32 各有一套全局编译配置,他们之间相互隔离,同一MCU项目下的组件编译时使用相同的全局编译配置,参考如下示意图:

../../_images/build_cmake_project_structure_with_config.svg

其中:

  • 可以看到每个MCU项目都各有一个独立的编译配置作用域。

  • 同一个MCU项目下的不同固件 image1image2image3 使用相同的编译配置。

  • 同一个组件在不同的MCU项目中会使用 不同的全局编译配置 分别编译,如上图中的 at_cmdproject_mcu1project_mcu2image2 中分别被编译。

组件编译CMakeLists.txt

组件public部分组件private部分 两部分组成:

  • 前者描述了该组件向 全局编译配置 中追加的内容

  • 后者描述了该 组件库文件 的编译配置。

组件public部分

该部分用于向 全局编译配置追加 如下编译配置:

  • 头文件路径: 如果其他组件要使用当前组件的头文件,则可以将当前组件头文件目录加入全局。

  • 预定义: 如果当前组件被添加编译时,需要同步在全局加入某些预定义(使用场景较少)。

  • 链接库: 如果 当前组件给下游客户时不提供源码,只提供库文件,则链接时需要链接库文件而不是target,所以需将要库文件路径加入全局以便链接时取出,库文件的编译可以在 组件private部分 中调用 ameba_add_external_app_library 完成。

上述三类配置在如下代码块中分别由 public_includespublic_definitionspublic_libraries 三个变量设置,用户可以在如下高亮代码行区域内对其进行更新。 可以参考模板 cmake/CMakeLists-template.cmake 中的一些示例,一些相关API可参考 list操作,具体例子可以参考 修改现有组件的编译配置

 1##########################################################################################
 2## * This part defines public part of the component
 3## * Public part will be used as global build configures for all component
 4
 5set(public_includes)                #public include directories, NOTE: relative path is OK
 6set(public_definitions)             #public definitions
 7set(public_libraries)               #public libraries(files), NOTE: linked with whole-archive options
 8
 9#------------------------------------------------------------------#
10# Component public part, user config begin(DO NOT remove this line)
11
12# set public_includes, public_definitions, public_libraries in this section
13
14# Component public part, user config end(DO NOT remove this line)
15#------------------------------------------------------------------#
16
17#WARNING: Fixed section, DO NOT change!
18ameba_global_include(${public_includes})
19ameba_global_define(${public_defines})
20ameba_global_library(${public_libraries}) #default: whole-achived

备注

如果当前组件没有必要对上述三类配置进行更新,上述高亮代码部分可以留空。

组件private部分

该部分描述了该组件库文件的编译配置,主要包括:

  • 源文件: 当前组件编译的源文件

  • 头文件路径: 仅对当前组件有效

  • 预定义: 仅对当前组件有效

  • 编译选项: 仅对当前组件有效

上述四类配置在如下代码块中分别由 private_sourcesprivate_includes , private_definitionsprivate_compile_options 四个变量设置,用户可以在高亮代码行区域内对其进行更新。 可以参考模板 cmake/CMakeLists-template.cmake 中的一些用法,一些相关API可参考 list操作

注意

组件最终的编译配置(特别是 头文件路径预定义编译选项)是由 组件private部分组件public部分 组成,头文件路径和编译选项前者优先级高于后者,例如组件实际头文件搜索路径包括:

  • 当前组件 private部分添加的头文件路径。

  • 全局编译配置 中的头文件路径, 包含 当前组件 的和 其他组件 追加的。

所以在 组件public部分 已经添加的编译配置就无需在 组件private部分 重复添加。

一般情况下建议:

  • 倾向于将头文件路径放在 组件private部分 避免头文件污染

  • 除非是比较通用或底层组件,会被很多其他组件用到,此时放在 组件public部分 可以提高复用性。

 1##########################################################################################
 2## * This part defines private part of the component
 3## * Private part is used to build target of current component
 4## * NOTE: The build API guarantees the global build configures(mentioned above)
 5## *       applied to the target automatically. So if any configure was already added
 6## *       to public above, it's unnecessary to add again below.
 7
 8#NOTE: User defined section, add your private build configures here
 9# You may use if-else condition to set these predefined variable
10# They are only for ameba_add_internal_library/ameba_add_external_app_library/ameba_add_external_soc_library
11set(private_sources)                 #private source files, NOTE: relative path is OK
12set(private_includes)                #private include directories, NOTE: relative path is OK
13set(private_definitions)             #private definitions
14set(private_compile_options)         #private compile_options
15
16#------------------------------#
17# Component private part, user config begin
18
19 # set private_sources, private_includes, private_definitions, private_compile_options in this section
20
21# Component private part, user config end
22#------------------------------#
23
24#WARNING: Select right API based on your component's release/not-release/standalone
25
26###NOTE: For open-source component, always build from source
27ameba_add_internal_library(foo
28    p_SOURCES
29        ${private_sources}
30    p_INCLUDES
31        ${private_includes}
32    p_DEFINITIONS
33        ${private_definitions}
34    p_COMPILE_OPTIONS
35        ${private_compile_options}
36)

备注

最佳实践

修改现有组件的编译配置

可以参考 组件编译CMakeLists.txt 相关说明,也可以使用 一些可用于复杂逻辑处理的判断类型,并且 list操作 API可以多次调用,如下是一些具体的例子:

  • 添加public头文件路径,建议使用相对路径:

    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
    )
    
  • 添加链接库路径,这里用到的变量可以参考 MCU项目相关的常量

    ameba_list_append(public_libraries
        ${c_SDK_LIB_APP_DIR}/lib_foo.a
    )
    
  • 添加源文件,建议使用相对路径:

    ameba_list_append(private_sources
        foo/foo.c
        bar/bar.c
    )
    
  • 添加private头文件路径:

    ameba_list_append(private_includes
        ../common  # Access parent dir
        foo
        bar
    )
    
  • 添加预定义:

    ameba_list_append(private_definitions
        __RTOS__
        MBEDTLS_CONFIG_FILE="mbedtls/config.h"
    )
    
  • 添加编译选项:

    ameba_list_append(private_compile_options
        -Wno-unused-parameter
    )
    

适配具备独立构建系统的代码

对于具备独立构建系统(如采用CMake/Makefile的第三方组件),可通过非侵入式集成方案实现链接进入固件,具体实施可依据其构建系统类型选择适配策略:

  1. 拷贝模板 cmake/CMakeLists-template.cmake 到一个合适的位置(比如为该代码组件新建一个wrap目录),并改名为 CMakeLists.txt

  2. 参考 修改现有组件的编译配置 设置 组件public部分

  3. 组件private部分 通过 ameba_add_subdirectory 添加原有 CMakeLists.txt 编译,然后使用 ameba_port_standalone_internal_library 进行适配:

    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. 参考 CMakeLists.txt调用关系图 在相应固件的 CMakeLists.txt 中添加上述 CMakeLists.txt 所在目录进行编译:

    ameba_add_subdirectory(path/to/your/wrap/cmakelists)
    
  5. 编译,测试

调整组件子模块组织关系

当一个组件很复杂,其中包含了多个子组件,此时组件根目录下的 CMakeLists.txt 通常用于添加各个子组件的组织关系,注意如下两点:

  • 因为 MCU项目中原则上不检查开关,直接add组件,所以组件的开关检查需要在这里完成,例如 component/bluetooth/CMakeLists.txt 开头:

    if(NOT CONFIG_BT)
        ameba_info("CONFIG_BT is off, bluetooth will not be built")
        return()
    endif()
    
  • 当拆分子组件时,组件根目录下的 CMakeLists.txt 要处理好底层子组件的添加逻辑,可以使用 一些可用于复杂逻辑处理的判断类型,例如 component/bluetooth/CMakeLists.txt

    ameba_add_subdirectory_ifnot(CONFIG_BT_INIC api)
    ameba_add_subdirectory_if(CONFIG_BT_AUDIO_CODEC_SUPPORT bt_audio)
    ameba_add_subdirectory(bt_mp)
    ameba_add_subdirectory(driver)
    ameba_add_subdirectory_ifnot(CONFIG_BT_INIC example)
    ameba_add_subdirectory(osif)
    ameba_add_subdirectory(rtk_coex)
    if(CONFIG_BT_ZEPHYR)
        ameba_add_subdirectory(zephyr)
    elseif(NOT CONFIG_BT_INIC)
        ameba_add_subdirectory(rtk_stack)  # refer to ble_mesh_stack
    endif()
    

component中新增组件

当需要新增一个独立组件时,按如下步骤实施:

  1. cmake/global_define.cmake 中添加组件的路径变量,参考如下组件:

    ameba_set(c_CMPT_WIFI_DIR         ${c_COMPONENT_DIR}/wifi)
    ameba_set(c_CMPT_WPAN_DIR         ${c_COMPONENT_DIR}/wpan)
    
    ameba_set(c_CMPT_CRASHDUMP_DIR    ${c_COMPONENT_DIR}/soc/common/crashdump)
    ameba_set(c_CMPT_LZMA_DIR         ${c_COMPONENT_DIR}/soc/common/lzma)
    
    ameba_set(c_CMPT_FOO_DIR         ${c_COMPONENT_DIR}/foo)  # new component
    

    注意

    如果组件路径与 SOC类型MCU类型 相关,则应将其加入到 cmake/global_define.cmakeameba_reset_global_define() 宏中,例如:

    macro(ameba_reset_global_define)
        ameba_set(c_CMPT_USRCFG_DIR ${c_COMPONENT_DIR}/soc/usrcfg/${c_SOC_TYPE})
        ameba_set(c_CMPT_BOOTLOADER_DIR ${c_CMPT_SOC_DIR}/bootloader)
    
        ameba_set(c_CMPT_FOO_DIR ${c_CMPT_SOC_DIR}/foo)  # new component
    endmacro()
    
  2. component 下新建组件目录并参考 组件编译CMakeLists.txt 添加相关 CMakeLists.txt

  3. 参考 CMakeLists.txt调用关系图 在相应固件的 CMakeLists.txt 中添加:

    ameba_add_subdirectory(${c_CMPT_FOO_DIR})
    

一些常用的CMake接口和预设常量

list操作

ameba_list_append

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

向list中追加元素,支持追加多个,参数说明:

  • list_name:list变量名

  • value:待追加的值

ameba_list_append_if

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

基于一定的条件向list中追加元素,支持追加多个,参数说明:

  • condition:判定条件的变量名

  • list_name:list变量名

  • value:当 condition 成立时向list中追加的值

  • p_ELSE:可选的关键字参数,其后面紧跟的值会在 condition 不成立时追加到list中

  • else value:当 condition 不成立时向list中追加的值

注意

condition 所表示的变量未定义或定义了但布尔值为 FALSE 都会被认为不成立

ameba_list_append_ifnot

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

基于一定的条件向list中追加元素,支持追加多个,与 ameba_list_append_if() 功能相反,参数说明:

  • condition:判定条件的变量名

  • list_name:list变量名

  • value:当 condition 不成立时向list中追加的值

  • p_ELSE:可选的关键字参数,其后面紧跟的值会在 condition 成立时追加到list中

  • else value:当 condition 成立时向list中追加的值

注意

condition 所表示的变量未定义或定义了但布尔值为 FALSE 都会被认为不成立

ameba_list_append_ifdef

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

基于一定的条件向list中追加元素,支持追加多个,参数说明:

  • condition:判定条件的变量名

  • list_name:list变量名

  • value:当 condition 被定义时向list中追加的值( 注意:此时 condition 可以是 FALSE )

  • p_ELSE:可选的关键字参数,其后面紧跟的值会在 condition 未定义时追加到list中

  • else value:当 condition 未定义时向list中追加的值

添加库

如下API用于将代码编译为静态库或者对已有target进行进一步处理

备注

这些API具有如下特点:

  • 静态库被 哪个固件链接取决于是在哪个固件CMakeLists.txt 中添加,例如在 image2/CMakeLists.txt 中添加,则相应静态库会被固件 image2 链接

  • 如下API中的target实际名称是由 name 参数及其他信息如 c_MCU_PROJECT_NAMEc_CURRENT_IMAGE 组合而成,用户可以在API调用过后使用变量 c_CURRENT_TARGET_NAME 来获取真实的target名称

  • 这些API内部会自动使用 全局编译配置 来编译当前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> ...]
)

添加一个静态库,执行编译且 相应target会被自动链接到当前固件,库文件输出到默认的 build 目录下,参数说明:

  • name:target名称,实际完整的库文件为 lib_${name}.a

  • p_SOURCES:target源文件

  • p_INCLUDES:target头文件路径

  • p_COMPILE_OPTIONS:target编译选项

  • p_DEFINITIONS:target预定义

  • p_DEPENDENCIES:target依赖

注意

该API生成的库文件在build目录下,其内部不会检查 CONFIG_AMEBA_RLS,始终生效

ameba_port_standalone_internal_library

ameba_port_standalone_internal_library(<name>)

将一个 已有 静态库target 添加到当前固件的链接 :

  • name:target名称

小技巧

特别适用于非侵入性的适配第三方库的 CMakeLists.txt,参考 适配具备独立构建系统的代码

注意

该API其内部不会检查 CONFIG_AMEBA_RLS,始终生效

ameba_add_external_app_library

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

添加一个静态库,执行编译但 相应target不会被自动链接到当前固件,需要在 组件public部分 中指定,库文件会被输出到 ${c_SDK_LIB_APP_DIR} (参考 MCU项目相关的常量)目录中,且会执行 objcopy -g -D 操作,参数说明:

  • name:target名称,实际完整的库文件为 lib_${name}.a

  • p_SOURCES:target源文件

  • p_INCLUDES:target头文件路径

  • p_COMPILE_OPTIONS:target编译选项

  • p_DEFINITIONS:target预定义

  • p_DEPENDENCIES:target依赖

注意

CONFIG_AMEBA_RLSTRUE 时该该API什么也不做直接返回

添加子目录

ameba_add_subdirectory

ameba_add_subdirectory(<dir>)

添加一个目录用于编译,可参考 add_subdirectory,此外,当 dir 为外部路径时会取其最后一级目录名作 binary_dir,参数说明:

  • dir:要添加编译的目录

ameba_add_subdirectory_if

ameba_add_subdirectory_if(<condition> <dir>)

基于一定的条件添加一个目录用于编译,其他同 ameba_add_subdirectory,参数说明:

  • condition:判定条件的变量名

  • dir:条件成立时要添加编译的目录

ameba_add_subdirectory_ifnot

ameba_add_subdirectory_ifnot(<condition> <dir>)

基于一定的条件添加一个目录用于编译,其他同 ameba_add_subdirectory,参数说明:

  • condition:判定条件的变量名

  • dir:条件不成立时要添加编译的目录

注意

condition 所表示的变量未定义或定义了但布尔值为 FALSE 都会被认为不成立

ameba_add_subdirectory_if_exist

ameba_add_subdirectory_if_exist(<dir>)

当路径不存在时,如果 CONFIG_AMEBA_RLSTRUE 时直接静默返回,否则会报错,路径存在时同 ameba_add_subdirectory,参数说明:

  • dir:条件不成立时要添加编译的目录

常量定义

CMake脚本 cmake/global_define.cmake 中定义了一些常量可以直接用于编写脚本,列举部分如下:

通用的常量

Variable Name

Value

c_BASEDIR

Root dir of the repo

c_CMAKE_FILES_DIR

${c_BASEDIR}/cmake

c_COMPONENT_DIR

${c_BASEDIR}/component

c_EMPTY_C_FILE

${c_CMAKE_FILES_DIR}/empty_file.c

SOC项目相关的常量

Variable Name

Value

c_SOC_TYPE

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

c_SOC_TYPE_UPPER

One of [“AMEBADPLUS”, “AMEBALITE”, “AMEBASMART”]

c_SOC_TYPE_CAMEL

One of [“AebaDplus”, “AmebaLite”, “AmebaSmart”]

c_SOC_PROJECT_DIR

${c_BASEDIR}/${c_SOC_TYPE}_gcc_project

MCU项目相关的常量

Variable Name

Value

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

组件相关的常量

Variable Name

Value

c_CMPT_APP_DIR

${c_COMPONENT_DIR}/application

c_CMPT_AT_CMD_DIR

${c_COMPONENT_DIR}/at_cmd

c_CMPT_AUDIO_DIR

${c_COMPONENT_DIR}/audio

c_CMPT_BLUETOOTH_DIR

${c_COMPONENT_DIR}/bluetooth

c_CMPT_DYN_APP_DIR

${c_COMPONENT_DIR}/dynamic_app

c_CMPT_ETHERNET_DIR

${c_COMPONENT_DIR}/ethernet

c_CMPT_EXAMPLE_DIR

${c_COMPONENT_DIR}/example

c_CMPT_FILE_SYSTEM_DIR

${c_COMPONENT_DIR}/file_system

c_CMPT_LWIP_DIR

${c_COMPONENT_DIR}/lwip

c_CMPT_NETWORK_DIR

${c_COMPONENT_DIR}/network

c_CMPT_OS_DIR

${c_COMPONENT_DIR}/os

c_CMPT_RTK_COEX_DIR

${c_COMPONENT_DIR}/rtk_coex

c_CMPT_SDIO_DIR

${c_COMPONENT_DIR}/sdio

c_CMPT_SSL_DIR

${c_COMPONENT_DIR}/ssl

c_CMPT_UI_DIR

${c_COMPONENT_DIR}/ui

c_CMPT_USB_DIR

${c_COMPONENT_DIR}/usb

c_CMPT_UTILS_DIR

${c_COMPONENT_DIR}/utils

c_CMPT_TFLITE_DIR

${c_COMPONENT_DIR}/tflite_micro

c_CMPT_WIFI_DIR

${c_COMPONENT_DIR}/wifi

c_CMPT_WPAN_DIR

${c_COMPONENT_DIR}/wpan

c_CMPT_CRASHDUMP_DIR

${c_COMPONENT_DIR}/soc/common/crashdump

c_CMPT_LZMA_DIR

${c_COMPONENT_DIR}/soc/common/lzma

c_CMPT_SOC_DIR

${c_COMPONENT_DIR}/soc/${c_SOC_TYPE}

常见问题与建议

查看某个源文件的详细编译参数

在相应源文件开头加入如下代码或者加入一些语法错误然后进行编译,编译器会在相应源文件处出错并打印详细编译参数

#error debug

Undefined Reference 错误

该错误一般发生在链接生成axf文件阶段,常见原因有如下几类:

  • 原因一:符号所在库文件未被链接

  • 原因二:符号所在源文件未被编译

  • 原因三:符号所在代码块未被编译

下面以 RTL8721Dx 为例,针对上述原因依次进行排查,如下是一个链接错误的输出(这里仅保留了部分信息用于展示):

 1[5/42] Linking C executable project_km4/asdk/make/image2/target_img2_km4.axf
 2FAILED: project_km4/asdk/make/image2/target_img2_km4.axf
 3ccache /opt/rtk-toolchain/asdk-10.3.1-4354/linux/newlib/bin/arm-none-eabi-gcc
 4-O2
 5-o project_km4/asdk/make/image2/target_img2_km4.axf
 6
 7-Wl,--whole-archive
 8project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
 9project_km4/asdk/make/image2/swlib/lib_swlib.a
10project_km4/asdk/make/image2/file_system/fatfs/lib_fatfs.a
11project_km4/asdk/make/image2/file_system/littlefs/lib_littlefs.a
12project_km4/asdk/make/image2/file_system/kv/lib_kv.a
13project_km4/asdk/make/image2/file_system/vfs/lib_vfs.a
14project_km4/asdk/make/image2/fwlib/lib_fwlib.a
15project_km4/asdk/make/image2/hal/lib_hal.a
16project_km4/asdk/make/image2/misc/lib_misc.a
17project_km4/asdk/make/image2/lwip/lib_lwip.a
18
19-Wl,--no-whole-archive
20
21-lm
22-O2
23-o project_km4/asdk/make/image2/target_img2_km4.axf
24
25-Wl,--whole-archive
26project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
27project_km4/asdk/make/image2/cmsis-dsp/lib_cmsis_dsp.a
28
29-Wl,--no-whole-archive
30
31-lm
32-lstdc++
33ld: amebadplus_gcc_project/project_km4/asdk/lib/soc/lib_chipinfo.a(ameba_rom_patch.o): in function `io_assert_failed':
34(.text.io_assert_failed+0x12): undefined reference to `rtk_log_write_nano'

针对上述错误首先确认基本信息,通过高亮行1,2,33,34可知:

  • 出现链接问题的 MCU项目 是:km4

  • 出现链接问题的 固件 是:image2,文件名 是:target_img2_km4.axf

  • 未定义的符号是rtk_log_write_nano,其是在函数 io_assert_failed() 中被调用的

  • 进一步确认:

    • 包含该符号定义的 源文件log.c

    • 包含该源文件的 组件swlib

    • 该组件的生成的 库文件lib_swlib.a

然后依次排查上述原因:

符号所在库文件未被链接

  1. 排查述错误信息中的链接参数是否包含 库文件lib_swlib.a

  2. 通过上述错误信息第9行可知 project_km4/asdk/make/image2/swlib/lib_swlib.a 已经被image2加入了链接,如果没有则需要排查:

一些可用于复杂逻辑处理的判断类型

SOC类型

SOC类型如 amebadplusamebaliteamebasmart 可以通过如下变量判断: CONFIG_AMEBADPLUSCONFIG_AMEBALITECONFIG_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()

注意

  1. SOC类型的区分应当尽可能消除,更好的方法是使用特性之类的开关配置替代

  2. 如果一定要使用应秉持向上兼容的原则,即未来有新增SOC类型时 无需改动此处逻辑 (比如增加新的 elseif),例如 :file:component/rtk_coex/CMakeLists.txt 中的写法:

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

MCU类型

SOC类型如 km0km4kr4ca32 等,可以通过STR类型变量 c_MCU_TYPE 获取:

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

CMake常见的一些debug方法

日志

可以使用CMake内置 message() 或可读性更好的 ameba_debug()ameba_info()ameba_warning()ameba_fatal()

使用如下方式打印日志, CMake在配置阶段执行到代码处会停止,通常可以用于分析代码执行情况

message(FATAL_ERROR "stop here")

常见CMake错误排查

小技巧

排查CMake错误要从终端输出中的 第一个 错误开始看起

执行外部命令参数错误

通常是在调用CMake接口 add_custom_target()add_custom_command()COMMAND 中的命令参数有误导致,而根源更多是因为某些CMake变量为空导致。典型错误输出如下, 特征是会包括CMake的 -E 参数的usage:

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

出现此错误对应的CMake代码如下:

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

在上述代码中,如果变量 output_path 为空,拷贝命令就会变成了上述错误信息第2行缺少目的路径从而导致报错

拷贝替换了一个源文件,但没有重新编译

CMake构建系统默认是增量编译,即源码或头文件发生变化时才会重新编译,而其检测变化的依据是 文件的修改时间是否比上次更新

当从别处拷贝了一个修改时间更老的文件,如果文件修改时间戳保持不变(Windows下资源管理器默认行为)就不会触发增量编译

解决方法:

  • 方法一:Windows环境中新建文件,然后拷贝内容

  • 方法二:执行 build.py -c 进行清理

进阶阅读

特殊的编译配置

以源文件为单位设置编译配置

仅对某个或某些源文件设置编译配置,如预定义等,例如为每个源文件添加一个预定义 __FILE_Z_STR__,该预定义展开为对应源文件的文件名字符串,实现如下:

ameba_list_append(private_sources
    foo.c
    bar.c
)

foreach(src ${private_sources})
    cmake_path(GET src FILENAME filename)
    set_source_files_properties(${src} PROPERTIES COMPILE_DEFINITIONS "__FILE_Z_STR__=\"${filename}\"")
endforeach()

使用CMake内置接口 set_source_files_properties 可以精确对每个源文件设置编译配置

按源文件类型设置编译选项

ameba_list_append(private_compile_options
    -Wno-multichar                              # for both asm/c/cpp language
    $<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>  # for c language only
    $<$<COMPILE_LANGUAGE:CXX>:-Wno-narrowing>   # for cpp language only
    $<$<COMPILE_LANGUAGE:CXX>:-std=c++17>       # for cpp language only
)

阻止编译警告错误

全局编译配置中 cmake/flags/common/compile_options.cmake 默认使用了 -Werror 选项,即所有编译警告都会当作编译错误处理,如需解除该设定可参考下列方法:

  • 方法一:仅对当前组件生效,在当前组件的 CMakeLists.txt 添加编译选项:

    ameba_list_append(private_compile_options
        -Wno-error
    )
    
  • 方法二:对所有组件生效,修改全局编译配置 cmake/flags/common/compile_options.cmake 注释掉 -Werror 即可