openUBMC中各个组件具有统一的代码结构,BMC Studio
开发工具支持CLI命令bingo new
快速创建一个符合openUBMC统一格式的组件。
命令说明
BMC Studio
开发工具的各子命令都支持通过-h/--help查看命令说明:
bingo new -h
执行结果:
创建组件
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的新组件示例:
bingo new -n my_app
执行此命令后,在终端根据提示输入对应值,便会在当前目录下快速生成一个名为my_app的组件仓。之后,开发者可以在组件仓目录下,通过bingo build
命令构建出my_app的Conan包。执行命令如下:
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中用户名的默认值是和通道相关联的,它拥有三种构建通道:
dev
:表示开发者通道,开发者在本地构建时的默认通道。rc
:表示候选发布包。stable
:表示发布包。
开发者可以在构建命令中通过传入--stage
参数来指定通道。例如,通过以下命令构建my_app
组件的rc
包:
bingo build --stage rc
构建通道为dev
时,用户名默认为openUBMC.dev
。构建通道为rc
或stable
时,用户名默认为openUBMC.release
。当然,开发者也可以通过--user
传参命令指定用户名,此时用户名不会根据通道发生变化。更多的组件构建命令请参考《组件的构建与发布》。
构建完成后,开发者可以在root/.conan/data
路径下,根据<包名>/版本号/用户名/通道
路径,查看构建产物。
组件仓架构介绍
自动生成的my_app组件的文件架构格式及简要介绍如下。在此组件仓中,开发者可以在此代码仓内编写属于自己的组件特性代码。
├── .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.md
和README.md
文件分别用于记录修改日志和组件介绍,不参与组件运行。CMakeLists.txt
、conanfile.py
是组件的编译、构建脚本,与组件的业务代码无关。更详细的介绍请参考《组件的构建与发布》。Makefile
是一个可移植的构建系统文件,由CMake
构建系统通过CMakeLists.txt
生成。permissions.ini
:权限配置文件。user_conf
:用户配置目录。其中,my_app.service
为组件启动配置文件。文件中定义了Skynet
读取/opt/bmc/apps/bmc_time/config.cfg
配置文件,以拉起服务。config.cfg
:Skynet
服务启动配置文件,在此文件中定义了服务入口文件为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/integration
和test/unit
目录中。具体的介绍请参考《组件的独立测试》。
openUBMC的业务组件通过模型描述文件MDS
来配置资源协作接口、组件依赖等,与此部分模型定义相关的代码并未出现在以上的代码架构中。开发者可以使用CLI
命令获取组件的全量代码:
cd <组件仓目录>
bingo gen
执行此命令,会在组件仓目录下自动生成gen
目录。gen
目录包含了模型描述文件所配置的资源协作接口的调用、订阅,和其他模型描述相关的代码。详细的介绍可参考《代码自动生成》。
组件启动流程
openUBMC通过Linux系统的服务管理工具Systemd
进行服务和资源的管理。在Systemd
的守护进程中,每个组件作为一个Unit
(单位),采用<组件名>.service
的Unit
文件定义相应服务的描述、属性以及需要运行的命令等,并将此文件存放在组件代码仓的user_conf/rootfs/etc/systemd/system/
路径下。
以创建的新组件my_app
为例,它的Unit
文件my_app.service
中定义了my_app
组件的默认配置信息:
[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_app
的Unit
文件定义,my_app
服务通过启动Skynet
被拉起。Skynet
的启动配置文件/opt/bmc/apps/my_app/config.cfg
默认定义如下:
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
文件:
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
文件定义如下:
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.json
、service.json
,和资源协作接口组件仓mdb_interface
的代码自动生成的类,是整个服务的入口,定义了组件初始化和服务端资源协作接口方法注册、对象创建等。