组件的创建和使用
更新时间:2024/12/16
在Gitcode上查看源码

openUBMC中各个组件具有统一的代码结构,BMC Studio开发工具支持CLI命令bingo new快速创建一个符合openUBMC统一格式的组件。

命令说明

BMC Studio开发工具的各子命令都支持通过-h/--help查看命令说明:

shell
bingo new -h

执行结果:

shell
创建组件

optional arguments:
  -h, --help            show this help message and exit
  -n NAME, --name NAME  指定组件名
  -t TYPE, --type TYPE  指定组件类型, 可选值: application
                        默认: application
  -l LANGUAGE, --language LANGUAGE
                        指定组件编程语言, 可选值: lua
                        默认: lua

指定参数传入,可以快速创建一个具有基础代码的组件仓。该代码仓已实现基本的启动服务、构建出包等功能。根据生成的组件代码仓,开发者只需关注业务代码编写即可。创建一个名为my_app的新组件示例:

shell
bingo new -n my_app

执行此命令后,在终端根据提示输入对应值,便会在当前目录下快速生成一个名为my_app的组件仓。之后,开发者可以在组件仓目录下,通过bingo build命令构建出my_app的Conan包。执行命令如下:

shell
cd my_app
git init
bingo build

命令执行成功后,控制台会输出包名/版本号@用户名/通道格式的构建产物标签,即my_app/0.0.1@openUBMC.dev/dev。其中,包名和版本号分别由mds/service.json文件中的name属性和version属性定义。开发者在每次修改提交时都应该修改version属性值加1;并在CHANGELOG.md文件中记录新版本的修改信息;openUBMC中用户名的默认值是和通道相关联的,它拥有三种构建通道:

  1. dev:表示开发者通道,开发者在本地构建时的默认通道。
  2. rc:表示候选发布包。
  3. stable:表示发布包。

开发者可以在构建命令中通过传入--stage参数来指定通道。例如,通过以下命令构建my_app组件的rc包:

shell
bingo build --stage rc

构建通道为dev时,用户名默认为openUBMC.dev。构建通道为rcstable时,用户名默认为openUBMC.release。当然,开发者也可以通过--user传参命令指定用户名,此时用户名不会根据通道发生变化。更多的组件构建命令请参考《组件的构建与发布》

构建完成后,开发者可以在root/.conan/data路径下,根据<包名>/版本号/用户名/通道路径,查看构建产物。

组件仓架构介绍

自动生成的my_app组件的文件架构格式及简要介绍如下。在此组件仓中,开发者可以在此代码仓内编写属于自己的组件特性代码。

shell
├── .clang-format
├── .gitignore
├── CHANGELOG.md                                     --记录修改日志信息
├── README.md                                        --组件介绍文件
├── CMakeLists.txt                                   --cmake构建脚本,一般包含编译和打包
├── Makefile
├── conanfile.py                                     --conan脚本,继承自conanbase.py,可以重写conanbase的方法
├── permissions.ini                                  --权限配置文件
├── config.cfg                                       --组件启动配置文件
├── mds                                              
   ├── model.json                                   --配置资源协作接口属性、方法、信号等
   └── service.json                                 --配置组件仓版本信息、需要依赖的接口和路径等
├── src                                              --源码文件夹
   ├── lualib
   └── my_app_app.lua                          --组件实现文件
   └── service
       └── main.lua                                 --定义skynet服务,是组件入口服务。
├── test
   ├── integration                                  --集成测试文件的存放目录
   ├── test_my_app.conf
   └── test_my_app.lua                         --集成测试的入口文件
   └── unit                                         --单元测试文件的存放目录
       └── test.lua                                 --单元测试的入口文件
└── user_conf                                        --用户配置,构建系统根据rootfs目录的文件原样安装到根文件系统对应位置
    └── rootfs
        └── etc
            └── systemd
                └── system
                    ├── multi-user.target.wants
   └── my_app.service          --Systemd进程启动配置文件
                    └── my_app.service

在此组件架构中,不同的文件和目录分别具有不用的功能职责:

  • CHANGELOG.mdREADME.md文件分别用于记录修改日志和组件介绍,不参与组件运行。

  • CMakeLists.txtconanfile.py是组件的编译、构建脚本,与组件的业务代码无关。更详细的介绍请参考《组件的构建与发布》

  • Makefile是一个可移植的构建系统文件,由CMake构建系统通过CMakeLists.txt生成。

  • permissions.ini:权限配置文件。

  • user_conf:用户配置目录。其中,my_app.service为组件启动配置文件。文件中定义了Skynet读取/opt/bmc/apps/bmc_time/config.cfg配置文件,以拉起服务。

  • config.cfgSkynet服务启动配置文件,在此文件中定义了服务入口文件为my_app/service/main.lua

  • mds:定义组件的模型描述。包括组件实现的资源协作接口、组件数据的持久化描述、组件的依赖等。存放统一格式的组件模型定义源文件,不同模型定义不同的json文件。其中,model.json文件描述组件数据模型,配置资源协作接口上挂载的属性、方法、信号等;service.json文件描述组件的服务模型,配置组件仓版本信息、需要依赖的接口和路径等。BMC Studios工具的CLI命令bingo gen会依赖此部分模型定义和资源协作接口定义自动生成相关代码。更详细的模型文档介绍请参考《MDS》

  • src目录作为源码文件目录,组件的实现代码在src/lualib/my_app_app.lua文件中定义,src/service/main.lua文件是组件APP的入口函数,拉起了此组件的服务。开发人员可以在src目录下编写自己的业务代码。具体的介绍请参考《组件的开发和代码编写》

  • test目录作为开发者测试相关代码的存放目录。在openUBMC中,开发者测试用于对组件代码开发做保障。因此,开发者在业务代码开发的同时,需要完成开发者测试。开发者测试采用集成测试和单元测试进行组件接口测试和组件功能函数测试,相关代码分别存放在test/integrationtest/unit目录中。具体的介绍请参考《组件的独立测试》

openUBMC的业务组件通过模型描述文件MDS来配置资源协作接口、组件依赖等,与此部分模型定义相关的代码并未出现在以上的代码架构中。开发者可以使用CLI命令获取组件的全量代码:

shell
cd <组件仓目>
bingo gen

执行此命令,会在组件仓目录下自动生成gen目录。gen目录包含了模型描述文件所配置的资源协作接口的调用、订阅,和其他模型描述相关的代码。详细的介绍可参考《代码自动生成》

组件启动流程

openUBMC通过Linux系统的服务管理工具Systemd进行服务和资源的管理。在Systemd的守护进程中,每个组件作为一个Unit(单位),采用<组件名>.serviceUnit文件定义相应服务的描述、属性以及需要运行的命令等,并将此文件存放在组件代码仓的user_conf/rootfs/etc/systemd/system/路径下。

以创建的新组件my_app为例,它的Unit文件my_app.service中定义了my_app组件的默认配置信息:

lua
[Unit]
Description=my_app service  
After=dbus.service              
Requires=dbus.service

[Service]
User=root
Restart=always
RestartSec=1
StartLimitInterval=0
EnvironmentFile=/dev/shm/dbus/.dbus
Environment="ROOT_DIR="
Environment="PROJECT_DIR="
WorkingDirectory=/opt/bmc/apps/my_app
ExecStart=/opt/bmc/skynet/skynet /opt/bmc/apps/my_app/config.cfg

[Install]
WantedBy=multi-user.target

该文件主要分为三个配置区段:

  • Unit段:配置了该服务的描述、依赖和随系统启动的方式。即在dbus服务拉起之后,再拉起本服务。
  • Service段:定义服务的具体管理和操作方法。即该服务默认运行在/opt/bmc/apps/my_app的工作目录中;通过Skynet读取/opt/bmc/apps/my_app/config.cfg配置文件信息启动服务。
  • Install段:multi-user.target是systemd默认的target。在此目录下保存上级目录systemd配置的软连接,用于支持自动拉起systemd配置对应的服务。

根据新组件my_appUnit文件定义,my_app服务通过启动Skynet被拉起。Skynet的启动配置文件/opt/bmc/apps/my_app/config.cfg默认定义如下:

lua
include "/opt/bmc/libmc/config.cfg"

config:set_root("/")
config:set_start("my_app/service/main")
config:include_app("my_app")
config:done()

该配置文件即为组件代码仓目录下的config.cfg文件,其本质是Lua脚本。openUBMC在opt/bmc/libmc/config.cfg中定义了基础配置模块。Skynet的配置文件通过导入此模块,并调用此模块中的方法,定义了各种文件运行的根目录、Skynet的启动脚本、以及在该Skynet进程中能够访问的Lua目录。 其中,Skynet的启动脚本定义为my_app/service/main.lua文件:

lua
require 'skynet.manager'
local skynet = require 'skynet'
local logging = require 'mc.logging'
local my_app_app = require 'my_app_app'

local CMD = {}

function CMD.exit()
    logging:notice('my_app service exit')
end

skynet.start(function()
    skynet.uniqueservice('sd_bus')
    skynet.register('my_app')
    local ok, err = pcall(my_app_app.new)
    if not ok then
        logging:error('my_app start failed, err: %s', err)
    end
    skynet.dispatch('lua', function(_, _, cmd, ...)
        local f = assert(CMD[cmd])
        skynet.ret(skynet.pack(f(...)))
    end)
end)

各组件代码仓使用src/lualib目录存放组件的lua代码,其中xxx_app.lua(xxx为组件名)是整个组件的入口。在上述Skynet的启动脚本中,通过引入my_app组件的入口文件my_app_app.lua,将my_app组件注册到Skynet中,并创建了my_app对象(my_app类在文件my_app_app.lua中定义,这里定义的my_app_app类即为my_app类)。注意:类实例化时,会自动执行此类的构造函数ctor(),再执行此类的初始化函数init()

my_app_app.lua文件定义如下:

lua
local class = require 'mc.class'
local service = require 'my_app.service'

local my_app = class(service)

function my_app:ctor()

end

function my_app:init()
    self.super.init(self)
end

return my_app

my_app类继承了该组件的my_app.service类。my_app.service类是根据MDS模型定义文件model.jsonservice.json,和资源协作接口组件仓mdb_interface的代码自动生成的类,是整个服务的入口,定义了组件初始化和服务端资源协作接口方法注册、对象创建等。