1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00
Files
vcmi/CI/emit_partial.py
2025-08-30 20:21:16 +03:00

140 lines
4.4 KiB
Python

#!/usr/bin/env python3
import json
import os
import pathlib
import re
import subprocess
import sys
from typing import Tuple, Optional, List
def run(cmd: List[str]) -> str:
"""Run a command and return stdout as text; return empty string on any error."""
try:
return subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT)
except Exception:
return ""
def detect(platform: str) -> Tuple[str, List[str], str]:
"""Detect cache tool, command to print stats, and a family label from platform."""
if platform.startswith("msvc"):
return ("sccache", ["sccache", "--show-stats"], "windows-msvc")
if platform.startswith("mingw_"):
return ("ccache", ["ccache", "-s"], "windows-mingw")
if platform.startswith("mac"):
return ("ccache", ["ccache", "-s"], "macos")
if platform == "ios":
return ("ccache", ["ccache", "-s"], "ios")
if platform.startswith("android"):
return ("ccache", ["ccache", "-s"], "android")
return ("ccache", ["ccache", "-s"], "other")
def parse_ccache(text: str) -> Tuple[int, int]:
"""
Parse ccache stats. Supports:
- Legacy lines: "Hits: 123" / "Misses: 45"
- Modern lines: "cache hit (direct) 10"
"cache hit (preprocessed) 5"
"cache hit (remote) 2" (optional)
"cache miss 12"
Returns (hits, misses).
"""
# Legacy format
m_hits = re.search(r"^\s*Hits:\s*(\d+)\b", text, re.M)
m_miss = re.search(r"^\s*Misses:\s*(\d+)\b", text, re.M)
if m_hits and m_miss:
return int(m_hits.group(1)), int(m_miss.group(1))
# Modern format: sum all hit buckets
def pick(pattern: str) -> int:
m = re.search(pattern, text, re.M | re.I)
return int(m.group(1)) if m else 0
hits_direct = pick(r"^cache hit\s*\(direct\)\s+(\d+)\b")
hits_pre = pick(r"^cache hit\s*\(preprocessed\)\s+(\d+)\b")
hits_remote = pick(r"^cache hit\s*\(remote\)\s+(\d+)\b") # may be absent
misses = pick(r"^cache miss\s+(\d+)\b")
hits_total = hits_direct + hits_pre + hits_remote
return hits_total, misses
def parse_sccache(text: str) -> Tuple[int, int]:
"""
Parse sccache --show-stats lines:
"Cache hits 123"
"Cache misses 45"
Returns (hits, misses).
"""
def pick(label: str) -> int:
m = re.search(rf"^{re.escape(label)}\s+(\d+)\b", text, re.M | re.I)
return int(m.group(1)) if m else 0
hits = pick("Cache hits")
misses = pick("Cache misses")
return hits, misses
def arch_label(platform: str, arch_env: Optional[str]) -> str:
"""Produce a nice arch label; prefer ARCH env when present."""
if arch_env:
return arch_env
mapping = {
"mac-intel": "Intel",
"mac-arm": "Apple Silicon",
"ios": "ARM64",
"msvc-x64": "x64",
"msvc-x86": "x86",
"msvc-arm64": "ARM64",
"mingw_x86": "x86",
"mingw_x86_64": "x64",
"android-32": "ARMv7",
"android-64": "ARM64",
"android-64-intel": "x86_64",
}
return mapping.get(platform, platform)
def main() -> int:
# Prefer our explicit PLATFORM env; fall back to VS's "Platform" on Windows if needed.
platform = os.getenv("PLATFORM") or os.getenv("Platform") or "unknown"
arch = arch_label(platform, os.getenv("ARCH"))
tool, cmd, family = detect(platform)
stats_raw = run(cmd)
if tool == "sccache":
hits, misses = parse_sccache(stats_raw)
else:
hits, misses = parse_ccache(stats_raw)
total = hits + misses
rate = f"{(100.0 * hits / total):.2f}%" if total else "n/a"
payload = {
"platform": platform,
"family": family,
"arch": arch,
"tool": tool,
"hits": hits,
"misses": misses,
"total": total,
"rate": rate,
"artifact_url": os.getenv("ARTIFACT_URL", ""),
"debug_symbols_url": os.getenv("DEBUG_SYMBOLS_URL", ""),
"aab_url": os.getenv("AAB_URL", ""),
"stats_cmd": " ".join(cmd),
"stats_raw": stats_raw,
}
outdir = pathlib.Path(".summary")
outdir.mkdir(parents=True, exist_ok=True)
outpath = outdir / f"{platform}.json"
outpath.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
print(f"Wrote {outpath}")
return 0
if __name__ == "__main__":
sys.exit(main())