数通平台软件:编译体系实现
数通平台是一项规模庞大的软件工程,软件代码量少则数百M,多则达到几个G甚至十几个G。同时,数通平台软件支持多种可定制化需求,如支持多产品形态,支持多操作系统,支持多种CPU类型,支持多种版本类型,支持指定组件/模块编译等等要求。因此,仅仅依靠《Linux下automake使用方法》中简绍的autoconf与automake方法,显然是无法胜任数通平台软件的代码编译工作的。
因此,数通平台需要开发者自行实现满足自身要求的编译体系(或称编译框架,系统构建体系等)。那么,数通平台的编译体系需要实现哪些定制化目标呢?由于编译体系与其工程代码目录的规划具有较大的关联性,故而我们先从数通平台的工程代码目录规划说起。
0、工程代码目录规划
工程代码目录规划与说明如下表所示:
根目录 | 一级目录 | 二级目录 | 三级目录 | 四级目录 | 五级目录 | 备注 |
CODE |
|
|
|
|
|
|
| build |
|
|
|
| 系统构建目录 |
|
| device |
|
|
| 用于存放编译&打包脚本,产品编译相关的配置文件 |
|
| make |
|
|
| 用于存放核心makefile文件 |
|
| obj |
|
|
| 用于存放编译中间文件,包括.o文件及其依赖关系文件 |
| interface |
|
|
|
| 用于存放平台对外提供的公共接口头文件,可被产品引用 |
| manuals |
|
|
|
| 用户手册目录 |
| platform |
|
|
|
| 平台软件源代码目录 |
|
| include |
|
|
| 用于存放平台对外提供的公共接口头文件,限平台内部使用 |
|
| mpls |
|
|
| MPLS业务子系统目录(中间层目录) |
|
|
| common |
|
| 用于存放本子系统内的各个业务或模块的公共文件 |
|
|
| include |
|
| 用于存放本子系统内公共头文件,仅限子系统内包含 |
|
|
| ldp |
|
| LDP业务目录(中间层目录) |
|
|
|
| common |
| 用于存放本业务内的各个组件或模块的公共文件 |
|
|
|
| include |
| 用于存放本业务内公共头文件,仅限业务内包含 |
|
|
|
| ldp_pm |
| LDP PM组件目录(底层目录) |
|
|
|
|
| include | 头文件目录 |
|
|
|
|
| source | 源文件目录 |
|
|
|
|
| makefile | 底层makefile |
module.cfg | 模块(或组件)编译与链接配置文件,扩展用 | |||||
|
|
|
| ldp_sc |
|
|
makefile |
| 中间层makefile | ||||
|
|
|
| module.cfg |
| 模块(或业务)编译与链接配置文件,扩展用 |
|
|
| … |
|
|
|
|
|
| makefile |
|
| 中间层makefile |
module.cfg | 模块(或子系统)编译与链接配置文件,扩展用 | |||||
|
| smf |
|
|
| 系统管理框架子系统 |
|
|
| sysmgr |
|
| 系统管理代码目录 |
|
|
| exec |
|
| 进程入口源文件目录,包括系统管理进程、容器进程等 |
|
| … | … |
|
|
|
|
| makefile |
|
|
| 中间层makefile |
| product |
|
|
|
| 用于存放产品源代码、产品提供的接口头文件与库文件 |
| resoure |
|
|
|
| 用于存放资源文件,如组件配置文件,命令行配置文件等 |
| tools |
|
|
|
| 工具与脚本文件目录 |
| target |
|
|
|
| 用于存放编译与链接生成的目标文件、发布文件等 |
工程代码目录规划考虑了平台、产品、公共接口,资源文件目录与构建目录等。其中,最重要的平台目录,其下级目录一般按照[子系统]/[业务]/[组件或模块]的层次逐级向下划分,如此划分的目标是使得路由器能够支持最小粒度的组件级的业务升级、编译或裁减。
平台目录下,每一级目录下可设置一个common目录,用于存放公共可复用的代码,在gcc链接时,会自动链入同级目录的组件或模块动态库中;同时,可设置一个include目录,用于存放公共接口头文件,这些头文件的可见范围仅限于同级目录及其子目录下的源文件。
为了实现的更标准化一些,每个底层(组件或模块)目录,均固定设置三个文件/目录:头文件include目录、源文件source目录、底层makefile文件;可扩展设置一个模块的编译与链接配置文件module.cfg。
对于资源文件目录resource,其子目录树的构造方法,完全可以参考平台目录platform子目录树。这样,平台代码实现分仓管理时,资源文件也天然地支持分仓管理了!若资源文件需要进行编译或链接,完全可以复用platform代码的编译体系架构。
注:一个目录下的源代码只能引用本级include目录下的头文件,或上级目录下的头文件,如common/include、include,不可以随意引用其他平级目录或下级目录下的头文件。
1、编译体系实现目标
-
集成统一的编译入口脚本;
如下所示,集成一个build.sh脚本,支持编译不同产品类型(device type)、操作系统类型(os type)、CPU类型(cpu type)和版本类型(version type)等,支持clean功能,自带help等。
[root@HLZ device]# ./build.sh help
===================================================================================================
build.sh usage:
---------------------------------------------------------------------------------------------------
build.sh [device type] [cpu type] [os type] [version type] [module] [lib type] [stdout]
# Build all or the related module (with path).
* Mandatory Parameters:
[device type] : vros/r8000/r6000/ne40/cx/...
[cpu type] : x86/x86_64/p2020/p204x/mips/...
[os type] : linux/win32/vxworks/...
[version type]: debug/release/publish/...
[module] : all/smf/ldp/ipc/...
* Optional Parameters:
[lib type] : dynamic/static <default: dynamic>
[stdout] : 1:output error; 2:output warning+; 3:nooutput <default: 0:standard output>
Exam.1: ./build.sh vros x86 linux debug all
Exam.2: ./build.sh vros x86 linux debug sysmgr/common
Exam.3: ./build.sh vros x86_64 linux debug smf/exec static
Exam.4: ./build.sh r8000 p2020 linux debug all 2
---------------------------------------------------------------------------------------------------
build.sh help
# Display this help information.
---------------------------------------------------------------------------------------------------
build.sh [device type] [cpu type] [os type] [version type] [module] clean
# Clean the related obj & target files of the module.
Exam.1: ./build.sh vros x86 linux debug all clean
===================================================================================================
-
支持指定任意的目标编译;
如上所示,build脚本的输入参数[module]即可为all,编译与链接整个工程目录下的软件;也可以指定任意一个组件、进程或模块等。
若要支持指定任意的目标编译,需要解决两个问题:一是准确地查找到指定的目标,过滤掉不相关的目标;二是能够自动地获取指定目标的所有上级目录(可能是多级的)的相关头文件路径,这个在编译那些引用了上级头文件的.c或.cpp文件时需要。
对于工程代码下名称不唯一的目录,需要支持截断路径的目标编译,例如./build.sh vros x86 linux debugsysmgr/common,表示指定编译与链接sysmgr目录下的common子目录。
-
支持自适应多核编译;
通过/proc/cpuinfo自动获取编译机器的CPU核心数,并通过-j参数传递给make实现多核编译。
-
支持交叉编译;
在普通的CPU类型为x86/x84_64编译机上,可以编译生成ppc、arm和mips环境上运行的软件。当然,前提条件是编译主机上安装了相关的交叉编译工具链[交叉编译工具链的具体安装方法,我后面再详述]。通过build.sh脚本输入参数cpu_type,在makefile中设置相应的gcc/ld/ar交叉编译程序,引用对应头文件,链接对应动态库的方法,实现交叉编译功能。
-
支持C与C++源代码的混合编译与链接;
若要支持C/C++混合编译功能,首先需要源码开发者的配合,把对外提供的接口全部用extern "C"声明括起来,防止g++(C++)工具对源代码进行编译时,对相关的接口函数进行符号扩展,导致后续链接错误的产生。C++与C的链接工具没有什么区别,都可以用gcc,只不过C++链接时需要加上-lstdc++选项,而C链接时不需要。更简单地,C++链接时直接用g++代替gcc即可。
支持C/C++源代码的混合编译与链接功能,关键点是实现自动检测&编译某个目录下的源代码文件:
若目录下全部是C代码,直接使用gcc工具编译与链接;
若目录下存在C++代码文件,则使用g++工具编译与链接,即该目录下既有C代码又有C++代码时,使用g++工具进行编译与链接。
-
灵活的链接依赖问题处理;
数通平台代码中,需要生成一些进程文件如系统控制进程、容器进程,或生成可定制的大lib文件等。这些目标文件的生成,依赖于其他目标文件的编译与链接即.o的生成,所以存在链接的依赖顺序问题。
可通过实现一个专门用于处理链接依赖问题的接口,屏蔽依赖问题对编译框架核心makefile的影响,同时简化了编译体系负责人的维护工作。
-
灵活的动态库与进程文件生成;
对于中间层目录,默认情况下会自动链接其下所有子目录生成的目标文件(.o文件)。若设置了该中间层makefile文件的SUBDIRS接口,则只链接指定子目录生成的.o文件。
动态链接时,默认情况下底层makefile所在的目录会自动生成动态库文件(即.so文件,或进程文件),中间层makefile所在的目录不生成动态库;若某个中间层目录下的makefile显式设置了生成LIB的标志位,则该中间层目录将自动链接所有子目录.o或SUBDIRS指定的子目录.o生成一个.so,而其下层目录均不再生成.so文件,除非下层目录的makefile显式设置了生成LIB的标志位。
在生成某个组件.so文件时,若检测到其本级目录下存在common文件夹,则自动优先编译该common目录,并链接common.o目标文件到该组件so中。
如果想把分散在多个不同目录下的文件编译&链接生成一个大的so文件,仅仅只需要罗列相关目录即可。
支持生成平台系统使用的进程文件,也支持生成独立插件形式的可执行文件,即不依赖平台中的lib文件,可以直接在设备或编译机器上运行。
-
支持生成独立小程序;
独立小程序,顾名思义就是不依赖于平台或产品动态库,能够独立运行的可执行文件。独立小程序的运行环境有两种:一种是直接在编译主机上运行,如热补丁文件的封装处理小程序;一种是在交叉编译的目标机器上运行,如设备环境的初始化程序,网络设置小程序等。是否需要生成独立小程序,可根据实际需要,通过可扩展文件module.cfg来灵活设置。
-
支持性能检测工具,可灵活使能/去使能设置;
常用的性能检测工具有[需要编译体系支持]:
-
性能检测工具gprof;
-
内存问题检测工具Address sanitizer (ASan),dmalloc;
-
覆盖率统计工具gcov等
编译体系中集成常用代码检测工具的实现方法,后续我们另起篇章说明。
9.支持编译模块级或文件级裁减;
产品差异代码的编译可以通过设置产品编译宏(或功能宏)来裁减,过度采用编译宏来裁减,会造成代码难以维护,一般不提倡。可以通过makefile裁减,模块级或文件级裁减在module.cfg中进行特殊处理。
2、MAKE中间文件目录规划
MAKE中间文件,主要包括编译与链接目标文件.o和编译依赖文件.d。这些中间文件可以存放在由编译体系构造的build/obj/$(CPU_TYPE)/$(OS_TYPE)/$(VERSION_TYPE)/$(LIB_TYPE)/根目录下,该目录下会构造出一颗类似于源代码的目录树,每一个底层makefile所在的目录对应一个叶子节点,其下自动创建objs和deps两个目录,分别用于存放.o和.d文件。
对于编译(错误)日志log文件,可以输出到这个构造根目录下。
3、MAKE目标文件的目录规划
MAKE目标文件,包括进程文件,.so文件等,均存放于由编译体系自动生成的target/$(CPU_TYPE)/$(OS_TYPE)/$(VERSION_TYPE)/$(LIB_TYPE)/根目录下。
4、编译体系分层设计
编译体系的makefile分三部分,分别是核心侧makefile,中间层makefile和底层makefile。编译体系的核心侧makefile是编译与链接设置&处理的核心模块,通常存放于一个固定的目录下,如本文中build/make目录,由makefile的开发人员维护,其余两者均为用户侧makefile,需要业务开发人员根据编译体系提供的用户手册来创建与编辑,并根据自身的实际需要进行设置。
Makefile分层架构设计图:
4.1、核心侧makefile
核心侧makefile主要有以下几个部分:
-
顶级入口makefile文件,由build.sh提供其编译参数并直接调用;
-
编译器、连接器设置与编译、链接基本选项设置makefile;
-
编译基本规则与编译依赖命令makefile;
-
链接方法makefile;
-
组件编译&链接规则makefile;
-
进程编译&链接规则makefile;
-
头文件目录路径设置makefile;
-
编译体系基本目录定义makefile;
-
链接依赖问题处理makefile;
-
用户配置makefile,设置版本信息与各种工具(ASAN/gprof/valgrind等)支持开关等;
-
DEBUG调试makefile;
4.2、中间层makefile
###中间层目录makefile文件
###头文件路径(相对路径),加入本级目录下除include、common/include目录以外的头文件目录
INC_PATH:=
###设置本模块下参与编译的子目录(默认为空时,其下所有子目录均参与编译)
SUBDIRS:=
###设置编译本模块所依赖的lib文件
DEP_LIBS:=
###本级目录是否生成库文件标识(true/false)
###注:底层目录默认true自动生成,中层目录默认false不生成)
MAKE_LIB:=
###执行编译&链接
include $(XOS_MAKE_PATH)/xos_make_rule.mak
为了便于用户即开发人员使用,中间层makefile仅设置四个基本接口:
-
INC_PATH:该接口用于设置头文件目录。若为空时,默认加入本级目录下的include、common/include目录,若需要添加其他特殊的头文件目录,在此处添加;
-
SUBDIRS:该接口用于设置该目录下参与编译&链接的子目录列表。若为空时,其下所有子目录均参与编译,当然,会自动过滤掉include目录;
-
DEP_LIBS:编译本模块所依赖的LIB文件,如开源库、产品库等;
-
MAKE_LIB:本级目录是否生成库文件的标识。中层目录默认置为false,不生成LIB库;底层目录默认置为true,自动生成LIB库。若上级目录设置为true,生成LIB库,则其下级目录默认为false,不再生成LIB库,除非显式的设置为true。
模块(或子系统/业务)可扩展的个性化配置文件,在xos_make_rule.mak中通过sinclude或-include统一引用。模块的扩展配置文件可以设置子系统/业务内的编译选项,链接选项,依赖文件目录列表,特殊链接方法的设置等。
4.3、底层makefile
###底层目录makefile文件
###头文件路径(相对路径),加入本级目录下除include、common/include目录以外的头文件目录
INC_PATH:=
###源文件路径(相对路径),加入本级目录下非src的源文件目录(该目录必须唯一)
SRC_PATH:=
###设置编译本模块所依赖的lib文件
DEP_LIBS:=
###本级目录是否生成库文件标识(true/false)
###注:底层目录默认true自动生成,中层目录默认false不生成)
MAKE_LIB:=
###执行编译&链接
include $(XOS_MAKE_PATH)/xos_make_rule.mak
类似地,底层makefile也简化为四个基本接口:
-
INC_PATH:该接口用于设置头文件目录。若为空时,默认加入本级目录下的include目录,若需要添加其他特殊的头文件目录,在此处添加;
-
SRC_PATH:该接口用于设置该目录下参与编译&链接的源文件的目录列表。若为空时,默认仅编译source目录下的源文件。若需要添加其他特殊的源文件目录,在此处添加;
-
DEP_LIBS:编译本模块所依赖的LIB文件,如开源库、产品库等;
-
MAKE_LIB:本级目录是否生成库文件的标识。底层目录默认置为true,自动生成LIB库;中层目录默认置为false,不生成LIB库。若上级目录设置为true,生成LIB库,则其下级目录默认为false,不再生成LIB库,除非显式的设置为true。
同样地,模块(或组件/进程)可扩展的个性化配置文件,在xos_make_rule.mak中通过sinclude或-include语句统一引用。模块的扩展配置文件可以设置组件/进程内的编译选项,链接选项,依赖文件目录列表,特殊链接方法的设置等。
上面示例的底层makefile用于生成库文件。若要生成进程文件,仅仅修改最后一行语句为include $(XOS_MAKE_PATH)/xos_make_exec.mak。
5、总结
本文简要描述了数通平台软件源码的编译体系,包括工程代码目录规划、编译体系实现目标、编译体系分层设计方案,以及详细说明了用户侧makefile对外接口设计方法。对于资源侧文件的编译与链接,可以采用类似的架构设计。