AIoT应用创新大赛--我的项目我做主,使用GN+Ninja来完成构建系统(VSCode开发)

2022-03-15 10:48:53 浏览数 (2)

背景

自从21年接触了OpenHarmony后,就对GN Ninja的构建系统特别感兴趣,然后自己尝试着做了一个简化版的构建系统。而本次比赛中,如果不考虑使用官方IDE的话,又不想用makefile(主要是不会写),所以还是尝试着用GN Ninja完成了rt1062的构建系统。windows下未验证相关配置内容,理论上可以使用。

项目地址

https://gitee.com/walker2048/rt1060_play/tree/gn+ninja/

本次比赛的内容都会在这个地址开源

源码目录结构说明

代码语言:shell复制
.
├── 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环境下执行):

代码语言:shell复制
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、编译构建命令:

代码语言:shell复制
ninja -C out/${BOARD}

3、建议使用方式:

我比较懒,喜欢直接在~/.bashrc中配置好BOARD环境变量,并设置命令别名:

代码语言:shell复制
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文件进行烧录,两者都是带了地址的,不需要指定烧录地址)

代码语言:shell复制
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)

代码语言:shell复制
//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)

代码语言:shell复制
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",前面是依赖组件的相对目录,后面是组件名称(显式调用)。

代码语言:shell复制
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文件来说,更容易看懂,也更容易阅读和维护。从组件解耦来说,真正能做到所有的组件都能通过健康的依赖完成整个项目构建的(没有循环依赖和恶性依赖),功能解耦就做的不错了。

构建脚本分析

代码语言:shell复制
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的功能相对来说也会有点复杂,但是用习惯之后,就很舒服了。

0 人点赞