12
12
"https://github.com/llvm/llvm-project/issues and add the "
13
13
"`infrastructure` label."
14
14
)
15
+ # The maximum number of lines to pull from a ninja failure.
16
+ NINJA_LOG_SIZE_THRESHOLD = 500
17
+
18
+
19
+ def _parse_ninja_log (ninja_log : list [str ]) -> list [tuple [str , str ]]:
20
+ """Parses an individual ninja log."""
21
+ failures = []
22
+ index = 0
23
+ while index < len (ninja_log ):
24
+ while index < len (ninja_log ) and not ninja_log [index ].startswith ("FAILED:" ):
25
+ index += 1
26
+ if index == len (ninja_log ):
27
+ # We hit the end of the log without finding a build failure, go to
28
+ # the next log.
29
+ return failures
30
+ failing_action = ninja_log [index - 1 ].split ("] " )[1 ]
31
+ failure_log = []
32
+ while (
33
+ index < len (ninja_log )
34
+ and not ninja_log [index ].startswith ("[" )
35
+ and not ninja_log [index ].startswith (
36
+ "ninja: build stopped: subcommand failed"
37
+ )
38
+ and len (failure_log ) < NINJA_LOG_SIZE_THRESHOLD
39
+ ):
40
+ failure_log .append (ninja_log [index ])
41
+ index += 1
42
+ failures .append ((failing_action , "\n " .join (failure_log )))
43
+ return failures
44
+
45
+
46
+ def find_failure_in_ninja_logs (ninja_logs : list [list [str ]]) -> list [tuple [str , str ]]:
47
+ """Extracts failure messages from ninja output.
48
+
49
+ This patch takes stdout/stderr from ninja in the form of a list of files
50
+ represented as a list of lines. This function then returns tuples containing
51
+ the name of the target and the error message.
52
+
53
+ Args:
54
+ ninja_logs: A list of files in the form of a list of lines representing the log
55
+ files captured from ninja.
56
+
57
+ Returns:
58
+ A list of tuples. The first string is the name of the target that failed. The
59
+ second string is the error message.
60
+ """
61
+ failures = []
62
+ for ninja_log in ninja_logs :
63
+ log_failures = _parse_ninja_log (ninja_log )
64
+ failures .extend (log_failures )
65
+ return failures
15
66
16
67
17
68
# Set size_limit to limit the byte size of the report. The default is 1MB as this
@@ -24,6 +75,7 @@ def generate_report(
24
75
title ,
25
76
return_code ,
26
77
junit_objects ,
78
+ ninja_logs : list [list [str ]],
27
79
size_limit = 1024 * 1024 ,
28
80
list_failures = True ,
29
81
):
@@ -61,15 +113,46 @@ def generate_report(
61
113
]
62
114
)
63
115
else :
64
- report .extend (
65
- [
66
- "The build failed before running any tests." ,
67
- "" ,
68
- SEE_BUILD_FILE_STR ,
69
- "" ,
70
- UNRELATED_FAILURES_STR ,
71
- ]
72
- )
116
+ ninja_failures = find_failure_in_ninja_logs (ninja_logs )
117
+ if not ninja_failures :
118
+ report .extend (
119
+ [
120
+ "The build failed before running any tests. Detailed "
121
+ "information about the build failure could not be "
122
+ "automatically obtained." ,
123
+ "" ,
124
+ SEE_BUILD_FILE_STR ,
125
+ "" ,
126
+ UNRELATED_FAILURES_STR ,
127
+ ]
128
+ )
129
+ else :
130
+ report .extend (
131
+ [
132
+ "The build failed before running any tests. Click on the "
133
+ "failure below to see the details." ,
134
+ "" ,
135
+ ]
136
+ )
137
+ for build_failure in ninja_failures :
138
+ failed_action , failure_message = build_failure
139
+ report .extend (
140
+ [
141
+ "<details>" ,
142
+ f"<summary>{ failed_action } </summary>" ,
143
+ "" ,
144
+ "```" ,
145
+ failure_message ,
146
+ "```" ,
147
+ "</details>" ,
148
+ ]
149
+ )
150
+ report .extend (
151
+ [
152
+ "" ,
153
+ UNRELATED_FAILURES_STR ,
154
+ ]
155
+ )
73
156
return "\n " .join (report )
74
157
75
158
tests_passed = tests_run - tests_skipped - tests_failed
@@ -114,14 +197,32 @@ def plural(num_tests):
114
197
elif return_code != 0 :
115
198
# No tests failed but the build was in a failed state. Bring this to the user's
116
199
# attention.
117
- report .extend (
118
- [
119
- "" ,
120
- "All tests passed but another part of the build **failed**." ,
121
- "" ,
122
- SEE_BUILD_FILE_STR ,
123
- ]
124
- )
200
+ ninja_failures = find_failure_in_ninja_logs (ninja_logs )
201
+ if not ninja_failures :
202
+ report .extend (
203
+ [
204
+ "" ,
205
+ "All tests passed but another part of the build **failed**. "
206
+ "Detailed information about the build failure could not be "
207
+ "automatically obtained." ,
208
+ "" ,
209
+ SEE_BUILD_FILE_STR ,
210
+ ]
211
+ )
212
+ else :
213
+ for build_failure in ninja_failures :
214
+ failed_action , failure_message = build_failure
215
+ report .extend (
216
+ [
217
+ "<details>" ,
218
+ f"<summary>{ failed_action } </summary>" ,
219
+ "" ,
220
+ "```" ,
221
+ failure_message ,
222
+ "```" ,
223
+ "</details>" ,
224
+ ]
225
+ )
125
226
126
227
if failures or return_code != 0 :
127
228
report .extend (["" , UNRELATED_FAILURES_STR ])
0 commit comments