feat:插件依赖管理(如管
This commit is contained in:
@@ -16,10 +16,12 @@ from src.plugin_system.base.component_types import (
|
||||
ActionInfo,
|
||||
CommandInfo,
|
||||
PluginInfo,
|
||||
PythonDependency,
|
||||
)
|
||||
from src.plugin_system.apis.plugin_api import PluginAPI, create_plugin_api, create_command_api
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
@@ -36,6 +38,7 @@ __all__ = [
|
||||
"ActionInfo",
|
||||
"CommandInfo",
|
||||
"PluginInfo",
|
||||
"PythonDependency",
|
||||
# API接口
|
||||
"PluginAPI",
|
||||
"create_plugin_api",
|
||||
@@ -43,6 +46,7 @@ __all__ = [
|
||||
# 管理器
|
||||
"plugin_manager",
|
||||
"component_registry",
|
||||
"dependency_manager",
|
||||
# 装饰器
|
||||
"register_plugin",
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@ from src.common.logger import get_logger
|
||||
from src.plugin_system.base.component_types import (
|
||||
PluginInfo,
|
||||
ComponentInfo,
|
||||
PythonDependency,
|
||||
)
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
|
||||
@@ -32,6 +33,7 @@ class BasePlugin(ABC):
|
||||
plugin_author: str = "" # 插件作者
|
||||
enable_plugin: bool = True # 是否启用插件
|
||||
dependencies: List[str] = [] # 依赖的其他插件
|
||||
python_dependencies: List[PythonDependency] = [] # Python包依赖
|
||||
config_file_name: Optional[str] = None # 配置文件名
|
||||
|
||||
def __init__(self, plugin_dir: str = None):
|
||||
@@ -60,6 +62,7 @@ class BasePlugin(ABC):
|
||||
is_built_in=False,
|
||||
config_file=self.config_file_name or "",
|
||||
dependencies=self.dependencies.copy(),
|
||||
python_dependencies=self.python_dependencies.copy(),
|
||||
)
|
||||
|
||||
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
|
||||
|
||||
@@ -33,6 +33,27 @@ class ChatMode(Enum):
|
||||
ALL = "all" # 所有聊天模式
|
||||
|
||||
|
||||
@dataclass
|
||||
class PythonDependency:
|
||||
"""Python包依赖信息"""
|
||||
|
||||
package_name: str # 包名称
|
||||
version: str = "" # 版本要求,例如: ">=1.0.0", "==2.1.3", ""表示任意版本
|
||||
optional: bool = False # 是否为可选依赖
|
||||
description: str = "" # 依赖描述
|
||||
install_name: str = "" # 安装时的包名(如果与import名不同)
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.install_name:
|
||||
self.install_name = self.package_name
|
||||
|
||||
def get_pip_requirement(self) -> str:
|
||||
"""获取pip安装格式的依赖字符串"""
|
||||
if self.version:
|
||||
return f"{self.install_name}{self.version}"
|
||||
return self.install_name
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComponentInfo:
|
||||
"""组件信息"""
|
||||
@@ -107,6 +128,7 @@ class PluginInfo:
|
||||
is_built_in: bool = False # 是否为内置插件
|
||||
components: List[ComponentInfo] = None # 包含的组件列表
|
||||
dependencies: List[str] = None # 依赖的其他插件
|
||||
python_dependencies: List[PythonDependency] = None # Python包依赖
|
||||
config_file: str = "" # 配置文件路径
|
||||
metadata: Dict[str, Any] = None # 额外元数据
|
||||
|
||||
@@ -115,5 +137,22 @@ class PluginInfo:
|
||||
self.components = []
|
||||
if self.dependencies is None:
|
||||
self.dependencies = []
|
||||
if self.python_dependencies is None:
|
||||
self.python_dependencies = []
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
def get_missing_packages(self) -> List[PythonDependency]:
|
||||
"""检查缺失的Python包"""
|
||||
missing = []
|
||||
for dep in self.python_dependencies:
|
||||
try:
|
||||
__import__(dep.package_name)
|
||||
except ImportError:
|
||||
if not dep.optional:
|
||||
missing.append(dep)
|
||||
return missing
|
||||
|
||||
def get_pip_requirements(self) -> List[str]:
|
||||
"""获取所有pip安装格式的依赖"""
|
||||
return [dep.get_pip_requirement() for dep in self.python_dependencies]
|
||||
|
||||
190
src/plugin_system/core/dependency_manager.py
Normal file
190
src/plugin_system/core/dependency_manager.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
插件依赖管理器
|
||||
|
||||
负责检查和安装插件的Python包依赖
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import importlib
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
from pathlib import Path
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.base.component_types import PythonDependency
|
||||
|
||||
logger = get_logger("dependency_manager")
|
||||
|
||||
|
||||
class DependencyManager:
|
||||
"""依赖管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.install_log: List[str] = []
|
||||
self.failed_installs: Dict[str, str] = {}
|
||||
|
||||
def check_dependencies(self, dependencies: List[PythonDependency]) -> Tuple[List[PythonDependency], List[PythonDependency]]:
|
||||
"""检查依赖包状态
|
||||
|
||||
Args:
|
||||
dependencies: 依赖包列表
|
||||
|
||||
Returns:
|
||||
Tuple[List[PythonDependency], List[PythonDependency]]: (缺失的依赖, 可选缺失的依赖)
|
||||
"""
|
||||
missing_required = []
|
||||
missing_optional = []
|
||||
|
||||
for dep in dependencies:
|
||||
if not self._is_package_available(dep.package_name):
|
||||
if dep.optional:
|
||||
missing_optional.append(dep)
|
||||
logger.warning(f"可选依赖包缺失: {dep.package_name} - {dep.description}")
|
||||
else:
|
||||
missing_required.append(dep)
|
||||
logger.error(f"必需依赖包缺失: {dep.package_name} - {dep.description}")
|
||||
else:
|
||||
logger.debug(f"依赖包已存在: {dep.package_name}")
|
||||
|
||||
return missing_required, missing_optional
|
||||
|
||||
def _is_package_available(self, package_name: str) -> bool:
|
||||
"""检查包是否可用"""
|
||||
try:
|
||||
importlib.import_module(package_name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def install_dependencies(self, dependencies: List[PythonDependency], auto_install: bool = False) -> bool:
|
||||
"""安装依赖包
|
||||
|
||||
Args:
|
||||
dependencies: 需要安装的依赖包列表
|
||||
auto_install: 是否自动安装(True时不询问用户)
|
||||
|
||||
Returns:
|
||||
bool: 安装是否成功
|
||||
"""
|
||||
if not dependencies:
|
||||
return True
|
||||
|
||||
logger.info(f"需要安装 {len(dependencies)} 个依赖包")
|
||||
|
||||
# 显示将要安装的包
|
||||
for dep in dependencies:
|
||||
install_cmd = dep.get_pip_requirement()
|
||||
logger.info(f" - {install_cmd} {'(可选)' if dep.optional else '(必需)'}")
|
||||
if dep.description:
|
||||
logger.info(f" 说明: {dep.description}")
|
||||
|
||||
if not auto_install:
|
||||
# 这里可以添加用户确认逻辑
|
||||
logger.warning("手动安装模式:请手动运行 pip install 命令安装依赖包")
|
||||
return False
|
||||
|
||||
# 执行安装
|
||||
success_count = 0
|
||||
for dep in dependencies:
|
||||
if self._install_single_package(dep):
|
||||
success_count += 1
|
||||
else:
|
||||
self.failed_installs[dep.package_name] = f"安装失败: {dep.get_pip_requirement()}"
|
||||
|
||||
logger.info(f"依赖安装完成: {success_count}/{len(dependencies)} 个成功")
|
||||
return success_count == len(dependencies)
|
||||
|
||||
def _install_single_package(self, dependency: PythonDependency) -> bool:
|
||||
"""安装单个包"""
|
||||
pip_requirement = dependency.get_pip_requirement()
|
||||
|
||||
try:
|
||||
logger.info(f"正在安装: {pip_requirement}")
|
||||
|
||||
# 使用subprocess安装包
|
||||
cmd = [sys.executable, "-m", "pip", "install", pip_requirement]
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"✅ 成功安装: {pip_requirement}")
|
||||
self.install_log.append(f"成功安装: {pip_requirement}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"❌ 安装失败: {pip_requirement}")
|
||||
logger.error(f"错误输出: {result.stderr}")
|
||||
self.install_log.append(f"安装失败: {pip_requirement} - {result.stderr}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"❌ 安装超时: {pip_requirement}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 安装异常: {pip_requirement} - {str(e)}")
|
||||
return False
|
||||
|
||||
def generate_requirements_file(self, plugins_dependencies: List[List[PythonDependency]],
|
||||
output_path: str = "plugin_requirements.txt") -> bool:
|
||||
"""生成插件依赖的requirements文件
|
||||
|
||||
Args:
|
||||
plugins_dependencies: 所有插件的依赖列表
|
||||
output_path: 输出文件路径
|
||||
|
||||
Returns:
|
||||
bool: 生成是否成功
|
||||
"""
|
||||
try:
|
||||
all_deps = {}
|
||||
|
||||
# 合并所有插件的依赖
|
||||
for plugin_deps in plugins_dependencies:
|
||||
for dep in plugin_deps:
|
||||
key = dep.install_name
|
||||
if key in all_deps:
|
||||
# 如果已存在,可以添加版本兼容性检查逻辑
|
||||
existing = all_deps[key]
|
||||
if dep.version and existing.version != dep.version:
|
||||
logger.warning(f"依赖版本冲突: {key} ({existing.version} vs {dep.version})")
|
||||
else:
|
||||
all_deps[key] = dep
|
||||
|
||||
# 写入requirements文件
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("# 插件依赖包自动生成\n")
|
||||
f.write("# Auto-generated plugin dependencies\n\n")
|
||||
|
||||
# 按包名排序
|
||||
sorted_deps = sorted(all_deps.values(), key=lambda x: x.install_name)
|
||||
|
||||
for dep in sorted_deps:
|
||||
requirement = dep.get_pip_requirement()
|
||||
if dep.description:
|
||||
f.write(f"# {dep.description}\n")
|
||||
if dep.optional:
|
||||
f.write(f"# Optional dependency\n")
|
||||
f.write(f"{requirement}\n\n")
|
||||
|
||||
logger.info(f"已生成插件依赖文件: {output_path} ({len(all_deps)} 个包)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成requirements文件失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_install_summary(self) -> Dict[str, any]:
|
||||
"""获取安装摘要"""
|
||||
return {
|
||||
"install_log": self.install_log.copy(),
|
||||
"failed_installs": self.failed_installs.copy(),
|
||||
"total_attempts": len(self.install_log),
|
||||
"failed_count": len(self.failed_installs)
|
||||
}
|
||||
|
||||
|
||||
# 全局依赖管理器实例
|
||||
dependency_manager = DependencyManager()
|
||||
@@ -9,7 +9,8 @@ if TYPE_CHECKING:
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.core.component_registry import component_registry
|
||||
from src.plugin_system.base.component_types import ComponentType, PluginInfo
|
||||
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||
from src.plugin_system.base.component_types import ComponentType, PluginInfo, PythonDependency
|
||||
|
||||
logger = get_logger("plugin_manager")
|
||||
|
||||
@@ -344,6 +345,109 @@ class PluginManager:
|
||||
logger.warning("插件热重载功能尚未实现")
|
||||
return False
|
||||
|
||||
def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, any]:
|
||||
"""检查所有插件的Python依赖包
|
||||
|
||||
Args:
|
||||
auto_install: 是否自动安装缺失的依赖包
|
||||
|
||||
Returns:
|
||||
Dict[str, any]: 检查结果摘要
|
||||
"""
|
||||
logger.info("开始检查所有插件的Python依赖包...")
|
||||
|
||||
all_required_missing = []
|
||||
all_optional_missing = []
|
||||
plugin_status = {}
|
||||
|
||||
for plugin_name, plugin_instance in self.loaded_plugins.items():
|
||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||
if not plugin_info or not plugin_info.python_dependencies:
|
||||
plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []}
|
||||
continue
|
||||
|
||||
logger.info(f"检查插件 {plugin_name} 的依赖...")
|
||||
|
||||
missing_required, missing_optional = dependency_manager.check_dependencies(
|
||||
plugin_info.python_dependencies
|
||||
)
|
||||
|
||||
if missing_required:
|
||||
all_required_missing.extend(missing_required)
|
||||
plugin_status[plugin_name] = {
|
||||
"status": "missing_required",
|
||||
"missing": [dep.package_name for dep in missing_required],
|
||||
"optional_missing": [dep.package_name for dep in missing_optional]
|
||||
}
|
||||
logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}")
|
||||
elif missing_optional:
|
||||
all_optional_missing.extend(missing_optional)
|
||||
plugin_status[plugin_name] = {
|
||||
"status": "missing_optional",
|
||||
"missing": [],
|
||||
"optional_missing": [dep.package_name for dep in missing_optional]
|
||||
}
|
||||
logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}")
|
||||
else:
|
||||
plugin_status[plugin_name] = {"status": "ok", "missing": []}
|
||||
logger.info(f"插件 {plugin_name} 依赖检查通过")
|
||||
|
||||
# 汇总结果
|
||||
total_missing = len(set(dep.package_name for dep in all_required_missing))
|
||||
total_optional_missing = len(set(dep.package_name for dep in all_optional_missing))
|
||||
|
||||
logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个")
|
||||
|
||||
# 如果需要自动安装
|
||||
install_success = True
|
||||
if auto_install and all_required_missing:
|
||||
# 去重
|
||||
unique_required = {}
|
||||
for dep in all_required_missing:
|
||||
unique_required[dep.package_name] = dep
|
||||
|
||||
logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...")
|
||||
install_success = dependency_manager.install_dependencies(
|
||||
list(unique_required.values()),
|
||||
auto_install=True
|
||||
)
|
||||
|
||||
return {
|
||||
"total_plugins_checked": len(plugin_status),
|
||||
"plugins_with_missing_required": len([p for p in plugin_status.values() if p["status"] == "missing_required"]),
|
||||
"plugins_with_missing_optional": len([p for p in plugin_status.values() if p["status"] == "missing_optional"]),
|
||||
"total_missing_required": total_missing,
|
||||
"total_missing_optional": total_optional_missing,
|
||||
"plugin_status": plugin_status,
|
||||
"auto_install_attempted": auto_install and bool(all_required_missing),
|
||||
"auto_install_success": install_success,
|
||||
"install_summary": dependency_manager.get_install_summary()
|
||||
}
|
||||
|
||||
def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool:
|
||||
"""生成所有插件依赖的requirements文件
|
||||
|
||||
Args:
|
||||
output_path: 输出文件路径
|
||||
|
||||
Returns:
|
||||
bool: 生成是否成功
|
||||
"""
|
||||
logger.info("开始生成插件依赖requirements文件...")
|
||||
|
||||
all_dependencies = []
|
||||
|
||||
for plugin_name, plugin_instance in self.loaded_plugins.items():
|
||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||
if plugin_info and plugin_info.python_dependencies:
|
||||
all_dependencies.append(plugin_info.python_dependencies)
|
||||
|
||||
if not all_dependencies:
|
||||
logger.info("没有找到任何插件依赖")
|
||||
return False
|
||||
|
||||
return dependency_manager.generate_requirements_file(all_dependencies, output_path)
|
||||
|
||||
|
||||
# 全局插件管理器实例
|
||||
plugin_manager = PluginManager()
|
||||
|
||||
Reference in New Issue
Block a user