构建策略
更新时间: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种状态。
  1. 创建任务:创建一个任务进程,
  2. 等待依赖:多进程意味着不能直接共享状态,为此我们创建了一个全局状态管理进程,任务会向全局状态管理进程轮询被依赖任务的状态。
  3. 执行任务:依赖任务都已完成后开始执行任务的run方法。当任一依赖任务失败时当前任务报错并退出执行;
  4. 创建子进程:每个子进程重复父进程的4个阶段,周而复始,直至所有任务执行完成,构建目标也就达成了。
  • 大创建子进程阶段会将任务自己的Config对象传递给子进程,这就是target_config类存在的意义,这个对象会由调度框架在任务的调度链往子进程方向传递。 work_config只是配置任务,可以修改也可以不修改Config对象,这完全是任务自己的行为,不受框架限制。

  • 任务状态

    • 任务可能执行成功也可能失败,每个进程都会对外报告自己的状态,当前的机制要求任一任务失败则会路上其它任务,整体报错退出。

    • 各任务处于不同的进程中,任务状态同步需要借助进程间通信完成,为此我们创建了一个全局状态管理进程用于进程间状态同步,该进程会在TCP:23456上接收任务上报的执行状态报文并记录在全局任务状态表中,也能接收任务状态查询报文并返回指定任务的运行状态。 任务等待过程就是向全局状态管理进程发送TCP报文轮询状态。