|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Generate GitHub Actions step summary for the CI build matrix. |
| 3 | +
|
| 4 | +Reads the matrix JSON from the MATRIX_JSON environment variable (or stdin as |
| 5 | +fallback) and the random seed from MATRIX_SEED, then writes a Markdown summary |
| 6 | +suitable for appending to $GITHUB_STEP_SUMMARY. |
| 7 | +""" |
| 8 | + |
| 9 | +import json |
| 10 | +import os |
| 11 | +import sys |
| 12 | + |
| 13 | + |
| 14 | +def yn(val: str) -> str: |
| 15 | + return "✅" if val == "True" else "—" |
| 16 | + |
| 17 | + |
| 18 | +def generate_matrix_summary(matrix: list[dict], seed: int | str | None = None) -> str: |
| 19 | + lines = [] |
| 20 | + |
| 21 | + lines.append("## 🎯 Build Matrix") |
| 22 | + lines.append("") |
| 23 | + |
| 24 | + meta = [] |
| 25 | + if seed is not None: |
| 26 | + meta.append(f"**Random seed:** `{seed}`") |
| 27 | + meta.append(f"**Total jobs:** {len(matrix)}") |
| 28 | + lines.extend(meta) |
| 29 | + lines.append("") |
| 30 | + |
| 31 | + # Main combinations table |
| 32 | + lines.append( |
| 33 | + "| Toolchain | C++ | Formatting | Contracts | Build" |
| 34 | + " | Modules | import_std | no_crtp | freestanding |" |
| 35 | + ) |
| 36 | + lines.append( |
| 37 | + "|-----------|-----|------------|-----------|-------|" |
| 38 | + ":-------:|:----------:|:-------:|:------------:|" |
| 39 | + ) |
| 40 | + for entry in sorted(matrix, key=lambda e: e["config-summary-str"]): |
| 41 | + t = entry["toolchain"] |
| 42 | + lines.append( |
| 43 | + f"| {t['name']}" |
| 44 | + f" | {entry['std']}" |
| 45 | + f" | {entry['formatting']}" |
| 46 | + f" | {entry['contracts']}" |
| 47 | + f" | {entry['build_type']}" |
| 48 | + f" | {yn(entry['cxx_modules'])}" |
| 49 | + f" | {yn(entry['import_std'])}" |
| 50 | + f" | {yn(entry['no_crtp'])}" |
| 51 | + f" | {yn(entry['freestanding'])} |" |
| 52 | + ) |
| 53 | + |
| 54 | + lines.append("") |
| 55 | + |
| 56 | + # Per-value coverage statistics in a collapsible block |
| 57 | + lines.append("<details><summary>📊 Coverage statistics</summary>") |
| 58 | + lines.append("") |
| 59 | + lines.append("```") |
| 60 | + |
| 61 | + field_getters: list[tuple[str, object]] = [ |
| 62 | + ("toolchain", lambda e: e["toolchain"]["name"]), |
| 63 | + ("std", lambda e: str(e["std"])), |
| 64 | + ("build_type", lambda e: e["build_type"]), |
| 65 | + ("contracts", lambda e: e["contracts"]), |
| 66 | + ("cxx_modules", lambda e: e["cxx_modules"]), |
| 67 | + ("std_format", lambda e: e["std_format"]), |
| 68 | + ("import_std", lambda e: e["import_std"]), |
| 69 | + ("no_crtp", lambda e: e["no_crtp"]), |
| 70 | + ("freestanding", lambda e: e["freestanding"]), |
| 71 | + ] |
| 72 | + for key, getter in field_getters: |
| 73 | + counts: dict[str, int] = {} |
| 74 | + for entry in matrix: |
| 75 | + v = getter(entry) |
| 76 | + counts[v] = counts.get(v, 0) + 1 |
| 77 | + for v, n in sorted(counts.items()): |
| 78 | + lines.append(f" {key}={v}: {n}") |
| 79 | + |
| 80 | + lines.append("```") |
| 81 | + lines.append("</details>") |
| 82 | + |
| 83 | + return "\n".join(lines) |
| 84 | + |
| 85 | + |
| 86 | +def main(): |
| 87 | + matrix_json = os.environ.get("MATRIX_JSON") or sys.stdin.read().strip() |
| 88 | + seed = os.environ.get("MATRIX_SEED") |
| 89 | + |
| 90 | + if not matrix_json: |
| 91 | + print( |
| 92 | + "Error: No matrix JSON provided (set MATRIX_JSON env var or pipe to stdin)", |
| 93 | + file=sys.stderr, |
| 94 | + ) |
| 95 | + sys.exit(1) |
| 96 | + |
| 97 | + try: |
| 98 | + matrix = json.loads(matrix_json) |
| 99 | + print(generate_matrix_summary(matrix, seed=seed)) |
| 100 | + except json.JSONDecodeError as e: |
| 101 | + print(f"Error: Invalid JSON: {e}", file=sys.stderr) |
| 102 | + sys.exit(1) |
| 103 | + |
| 104 | + |
| 105 | +if __name__ == "__main__": |
| 106 | + main() |
0 commit comments