diff --git a/buildkite/pipeline_generator/pipeline_generator.py b/buildkite/pipeline_generator/pipeline_generator.py index 20aa9512..73020009 100644 --- a/buildkite/pipeline_generator/pipeline_generator.py +++ b/buildkite/pipeline_generator/pipeline_generator.py @@ -5,6 +5,7 @@ from step import read_steps_from_job_dir, group_steps from buildkite_step import convert_group_step_to_buildkite_step from global_config import init_global_config, get_global_config +from utils_lib.git_utils import check_precommit_passed class PipelineGenerator: @@ -20,6 +21,12 @@ def __init__( def generate(self): global_config = get_global_config() + # Fail if pre-commit check has not passed on the PR + if global_config["pull_request"] and global_config["pull_request"] != "false": + check_precommit_passed( + global_config["commit"], global_config["github_repo_name"] + ) + # Skip if changes are doc-only (unless RUN_ALL is set) if global_config["docs_only_disable"] == "0" and not global_config["run_all"]: if is_docs_only_change(global_config["list_file_diff"]): diff --git a/buildkite/pipeline_generator/utils_lib/git_utils.py b/buildkite/pipeline_generator/utils_lib/git_utils.py index 073f1f3c..fd708aae 100644 --- a/buildkite/pipeline_generator/utils_lib/git_utils.py +++ b/buildkite/pipeline_generator/utils_lib/git_utils.py @@ -1,5 +1,6 @@ import subprocess import os +import time from typing import List, Optional import requests @@ -53,6 +54,68 @@ def get_list_file_diff(branch: str, merge_base_commit: Optional[str]) -> List[st raise RuntimeError("Failed to determine merge base commit for git diff.") +def check_precommit_passed(commit: str, repo_name: str) -> None: + """Poll until the pre-commit GitHub Actions check run completes, then fail if not successful. + + Raises RuntimeError if the check fails, times out, or is not found. + """ + max_wait = 600 + wait_interval = 30 + elapsed = 0 + api_url = f"https://api.github.com/repos/{repo_name}/commits/{commit}/check-runs" + + while True: + response = requests.get(api_url) + response.raise_for_status() + check_runs = response.json().get("check_runs", []) + precommit_run = next( + (run for run in check_runs if run["name"] == "pre-commit"), None + ) + + if precommit_run and precommit_run["status"] == "completed": + conclusion = precommit_run["conclusion"] + if conclusion == "success": + print(f"pre-commit check passed on commit {commit}.") + return + subprocess.run( + [ + "buildkite-agent", + "annotate", + f":x: pre-commit check has not passed on this PR (conclusion: {conclusion}). " + "Please fix pre-commit issues before running CI.", + "--style", + "error", + ], + check=False, + ) + raise RuntimeError( + f"pre-commit check failed on commit {commit} (conclusion: {conclusion})." + ) + + if elapsed >= max_wait: + subprocess.run( + [ + "buildkite-agent", + "annotate", + ":warning: Timed out waiting for pre-commit check to complete.", + "--style", + "warning", + ], + check=False, + ) + raise RuntimeError( + f"Timed out after {max_wait}s waiting for pre-commit check on commit {commit}." + ) + + status = precommit_run["status"] if precommit_run else "not found" + print( + f"pre-commit check is not yet complete (status: {status}). " + f"Waiting {wait_interval}s... ({elapsed}/{max_wait}s)" + ) + time.sleep(wait_interval) + elapsed += wait_interval + + def get_pr_labels(pull_request: str, repo_name: str) -> List[str]: if not pull_request or pull_request == "false": return []