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