"""依赖自动安装插件 - 扫描所有插件的声明文件,检查并安装系统依赖 功能说明: 1. 扫描所有插件目录下的 manifest.json 文件 2. 读取每个插件声明的系统依赖 (system_dependencies 字段) 3. 检查这些系统依赖是否已安装 4. 对于未安装的依赖,使用系统包管理器自动安装 5. 通过 PL 注入机制向插件加载器注册功能接口 """ import subprocess import shutil import json from pathlib import Path from typing import Any, Optional, List, Dict from oss.plugin.types import Plugin class SystemDependencyChecker: """系统依赖检查器""" def __init__(self): self.package_managers = { "apt": ["apt-get", "apt"], "yum": ["yum", "dnf"], "pacman": ["pacman"], "brew": ["brew"], "apk": ["apk"], } self.detected_pm = self._detect_package_manager() def _detect_package_manager(self) -> str: """检测系统包管理器""" for pm, commands in self.package_managers.items(): for cmd in commands: if shutil.which(cmd): return pm return "unknown" def check_command(self, command: str) -> bool: """检查命令是否可用""" return shutil.which(command) is not None def check_package(self, package: str) -> bool: """检查系统包是否已安装""" if not self.detected_pm or self.detected_pm == "unknown": return False try: if self.detected_pm in ["apt", "apt-get"]: result = subprocess.run( ["dpkg", "-l", package], capture_output=True, text=True, timeout=30 ) return result.returncode == 0 and "ii" in result.stdout elif self.detected_pm in ["yum", "dnf"]: result = subprocess.run( ["rpm", "-q", package], capture_output=True, text=True, timeout=30 ) return result.returncode == 0 elif self.detected_pm == "pacman": result = subprocess.run( ["pacman", "-Q", package], capture_output=True, text=True, timeout=30 ) return result.returncode == 0 elif self.detected_pm == "brew": result = subprocess.run( ["brew", "list", package], capture_output=True, text=True, timeout=30 ) return result.returncode == 0 elif self.detected_pm == "apk": result = subprocess.run( ["apk", "info", "-e", package], capture_output=True, text=True, timeout=30 ) return result.returncode == 0 except Exception: pass return False def install_package(self, package: str) -> bool: """安装系统包""" if not self.detected_pm or self.detected_pm == "unknown": return False try: if self.detected_pm in ["apt", "apt-get"]: result = subprocess.run( ["apt-get", "install", "-y", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 elif self.detected_pm == "yum": result = subprocess.run( ["yum", "install", "-y", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 elif self.detected_pm == "dnf": result = subprocess.run( ["dnf", "install", "-y", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 elif self.detected_pm == "pacman": result = subprocess.run( ["pacman", "-S", "--noconfirm", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 elif self.detected_pm == "brew": result = subprocess.run( ["brew", "install", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 elif self.detected_pm == "apk": result = subprocess.run( ["apk", "add", package], capture_output=True, text=True, timeout=300 ) return result.returncode == 0 except Exception: pass return False def check_and_install(self, package: str, auto_install: bool = True) -> Dict[str, Any]: """检查并安装包""" result = { "package": package, "installed": self.check_package(package), "action": "none", "success": True, "message": "" } if result["installed"]: result["message"] = f"包 '{package}' 已安装" return result if not auto_install: result["action"] = "skipped" result["message"] = f"包 '{package}' 未安装,但自动安装已禁用" result["success"] = False return result result["action"] = "installing" if self.install_package(package): result["installed"] = True result["success"] = True result["message"] = f"包 '{package}' 安装成功" else: result["success"] = False result["message"] = f"包 '{package}' 安装失败" return result class AutoDependencyPlugin(Plugin): """依赖自动安装插件""" def __init__(self): self.checker = SystemDependencyChecker() self.scan_dirs: List[str] = [] self.auto_install: bool = True self._plugin_loader_ref: Optional[Any] = None def init(self, deps: Optional[Dict[str, Any]] = None): """初始化插件""" if deps: self.scan_dirs = deps.get("scan_dirs", ["store"]) self.auto_install = deps.get("auto_install", True) # 获取插件加载器引用(通过依赖注入) if "plugin-loader" in deps: self._plugin_loader_ref = deps["plugin-loader"] def start(self): """启动插件""" pass def stop(self): """停止插件""" pass def scan_plugin_manifests(self, base_dir: str = "store") -> List[Dict[str, Any]]: """扫描所有插件的 manifest.json 文件 Returns: 包含所有插件信息的列表,每个元素包含: - plugin_name: 插件名称 - plugin_dir: 插件目录路径 - manifest: manifest.json 内容 - system_dependencies: 系统依赖列表 """ results = [] base_path = Path(base_dir) if not base_path.exists(): return results # 扫描所有插件目录 for vendor_dir in base_path.iterdir(): if not vendor_dir.is_dir(): continue for plugin_dir in vendor_dir.iterdir(): if not plugin_dir.is_dir(): continue manifest_file = plugin_dir / "manifest.json" if not manifest_file.exists(): continue try: with open(manifest_file, "r", encoding="utf-8") as f: manifest = json.load(f) # 提取系统依赖 system_deps = manifest.get("system_dependencies", []) results.append({ "plugin_name": plugin_dir.name.rstrip("}"), "plugin_dir": str(plugin_dir), "manifest": manifest, "system_dependencies": system_deps }) except Exception: continue return results def check_all_dependencies(self, base_dir: str = "store") -> Dict[str, Any]: """检查所有插件的系统依赖 Args: base_dir: 基础扫描目录 Returns: 检查结果字典,包含: - total_plugins: 扫描的插件总数 - plugins_with_deps: 有系统依赖的插件数 - dependencies: 依赖检查结果列表 - missing_count: 缺失的依赖数量 - installed_count: 已安装的依赖数量 """ plugins = self.scan_plugin_manifests(base_dir) all_deps = {} # {package: [plugin_names]} for plugin in plugins: for dep in plugin["system_dependencies"]: if dep not in all_deps: all_deps[dep] = [] all_deps[dep].append(plugin["plugin_name"]) results = [] installed_count = 0 missing_count = 0 for package, plugin_names in all_deps.items(): is_installed = self.checker.check_package(package) if is_installed: installed_count += 1 else: missing_count += 1 results.append({ "package": package, "installed": is_installed, "required_by": plugin_names }) return { "total_plugins": len(plugins), "plugins_with_deps": sum(1 for p in plugins if p["system_dependencies"]), "dependencies": results, "missing_count": missing_count, "installed_count": installed_count } def install_missing_dependencies(self, base_dir: str = "store") -> Dict[str, Any]: """安装所有缺失的系统依赖 Args: base_dir: 基础扫描目录 Returns: 安装结果字典,包含: - total_to_install: 需要安装的包数量 - success_count: 成功安装的包数量 - failed_count: 安装失败的包数量 - results: 每个包的安装结果 """ check_result = self.check_all_dependencies(base_dir) to_install = [dep for dep in check_result["dependencies"] if not dep["installed"]] install_results = [] success_count = 0 failed_count = 0 for dep in to_install: result = self.checker.check_and_install(dep["package"], auto_install=True) result["required_by"] = dep["required_by"] install_results.append(result) if result["success"]: success_count += 1 else: failed_count += 1 return { "total_to_install": len(to_install), "success_count": success_count, "failed_count": failed_count, "results": install_results } def get_system_info(self) -> Dict[str, Any]: """获取系统信息""" return { "package_manager": self.checker.detected_pm, "auto_install_enabled": self.auto_install, "scan_directories": self.scan_dirs } def register_pl_functions(self, injector: Any): """注册 PL 注入功能 通过 PL 注入机制向插件加载器注册以下功能: - auto-dependency:scan: 扫描所有插件的系统依赖 - auto-dependency:check: 检查依赖安装状态 - auto-dependency:install: 安装缺失的依赖 - auto-dependency:info: 获取插件系统信息 """ # 注册扫描功能 def scan_deps(scan_dir: str = "store") -> Dict[str, Any]: """扫描所有插件的声明文件""" return self.scan_plugin_manifests(scan_dir) injector.register_function( "auto-dependency:scan", scan_deps, "扫描所有插件的声明文件,获取系统依赖列表" ) # 注册检查功能 def check_deps(scan_dir: str = "store") -> Dict[str, Any]: """检查所有系统依赖的安装状态""" return self.check_all_dependencies(scan_dir) injector.register_function( "auto-dependency:check", check_deps, "检查所有插件声明的系统依赖是否已安装" ) # 注册安装功能 def install_deps(scan_dir: str = "store") -> Dict[str, Any]: """安装所有缺失的系统依赖""" return self.install_missing_dependencies(scan_dir) injector.register_function( "auto-dependency:install", install_deps, "自动安装所有缺失的系统依赖" ) # 注册信息功能 def get_info() -> Dict[str, Any]: """获取插件系统信息""" return self.get_system_info() injector.register_function( "auto-dependency:info", get_info, "获取自动依赖插件的系统信息" ) def New() -> AutoDependencyPlugin: """创建插件实例""" return AutoDependencyPlugin()