构建策略
更新时间:2024/12/27
在Gitcode上查看源码装配式构建构架
为满足构建场景和目标,实现构建系统的灵活定制和可扩展能力,openUBMC重构了构建框架,形成一套任务编排框架,其核心是任务标准化、目标可描述和自动化调度。
- 任务标准化:框架提供了一个任务基类,所有任务都继承该基类,具有相同的参数传递方式、任务创建逻辑,统一任务执行和产物收集,为调度核心提供统一的调度范式;
- 目标可描述:使用文本文件描述完成构建目标所需要的任务拓扑,包括需要执行任务类、依赖的任务、子任务等。
- 自动化调度:调度器会分析目标描述文件得到完整的任务拓扑,依据任务间依赖关系和任务状态,创建多进程实例化任务类,实现并行构建。
装配式构建框架由全局配置、基础任务类、任务实现、目标描述文件、多进程调度框架、性能测试工具、全局状态管理进程功能。
全局配置
- 为了在装配式方案调用任务时传递参数,我们定义了一个Config对象,该对象记录了公共配置清单。
基础Work类
- Work类定义了run和install方法,还提供了一些具体任务实现需要的基础能力,基础类初始化需要还会传入一个全局配置对象,为后续任务执行提供输入。
python
import sys
import os
import logging
from colorama import Fore, Back, Style
from iutils.config import Config
from multiprocessing import Process
cwd = os.path.join(os.getcwd(), os.path.dirname(__file__))
sys.path.append(f"{cwd}/../")
class Work(Process):
name = "WorkBase"
description="基础类"
work_name = ""
def __init__(self, config: Config, work_name=""):
Process.__init__(self)
self.config = config
self.work_name = work_name
# 子类必须实现run方法,未实现时异常
def run(self):
logging.info(f"{__file__} error")
raise Exception
# 安装函数,将各work的产出安装到config.rootfs目录中
def install(self):
pass
# 命令执行标准接口,具体实现已省略
def run_command(self, command, ignore_error=False, show_log=False, sudo=False, command_echo=True, timeout=600, command_key = None):
pass
# 带颜色的错误日志,具体实现已省略
def log_error(self, msg, frame_cnt=1):
pass
# 带颜色的告警日志,具体实现已省略
def log_warn(self, msg, frame_cnt=1):
pass
任务实现
- 任务的实现需要从Work类继承,从Work类定义可知任务类从Process类继承了多进程执行能力,调度框架正式基于Proess的能力实现了多进程调度执行。
- 如下是简单的环境初始化文件示例。
python
#!/usr/bin/env python
# coding:utf-8
import os
import sys
import subprocess
import jsonschema
import json
import yaml
import logging
cwd = os.path.join(os.getcwd(), os.path.dirname(__file__))
sys.path.append(f"{cwd}/../")
sys.path.append(f"{cwd}/../../")
sys.path.append(f"{cwd}/../../../")
from works.work import Work
from iutils.config import Config
class WorkPrepare(Work):
def __init__(self, config: Config, work_name = ""):
""" 任务初始化,会传入一个Config全局配置对象,实现全局配置归一 """
super(WorkBuildCommon, self).__init__(config, work_name)
def run(self):
# 任务执行,此处实现任务逻辑
def install(self):
# 安装阶段
# 提供脚本单独调试运行能力
if __name__ == "__main__":
cfg = Config()
cfg.parse_args()
wk = WorkEnvirPrepare(cfg)
wk.run()
- 所有任务都有if name == "main":的代码,所有脚本都可以单独调用,调用时需要进入build目录,再执行python3 works/work_prepare.py
目标描述文件
- 目标描述文件采用yaml格式,描述内容有任务名、任务类、子任务和依赖任务等。
yml
# 任务示例,当任务被依赖时需要
- name: work.prepare_env
# 任务python类,由独立进程执行
klass: works.work_prepare.WorkPrepare
# 子任务列表,当klass执行完成后递归执行子系统,执行逻辑完全相同
subworks:
- name: work.dependency.bison
klass: works.dependency.work_install_bison.WorkInstallBison
- name: work.dependency.flatbuffers
klass: works.dependency.work_install_flatbuffers.WorkInstallflatbuffers
- name: work.build.conan_debug_rc
klass: works.build.work_build_conan.WorkBuildConan
# 任务的依赖列表,可以等待多个任务,只有当所有依赖都执行完成后当前任务才会执行
wait:
- work.prepare_env
# 任务私有配置,构建框架会查询任务是否有set_xxx方法(以下示例则是查询set_stage(self, value)方法是否存在)
# 随后调用set_xxx方法配置任务。
work_config:
stage:
rc
# 全局任务配置,不同于任务私有配置,该配置会作用于全局配置对象,调用配置对象的set_xxx方法变更全局配置对象的值
target_config:
build_type: release
- 如示例注释,任务目录为扩展框架的配置能力,我们定义了target_config和work_config配置能力,前者配置Config对象,后者配置Work对象。 在调度框架中会解释两者的作用。
多进程调度框架
- 我们采用了多进程调度方案,每个任务都是在独立进程中执行,每一个任务(包括子系统)都有创建任务、等待依赖、执行任务和创建子进程4个运行阶段,以及运行、完成、错误3种状态。
- 创建任务:创建一个任务进程,
- 等待依赖:多进程意味着不能直接共享状态,为此我们创建了一个全局状态管理进程,任务会向全局状态管理进程轮询被依赖任务的状态。
- 执行任务:依赖任务都已完成后开始执行任务的run方法。当任一依赖任务失败时当前任务报错并退出执行;
- 创建子进程:每个子进程重复父进程的4个阶段,周而复始,直至所有任务执行完成,构建目标也就达成了。
大创建子进程阶段会将任务自己的Config对象传递给子进程,这就是target_config类存在的意义,这个对象会由调度框架在任务的调度链往子进程方向传递。 work_config只是配置任务,可以修改也可以不修改Config对象,这完全是任务自己的行为,不受框架限制。
任务状态
任务可能执行成功也可能失败,每个进程都会对外报告自己的状态,当前的机制要求任一任务失败则会路上其它任务,整体报错退出。
各任务处于不同的进程中,任务状态同步需要借助进程间通信完成,为此我们创建了一个全局状态管理进程用于进程间状态同步,该进程会在TCP:23456上接收任务上报的执行状态报文并记录在全局任务状态表中,也能接收任务状态查询报文并返回指定任务的运行状态。 任务等待过程就是向全局状态管理进程发送TCP报文轮询状态。