Files
matrix-screen-controller/核桃派软件源代码/scripts/offline_wheels.py
T

81 lines
3.5 KiB
Python

"""Standard-library preflight for the device's offline Python requirements."""
from __future__ import annotations
import hashlib
from pathlib import Path
import re
_REQUIREMENT = re.compile(r"^([A-Za-z0-9_.-]+)(?:\[[A-Za-z0-9_,.-]+\])?(.*)$")
_DIGEST_LINE = re.compile(r"^([0-9a-f]{64}) ([^/\\]+\.whl)$")
def _normalized(name: str) -> str:
return re.sub(r"[-_.]+", "-", name).lower()
def _requirements(source: Path, path: Path, visited: set[Path]) -> list[tuple[str, str]]:
path = path.resolve()
try:
path.relative_to(source.resolve())
except ValueError as exc:
raise ValueError("requirement include escapes the source directory") from exc
if path in visited:
return []
visited.add(path)
result: list[tuple[str, str]] = []
for line in path.read_text(encoding="utf-8").splitlines():
item = line.split("#", 1)[0].strip()
if not item:
continue
if item.startswith("-r "):
result.extend(_requirements(source, path.parent / item[3:].strip(), visited))
continue
match = _REQUIREMENT.fullmatch(item)
if match is None:
raise ValueError(f"unsupported device requirement in {path.name}")
name, specifier = match.groups()
if specifier and not specifier.startswith(("==", ">=", "<=", ">", "<", "!=", "~=")):
raise ValueError(f"unsupported device requirement in {path.name}")
result.append((_normalized(name), specifier))
return result
def validate_offline_wheels(source: Path, wheelhouse: Path) -> None:
"""Reject a missing direct dependency or inconsistent wheel SHA-256 list.
Full transitive and platform resolution remains a separate offline pip gate
against the extracted candidate, followed by the board's actual venv install.
"""
source = Path(source).resolve()
wheelhouse = Path(wheelhouse).resolve()
requirements = _requirements(source, source / "requirements-dev.txt", set())
listed: dict[str, str] = {}
for line in (wheelhouse / "SHA256SUMS").read_text(encoding="ascii").splitlines():
match = _DIGEST_LINE.fullmatch(line)
if match is None or match[2] in listed:
raise ValueError("offline wheel SHA256SUMS contains an invalid or repeated entry")
listed[match[2]] = match[1]
actual = {path.name for path in wheelhouse.iterdir() if path.is_file() and path.suffix == ".whl"}
if not listed or actual != set(listed):
raise ValueError("offline wheel files and SHA256SUMS do not match")
packages: dict[str, set[str]] = {}
for filename, expected in listed.items():
parts = filename.removesuffix(".whl").split("-")
if len(parts) < 5:
raise ValueError(f"invalid offline wheel filename: {filename}")
name, version = _normalized(parts[0]), parts[1]
packages.setdefault(name, set()).add(version)
digest = hashlib.sha256()
with (wheelhouse / filename).open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
if digest.hexdigest() != expected:
raise ValueError(f"offline wheel digest differs from SHA256SUMS: {filename}")
for name, specifier in requirements:
versions = packages.get(name, set())
if not versions:
raise ValueError(f"offline wheel missing for direct requirement: {name}")
if specifier.startswith("==") and specifier[2:] not in versions:
raise ValueError(f"offline wheel version missing for direct requirement: {name}{specifier}")