aps-agent/scripts/platform-deps-check.py

106 lines
3.7 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
r"""平台依赖检查(矩阵 102 依赖矩阵部分)。
检测当前环境:python 版本、平台/架构、requirements.txt 全部依赖可导入性与版本下限。
用法:.venv\Scripts\python.exe scripts/platform-deps-check.py (退出码 0=通过)
"""
from __future__ import annotations
import importlib
import platform
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
_MIN_PYTHON = (3, 11)
_REQUIRE_PATTERN = re.compile(r"^([A-Za-z0-9_\-\.]+)(?:\[[^\]]*\])?(>=|==|~=|<=)?([0-9][0-9.]*)?")
# 平台敏感依赖:wheel 覆盖性标注(文档见 docs/architecture/platform-matrix.md)
_PLATFORM_SENSITIVE = {"ortools", "cryptography", "uvicorn"}
# pip 包名 → 实际导入模块名(不一致映射)
_IMPORT_NAME_MAP = {
"python-dotenv": "dotenv",
"python-docx": "docx",
"python-multipart": "multipart",
"pyyaml": "yaml",
}
def _check_python() -> list[str]:
problems = []
if sys.version_info < _MIN_PYTHON:
problems.append(f"python {sys.version_info.major}.{sys.version_info.minor} < 3.11")
return problems
def _check_platform() -> dict:
machine = platform.machine().lower()
arch = "aarch64" if machine in ("aarch64", "arm64") else "x86_64" if machine in ("amd64", "x86_64") else machine
system = platform.system()
return {"system": system, "arch": arch, "platform": platform.platform()}
def _parse_requirements() -> list[tuple[str, str | None, str | None]]:
req = ROOT / "requirements.txt"
out: list[tuple[str, str | None, str | None]] = []
if not req.exists():
return out
for line in req.read_text(encoding="utf-8").splitlines():
line = line.split("#", 1)[0].strip()
if not line:
continue
m = _REQUIRE_PATTERN.match(line)
if m:
out.append((m.group(1), m.group(2), m.group(3)))
return out
def _check_deps() -> tuple[list[str], list[dict]]:
problems: list[str] = []
detail: list[dict] = []
for name, op, ver in _parse_requirements():
import_name = _IMPORT_NAME_MAP.get(name, name.replace("-", "_"))
try:
mod = importlib.import_module(import_name)
except ImportError:
try:
mod = importlib.import_module(name)
except ImportError:
problems.append(f"依赖缺失: {name}")
detail.append({"name": name, "ok": False, "sensitive": name in _PLATFORM_SENSITIVE})
continue
version = getattr(mod, "__version__", "?")
ok = True
if ver and op == ">=":
try:
import packaging.version
ok = packaging.version.parse(str(version)) >= packaging.version.parse(ver)
except Exception:
ok = True # 无 packaging 或版本解析失败时不阻断
detail.append({"name": name, "version": str(version), "ok": ok, "sensitive": name in _PLATFORM_SENSITIVE})
if not ok:
problems.append(f"版本不满足: {name} {version} < {ver}")
return problems, detail
def main() -> int:
problems = _check_python()
platform_info = _check_platform()
dep_problems, detail = _check_deps()
print(f"python: {sys.version.split()[0]} | system: {platform_info['system']} | arch: {platform_info['arch']}")
print(f"deps: {sum(1 for d in detail if d['ok'])}/{len(detail)} ok | sensitive: "
f"{[d['name'] for d in detail if d['sensitive'] and d['ok']]}")
all_problems = problems + dep_problems
for p in all_problems:
print("FAIL:", p)
if not all_problems:
print("platform-deps-check: PASS")
return 1 if all_problems else 0
if __name__ == "__main__":
sys.exit(main())