更改项目名为NebulaShell
This commit is contained in:
410
store/@{NebulaShell}/auto-dependency/main.py
Normal file
410
store/@{NebulaShell}/auto-dependency/main.py
Normal file
@@ -0,0 +1,410 @@
|
||||
"""依赖自动安装插件 - 扫描所有插件的声明文件,检查并安装系统依赖
|
||||
|
||||
功能说明:
|
||||
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 as e:
|
||||
import traceback; print(f"[main.py] 错误:{type(e).__name__}:{e}"); traceback.print_exc()
|
||||
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 as e:
|
||||
import traceback; print(f"[main.py] 错误:{type(e).__name__}:{e}"); traceback.print_exc()
|
||||
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 as e:
|
||||
import traceback; print(f"[main.py] 错误:{type(e).__name__}:{e}"); traceback.print_exc()
|
||||
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()
|
||||
Reference in New Issue
Block a user