一、为什么我喜欢用GN + Ninja来完成单片机项目的构建工作
自从2020年12月接触OpenHarmony后,就开始对GN + Ninja的构建系统非常感兴趣。然后自己尝试着借鉴OpenHarmony的构建系统做了纯GN + Ninja实现的mcu构建系统,并借鉴了buildroot的envsetup.sh脚本,制作了简化构建命令和切换开发板的脚本。
为什么我会坚持用GN + Ninja 作为构建系统呢?主要是 GN + Ninja 构建系统有以下几个优点:
- 超快的构建和编译速度(吊打makefile、scons,比CMake快一点点)
- GN配置文件可读性更强,配置也灵活
- 编译配置通过命令可以直接查询,也可以在生成的Ninja构建工程里验证。
- 组件依赖可以查询,依赖层级通过各组件的BUILD.gn文件直接看到
- 使用相对路径,项目灵活性高
- 配置好组件后,移植新板子的工作比makefile简单
GN + Ninja 构建系统的缺点
- 严重缺乏中文教程和文档(需要自行阅读英文文档和help,上手难度较高)
- 跨平台支持并不是非常好(win下的路径和命令格式都和Linux有区别)
口说无凭,我们可以看一下实际的效果
全新编译大约260个文件的(已清理过编译产物),包含腾讯TinyOS内核以及LVGL的项目,耗时仅需要5.12秒。在配置比较老的机子(例如6年前的I5处理器),也可以做到30秒内完成全新编译。开启ccache后,几乎任意电脑都可以在0.5秒内完成。
该工程大致组件和资源占用情况如图
如何查看工程的编译参数和依赖(已添加注释)
$ mdesc
Target
type: executable
toolchain:
configs (in order applying, try also --tree)
outputs
asmflags
-mcpu=cortex-m33
-mthumb
-mfloat-abi=hard
-mfpu=fpv5-sp-d16
-g
-x
assembler-with-cpp
cflags
-mcpu=cortex-m33
-mthumb
-mfloat-abi=hard
-mfpu=fpv5-sp-d16
-g
-O2
-fmessage-length=0
-fsigned-char
-ffunction-sections
-fdata-sections
-fdiagnostics-color=always
-fno-builtin
-fno-strict-aliasing
-Wunused
-Wuninitialized
cflags_c
-MP
-MD
-std=gnu99
cflags_cc
-std=c++11
defines
LV_CONF_INCLUDE_SIMPLE
D_RA_CORE=CM33
D_RENESAS_RA_
include_dirs
ldflags
-mcpu=cortex-m33
-mthumb
-mfloat-abi=hard
-mfpu=fpv5-sp-d16
-g
-O2
-T
../../hardware/chip/ra4m2/ra/fsp.ld
--specs=rdimon.specs
-nostartfiles
-Xlinker
--gc-sections
Direct dependencies (try also "--all", "--tree", or even "--all --tree")
二、GN + Ninja 构建流程
- 通过命令行指定的product参数加载板级配置文件BUILD.gn和product.gni(hardware/board/${product}目录下)
- 使用product.gni的配置,配置工具链(product.gni负责传递参数,build目录的配置文件进行相关配置)
- 使用BUILD.gn的配置,生成依赖树,并检查按依赖顺序依次检查目标的c文件,生成ninja配置
- 调用ninja命令开始构建并完成编译构建工作
2.1 新建product目录(板级支持)并编写BUILD.gn和product.gni配置文件
BUILD.gn文件的配置内容
executable("ra4m2_dev") {
deps = [
":bsp",
"//hardware/chip/${chip}:${chip}_sdk",
]
}
source_set("bsp") {
sources = [
"ra_gen/common_data.c",
"ra_gen/hal_data.c",
"ra_gen/main.c",
"ra_gen/pin_data.c",
"ra_gen/vector_data.c",
"src/hal_entry.c",
]
}
product.gni文件配置内容
declare_args() {
compiler = "arm_none_eabi_gcc"
gcc = "arm-none-eabi-gcc"
chip = "ra4m2"
vendor = "Renesas"
app = "dummy"
kernel = "None"
build_type = "release"
ccache = true
}
然后板级支持这边是搞定了,接下来搞芯片级支持目录
2.2 新建chip目录(芯片级支持)并编写BUILD.gn配置文件
新建hardware/chip/ra4m2目录,并拷贝示范代码(e2 studio生成的)里的ra目录到该目录下。然后编写BUILD.gn文件。
路径为hardware/chip/ra4m2/BUILD.gn
文件内容为(可以在e2 studio 里清理构建后重新构建一次,拷贝需要编译的c文件列表)
source_set("ra4m2_sdk") {
sources = [
"ra/fsp/src/bsp/cmsis/Device/RENESAS/Source/startup.c",
"ra/fsp/src/bsp/cmsis/Device/RENESAS/Source/system.c",
"ra/fsp/src/bsp/mcu/all/bsp_clocks.c",
"ra/fsp/src/bsp/mcu/all/bsp_common.c",
"ra/fsp/src/bsp/mcu/all/bsp_delay.c",
"ra/fsp/src/bsp/mcu/all/bsp_group_irq.c",
"ra/fsp/src/bsp/mcu/all/bsp_guard.c",
"ra/fsp/src/bsp/mcu/all/bsp_io.c",
"ra/fsp/src/bsp/mcu/all/bsp_irq.c",
"ra/fsp/src/bsp/mcu/all/bsp_register_protection.c",
"ra/fsp/src/bsp/mcu/all/bsp_rom_registers.c",
"ra/fsp/src/bsp/mcu/all/bsp_sbrk.c",
"ra/fsp/src/bsp/mcu/all/bsp_security.c",
"ra/fsp/src/r_ioport/r_ioport.c",
]
}
接下来把示范代码(e2 studio生成的)里的 script/fsp.ld 和 Debug/memory_regions.ld 拷贝到 hardware/chip/ra4m2/ra目录下
然后修改hardware/chip/ra4m2/ra/fsp.ld文件的第5行,将memory_regions.ld的相对路径补全,否则链接时会失败。
INCLUDE ../../hardware/chip/ra4m2/ra/memory_regions.ld
然后板级配置就完成了。
2.3 新建build目录里的芯片支持
创建目录和文件,配置文件的路径是build/config/Renesas/BUILD.gn(默认会去build/config/{vendor
}/ 路径查找位置,vendor 参数由板级支持的product.gni文件提供)
build/config/Renesas/BUILD.gn的具体配置,可以从e2 studio 的示范代码里,查看makefile挖出来。主要是需要设置芯片类型,浮点支持,以及链接脚本,公共头文件等内容。
config("default") {
defines = [
"D_RA_CORE=CM33",
"D_RENESAS_RA_",
]
cflags = []
cflags += [
"-fmessage-length=0",
"-fsigned-char",
"-ffunction-sections",
"-fdata-sections",
"-fdiagnostics-color=always",
"-fno-builtin",
"-fno-strict-aliasing",
]
cflags += [
"-Wunused",
"-Wuninitialized",
]
ldflags = [
"-nostartfiles",
"-Xlinker",
"--gc-sections",
]
asmflags = [
"-x",
"assembler-with-cpp",
]
}
config("ra4m2") {
cflags = [
"-mcpu=cortex-m33",
"-mthumb",
"-mfloat-abi=hard",
"-mfpu=fpv5-sp-d16",
]
asmflags = cflags
cflags += [
"-g",
"-O2",
]
asmflags += [ "-g" ]
cflags_c = [ "-std=gnu99" ]
cflags_cc = [ "-std=c++11" ]
ldflags = cflags
ldflags += [
"-T",
"../../hardware/chip/${chip}/ra/fsp.ld",
"--specs=rdimon.specs",
]
include_dirs = [
"//hardware/chip/${chip}/ra/fsp/inc",
"//hardware/chip/${chip}/ra/fsp/inc/api",
"//hardware/chip/${chip}/ra/fsp/inc/instances",
"//hardware/chip/${chip}/ra/arm/CMSIS_5/CMSIS/Core/Include",
"//hardware/chip/${chip}/ra/fsp/inc/api",
"//hardware/board/${product}/src",
"//hardware/board/${product}/ra_cfg/fsp_cfg",
"//hardware/board/${product}/ra_cfg/fsp_cfg/bsp",
"//hardware/board/${product}/ra_gen",
]
}
添加完毕后,就可以愉快的编译啦。
编译测试
在命令行进入 git clone 下来的目录,运行以下指令加载环境
source build/envsetup.sh
若之前有配置好产品目标,则按上一次配置目标配置环境,并显示相关信息。
接下来运行 lunch 选择产品,ra4m2_dev
选择产品后会输出相关信息,并检查gn、ninja、工具链是否安装,对应路径(没安装的需要自行手动安装)
然后运行mbuild就可以开始编译了,因为文件非常少,2秒左右就完成了(ccache 命中缓存仅需 0.5秒以内)。
查看编译产物的目录,可以看到成功生成了bin、elf等文件
使用psize命令查看资源汇总使用情况,也和e2 studio 示范项目的资源使用情况差不多(flash部分的组件占用信息分析错误,需要再调整map分析代码)
开源项目链接地址: 死龙的MCU游乐场