emit_partial.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. import pathlib
  5. import re
  6. import subprocess
  7. import sys
  8. from typing import Tuple, Optional, List
  9. def run(cmd: List[str]) -> str:
  10. """Run a command and return stdout as text; return empty string on any error."""
  11. try:
  12. return subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT)
  13. except Exception:
  14. return ""
  15. def detect(platform: str) -> Tuple[str, List[str], str]:
  16. """Detect cache tool, command to print stats, and a family label from platform."""
  17. if platform.startswith("msvc"):
  18. return ("sccache", ["sccache", "--show-stats"], "windows-msvc")
  19. if platform.startswith("mingw_"):
  20. return ("ccache", ["ccache", "-s"], "windows-mingw")
  21. if platform.startswith("mac"):
  22. return ("ccache", ["ccache", "-s"], "macos")
  23. if platform == "ios":
  24. return ("ccache", ["ccache", "-s"], "ios")
  25. if platform.startswith("android"):
  26. return ("ccache", ["ccache", "-s"], "android")
  27. return ("ccache", ["ccache", "-s"], "other")
  28. def parse_ccache(text: str) -> Tuple[int, int]:
  29. """
  30. Parse ccache stats. Supports:
  31. - Legacy lines: "Hits: 123" / "Misses: 45"
  32. - Modern lines: "cache hit (direct) 10"
  33. "cache hit (preprocessed) 5"
  34. "cache hit (remote) 2" (optional)
  35. "cache miss 12"
  36. Returns (hits, misses).
  37. """
  38. # Legacy format
  39. m_hits = re.search(r"^\s*Hits:\s*(\d+)\b", text, re.M)
  40. m_miss = re.search(r"^\s*Misses:\s*(\d+)\b", text, re.M)
  41. if m_hits and m_miss:
  42. return int(m_hits.group(1)), int(m_miss.group(1))
  43. # Modern format: sum all hit buckets
  44. def pick(pattern: str) -> int:
  45. m = re.search(pattern, text, re.M | re.I)
  46. return int(m.group(1)) if m else 0
  47. hits_direct = pick(r"^cache hit\s*\(direct\)\s+(\d+)\b")
  48. hits_pre = pick(r"^cache hit\s*\(preprocessed\)\s+(\d+)\b")
  49. hits_remote = pick(r"^cache hit\s*\(remote\)\s+(\d+)\b") # may be absent
  50. misses = pick(r"^cache miss\s+(\d+)\b")
  51. hits_total = hits_direct + hits_pre + hits_remote
  52. return hits_total, misses
  53. def parse_sccache(text: str) -> Tuple[int, int]:
  54. """
  55. Parse sccache --show-stats lines:
  56. "Cache hits 123"
  57. "Cache misses 45"
  58. Returns (hits, misses).
  59. """
  60. def pick(label: str) -> int:
  61. m = re.search(rf"^{re.escape(label)}\s+(\d+)\b", text, re.M | re.I)
  62. return int(m.group(1)) if m else 0
  63. hits = pick("Cache hits")
  64. misses = pick("Cache misses")
  65. return hits, misses
  66. def arch_label(platform: str, arch_env: Optional[str]) -> str:
  67. """Produce a nice arch label; prefer ARCH env when present."""
  68. if arch_env:
  69. return arch_env
  70. mapping = {
  71. "mac-intel": "Intel",
  72. "mac-arm": "Apple Silicon",
  73. "ios": "ARM64",
  74. "msvc-x64": "x64",
  75. "msvc-x86": "x86",
  76. "msvc-arm64": "ARM64",
  77. "mingw_x86": "x86",
  78. "mingw_x86_64": "x64",
  79. "android-32": "ARMv7",
  80. "android-64": "ARM64",
  81. "android-64-intel": "x86_64",
  82. }
  83. return mapping.get(platform, platform)
  84. def main() -> int:
  85. # Prefer our explicit PLATFORM env; fall back to VS's "Platform" on Windows if needed.
  86. platform = os.getenv("PLATFORM") or os.getenv("Platform") or "unknown"
  87. arch = arch_label(platform, os.getenv("ARCH"))
  88. tool, cmd, family = detect(platform)
  89. stats_raw = run(cmd)
  90. if tool == "sccache":
  91. hits, misses = parse_sccache(stats_raw)
  92. else:
  93. hits, misses = parse_ccache(stats_raw)
  94. total = hits + misses
  95. rate = f"{(100.0 * hits / total):.2f}%" if total else "n/a"
  96. payload = {
  97. "platform": platform,
  98. "family": family,
  99. "arch": arch,
  100. "tool": tool,
  101. "hits": hits,
  102. "misses": misses,
  103. "total": total,
  104. "rate": rate,
  105. "artifact_url": os.getenv("ARTIFACT_URL", ""),
  106. "debug_symbols_url": os.getenv("DEBUG_SYMBOLS_URL", ""),
  107. "aab_url": os.getenv("AAB_URL", ""),
  108. "stats_cmd": " ".join(cmd),
  109. "stats_raw": stats_raw,
  110. }
  111. outdir = pathlib.Path(".summary")
  112. outdir.mkdir(parents=True, exist_ok=True)
  113. outpath = outdir / f"{platform}.json"
  114. outpath.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
  115. print(f"Wrote {outpath}")
  116. return 0
  117. if __name__ == "__main__":
  118. sys.exit(main())