159 lines · python
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.2# See https://llvm.org/LICENSE.txt for license information.3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception4"""Script for getting explanations from the premerge advisor."""5 6import argparse7import platform8import sys9import json10 11# TODO(boomanaiden154): Remove the optional call once we can require Python12# 3.10.13from typing import Optional14 15import requests16import github17import github.PullRequest18 19import generate_test_report_lib20 21PREMERGE_ADVISOR_URL = (22 "http://premerge-advisor.premerge-advisor.svc.cluster.local:5000/explain"23)24COMMENT_TAG = "<!--PREMERGE ADVISOR COMMENT: {platform}-->"25 26 27def get_comment_id(platform: str, pr: github.PullRequest.PullRequest) -> Optional[int]:28 platform_comment_tag = COMMENT_TAG.format(platform=platform)29 for comment in pr.as_issue().get_comments():30 if platform_comment_tag in comment.body:31 return comment.id32 return None33 34 35def get_comment(36 github_token: str,37 pr_number: int,38 body: str,39) -> dict[str, str]:40 repo = github.Github(github_token).get_repo("llvm/llvm-project")41 pr = repo.get_issue(pr_number).as_pull_request()42 body = COMMENT_TAG.format(platform=platform.system()) + "\n" + body43 comment = {"body": body}44 comment_id = get_comment_id(platform.system(), pr)45 if comment_id:46 comment["id"] = comment_id47 return comment48 49 50def main(51 commit_sha: str,52 build_log_files: list[str],53 github_token: str,54 pr_number: int,55 return_code: int,56):57 """The main entrypoint for the script.58 59 This function parses failures from files, requests information from the60 premerge advisor, and may write a Github comment depending upon the output.61 There are four different scenarios:62 1. There has never been a previous failure and the job passes - We do not63 create a comment. We write out an empty file to the comment path so the64 issue-write workflow knows not to create anything.65 2. There has never been a previous failure and the job fails - We create a66 new comment containing the failure information and any possible premerge67 advisor findings.68 3. There has been a previous failure and the job passes - We update the69 existing comment by passing its ID and a passed message to the70 issue-write workflow.71 4. There has been a previous failure and the job fails - We update the72 existing comment in the same manner as above, but generate the comment73 as if we have a failure.74 75 Args:76 commit_sha: The base commit SHA for this PR run.77 build_log_files: The list of JUnit XML files and ninja logs.78 github_token: The token to use to access the Github API.79 pr_number: The number of the PR associated with this run.80 return_code: The numerical return code of ninja/CMake.81 """82 junit_objects, ninja_logs = generate_test_report_lib.load_info_from_files(83 build_log_files84 )85 test_failures = generate_test_report_lib.get_failures(junit_objects)86 current_platform = f"{platform.system()}-{platform.machine()}".lower()87 explanation_request = {88 "base_commit_sha": commit_sha,89 "platform": current_platform,90 "failures": [],91 }92 if test_failures:93 for _, failures in test_failures.items():94 for name, failure_messsage in failures:95 explanation_request["failures"].append(96 {"name": name, "message": failure_messsage}97 )98 elif return_code != 0:99 ninja_failures = generate_test_report_lib.find_failure_in_ninja_logs(ninja_logs)100 for name, failure_message in ninja_failures:101 explanation_request["failures"].append(102 {"name": name, "message": failure_message}103 )104 comments = []105 advisor_explanations = []106 if return_code != 0:107 advisor_response = requests.get(108 PREMERGE_ADVISOR_URL, json=explanation_request, timeout=5109 )110 if advisor_response.status_code == 200:111 print(advisor_response.json())112 advisor_explanations = advisor_response.json()113 else:114 print(advisor_response.reason)115 comments.append(116 get_comment(117 github_token,118 pr_number,119 generate_test_report_lib.generate_report(120 generate_test_report_lib.compute_platform_title(),121 return_code,122 junit_objects,123 ninja_logs,124 failure_explanations_list=advisor_explanations,125 ),126 )127 )128 if return_code == 0 and "id" not in comments[0]:129 # If the job succeeds and there is not an existing comment, we130 # should not write one to reduce noise.131 comments = []132 with open("comments", "w") as comment_file_handle:133 json.dump(comments, comment_file_handle)134 135 136if __name__ == "__main__":137 parser = argparse.ArgumentParser()138 parser.add_argument("commit_sha", help="The base commit SHA for the test.")139 parser.add_argument("return_code", help="The build's return code", type=int)140 parser.add_argument("github_token", help="Github authentication token", type=str)141 parser.add_argument("pr_number", help="The PR number", type=int)142 parser.add_argument(143 "build_log_files", help="Paths to JUnit report files and ninja logs.", nargs="*"144 )145 args = parser.parse_args()146 147 # Skip looking for results on AArch64 for now because the premerge advisor148 # service is not available on AWS currently.149 if platform.machine() == "arm64" or platform.machine() == "aarch64":150 sys.exit(0)151 152 main(153 args.commit_sha,154 args.build_log_files,155 args.github_token,156 args.pr_number,157 args.return_code,158 )159