232 lines
7.9 KiB
Python
232 lines
7.9 KiB
Python
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:
|
||
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:
|
||
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:
|
||
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):
|
||
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 scan_plugin_manifests(self, base_dir: str = "store") -> List[Dict[str, Any]]:
|
||
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]:
|
||
plugins = self.scan_plugin_manifests(base_dir)
|
||
|
||
all_deps = {} 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]:
|
||
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]:
|
||
|
||
通过 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.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.get_system_info()
|
||
|
||
injector.register_function(
|
||
"auto-dependency:info",
|
||
get_info,
|
||
"获取自动依赖插件的系统信息"
|
||
)
|
||
|
||
|
||
def New() -> AutoDependencyPlugin:
|