组件是构成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和其他组件。关于版本发布细则参考日常发布流程。