Path-based review (workflows, composite actions, eslint/prettier config, the MCP catalog, install-hook files) moves to .github/CODEOWNERS. GitHub requests and enforces that review, so CI no longer classifies those paths, checks a label, or reruns failed jobs when the label is added. Removes review-labels.yml, emit_review_status.py, the rerun-review-labels job, and the ci_review / ci_review_files / mcp_catalog classifier outputs. The run-e2e rerun job in label-rerun.yml stays. The supply-chain scan keeps its content patterns (base64 decode + exec, obfuscated subprocess), which CODEOWNERS cannot express. It fails on its own again, as it did before #68833 routed it through the label gate. Its .pth and install-hook path checks are dropped in favour of CODEOWNERS.
107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Strict CI aggregate gate (shared by ci.yaml and the stable orchestrator).
|
|
|
|
Behavior tests: tests/ci/test_required_results.py
|
|
|
|
Input: the JSON ``toJSON(needs)`` of the all-checks-pass job on stdin.
|
|
Any non-``success`` result fails the gate. ``skipped`` additionally fails in
|
|
release mode unless the job is in :data:`EXCLUDED_JOBS` (PR-only jobs that
|
|
cannot run on a tag event, plus the deferred Desktop E2E). The OSV scan is
|
|
advisory in its findings only — its execution is required.
|
|
|
|
echo "$NEEDS" | python3 scripts/ci/required_results.py [--release]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from typing import Any
|
|
|
|
# Jobs that can never run on a release (push/tag) event, plus the deferred
|
|
# Desktop E2E. These are the only skips a strict run tolerates.
|
|
PR_ONLY_JOBS = ("history-check", "lockfile-diff", "supply-chain")
|
|
DEFERRED_JOBS = ("e2e-desktop",)
|
|
EXCLUDED_JOBS = frozenset((*PR_ONLY_JOBS, *DEFERRED_JOBS))
|
|
|
|
NEEDS_JSON_OUTPUT = "needs-json"
|
|
|
|
|
|
def evaluate_gate(
|
|
needs: dict[str, dict[str, Any]] | None,
|
|
release: bool = False,
|
|
) -> dict[str, Any]:
|
|
"""Verdict for a ``needs`` context; see the module docstring.
|
|
|
|
Returns ``ok``, plus sorted ``failed`` (every non-success, non-allowed
|
|
entry) and ``allowed_skips``.
|
|
"""
|
|
failed: list[str] = []
|
|
allowed_skips: list[str] = []
|
|
entries = needs or {}
|
|
if not entries:
|
|
failed.append("<no-needs>")
|
|
for name, info in entries.items():
|
|
result = (info or {}).get("result")
|
|
if result == "success":
|
|
continue
|
|
if result == "skipped" and (not release or name in EXCLUDED_JOBS):
|
|
allowed_skips.append(name)
|
|
continue
|
|
failed.append(name)
|
|
return {
|
|
"ok": not failed,
|
|
"failed": sorted(failed),
|
|
"allowed_skips": sorted(allowed_skips),
|
|
}
|
|
|
|
|
|
def compact_results(needs: dict[str, dict[str, Any]] | None) -> dict[str, str]:
|
|
"""{job_name: result} for every entry, for the PR comment assembler."""
|
|
return {name: (info or {}).get("result", "") for name, info in (needs or {}).items()}
|
|
|
|
|
|
def render_report(needs: dict[str, dict[str, Any]] | None, verdict: dict[str, Any]) -> list[str]:
|
|
"""Per-job lines plus ::error:: annotations for the failed set."""
|
|
lines: list[str] = []
|
|
for name in sorted(needs or {}):
|
|
result = (needs[name] or {}).get("result", "unknown")
|
|
icon = {"success": "✅", "skipped": "⏭️"}.get(result, "❌")
|
|
lines.append(f"{icon} {name}: {result}")
|
|
if verdict["failed"]:
|
|
failed = verdict["failed"]
|
|
lines.append(f"::error::{len(failed)} job(s) failed: {', '.join(failed)}")
|
|
return lines
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument(
|
|
"--release",
|
|
action="store_true",
|
|
help="Strict mode: a skipped excluded job is the only tolerated skip.",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
needs = json.load(sys.stdin)
|
|
verdict = evaluate_gate(needs, release=args.release)
|
|
compact = compact_results(needs)
|
|
|
|
output_file = os.environ.get("GITHUB_OUTPUT")
|
|
if output_file:
|
|
with open(output_file, "a", encoding="utf-8") as fh:
|
|
fh.write(f"{NEEDS_JSON_OUTPUT}={json.dumps(compact)}\n")
|
|
|
|
print(f"{NEEDS_JSON_OUTPUT}={json.dumps(compact)}")
|
|
for line in render_report(needs, verdict):
|
|
print(line)
|
|
if verdict["ok"]:
|
|
print("All checks passed" if args.release else "All checks passed (or were skipped)")
|
|
return 0 if verdict["ok"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|