106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
# -*- 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())
|