概述
概念
SOC项目:每个SOC项目根目录都包含一个
CMakeLists.txt
作为构建入口,其下所有MCU项目都是在这里添加的。MCU项目:每个MCU项目使用统一的工具链和配置参数(个别配置会因固件或ROM有差异),不同MCU项目可能使用不同的工具链,MCU项目下又包括了一个或多个固件的构建。
固件:主要包括
image1
,image2
,image3
,三者区别如下: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
目录结构和调用关系如下图所示:
注意
在上图中, image1
,image2
,image3
目录中的 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)
备注
源文件,头文件路径,编译选项,预定义 用于组件编译。
链接选项 和 链接库 用于固件链接。
全局编译配置来源
主要由两部分来源:
cmake/flags
中的设置,参考 SDK CMake结构图各个组件 组件public部分 中追加的内容
注意
最终全局编译配置中各组件追加部分的顺序(如头文件路径的顺序)取决于组件被固件add的顺序
这个顺序是不可靠的,在顺序敏感的配置中(如头文件路径)最好通过别的方式处理,详见 组件private部分
全局编译配置作用域
全局编译配置 的作用域为各MCU项目,如 RTL8721Dx
中 km0
和 km4
各有一套全局编译配置, RTL8730E
中 km0
,km4
及 ca32
各有一套全局编译配置,他们之间相互隔离,同一MCU项目下的组件编译时使用相同的全局编译配置,参考如下示意图:
其中:
可以看到每个MCU项目都各有一个独立的编译配置作用域。
同一个MCU项目下的不同固件
image1
,image2
,image3
使用相同的编译配置。同一个组件在不同的MCU项目中会使用 不同的全局编译配置 分别编译,如上图中的
at_cmd
在project_mcu1
和project_mcu2
的image2
中分别被编译。
组件编译CMakeLists.txt
由 组件public部分 和 组件private部分 两部分组成:
前者描述了该组件向 全局编译配置 中追加的内容
后者描述了该 组件库文件 的编译配置。
组件public部分
该部分用于向 全局编译配置 中 追加 如下编译配置:
头文件路径: 如果其他组件要使用当前组件的头文件,则可以将当前组件头文件目录加入全局。
预定义: 如果当前组件被添加编译时,需要同步在全局加入某些预定义(使用场景较少)。
链接库: 如果 当前组件给下游客户时不提供源码,只提供库文件,则链接时需要链接库文件而不是target,所以需将要库文件路径加入全局以便链接时取出,库文件的编译可以在 组件private部分 中调用 ameba_add_external_app_library 完成。
上述三类配置在如下代码块中分别由 public_includes
,public_definitions
,public_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_sources
,private_includes
,
private_definitions
,private_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)
备注
上述代码块中使用 ameba_add_internal_library 编译库文件,此外还可以根据需要选择其他API,详见 添加库,上述高亮代码部分修改可参考 修改现有组件的编译配置。
如果组件已经有独立构建脚本的目录,则参考 适配具备独立构建系统的代码。
最佳实践
修改现有组件的编译配置
可以参考 组件编译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/CMakeLists-template.cmake
到源文件目录下,并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件public部分 和 组件private部分
组件private部分 选择 ameba_add_internal_library 作为编译的api
参考 CMakeLists.txt调用关系图 在相应MCU项目下对应固件的
CMakeLists.txt
中添加上述源码目录进行编译ameba_add_subdirectory(path/to/your/cmakelists)
编译,测试
适配具备独立构建系统的代码
对于具备独立构建系统(如采用CMake/Makefile的第三方组件),可通过非侵入式集成方案实现链接进入固件,具体实施可依据其构建系统类型选择适配策略:
拷贝模板
cmake/CMakeLists-template.cmake
到一个合适的位置(比如为该代码组件新建一个wrap目录),并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件public部分
在 组件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
参考 CMakeLists.txt调用关系图 在相应固件的
CMakeLists.txt
中添加上述CMakeLists.txt
所在目录进行编译:ameba_add_subdirectory(path/to/your/wrap/cmakelists)
编译,测试
拷贝模板
cmake/CMakeLists-template.cmake
到一个合适的位置(比如为该代码组件新建一个wrap目录),并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件public部分,特别是是设置
public_libraries
为最终库文件的路径组件private部分 使用如下代码,注意修改其中的路径或名称:
add_custom_target(foo ALL # Replace foo with new name as you wish COMMAND ${CMAKE_COMMAND} -E echo "Building foo project" # Some prompt message while building COMMAND make -j -C /path/to/your/makefile all # Set your makefile path COMMENT "Building foo project using make" ) ameba_target_depend(${c_CURRENT_IMAGE} foo) # Add dependency, foo MUST be same as in add_custom_target above
参考 CMakeLists.txt调用关系图 在相应固件的
CMakeLists.txt
中添加上述CMakeLists.txt
所在目录进行编译ameba_add_subdirectory(path/to/your/wrap/cmakelists)
编译,测试
调整组件子模块组织关系
当一个组件很复杂,其中包含了多个子组件,此时组件根目录下的 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中新增组件
当需要新增一个独立组件时,按如下步骤实施:
在
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.cmake
的ameba_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()
在
component
下新建组件目录并参考 组件编译CMakeLists.txt 添加相关CMakeLists.txt
参考 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_NAME,c_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_RLS
为 TRUE
时该该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_RLS
为 TRUE
时直接静默返回,否则会报错,路径存在时同 ameba_add_subdirectory,参数说明:
dir
:条件不成立时要添加编译的目录
常量定义
CMake脚本 cmake/global_define.cmake
中定义了一些常量可以直接用于编写脚本,列举部分如下:
通用的常量
Variable Name |
Value |
---|---|
|
Root dir of the repo |
|
|
|
|
|
|
SOC项目相关的常量
Variable Name |
Value |
---|---|
|
One of [“amebadplus”, “amebalite”, “amebasmart”] |
|
One of [“AMEBADPLUS”, “AMEBALITE”, “AMEBASMART”] |
|
One of [“AebaDplus”, “AmebaLite”, “AmebaSmart”] |
|
|
MCU项目相关的常量
Variable Name |
Value |
---|---|
|
One of [“km0”, “km4”, “kr4”, “ca32”, …] |
|
If MCU project folder name is project_xxx, value is xxx |
|
Current image target name, value like: target_img2_km4 |
|
Kconfig file path for current MCU project |
|
|
|
|
|
|
组件相关的常量
Variable Name |
Value |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
常见问题与建议
查看某个源文件的详细编译参数
在相应源文件开头加入如下代码或者加入一些语法错误然后进行编译,编译器会在相应源文件处出错并打印详细编译参数
#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
然后依次排查上述原因:
符号所在库文件未被链接
排查述错误信息中的链接参数是否包含 库文件:
lib_swlib.a
通过上述错误信息第9行可知
project_km4/asdk/make/image2/swlib/lib_swlib.a
已经被image2加入了链接,如果没有则需要排查:image2的
CMakeLists.txt
中是否addswlib
组件检查组件的
CMakeLists.txt
,如果使用了 ameba_add_external_app_library 等API要检查其 组件public部分 中的public_libraries
是否正确添加库文件
符号所在源文件未被编译
在CMake的
build
目录中的MCU项目目录project_km4
查找文件是否存在log.o
:
执行命令:
find build/project_km4 -name log.o
结果如下:
build/project_km4/asdk/make/image1/swlib/CMakeFiles/swlib_target_loader_km4.dir/log.o
build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o
执行命令:
dir /s build\project_km4\log.o
结果如下:
C:\Users\sdk\amebadplus_gcc_project\build\project_km4\asdk\make\image1\swlib\CMakeFiles\swlib_target_loader_km4.dir 的目录
2025/03/12 14:56 21,096 log.o
1 个文件 21,096 字节
C:\Users\sdk\amebadplus_gcc_project\build\project_km4\asdk\make\image2\swlib\CMakeFiles\swlib_image2_km4.dir 的目录
2025/03/12 14:56 21,096 log.o
1 个文件 21,096 字节
查看黄色标记部分,如果未出现表示
log.c
未被编译,此时需要排查swlib
的CMakeLists.txt
中的问题如: 遗漏源文件,逻辑错误等
符号所在代码块未被编译
参考 原因二 中步骤找到
log.o
路径使用如下命令(这里需要用到工具链, 具体路径请参考 安装工具链)查看
log.o
中是否包含符号rtk_log_write_nano
:执行命令:
/opt/rtk-toolchain/asdk-10.3.1/linux/newlib/bin/arm-none-eabi-nm build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o | grep -w rtk_log_write_nano #Or use default nm: nm build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o | grep -w rtk_log_write_nano
执行命令:
C:\rtk-toolchain\asdk-10.3.1-4354\mingw32\newlib\bin\arm-none-eabi-nm.exe build\project_km4\asdk\make\image2\swlib\CMakeFiles\swlib_image2_km4.dir\log.o | findstr /R "\<rtk_log_write_nano\>"
如果输出 不包含 如下内容(注意其中的
T
)则表明log.a
未包含符号,此时需要排查log.c
中的条件编译是否正确00000000 T rtk_log_write_nano
一些可用于复杂逻辑处理的判断类型
SOC类型
SOC类型如 amebadplus
,amebalite
,amebasmart
可以通过如下变量判断: 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()
注意
SOC类型的区分应当尽可能消除,更好的方法是使用特性之类的开关配置替代
如果一定要使用应秉持向上兼容的原则,即未来有新增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类型如 km0
,km4
,kr4
,ca32
等,可以通过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
即可