219 lines
8.5 KiB
Python
219 lines
8.5 KiB
Python
|
# 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
|