初次创建仓库提交代码
1. 已经构建好了架子了。 2. 添加了示例的插件
This commit is contained in:
214
core/behavior_tree.py
Normal file
214
core/behavior_tree.py
Normal file
@ -0,0 +1,214 @@
|
||||
import os, sys
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from core.defines import ReleaseErr
|
||||
from thirdparty.py_trees.behaviour import Behaviour
|
||||
from thirdparty.py_trees.common import Access
|
||||
from thirdparty.py_trees.composites import Selector, Parallel, Sequence, Composite
|
||||
# from thirdparty.py_trees.decorators import Decorator
|
||||
from thirdparty.py_trees import blackboard
|
||||
from thirdparty.py_trees.common import ParallelPolicy
|
||||
from thirdparty.py_trees.behaviour import Behaviour
|
||||
import typing
|
||||
from core.logger import get_logger
|
||||
|
||||
log = get_logger()
|
||||
|
||||
class ReleaseFlowBlackboard(blackboard.Client):
|
||||
"""
|
||||
重载blackboard.Client用于适配一些我们的需求
|
||||
每个节点访问其所需要的数据的时候都需要先进行注册,注册之后方可访问
|
||||
"""
|
||||
def register_key(self, key: str, access: Access = Access.WRITE, required: bool=False, remap_to: str=None):
|
||||
"""
|
||||
重写register_key方法,用于注册key,默认具备可读可写的属性
|
||||
"""
|
||||
super().register_key(key=key, access=access, required=required, remap_to=remap_to)
|
||||
|
||||
def require_key(self, key: str):
|
||||
"""
|
||||
强制要求key存在
|
||||
"""
|
||||
self.register_key(key=key, access=Access.WRITE, required=True)
|
||||
|
||||
class ReleaseFlowAction(Behaviour, ABC):
|
||||
"""
|
||||
Release Flow 中的最小单元,其本质是一个Behaviour Tree的节点
|
||||
继承了py_trees.behaviour.Behaviour的接口
|
||||
同时针对我们的Release Flow添加了一些实现的细节
|
||||
- setup() 方法用于初始化节点。需要主动调用
|
||||
- process() 方法在每次节点开始执行时调用。
|
||||
- update() 方法是节点的核心逻辑,返回节点的状态(SUCCESS, FAILURE, RUNNING)。
|
||||
- shutdown() 方法用于清理节点。和setup()成对使用
|
||||
"""
|
||||
def __init__(self, name: str, description: str = ""):
|
||||
Behaviour.__init__(self, name)
|
||||
# blackboard 是所有 flow action 共享的,每个action自己注册自己需要的key
|
||||
self.blackboard = self.attach_blackboard_client(name=name)
|
||||
self.description = description
|
||||
|
||||
# 重写attach_blackboard_client方法,用于创建ReleaseFlowBlackboard实例
|
||||
def attach_blackboard_client(self, name: str=None, namespace: str=None
|
||||
) -> ReleaseFlowBlackboard:
|
||||
if name is None:
|
||||
count = len(self.blackboards)
|
||||
name = self.name if (count == 0) else self.name + "-{}".format(count)
|
||||
new_blackboard = ReleaseFlowBlackboard(
|
||||
name=name,
|
||||
namespace=namespace
|
||||
)
|
||||
self.blackboards.append(new_blackboard)
|
||||
return new_blackboard
|
||||
|
||||
# 子类可以重写setup方法,用于初始化
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
Behaviour 基类中每次tick都会call该函数执行实际的业务逻辑,我们为了方便理解,将名字改为process,
|
||||
只要子类实现了process方法,就可以在Release Flow中使用
|
||||
"""
|
||||
try:
|
||||
self.process()
|
||||
except Exception as e:
|
||||
from thirdparty.py_trees.common import Status
|
||||
self.status = Status.FAILURE
|
||||
log.error(f"Action {self.name} 执行失败: {str(e)}")
|
||||
|
||||
@abstractmethod
|
||||
def process(self):
|
||||
"""
|
||||
子类实现process以用于实际的业务逻辑
|
||||
"""
|
||||
pass
|
||||
|
||||
def ReleaseFlowActionDecorator(cls):
|
||||
"""
|
||||
装饰器,用于自动生成行为树节点的名称,并自动处理__init__函数
|
||||
目的就是为了使得自动生成一个name,而不需要手动去写
|
||||
使用方法:
|
||||
@ReleaseFlowActionDecorator
|
||||
class MyAction(ReleaseFlowAction):
|
||||
pass
|
||||
"""
|
||||
# 保存原始的__init__(如果存在的话)
|
||||
original_init = cls.__init__ if '__init__' in cls.__dict__ else None
|
||||
# 如果不是ReleaseFlowAction的子类,则不进行装饰
|
||||
if not issubclass(cls, ReleaseFlowAction):
|
||||
return cls
|
||||
|
||||
def new_init(self, name:str=None, description:str=''):
|
||||
if name is None:
|
||||
name = f"{self.__module__}.{self.__class__.__name__}"
|
||||
# 如果原始类有自定义的__init__,则调用它
|
||||
if original_init:
|
||||
original_init(self, name, description)
|
||||
else:
|
||||
super(cls, self).__init__(name, description)
|
||||
|
||||
cls.__init__ = new_init
|
||||
return cls
|
||||
|
||||
|
||||
class ReleaseFlowBuilder:
|
||||
"""行为树构建器"""
|
||||
def __init__(self):
|
||||
self.current_node = None
|
||||
self.root = None
|
||||
self.node_stack = []
|
||||
|
||||
def sequence(self, name: str, description: str = "") -> 'ReleaseFlowBuilder':
|
||||
"""创建序列节点"""
|
||||
if isinstance(name, Enum):
|
||||
name = name.value
|
||||
node = Sequence(name, description)
|
||||
self._add_node(node)
|
||||
self.node_stack.append(node)
|
||||
return self
|
||||
|
||||
def selector(self, name: str, description: str = "") -> 'ReleaseFlowBuilder':
|
||||
"""创建选择节点"""
|
||||
if isinstance(name, Enum):
|
||||
name = name.value
|
||||
node = Selector(name, description)
|
||||
self._add_node(node)
|
||||
self.node_stack.append(node)
|
||||
return self
|
||||
|
||||
def parallel(self, name: str, description: str = "",
|
||||
policy: ParallelPolicy.Base = ParallelPolicy.SuccessOnAll(),
|
||||
children: typing.List[Behaviour] = None) -> 'ReleaseFlowBuilder':
|
||||
"""创建并行节点"""
|
||||
if isinstance(name, Enum):
|
||||
name = name.value
|
||||
node = Parallel(name, policy, children)
|
||||
self._add_node(node)
|
||||
self.node_stack.append(node)
|
||||
return self
|
||||
|
||||
def action(self, action: ReleaseFlowAction) -> 'ReleaseFlowBuilder':
|
||||
"""创建动作节点"""
|
||||
if isinstance(action, list):
|
||||
for a in action:
|
||||
self._add_node(a)
|
||||
else:
|
||||
self._add_node(action)
|
||||
return self
|
||||
|
||||
def end(self) -> 'ReleaseFlowBuilder':
|
||||
"""结束当前复合节点或装饰器节点"""
|
||||
if self.node_stack:
|
||||
self.node_stack.pop()
|
||||
return self
|
||||
|
||||
def build(self) -> Behaviour:
|
||||
"""构建并返回行为树根节点"""
|
||||
if not self.root:
|
||||
raise ReleaseErr("No nodes added to behavior tree")
|
||||
|
||||
# 确保所有节点都已关闭
|
||||
if self.node_stack:
|
||||
raise ReleaseErr(f"Unclosed nodes: {[node.name for node in self.node_stack]}")
|
||||
|
||||
return self.root
|
||||
|
||||
def _add_node(self, node: Behaviour):
|
||||
"""添加节点到当前位置"""
|
||||
if not self.root:
|
||||
# 第一个节点作为根节点
|
||||
self.root = node
|
||||
self.current_node = node
|
||||
return
|
||||
|
||||
if not self.node_stack:
|
||||
# 如果没有开放的节点,直接替换根节点
|
||||
self.root = node
|
||||
self.current_node = node
|
||||
return
|
||||
|
||||
parent = self.node_stack[-1]
|
||||
|
||||
# 将节点添加到父节点
|
||||
if isinstance(parent, Composite):
|
||||
parent.add_child(node)
|
||||
else:
|
||||
raise ReleaseErr(f"Parent node {parent.name} is not a Composite")
|
||||
# elif isinstance(parent, Decorator):
|
||||
# parent.set_child(node)
|
||||
# # 装饰器只能有一个子节点,所以自动结束
|
||||
# if isinstance(node, (Composite, Decorator)):
|
||||
# # 但如果子节点是复合节点或装饰器,保持它开放
|
||||
# pass
|
||||
# else:
|
||||
# self.node_stack.pop()
|
||||
# 添加如下magic函数以用于支持with语句,可简化代码编写,依靠缩进使得层次关系更加清晰
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.end() # 结束序列
|
85
core/defines.py
Normal file
85
core/defines.py
Normal file
@ -0,0 +1,85 @@
|
||||
from enum import Enum
|
||||
import json
|
||||
|
||||
|
||||
class ReleasePhase(str, Enum):
|
||||
ENV = "PhaseEnv"
|
||||
CONFIG = "PhaseConfig"
|
||||
BUILD = "PhaseBuild"
|
||||
CHECK = "PhaseCheck"
|
||||
PACKAGE = "PhasePackage"
|
||||
VERIFY = "PhaseVerify"
|
||||
|
||||
|
||||
class ProcessStep(str, Enum):
|
||||
"""
|
||||
ReleaseFlow中每个阶段的几个执行步骤,顺序执行
|
||||
"""
|
||||
|
||||
PRE = "Pre"
|
||||
PROCESS = "Process"
|
||||
POST = "Post"
|
||||
|
||||
|
||||
class ReleaseErr(Exception):
|
||||
"""Release tool专用异常基类,包含错误信息和上下文"""
|
||||
|
||||
def __init__(self, message: str, ctx: dict = None):
|
||||
self.message = message
|
||||
self.ctx = ctx or {}
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.message} (context: {self.ctx})" if self.ctx else self.message
|
||||
|
||||
|
||||
class Dict(dict):
|
||||
"""
|
||||
一个字典,支持点号访问,从xxx[xxx][xxx] 的方式转化为 xxx.xxx.xxx 的形式访问字典中的值
|
||||
如果key不存在,返回一个空的字典
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 将字典中的所有值递归转换为 Dict
|
||||
for key, value in self.items():
|
||||
if isinstance(value, dict):
|
||||
self[key] = Dict(value)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "Dict":
|
||||
"""
|
||||
从普通字典创建一个 Dict 对象
|
||||
:param data: 要转换的字典
|
||||
:return: Dict 对象
|
||||
"""
|
||||
return cls(data)
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key in self:
|
||||
value = self[key]
|
||||
# 如果值是字典,递归转换为 Dict
|
||||
if isinstance(value, dict):
|
||||
return Dict(value)
|
||||
return value
|
||||
# 如果key不存在,返回None
|
||||
# return Dict()
|
||||
raise AttributeError(f"In object not found '{key}'")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
self[key] = value
|
||||
|
||||
def __str__(self):
|
||||
string = json.dumps(self, indent=4)
|
||||
return string
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class List(list):
|
||||
def __str__(self):
|
||||
return json.dumps(self, indent=4)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
82
core/logger.py
Normal file
82
core/logger.py
Normal file
@ -0,0 +1,82 @@
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
TRACE = 5
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
NOTSET = logging.NOTSET
|
||||
|
||||
def get_logger(name=None):
|
||||
if name is None:
|
||||
name = 'alkaid_release_platform'
|
||||
# 创建一个 logger
|
||||
logger = logging.getLogger(name)
|
||||
if logger.handlers:
|
||||
return logger
|
||||
|
||||
#定义一个新的日志级别,用于输出trace信息
|
||||
logging.TRACE = TRACE
|
||||
logging.addLevelName(logging.TRACE, "TRACE")
|
||||
def trace(self, message, *args, **kws):
|
||||
self._log(logging.TRACE, message, args, **kws)
|
||||
logging.Logger.trace = trace
|
||||
|
||||
logger.setLevel(INFO)
|
||||
class LogColors:
|
||||
RESET = "\033[0m"
|
||||
TRACE = "\033[35m" # 紫色
|
||||
DEBUG = "\033[34m" # 蓝色
|
||||
INFO = "\033[32m" # 绿色
|
||||
WARNING = "\033[33m" # 黄色
|
||||
ERROR = "\033[31m" # 红色
|
||||
CRITICAL = "\033[41m" # 红底白字
|
||||
|
||||
# 创建一个log的颜色格式化器,使得输出的log信息在控制台上有颜色区分
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
def __init__(self, msg, use_color=True):
|
||||
logging.Formatter.__init__(self, msg)
|
||||
self.use_color = use_color
|
||||
def format(self, record):
|
||||
level_color = {
|
||||
logging.TRACE: LogColors.TRACE,
|
||||
logging.DEBUG: LogColors.DEBUG,
|
||||
logging.INFO: LogColors.INFO,
|
||||
logging.WARNING: LogColors.WARNING,
|
||||
logging.ERROR: LogColors.ERROR,
|
||||
logging.CRITICAL: LogColors.CRITICAL
|
||||
}
|
||||
color = level_color.get(record.levelno, LogColors.RESET)
|
||||
message = super().format(record)
|
||||
return f"{color}{message}{LogColors.RESET}" if os.isatty(sys.stdout.fileno()) else message
|
||||
|
||||
# 创建一个带颜色的控制台处理器
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(INFO)
|
||||
# 创建一个带颜色的格式化器并将其添加到处理器
|
||||
formatter = ColoredFormatter('[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]: %(message)s')
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
# 将处理器添加到 logger
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# 定义一个方法,用于设置日志级别,并将其绑定到 logger 对象
|
||||
def set_level(self, level):
|
||||
lv_map = {
|
||||
"trace":TRACE,
|
||||
"debug":DEBUG,
|
||||
"info":INFO,
|
||||
"warning":WARNING,
|
||||
"error":ERROR,
|
||||
"critical":CRITICAL
|
||||
}
|
||||
level = lv_map.get(level, DEBUG)
|
||||
for handler in self.handlers:
|
||||
handler.setLevel(level)
|
||||
logger.setLevel(level)
|
||||
logging.Logger.set_level = set_level
|
||||
return logger
|
||||
|
219
core/plugin.py
Normal file
219
core/plugin.py
Normal file
@ -0,0 +1,219 @@
|
||||
# core/plugin.py
|
||||
from abc import ABC, abstractmethod
|
||||
import importlib
|
||||
from enum import Enum
|
||||
import os,sys
|
||||
import pkgutil
|
||||
import inspect
|
||||
from typing import Dict, List, Type
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from core.behavior_tree import ReleaseFlowAction
|
||||
from core.defines import ReleasePhase, ProcessStep, Dict
|
||||
import core.logger as logger
|
||||
log = logger.get_logger()
|
||||
|
||||
"""
|
||||
# format: PluginName.ReleaseStep.ProcessStep
|
||||
ReleaseFlowCommon.StepEnv.Pre
|
||||
"""
|
||||
|
||||
class PluginType(str, Enum):
|
||||
"""
|
||||
为插件分类,目前仅有一种插件,FLOW类型的插件用于执行Release Flow
|
||||
"""
|
||||
FLOW = 'flow'
|
||||
|
||||
class PluginInterface(ABC):
|
||||
"""PluginInterface 插件基类"""
|
||||
def __init__(self, type: PluginType):
|
||||
self.type = type
|
||||
|
||||
def get_type(self) -> PluginType:
|
||||
return self.type
|
||||
|
||||
@abstractmethod
|
||||
def get_version(self) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_description(self) -> str:
|
||||
"""返回插件描述"""
|
||||
pass
|
||||
|
||||
class ReleaseFlowPlugin(PluginInterface, ABC):
|
||||
"""ReleaseFlowPlugin 插件基类"""
|
||||
def __init__(self):
|
||||
PluginInterface.__init__(self, PluginType.FLOW)
|
||||
self.btrees:Dict = Dict()
|
||||
|
||||
@abstractmethod
|
||||
def get_version(self) -> str:
|
||||
"""返回插件版本"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_description(self) -> str:
|
||||
"""返回插件描述"""
|
||||
pass
|
||||
# 支持使用.操作符访问btrees
|
||||
def __getattr__(self, name: str) -> Dict:
|
||||
return self.btrees[name]
|
||||
|
||||
def ReleaseFlowPluginDecorator(cls):
|
||||
"""装饰器,用于自动注册ReleaseFlow插件, 每个 ReleaseFlowPlugin 插件上加上这个装饰器, 插件初始化时会自动搜索并注册行为树
|
||||
Args:
|
||||
cls: 插件类
|
||||
Returns:
|
||||
插件类
|
||||
"""
|
||||
original_init = cls.__init__
|
||||
# 如果不是ReleaseFlowPlugin的子类,则不进行装饰
|
||||
if not issubclass(cls, ReleaseFlowPlugin):
|
||||
return cls
|
||||
def behavior_tree_register(self: ReleaseFlowPlugin):
|
||||
"""
|
||||
搜索当前目录下的所有行为树节点,并根据命名注册到btrees中
|
||||
"""
|
||||
def _load_actions_from_package(self, package_name: str):
|
||||
"""从包中加载插件"""
|
||||
package = importlib.import_module(package_name)
|
||||
for _, name, is_pkg in pkgutil.iter_modules(package.__path__, f"{package_name}."):
|
||||
if not is_pkg:
|
||||
_load_actions(self, name)
|
||||
else:
|
||||
_load_actions_from_package(self, f"{package_name}.{name}")
|
||||
def _load_actions(self, module_name: str):
|
||||
"""加载单个behavior action"""
|
||||
# log.debug(f"Try to check action: {module_name}")
|
||||
module = importlib.import_module(module_name)
|
||||
# 遍历模块中的所有属性名称
|
||||
for attribute_name in dir(module):
|
||||
attribute = getattr(module, attribute_name)
|
||||
if not isinstance(attribute, type): # 检查是否是类
|
||||
continue
|
||||
if not issubclass(attribute, ReleaseFlowAction): # 检查是否是插件类
|
||||
continue
|
||||
if inspect.isabstract(attribute): # 检查是否是抽象类
|
||||
continue
|
||||
log.info(f"Loaded action: {attribute.__name__}")
|
||||
self.sub_actions[attribute.__name__] = attribute
|
||||
def discover_actions(self:ReleaseFlowPlugin):
|
||||
module = inspect.getmodule(self)
|
||||
path = os.path.relpath(os.path.dirname(module.__file__)).replace('/', '.')
|
||||
# log.debug(f"Try to search action in: {path}")
|
||||
self.sub_actions = {}
|
||||
for _, name, is_pkg in pkgutil.iter_modules([os.path.dirname(module.__file__)]):
|
||||
# log.trace(f"Try to load action: {name}")
|
||||
if is_pkg:
|
||||
_load_actions_from_package(self, f"{path}.{name}")
|
||||
else:
|
||||
_load_actions(self, f"{path}.{name}")
|
||||
|
||||
if not isinstance(self, ReleaseFlowPlugin):
|
||||
log.warning(f"Plugin class {self.__class__.__name__} is not a ReleaseFlowPlugin")
|
||||
raise ReleaseErr("The plugin class decorator is not a ReleaseFlowPlugin")
|
||||
discover_actions(self)
|
||||
for phase in ReleasePhase:
|
||||
phase = phase.value
|
||||
self.btrees[phase] = Dict()
|
||||
for step in ProcessStep:
|
||||
step = step.value
|
||||
action_class = f"_{phase}_{step}"
|
||||
if not self.sub_actions.get(action_class):
|
||||
log.trace(f"Action {action_class} not found")
|
||||
continue
|
||||
log.debug(f"Register ReleaseFlow behavior tree node: {action_class}")
|
||||
if not self.btrees[phase].get(step):
|
||||
self.btrees[phase][step] = self.sub_actions[action_class]
|
||||
else:
|
||||
log.warning(f"Behavior tree node {action_class} already exists: {self.btrees[phase][step]}")
|
||||
def new_init(self, *args, **kwargs):
|
||||
original_init(self, *args, **kwargs)
|
||||
behavior_tree_register(self)
|
||||
# 添加标记,表示该类使用了flow action 装饰器,用于检查是否注册了行为树
|
||||
cls._is_action_registered = True
|
||||
cls.__init__ = new_init
|
||||
return cls
|
||||
|
||||
class PluginManager:
|
||||
"""插件管理器"""
|
||||
def __init__(self, plugin_dir: str = "plugins"):
|
||||
self.plugin_dir = plugin_dir
|
||||
self.plugins: Dict[str, Type[PluginInterface]] = {}
|
||||
self.loaded_plugins: Dict[str, PluginInterface] = {}
|
||||
self.discover_plugins()
|
||||
|
||||
def discover_plugins(self):
|
||||
"""发现并加载所有插件"""
|
||||
for _, name, is_pkg in pkgutil.iter_modules([self.plugin_dir]):
|
||||
plugin_dir = f"{self.plugin_dir}.{name}"
|
||||
log.trace(f"Try to search plugin in: {plugin_dir}")
|
||||
if is_pkg:
|
||||
self._load_plugins_from_package(plugin_dir)
|
||||
else:
|
||||
self._load_plugin(plugin_dir)
|
||||
|
||||
def _load_plugins_from_package(self, package_name: str):
|
||||
"""从包中加载插件"""
|
||||
package = importlib.import_module(package_name)
|
||||
for _, name, is_pkg in pkgutil.iter_modules(package.__path__, f"{package_name}."):
|
||||
if not is_pkg:
|
||||
self._load_plugin(name)
|
||||
else:
|
||||
self._load_plugins_from_package(f"{package_name}.{name}")
|
||||
|
||||
def _load_plugin(self, module_name: str):
|
||||
"""加载单个插件"""
|
||||
module = importlib.import_module(module_name)
|
||||
for attribute_name in dir(module):
|
||||
plugin_class = getattr(module, attribute_name)
|
||||
if not isinstance(plugin_class, type): # 检查是否是类
|
||||
continue
|
||||
if not issubclass(plugin_class, PluginInterface): # 检查是否是插件类
|
||||
continue
|
||||
if inspect.isabstract(plugin_class): # 检查是否是抽象类
|
||||
continue
|
||||
# 检查插件类是否使用了 ReleaseFlowActionRegister 装饰器
|
||||
if issubclass(plugin_class, ReleaseFlowPlugin) and not hasattr(plugin_class, '_is_action_registered'):
|
||||
log.warning(f"Plugin class {plugin_class.__name__} does not have the ReleaseFlowActionRegister decorator.")
|
||||
continue
|
||||
|
||||
log.info(f"Loaded plugin: {plugin_class.__name__}")
|
||||
self.plugins[plugin_class.__name__] = plugin_class
|
||||
|
||||
def get_plugin(self, name: str) -> PluginInterface:
|
||||
"""获取插件实例, 每个插件实例只初始化一次
|
||||
Args:
|
||||
name: 插件名称
|
||||
Returns:
|
||||
插件实例
|
||||
"""
|
||||
if name not in self.loaded_plugins:
|
||||
if name in self.plugins:
|
||||
self.loaded_plugins[name] = self.plugins[name]()
|
||||
else:
|
||||
raise ValueError(f"Plugin {name} not found")
|
||||
return self.loaded_plugins[name]
|
||||
|
||||
|
||||
def get_available_plugins(self) -> List[str]:
|
||||
"""获取所有可用插件名称"""
|
||||
return list(self.plugins.keys())
|
||||
|
||||
def __str__(self) -> str:
|
||||
string = ""
|
||||
string += f"Plugin directory: {self.plugin_dir}\n"
|
||||
string += "Available plugins:"
|
||||
string += "[" + " \n".join(self.plugins.keys()) + "]\n"
|
||||
string += "Loaded plugins:"
|
||||
string += "[" + " \n".join(self.loaded_plugins.keys()) + "]\n"
|
||||
return string
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
def __getattr__(self, name: str) -> PluginInterface:
|
||||
# 获取插件实例
|
||||
plugin = self.get_plugin(name)
|
||||
return plugin
|
157
core/release_flow.py
Normal file
157
core/release_flow.py
Normal file
@ -0,0 +1,157 @@
|
||||
import functools
|
||||
import os,sys
|
||||
import threading
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
import thirdparty.py_trees as py_trees
|
||||
from thirdparty.py_trees.trees import BehaviourTree
|
||||
from core.behavior_tree import ReleaseFlowBuilder
|
||||
from core.plugin import PluginManager
|
||||
from core.defines import ReleasePhase, ProcessStep, ReleaseErr, Dict
|
||||
from database import *
|
||||
import core.logger as logger
|
||||
log = logger.get_logger()
|
||||
|
||||
def draw_release_flow_status(snapshot_visitor, behaviour_tree):
|
||||
"""保存行为树状态为HTML文件"""
|
||||
mutex = threading.Lock()
|
||||
with mutex:
|
||||
# timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"alkaid_release_flow_status.html"
|
||||
with open(filename, 'w') as f:
|
||||
f.write('<html><head><title>SStar Alkaid Release Flow Status</title><body>')
|
||||
f.write(py_trees.display.xhtml_tree(
|
||||
behaviour_tree.root,
|
||||
show_status=True,
|
||||
visited=snapshot_visitor.visited,
|
||||
previously_visited=snapshot_visitor.visited
|
||||
))
|
||||
f.write("</body></html>")
|
||||
filename = f"alkaid_release_flow_status.dot"
|
||||
with open(filename, 'w') as f:
|
||||
f.write(str(py_trees.display.dot_tree(behaviour_tree.root, with_blackboard_variables=True)))
|
||||
# log.info(f"Behavior tree saved to {filename}")
|
||||
|
||||
# 构建环境阶段 (验证配置、拉取code等基础环境的准备)
|
||||
def _construct_env_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.ENV):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
if not ReleaseOptions.SkipSyncCode:
|
||||
builder.action(plugins.BaseLine.PhaseEnv.Process())
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
# 配置编译文件阶段 (defconfig mkfile 等编译控制文件的配置)
|
||||
def _construct_config_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.CONFIG):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
# 构建阶段(编译构建sdk,根据上一步的配置执行编译构建)
|
||||
def _construct_build_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.BUILD):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
# 检查阶段(检查SDK编译结果是否符合预期)
|
||||
def _construct_check_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.CHECK):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
# 打包阶段 (将SDK其他文件诸如doc,tool等打包)
|
||||
def _construct_package_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.PACKAGE):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
# 验证阶段 (验证Release的SDK是否可以正常工作)
|
||||
def _construct_verify_phase(builder: ReleaseFlowBuilder, plugins: PluginManager):
|
||||
with builder.sequence(ReleasePhase.VERIFY):
|
||||
with builder.sequence(ProcessStep.PRE):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.PROCESS):
|
||||
pass
|
||||
with builder.sequence(ProcessStep.POST):
|
||||
pass
|
||||
|
||||
class ReleaseFlow():
|
||||
def __init__(self):
|
||||
# 我们的Flow默认是一个串行的流程,所以根节点用Sequence
|
||||
self.builder = ReleaseFlowBuilder()
|
||||
self.tree = None
|
||||
self.flow = None
|
||||
self.timer = None
|
||||
self.status_update_interval = 1 # 定时器间隔时间,单位秒
|
||||
self.running = False
|
||||
|
||||
# 构建 Alkaid Release流程
|
||||
def construct(self, plugins: PluginManager):
|
||||
with self.builder.sequence("AlkaidReleaseFlow") as builder:
|
||||
_construct_env_phase(builder, plugins)
|
||||
_construct_config_phase(builder, plugins)
|
||||
_construct_build_phase(builder, plugins)
|
||||
_construct_check_phase(builder, plugins)
|
||||
_construct_package_phase(builder, plugins)
|
||||
_construct_verify_phase(builder, plugins)
|
||||
self.flow = self.builder.build()
|
||||
|
||||
def _start_update_status(self, snapshot_visitor):
|
||||
"""启动定时器"""
|
||||
if self.timer is not None:
|
||||
return
|
||||
|
||||
def timer_callback():
|
||||
if self.tree is not None:
|
||||
draw_release_flow_status(snapshot_visitor, self.tree)
|
||||
# 重新设置定时器
|
||||
self.timer = threading.Timer(self.status_update_interval, timer_callback)
|
||||
self.timer.start()
|
||||
|
||||
# 启动第一个定时器
|
||||
self.timer = threading.Timer(self.status_update_interval, timer_callback)
|
||||
self.tree.add_post_tick_handler(functools.partial(draw_release_flow_status, snapshot_visitor))
|
||||
self.timer.start()
|
||||
|
||||
# 运行Release 流程
|
||||
def run(self):
|
||||
if self.flow is None:
|
||||
raise ReleaseErr("Flow is not built")
|
||||
# 使用封装好的BehaviourTree去管理流程
|
||||
self.tree = BehaviourTree(root=self.flow)
|
||||
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
|
||||
self.tree.visitors.append(snapshot_visitor)
|
||||
self.tree.setup() # 初始化树
|
||||
# 启动定时器
|
||||
self._start_update_status(snapshot_visitor)
|
||||
# print(
|
||||
# py_trees.display.dot_tree(
|
||||
# self.tree.root
|
||||
# )
|
||||
# )
|
||||
self.tree.tick() # 触发tick进行执行
|
||||
return self.tree.root.status
|
||||
|
||||
def shutdown(self):
|
||||
if self.timer is not None:
|
||||
self.timer.cancel()
|
||||
self.timer = None
|
||||
if self.tree is not None:
|
||||
self.tree.shutdown()
|
Reference in New Issue
Block a user