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