diff options
Diffstat (limited to '.ci/premerge_advisor_explain.py')
| -rw-r--r-- | .ci/premerge_advisor_explain.py | 117 |
1 files changed, 107 insertions, 10 deletions
diff --git a/.ci/premerge_advisor_explain.py b/.ci/premerge_advisor_explain.py index 06c6cb9..6296599 100644 --- a/.ci/premerge_advisor_explain.py +++ b/.ci/premerge_advisor_explain.py @@ -4,20 +4,81 @@ """Script for getting explanations from the premerge advisor.""" import argparse -import os import platform import sys +import json + +# TODO(boomanaiden154): Remove the optional call once we can require Python +# 3.10. +from typing import Optional import requests +import github +import github.PullRequest import generate_test_report_lib PREMERGE_ADVISOR_URL = ( "http://premerge-advisor.premerge-advisor.svc.cluster.local:5000/explain" ) +COMMENT_TAG = "<!--PREMERGE ADVISOR COMMENT: {platform}-->" + + +def get_comment_id(platform: str, pr: github.PullRequest.PullRequest) -> Optional[int]: + platform_comment_tag = COMMENT_TAG.format(platform=platform) + for comment in pr.as_issue().get_comments(): + if platform_comment_tag in comment.body: + return comment.id + return None + + +def get_comment( + github_token: str, + pr_number: int, + body: str, +) -> dict[str, str]: + repo = github.Github(github_token).get_repo("llvm/llvm-project") + pr = repo.get_issue(pr_number).as_pull_request() + body = COMMENT_TAG.format(platform=platform.system()) + "\n" + body + comment = {"body": body} + comment_id = get_comment_id(platform.system(), pr) + if comment_id: + comment["id"] = comment_id + return comment -def main(commit_sha: str, build_log_files: list[str]): +def main( + commit_sha: str, + build_log_files: list[str], + github_token: str, + pr_number: int, + return_code: int, +): + """The main entrypoint for the script. + + This function parses failures from files, requests information from the + premerge advisor, and may write a Github comment depending upon the output. + There are four different scenarios: + 1. There has never been a previous failure and the job passes - We do not + create a comment. We write out an empty file to the comment path so the + issue-write workflow knows not to create anything. + 2. There has never been a previous failure and the job fails - We create a + new comment containing the failure information and any possible premerge + advisor findings. + 3. There has been a previous failure and the job passes - We update the + existing comment by passing its ID and a passed message to the + issue-write workflow. + 4. There has been a previous failure and the job fails - We update the + existing comment in the same manner as above, but generate the comment + as if we have a failure. + + Args: + commit_sha: The base commit SHA for this PR run. + build_log_files: The list of JUnit XML files and ninja logs. + github_token: The token to use to access the Github API. + pr_number: The number of the PR associated with this run. + return_code: The numerical return code of ninja/CMake. + """ junit_objects, ninja_logs = generate_test_report_lib.load_info_from_files( build_log_files ) @@ -34,22 +95,52 @@ def main(commit_sha: str, build_log_files: list[str]): explanation_request["failures"].append( {"name": name, "message": failure_messsage} ) - else: + elif return_code != 0: ninja_failures = generate_test_report_lib.find_failure_in_ninja_logs(ninja_logs) for name, failure_message in ninja_failures: explanation_request["failures"].append( {"name": name, "message": failure_message} ) - advisor_response = requests.get(PREMERGE_ADVISOR_URL, json=explanation_request) - if advisor_response.status_code == 200: - print(advisor_response.json()) - else: - print(advisor_response.reason) + comments = [] + advisor_explanations = [] + if return_code != 0: + advisor_response = requests.get( + PREMERGE_ADVISOR_URL, json=explanation_request, timeout=5 + ) + if advisor_response.status_code == 200: + print(advisor_response.json()) + advisor_explanations = advisor_response.json() + else: + print(advisor_response.reason) + comments.append( + get_comment( + github_token, + pr_number, + generate_test_report_lib.generate_report( + generate_test_report_lib.compute_platform_title(), + return_code, + junit_objects, + ninja_logs, + failure_explanations_list=advisor_explanations, + ), + ) + ) + if return_code == 0 and "id" not in comments[0]: + # If the job succeeds and there is not an existing comment, we + # should not write one to reduce noise. + comments = [] + comments_file_name = f"comments-{platform.system()}-{platform.machine()}" + with open(comments_file_name, "w") as comment_file_handle: + json.dump(comments, comment_file_handle) + print(f"Wrote comments to {comments_file_name}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("commit_sha", help="The base commit SHA for the test.") + parser.add_argument("return_code", help="The build's return code", type=int) + parser.add_argument("github_token", help="Github authentication token", type=str) + parser.add_argument("pr_number", help="The PR number", type=int) parser.add_argument( "build_log_files", help="Paths to JUnit report files and ninja logs.", nargs="*" ) @@ -57,7 +148,13 @@ if __name__ == "__main__": # Skip looking for results on AArch64 for now because the premerge advisor # service is not available on AWS currently. - if platform.machine() == "arm64": + if platform.machine() == "arm64" or platform.machine() == "aarch64": sys.exit(0) - main(args.commit_sha, args.build_log_files) + main( + args.commit_sha, + args.build_log_files, + args.github_token, + args.pr_number, + args.return_code, + ) |
