组件是构成OS的基本单元,本文档通过约定组件定义、组件构成、组件操作、以及组件版本管理等内容,帮助开发者统一认识,同时也为OS构建系统和uCube等工具开发提供规范,为形成统一的组件生态打下基础。
本文适用于组件开发人员,组件管理工具和基础设施。
2 组件定义
2.1 什么是组件
从系统角度看,除了构建脚本和辅助工具外,一切都是组件;根据组件的应用范围,可以将组件划分为以下三类:
- BSP组件:物理上包含board、arch、mcu三个组件的集合;
- 系统组件:一组独立功能的集合,等同于其他系统上库(Library)的概念;
- 应用组件:直接向用户提供服务的App或Example。
2.2 组件划分粒度
原则上,应该以尽量细的粒度划分组件。影响组件粒度的因素包括:
- 独立性:组件功能应该相对独立,可以单独对外提供接口和服务;
- 耦合性:如果组件必须依赖其他组件,依靠自身始终无法对外提供服务,则考虑合并为一个组件;
- 相关性:如果一组组件共同完成一项功能,且没有被其他组件依赖,未来也没有被依赖的可能,则考虑合并为一个组件。
2.3 组件依赖关系
组件的依赖关系分为两种:必选依赖和可选依赖。
- 必选依赖:是指组件A在完成某个功能时,必须引入组件B,一起配合。例如:HTTP组件,完成访问HTTP服务器的功能,必须引入TCP/IP组件。
- 可选依赖:是指组件A在完成某个功能时,可以引入组件C,也可以引入组件D。例如:HTTP组件,要访问HTTPS服务器,可以引入mbedtls组件,也可以引入openssl组件,来完成访问服务器中加解密的功能。
3 组件构成
组件是OS功能和构建的基本单元。为了使OS能被灵活地配置,适应多种硬件平台。组件本身应当:
- 可配置:具有高度可配置的能力,依赖关系被清晰地定义和管理;
- 可发布:具有独立的版本管理和迭代能力;
- 文档全:完善的说明文档,描述组件功能和特性。
除了源代码之外,为了支持上述能力,组件应该包含:
- Config.in:负责提供组件配置选项;
- aos.mk:负责提供构建规则,解析配置文件中定义的选项,定义各选项之间的依赖关系;
- README.md:说明文档的基本功能,组件依赖和使用方法。
以上文件将作为生成组件元数据的基础,组件基础设施和工具通过组件元数据完成组件管理。
一个简单的组件示例如下:
mycomp
├── aos.mk # 负责提供构建规则
├── Config.in # 负责提供组件配置
├── doc # 组件使用手册、API说明等文档
├── example # 测试组件功能的示例APP
│ ├── aos.mk
│ ├── app.config
│ ├── app_main.c
│ ├── Config.in
│ ├── main.c
│ └── README.md
├── inc # 组件的内部头文件
├── mycomp # 组件的对外头文件
│ └── mycomp.h
├── README.md # 组件的简要说明
└── src # 组件的源代码
└── mycomp.c
注意:
针对仅组件内部引用的头文件,是放在inc目录下;
针对组件对外公开API和数据结构的头文件,放在与组件同名的目录下。组件发布时,会将该目录复制到AliOS Things的顶层目录下的include中。
3.1 配置文件Config.in
组件需要提供配置文件Config.in,以便通过OS构建系统完成组件的配置工作。通常情况下,每个组件都应该具有:
- 一个唯一的配置ID,用于指示组件是否被使能;
- 根据组件配置能力提供具体的配置选项。
文件基本语法参考官方文档《kconfig-language.txt》以及网友整理的《Linux源码Kconfig文件语法分析》。
3.1.1 配置项命名规范
- 配置项全部使用大写字母,各域之间使用“_”分隔;
- 组件ID定义:AOS_组件类型_组件名称。类型取值范围:APP,COMP,BOARD,MCU,ARCH。例如:AOS_BOARD_DEVELOPERKIT,AOS_APP_HELLOWORLD,AOS_COMP_RHINO。
- 组件内部配置项推荐的定义方式:组件名称_CONFIG_XXX。例如:RHINO_CONFIG_SEM。
3.1.2 组件依赖的规范
- 组件默认为使能,且不允许改为禁止;否则有可能无法引入Config.in中指定的其它配置信息。
- 不在Config.in中使用select使能其它组件,而是在组件Makefile文件aos.mk里面选择依赖组件。
- 可以配置可选组件的选择条件,配合组件Makefile完成选择依赖组件。
3.1.3 组件配置示例
# components/network/http
# 组件唯一ID,默认为使能,不允许改为n
config AOS_COMP_HTTP
bool
default y
# 组件内部的配置项
menu "HTTP Client Configuration"
config HTTP_CONFIG_SERVER_NAME_SIZE
int "The size of server name"
default 64
help
The size of server name in bytes
config HTTP_CONFIG_SECURE
bool "Support HTTP Secure"
default n
help
set to y if use HTTPS
default n
if HTTP_CONFIG_SECURE
# 配置可选组件mbedtls、itls的条件HTTP_CONFIG_SECURE_TLS、HTTP_CONFIG_SECURE_ITLS
# 组件Makefile根据此处的配置,引入相应的组件
choice
prompt "Security Selection"
default HTTP_CONFIG_SECURE_TLS
help
HTTPS over MbedTLS or iTLS
config HTTP_CONFIG_SECURE_TLS
bool "HTTPS over MbedTLS"
config HTTP_CONFIG_SECURE_ITLS
bool "HTTPS over iTLS"
endchoice
endif
endmenu
3.2 组件Makefile aos.mk
为了方便统一管理和标识组件,组件Makefile将统一命名为“aos.mk”。
3.2.1 通用组件配置信息
为了支持组件化编译和发布,Makefile需要提供以下信息:
- 必选字段:
- NAME:组件名称,是相应Config.in里组件ID中的组件名称的小写版
- $(NAME)_MBINS_TYPE:MBINS类型定义,支持三种类型:kernel,app,share
- $(NAME)_VERSION:组件版本(新增)
- $(NAME)_SUMMARY:单行描述信息
- 按需填写字段:
- $(NAME)_COMPONENTS:组件依赖关系(允许指定依赖组件的版本)
- $(NAME)_COMPONENTS-$(bool型配置选项):组件可选依赖关系
- $(NAME)_SOURCES:组件源文件
- $(NAME)_INCLUDES:编译组件需要依赖的头文件
注意:参考Linux kernel处理bool类型配置选项的方式,允许组件以`$(NAME)_COMPONENTS-$(bool类型配置选项) += 组件名称`的方式按需为变量赋值。该方式同样适用于$(NAME)_SOURCES,$(NAME)_INCLUDES等。
- 其他可选字段:
- $(NAME)_LICENSE:许可证信息,默认值:Apache 2
- $(NAME)_VENDER:组件提供者,默认值:Alibaba
- $(NAME)_URL:主页地址,默认值:无
- $(NAME)_DESCRIPTION:多行描述信息,默认值:无
- $(NAME)_PREBUILT_LIBRARY:默认链接的.a,比如厂商的驱动.a等
- $(NAME)_LINK_FILES:armcc编译的时候才使用,为了解决COMPILER=armcc时,链接.a符号不全的问题
3.2.2 通用全局变量
全局变量用来定义和影响全局构建行为,根据需要定义以下变量:
- GLOBAL_INCLUDES:全局可见的头文件搜索路径,用于对外提供头文件
- GLOBAL_CFLAGS:全局可见编译选项,建议使用组件配置文件Config.in管理
- GLOBAL_CXXFLAGS:全局C++编译器选项
- GLOBAL_LDFLAGS:全局可见链接选项
- GLOBAL_DEFINES:全局可见宏定义,建议使用组件配置文件Config.in管理
- GLOBAL_ASMFLAGS:全局汇编选项
- ......
注意:使用GLOBAL_INCLUDES添加头文件路径保持 最小够用 的原则,尽量不包含其它组件的头文件路径。
3.2.3 BSP相关变量
BSP组件在物理上由board,arch,mcu三部分组成,指定board即确定了相应的arch和mcu。为了支持这一特殊依赖关系,board组件Makefile中需要提供以下信息:
- 必选字段:
- HOST_ARCH:CPU框架类型
- HOST_MCU_FAMILY:MCU名称,对应platform/mcu/*
- 其他可选字段:
- SUPPORT_MBINS:是否支持多bin编译,例如:“yes”-支持,“no”-不支持
- HOST_MCU_NAME:用来区分mcu系列下的具体某MCU名
- ENABLE_VFP:用来区分链接的库是否包含浮点数,使用umesh,a, mbmaster.a, activation,a时需要使用
- 系统自定义字段:系统或组件定义的其他字段和变量
3.2.4 在Makefile中支持KConfig选项
KConfig选项最终以两种方式影响系统:
- 全局生效的Makefile变量
- 全局生效的C语言宏定义
在Makefile中直接引用全局变量,例如:
ifeq ($(HTTP_CONFIG_SECURE),)
...
endif
在源代码中引用宏定义。例如:
#if HTTP_CONFIG_SECURE
...
#else
...
#endif
// 或者
#if !HTTP_CONFIG_SECURE
...
#else
...
#endif
3.2.5组件Makefile示例
## 必须定义:名称、多bin类型、版本、摘要
NAME := http
$(NAME)_MBINS_TYPE := kernel
$(NAME)_VERSION := 1.0.1
$(NAME)_SUMMARY := http client component
## 按需定义:
# 固定源码
$(NAME)_INCLUDES += include
$(NAME)_SOURCES := src/http_client.c \
src/http_string.c \
src/http_parser.c \
src/http_upload.c \
wrappers/http_aos_wrapper.c
# 固定导出头文件
GLOBAL_INCLUDES += ../../../include/network/http
# 必选依赖的组件
# $(NAME)_COMPONENTS += lwip
# 可选依赖的组件
$(NAME)_COMPONENTS-$(CONFIG_HTTP_SECURE_TLS) += mbedtls
$(NAME)_COMPONENTS-$(CONFIG_HTTP_SECURE_ITLS) += itls
# 不要使用以下方式引入可选依赖的组件
# ifeq (y,$(CONFIG_HTTP_SECURE_TLS))
# $(NAME)_COMPONENTS += mbedtls
# endif
# 根据配置选项控制的源码,例如
# $(NAME)_SOURCES-$(AOS_XXX) += yyy.c
# 注意:所有通过-D定义的选项都可以转化为KConfig配置,不需要在Makefile中重复定义,例如:
# GLOBAL_CFLAGS += -DXXX
# GLOBAL_DEFINES += XXX
# 根据配置选项处理其他逻辑,例如
# ifeq ($(AOS_ZZZ),value)
# ...
# endif
3.3 组件编写规范
为了使组件在不同硬件间可以快速复用,对于直接操作硬件设备的组件,必须满足以下规范:
1)使用OS提供的HAL API控制硬件,而不直接调用芯片厂商提供的底层API。
2)操作硬件设备的端口,如GPIO、I2C等,在组件API的输入参数中指定,而不能直接在组件内部指定。
a、对于使用GPIO类控制硬件的组件,GPIO端口必须以GPIO编号方式指定,如0表示PA0,1表示PA1等。具体编号参见各MCU下的映射关系表。
b、对于使用复合类端口控制硬件的组件,如I2C、UART、SPI等,复合类端口必须以相应的结构体句柄方式指定,如i2c_dev_t等。
组件编写示例和调用示例如下:
// GPIO类组件实现示例:
#include "aos_P9813.h"
gpio_dev_t clk;
gpio_dev_t din;
void P9813_init(int clk_pin_num,int din_pin_num)
{
din.port = clk_pin_num;
din.config = OUTPUT_PUSH_PULL;
hal_gpio_init(&din);
clk.port = din_pin_num;
clk.config = OUTPUT_PUSH_PULL;
hal_gpio_init(&clk);
}
// GPIO类组件调用示例:
P9813_init(24,25);
// 复合类组件实现示例:
#include "aos_AT24C.h"
void AT24C_init(i2c_dev_t *pi2c_dev)
{
hal_i2c_init(pi2c_dev);
........
}
// 复合类组件调用示例:
i2c_dev_t i2c_dev;
i2c_dev.port = PORT_I2C_1;
i2c_dev.config.address_width = I2C_HAL_ADDRESS_WIDTH_7BIT;
i2c_dev.config.freq = I2C_BUS_BIT_RATES_400K;
i2c_dev.config.mode = I2C_MODE_MASTER;
AT24C_init(&i2c_dev);
3.4 组件预定义配置
组件预定义配置是由组件提供者预设,主要用来配置其依赖组件的参数。
仅App组件和board组件包含预定义配置。App组件的预定义配置为app.config,放置于该App根目录下;board组件的预定义配置为board.config,放置于该board根目录下。
例如,OS内核rhino组件默认采用单核CPU,即RHINO_CONFIG_CPU_NUM的默认值为1。使用双核CPU的board,可以在其相应的board.config中加入 RHINO_CONFIG_CPU_NUM=2
。
又如,LwIP组件默认不使能telnet功能,即LWIP_CONFIG_TELNETD_ENABLED的默认值为n。对于需要使用telnet功能的app,可以在其app.config中加入 LWIP_CONFIG_TELNETD_ENABLED=y
。
对于默认已配置的宏,可以通过以下方式禁止:# <宏名称> is not set
。
例如mbedtls组件的MBEDTLS_CONFIG_DTLS默认值为y,在app.config或者board.config中加入# MBEDTLS_CONFIG_DTLS is not set
,可将该宏取消定义。
组件预定义配置的优先级是 app.config > board.config > 组件的默认配置。
用户创建工程时,将采用以下流程生成工程配置的数据文件.config。
4 组件管理
4.1 组件元数据
组件元数据是执行组件管理的基础,主要用来描述组件基本信息、系统中包含的组件索引等等。这些数据将用来构建组件仓库,支持uCube完成组件操作。
4.2 组件依赖定义
组件依赖定义放在组件Makefile中。配置文件中可以定义可选依赖的组件的条件,不能直接选择组件。例如:
# 组件的配置文件Config.in
# 定义可选依赖组件的条件
config HTTP_CONFIG_SECURE_TLS
bool "HTTPS over MbedTLS"
default n
help
HTTPS over MbedTLS
# 组件Makefile aos.mk
# 必选依赖的组件
$(NAME)_COMPONENTS += lwip
# 可选依赖的组件
$(NAME)_COMPONENTS-$(CONFIG_HTTP_SECURE_TLS) += mbedtls
在现有依赖基础上,引入版本信息,允许依赖特定版本的组件,依赖指定格式如下:
$(NAME)_COMPONENTS := 组件名(操作符+版本号)
例如:
$(NAME)_COMPONENTS := rhino(>=2.0.0)
操作符:=、<、>、<=、>=
版本号:参见组件版本号定义
注意:
- 使用半角括号“()”包含版本信息;
- “组件名(操作符+版本号)”中间不能出现空格。
4.2 组件相关操作
基于组件化,uCube工具可以针对组件执行如下操作:
- 安装
- 升级
- 搜索、查询
- 删除
- ...
uCube(也叫aos-cube)的详细操作可参考《集成开发管理工具aos-cube》。
5 组件版本管理
5.1 组件版本号
版本格式:主版本号.次版本号.修订号.[bugfix],版本号递增规则如下:
- 主版本号:当做了不兼容的 API 修改;
- 次版本号:当做了向下兼容的功能性新增;
- 修订号:当做了向下兼容的问题修正;
- bugfix:发布后做了紧急bug修复。
5.2 版本发布
除了随OS统一发布外,组件可以独立发布新版本,而不需要重新发布OS和其他组件。关于版本发布细则参考日常发布流程。
在文档使用中是否遇到以下问题
更多建议
匿名提交