44"""Script for getting explanations from the premerge advisor."""
55
66import argparse
7- import os
87import platform
98import sys
9+ import json
10+
11+ # TODO(boomanaiden154): Remove the optional call once we can require Python
12+ # 3.10.
13+ from typing import Optional
1014
1115import requests
16+ import github
17+ import github .PullRequest
1218
1319import generate_test_report_lib
1420
1521PREMERGE_ADVISOR_URL = (
1622 "http://premerge-advisor.premerge-advisor.svc.cluster.local:5000/explain"
1723)
24+ COMMENT_TAG = "<!--PREMERGE ADVISOR COMMENT: {platform}-->"
25+
26+
27+ def 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 .id
32+ return None
33+
1834
35+ def 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 " + body
43+ comment = {"body" : body }
44+ comment_id = get_comment_id (platform .system (), pr )
45+ if comment_id :
46+ comment ["id" ] = comment_id
47+ return comment
1948
20- def main (commit_sha : str , build_log_files : list [str ]):
49+
50+ def 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 the
60+ 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 not
63+ create a comment. We write out an empty file to the comment path so the
64+ issue-write workflow knows not to create anything.
65+ 2. There has never been a previous failure and the job fails - We create a
66+ new comment containing the failure information and any possible premerge
67+ advisor findings.
68+ 3. There has been a previous failure and the job passes - We update the
69+ existing comment by passing its ID and a passed message to the
70+ issue-write workflow.
71+ 4. There has been a previous failure and the job fails - We update the
72+ existing comment in the same manner as above, but generate the comment
73+ 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+ """
2182 junit_objects , ninja_logs = generate_test_report_lib .load_info_from_files (
2283 build_log_files
2384 )
@@ -34,32 +95,64 @@ def main(commit_sha: str, build_log_files: list[str]):
3495 explanation_request ["failures" ].append (
3596 {"name" : name , "message" : failure_messsage }
3697 )
37- else :
98+ elif return_code != 0 :
3899 ninja_failures = generate_test_report_lib .find_failure_in_ninja_logs (ninja_logs )
39100 for name , failure_message in ninja_failures :
40101 explanation_request ["failures" ].append (
41102 {"name" : name , "message" : failure_message }
42103 )
43- advisor_response = requests .get (
44- PREMERGE_ADVISOR_URL , json = explanation_request , timeout = 5
104+ comments = []
105+ advisor_explanations = []
106+ if return_code != 0 :
107+ advisor_response = requests .get (
108+ PREMERGE_ADVISOR_URL , json = explanation_request , timeout = 5
109+ )
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+ )
45127 )
46- if advisor_response .status_code == 200 :
47- print (advisor_response .json ())
48- else :
49- print (advisor_response .reason )
128+ if return_code == 0 and "id" not in comments [0 ]:
129+ # If the job succeeds and there is not an existing comment, we
130+ # 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 )
50134
51135
52136if __name__ == "__main__" :
53137 parser = argparse .ArgumentParser ()
54138 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 )
55142 parser .add_argument (
56143 "build_log_files" , help = "Paths to JUnit report files and ninja logs." , nargs = "*"
57144 )
58145 args = parser .parse_args ()
59146
60147 # Skip looking for results on AArch64 for now because the premerge advisor
61148 # service is not available on AWS currently.
62- if platform .machine () == "arm64" :
149+ if platform .machine () == "arm64" or platform . machine () == "aarch64" :
63150 sys .exit (0 )
64151
65- main (args .commit_sha , args .build_log_files )
152+ main (
153+ args .commit_sha ,
154+ args .build_log_files ,
155+ args .github_token ,
156+ args .pr_number ,
157+ args .return_code ,
158+ )
0 commit comments