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

106 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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())