本帖最后由 jf_50393217 于 2022-3-18 09:40 编辑
背景
自从21年接触了OpenHARMony后,就对GN+Ninja的构建系统特别感兴趣,然后自己尝试着做了一个简化版的构建系统。而本次比赛中,如果不考虑使用官方IDE的话,又不想用makefile(主要是不会写),所以还是尝试着用GN+Ninja完成了rt1062的构建系统。windows下未验证相关配置内容,理论上可以使用。
项目地址(已更新地址)
(https://gitee.com/walker2048/rt1060_play)
本次比赛的内容都会在这个地址开源
源码目录结构说明
- .
- ├── build #编译构建配置文件
- ├── components #常规组件(与硬件无关的组件)
- ├── hardware #硬件相关代码
- ├── out #编译产物目录(运行编译命令后生成)
- ├── solutions #应用程序目录
- └── TinyOS #腾讯TinyOS内核目录
复制代码
对于喜欢瞎折腾的人来说,没有使用自己最熟悉的目录和源码结构更开心的事儿了(**我的项目我做主,折腾不嫌事儿多**)。毕竟熟悉一个RTOS也是需要花费很多时间的。好在腾讯TinyOS的定位就是轻量化代码,简化代码功能和配置,能轻松的适应。如果大家不喜欢这样的目录结构,只需要自己修改对应的目录,并更新依赖配置即可。同时记得修改build/config/compiler/BUILD.gn文件中的 include_dirs字段内容,更新头文件目录就可以了。难度并不高。
GN + Ninja构建环境的优势
1. 代码依赖树清晰明了
2. GN语法容易看得懂
3. 构建脚本分工明确
4. 编译参数可见
5. 构建速度快
综上所述,GN + Ninja可以成为个人或者公司考虑新的构建系统时,一个非常优质的选择方案。
使用方法
1、 构建配置命令(使用export BOARD=TencentOS_tiny_EVB_AIoT命令先设定好BOARD环境变量,然后在bash环境下执行):
- gn gen out/${BOARD} --args="product = "${BOARD}""
复制代码
命令解析:若修改了构建脚本(如BUILD.gn文件),应先执行gn gen命令生成新的ninja构建文件,ninja才知道构建内容有变化了。一般不用和ninja命令拆分。out/${BOARD}为指定生成的编译构建配置目录, --args="product = "${BOARD}""为传递给gn的参数,告诉gn为product名称是${BOARD}环境变量配置相关参数(如查找真实的executable对象和相关依赖,以及编译参数)。
2、编译构建命令:
3、建议使用方式:
我比较懒,喜欢直接在~/.bashrc中配置好BOARD环境变量,并设置命令别名:
- alias gbuild='gn gen out/${BOARD} --args="product = "${BOARD}"" && ninja -C out/${BOARD}'
- alias gdesc='gn desc out/${BOARD} --args="product = "${BOARD}"" //hardware/board/${BOARD}'
- alias gformat='find . -name *.gni | xargs gn format && find . -name BUILD.gn | xargs gn format'
复制代码
例如以上分别是gbuild命令,执行了构建配置命令(一般在几毫秒到几十毫秒级别)和编译构建命令(全新构建在11秒左右,增量编译不到1秒,比官方工具快多了);gdesc命令,用于获取编译配置相关的依赖树,编译参数等内容;gformat命令,用于格式化gn配置文件。
也许有朋友会问怎样清理编译产物,直接rm -rf out目录即可。
4、烧录命令:
烧录使用pyocd进行烧录,执行命令(可使用elf文件,hex文件进行烧录,两者都是带了地址的,不需要指定烧录地址)
- pyocd flash out/${BOARD}/bin/${BOARD}.hex
复制代码
5、添加源码
完成功能不可避免的需要修改源码,添加c源文件和.h头文件。我们先说一下添加c源文件,可以在组件目录添加c源文件,只需要在组件的BUILD.gn配置文件中修改sources字段内容即可。若需要新增组件,可以复制其他组件的BUILD.gn文件过来,修改组件名称,组件依赖(deps),源文件(sources)等内容即可。若需要添加.h头文件,分为两种情况。1、改文件仅在组件内使用,此时不需要定义头文件目录(按相对路径引用即可);2、若该头文件为组件对外接口定义文件,则需要在前面提到的build/config/compiler/BUILD.gn文件中的 修改include_dirs字段内容。
对于不想了解细节的同学,只需要关注前面的内容即可,有兴趣了解GN构建系统的,可以往下看。
=====================================================================
接下来我们来说明一下GN构建配置文件和一些知识点。由于gn在国内项目应用的非常少,中文资料是少得可怜,想学习gn知识的,只能通过gn help命令和官网文档(基本也和help命令差不多),以及实际应用来学习。好在gn的配置文件是可读性比较高的,理解一些基本的知识点就可以用了。
GN的组件依赖
GN构建系统,它的依赖树根节点是executable类型的对象,然后在这个对象的依赖组件上,延伸至末端组件。例如本项目的依赖树展开为如下内容(可通过命令gn desc out/${BOARD} --args="product = "${BOARD}"" //hardware/board/${BOARD} deps --tree来获得,该命令的${BOARD}环境变量为TencentOS_tiny_EVB_AIoT)
- //TinyOS:TinyOS
- //TinyOS/arch/arm/arm-v7m/common:common
- //TinyOS/arch/arm/arm-v7m/cortex-m7:cortex-m7
- //TinyOS/kernel:kernel
- //components:components
- //components/drivers:drivers
- //components/lists:lists
- //components/uart:uart
- //components/utilities:utilities
- //hardware/board/TencentOS_tiny_EVB_AIoT:startup
- //hardware/board/TencentOS_tiny_EVB_AIoT/device:device
- //hardware/board/TencentOS_tiny_EVB_AIoT/xip:xip
- //solutions/helloworld:helloworld
复制代码
例如第一行的//TinyOS:TinyOS,这是executable对象所引用的第一个依赖组件,它的路径是根目录下的TinyOS目录,在此目录下的BUILD.gn配置文件中,使用的是TinyOS同名的对象。下面的例子会说明GN的组件常见配置。
GN组件配置文件语法说明
举个例子,我们拿TinyOS组件的配置文件作为例子(文件路径为TinyOS/BUILD.gn)
- source_set("TinyOS") {
- deps = [
- "arch/arm/arm-v7m/common",
- "arch/arm/arm-v7m/cortex-m7",
- "kernel",
- ]
- }
复制代码
在这个文件里,我们定义了一个名称为TinyOS的源代码集合对象,为什么要命名成跟文件夹名称一致的对象名称呢?这是GN其中一个规则:若上级指定依赖时,仅给出了路径,那默认的组件对象名称就是该路径最后的文件夹名称(隐式调用)。例如上面所示,TinyOS组件下依赖有三个组件,分别是arch路径的common组件和cortex-m7组件,以及kernel组件。这三个组件文件夹下,也有BUILD.gn配置文件,里面定义了组件名称和组件包含内容(若有依赖则添加deps依赖,无依赖则添加源码)。deps为本组件依赖的组件名称
# 末端组件配置语法说明
kernel组件下包含的源码定义如下(文件路径为TinyOS/kernel/BUILD.gn)。本文件中,组件名称与文件夹一致,上级依赖调用时不需要指定组件名称。若组件名称与文件夹名称不一致,则需指定组件名称。如本例中,source_set("kernel"),如果想定义成另一个组件名称(同目录有不同的组件,并且目录名称与组件名称不相符的情况下),可以改成source_set("kernel_name"),而上级组件调用时,应在目录后添加组件名称,比如"kernel:kernel_name",前面是依赖组件的相对目录,后面是组件名称(显式调用)。
- source_set("kernel") {
- sources = [
- "core/tos_barrier.c",
- "core/tos_binary_heap.c",
- "core/tos_bitmap.c",
- "core/tos_char_fifo.c",
- "core/tos_completion.c",
- "core/tos_countdownlatch.c",
- "core/tos_event.c",
- "core/tos_global.c",
- "core/tos_mail_queue.c",
- "core/tos_message_queue.c",
- "core/tos_mmblk.c",
- "core/tos_mmheap.c",
- "core/tos_mutex.c",
- "core/tos_pend.c",
- "core/tos_priority_mail_queue.c",
- "core/tos_priority_message_queue.c",
- "core/tos_priority_queue.c",
- "core/tos_ring_queue.c",
- "core/tos_robin.c",
- "core/tos_rwlock.c",
- "core/tos_sched.c",
- "core/tos_sem.c",
- "core/tos_stopwatch.c",
- "core/tos_sys.c",
- "core/tos_task.c",
- "core/tos_tick.c",
- "core/tos_time.c",
- "core/tos_timer.c",
- "pm/tos_pm.c",
- "pm/tos_tickless.c",
- ]
- }
复制代码
如上所示,GN的组件依赖,相对传统makefile和cmake文件来说,更容易看懂,也更容易阅读和维护。从组件解耦来说,真正能做到所有的组件都能通过健康的依赖完成整个项目构建的(没有循环依赖和恶性依赖),功能解耦就做的不错了。
# 构建脚本分析
- build/ #构建脚本目录
- ├── config #针对产品的配置目录
- │ ├── compiler
- │ │ └── BUILD.gn #编译器常见配置内容,例如编译优化设置等
- │ ├── product.gni #在这里设置产品相关的一些编译判断参数
- │ └── rt1062
- │ └── BUILD.gn #rt1062相关的宏定义和专用编译参数
- └── toolchain
- ├── BUILD.gn #工具链配置
- ├── clang.gni #预留给clang的配置文件
- └── gcc.gni #arm-none-eabi-gcc配置,编译命令构成配置
- .gn #GN读取的根配置,一般只引用其他文件
- BUILD.gn #这里的配置为的是以后方便添加开发板
- BUILDCONFIG.gn #在这里将各种工具和开发板的配置串起来
复制代码
基本上只需要修改build目录就能完成编译参数的变更,对应功能也拆分得比较细,如果能详细做注释就更好了。因为我不是专业的工程师,有很多注释内容不准确,甚至错误也有可能。
以上就是本次分享的内容,gn的功能相对来说也会有点复杂,但是用习惯之后,就很舒服了。
|