@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Проверка vendor/wheels против requirements-prod.txt (блокирует сборку ZIP при пробелах)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from install_deps import missing_wheelhouse_packages
|
||||
|
||||
|
||||
def wheelhouse_stats(wheels_dir: Path) -> tuple[int, int]:
|
||||
"""(число .whl/.tar.gz, суммарный размер байт)."""
|
||||
count = 0
|
||||
total = 0
|
||||
if not wheels_dir.is_dir():
|
||||
return 0, 0
|
||||
for path in wheels_dir.iterdir():
|
||||
if not path.is_file():
|
||||
continue
|
||||
if path.name in (".gitkeep", "manifest.json"):
|
||||
continue
|
||||
if path.suffix not in (".whl", ".gz", ".zip"):
|
||||
continue
|
||||
count += 1
|
||||
total += path.stat().st_size
|
||||
return count, total
|
||||
|
||||
|
||||
def verify_offline_closure(req_file: Path, wheels_dir: Path, python_exe: str) -> None:
|
||||
"""pip install --dry-run только из wheelhouse (все транзитивные deps)."""
|
||||
cmd = [
|
||||
python_exe,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"--no-index",
|
||||
f"--find-links={wheels_dir}",
|
||||
"-r",
|
||||
str(req_file),
|
||||
"--dry-run",
|
||||
"--ignore-installed",
|
||||
]
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
|
||||
|
||||
def write_manifest(
|
||||
req_file: Path,
|
||||
wheels_dir: Path,
|
||||
manifest_path: Path,
|
||||
*,
|
||||
build_arch: str = "",
|
||||
build_platform: str = "",
|
||||
) -> None:
|
||||
req_hash = hashlib.sha256(req_file.read_bytes()).hexdigest() if req_file.is_file() else ""
|
||||
whl_count, whl_bytes = wheelhouse_stats(wheels_dir)
|
||||
wheels = sorted(
|
||||
p.name
|
||||
for p in wheels_dir.glob("*")
|
||||
if p.is_file() and p.name not in (".gitkeep", "manifest.json")
|
||||
)
|
||||
payload = {
|
||||
"requirements_sha256": req_hash,
|
||||
"wheel_count": whl_count,
|
||||
"wheelhouse_bytes": whl_bytes,
|
||||
"wheels": wheels,
|
||||
}
|
||||
if build_arch:
|
||||
payload["build_arch"] = build_arch
|
||||
if build_platform:
|
||||
payload["build_platform"] = build_platform
|
||||
manifest_path.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def format_bytes(n: int) -> str:
|
||||
if n >= 1024 * 1024:
|
||||
return f"{n / (1024 * 1024):.1f} MB"
|
||||
if n >= 1024:
|
||||
return f"{n / 1024:.0f} KB"
|
||||
return f"{n} B"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Verify vendor/wheels for release ZIP")
|
||||
parser.add_argument("--req", type=Path, default=ROOT / "requirements-prod.txt")
|
||||
parser.add_argument("--wheels", type=Path, default=ROOT / "vendor" / "wheels")
|
||||
parser.add_argument("--python", default=sys.executable, help="Python для pip dry-run")
|
||||
parser.add_argument("--write-manifest", action="store_true")
|
||||
parser.add_argument("--verify-closure", action="store_true", help="pip dry-run offline install")
|
||||
parser.add_argument("--build-arch", default="", help="Архитектура build-машины (uname -m)")
|
||||
parser.add_argument("--build-platform", default="", help="ОС build-машины")
|
||||
args = parser.parse_args()
|
||||
|
||||
missing = missing_wheelhouse_packages(args.req, args.wheels)
|
||||
whl_count, whl_bytes = wheelhouse_stats(args.wheels)
|
||||
|
||||
if args.write_manifest and not missing:
|
||||
write_manifest(
|
||||
args.req,
|
||||
args.wheels,
|
||||
args.wheels / "manifest.json",
|
||||
build_arch=args.build_arch.strip(),
|
||||
build_platform=args.build_platform.strip(),
|
||||
)
|
||||
|
||||
if missing:
|
||||
print("verify_release_deps: missing wheels for:", file=sys.stderr)
|
||||
for m in missing:
|
||||
print(f" - {m}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if whl_count == 0:
|
||||
print("verify_release_deps: wheelhouse empty", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(
|
||||
f"verify_release_deps: OK — {whl_count} packages, "
|
||||
f"{format_bytes(whl_bytes)} in {args.wheels}"
|
||||
)
|
||||
|
||||
if args.verify_closure:
|
||||
try:
|
||||
verify_offline_closure(args.req, args.wheels, args.python)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print("verify_release_deps: offline pip closure FAILED:", file=sys.stderr)
|
||||
if exc.stderr:
|
||||
print(exc.stderr, file=sys.stderr)
|
||||
if exc.stdout:
|
||||
print(exc.stdout, file=sys.stderr)
|
||||
return 1
|
||||
print("verify_release_deps: offline pip closure OK (all deps from wheelhouse)")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user