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 |