blob: b885ad2f618270ff795b85a95b417803354185c4 [file] [log] [blame]
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -08001# python3
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -08002# Copyright (C) 2019 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Grep warnings messages and output HTML tables or warning counts in CSV.
17
18Default is to output warnings in HTML tables grouped by warning severity.
19Use option --byproject to output tables grouped by source file projects.
20Use option --gencsv to output warning counts in CSV format.
21"""
22
23# List of important data structures and functions in this script.
24#
25# To parse and keep warning message in the input file:
26# severity: classification of message severity
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080027# warn_patterns:
28# warn_patterns[w]['category'] tool that issued the warning, not used now
29# warn_patterns[w]['description'] table heading
30# warn_patterns[w]['members'] matched warnings from input
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080031# warn_patterns[w]['patterns'] regular expressions to match warnings
32# warn_patterns[w]['projects'][p] number of warnings of pattern w in p
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -080033# warn_patterns[w]['severity'] severity tuple
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080034# project_list[p][0] project name
35# project_list[p][1] regular expression to match a project path
36# project_patterns[p] re.compile(project_list[p][1])
37# project_names[p] project_list[p][0]
38# warning_messages array of each warning message, without source url
39# warning_records array of [idx to warn_patterns,
40# idx to project_names,
41# idx to warning_messages]
42# android_root
43# platform_version
44# target_product
45# target_variant
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -080046# parse_input_file
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080047#
48# To emit html page of warning messages:
49# flags: --byproject, --url, --separator
50# Old stuff for static html components:
51# html_script_style: static html scripts and styles
52# htmlbig:
53# dump_stats, dump_html_prologue, dump_html_epilogue:
54# emit_buttons:
55# dump_fixed
56# sort_warnings:
57# emit_stats_by_project:
58# all_patterns,
59# findproject, classify_warning
60# dump_html
61#
62# New dynamic HTML page's static JavaScript data:
63# Some data are copied from Python to JavaScript, to generate HTML elements.
64# FlagURL args.url
65# FlagSeparator args.separator
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -080066# SeverityColors: list of colors for all severity levels
67# SeverityHeaders: list of headers for all severity levels
68# SeverityColumnHeaders: list of column_headers for all severity levels
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080069# ProjectNames: project_names, or project_list[*][0]
70# WarnPatternsSeverity: warn_patterns[*]['severity']
71# WarnPatternsDescription: warn_patterns[*]['description']
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -080072# WarningMessages: warning_messages
73# Warnings: warning_records
74# StatsHeader: warning count table header row
75# StatsRows: array of warning count table rows
76#
77# New dynamic HTML page's dynamic JavaScript data:
78#
79# New dynamic HTML related function to emit data:
80# escape_string, strip_escape_string, emit_warning_arrays
81# emit_js_data():
82
83from __future__ import print_function
84import argparse
85import cgi
86import csv
87import io
88import multiprocessing
89import os
90import re
91import signal
92import sys
93
94# pylint:disable=relative-beyond-top-level
95from . import cpp_warn_patterns
96from . import java_warn_patterns
97from . import make_warn_patterns
98from . import other_warn_patterns
99from . import tidy_warn_patterns
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800100# pylint:disable=g-importing-member
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800101from .android_project_list import project_list
102from .severity import Severity
103
104parser = argparse.ArgumentParser(description='Convert a build log into HTML')
105parser.add_argument('--csvpath',
106 help='Save CSV warning file to the passed absolute path',
107 default=None)
108parser.add_argument('--gencsv',
109 help='Generate a CSV file with number of various warnings',
110 action='store_true',
111 default=False)
112parser.add_argument('--byproject',
113 help='Separate warnings in HTML output by project names',
114 action='store_true',
115 default=False)
116parser.add_argument('--url',
117 help='Root URL of an Android source code tree prefixed '
118 'before files in warnings')
119parser.add_argument('--separator',
120 help='Separator between the end of a URL and the line '
121 'number argument. e.g. #')
122parser.add_argument('--processes',
123 type=int,
124 default=multiprocessing.cpu_count(),
125 help='Number of parallel processes to process warnings')
126parser.add_argument(dest='buildlog', metavar='build.log',
127 help='Path to build.log file')
128args = parser.parse_args()
129
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800130warn_patterns = make_warn_patterns.warn_patterns
131warn_patterns.extend(cpp_warn_patterns.warn_patterns)
132warn_patterns.extend(java_warn_patterns.warn_patterns)
133warn_patterns.extend(tidy_warn_patterns.warn_patterns)
134warn_patterns.extend(other_warn_patterns.warn_patterns)
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800135
136project_patterns = []
137project_names = []
138warning_messages = []
139warning_records = []
140
141
142def initialize_arrays():
143 """Complete global arrays before they are used."""
144 global project_names, project_patterns
145 project_names = [p[0] for p in project_list]
146 project_patterns = [re.compile(p[1]) for p in project_list]
147 for w in warn_patterns:
148 w['members'] = []
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800149 # Each warning pattern has a 'projects' dictionary, that
150 # maps a project name to number of warnings in that project.
151 w['projects'] = {}
152
153
154initialize_arrays()
155
156
157android_root = ''
158platform_version = 'unknown'
159target_product = 'unknown'
160target_variant = 'unknown'
161
162
163##### Data and functions to dump html file. ##################################
164
165html_head_scripts = """\
166 <script type="text/javascript">
167 function expand(id) {
168 var e = document.getElementById(id);
169 var f = document.getElementById(id + "_mark");
170 if (e.style.display == 'block') {
171 e.style.display = 'none';
172 f.innerHTML = '&#x2295';
173 }
174 else {
175 e.style.display = 'block';
176 f.innerHTML = '&#x2296';
177 }
178 };
179 function expandCollapse(show) {
180 for (var id = 1; ; id++) {
181 var e = document.getElementById(id + "");
182 var f = document.getElementById(id + "_mark");
183 if (!e || !f) break;
184 e.style.display = (show ? 'block' : 'none');
185 f.innerHTML = (show ? '&#x2296' : '&#x2295');
186 }
187 };
188 </script>
189 <style type="text/css">
190 th,td{border-collapse:collapse; border:1px solid black;}
191 .button{color:blue;font-size:110%;font-weight:bolder;}
192 .bt{color:black;background-color:transparent;border:none;outline:none;
193 font-size:140%;font-weight:bolder;}
194 .c0{background-color:#e0e0e0;}
195 .c1{background-color:#d0d0d0;}
196 .t1{border-collapse:collapse; width:100%; border:1px solid black;}
197 </style>
198 <script src="https://www.gstatic.com/charts/loader.js"></script>
199"""
200
201
202def html_big(param):
203 return '<font size="+2">' + param + '</font>'
204
205
206def dump_html_prologue(title):
207 print('<html>\n<head>')
208 print('<title>' + title + '</title>')
209 print(html_head_scripts)
210 emit_stats_by_project()
211 print('</head>\n<body>')
212 print(html_big(title))
213 print('<p>')
214
215
216def dump_html_epilogue():
217 print('</body>\n</head>\n</html>')
218
219
220def sort_warnings():
221 for i in warn_patterns:
222 i['members'] = sorted(set(i['members']))
223
224
225def emit_stats_by_project():
226 """Dump a google chart table of warnings per project and severity."""
227 # warnings[p][s] is number of warnings in project p of severity s.
228 # pylint:disable=g-complex-comprehension
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800229 warnings = {p: {s.value: 0 for s in Severity.levels} for p in project_names}
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800230 for i in warn_patterns:
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800231 # pytype: disable=attribute-error
232 s = i['severity'].value
233 # pytype: enable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800234 for p in i['projects']:
235 warnings[p][s] += i['projects'][p]
236
237 # total_by_project[p] is number of warnings in project p.
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800238 total_by_project = {
239 p: sum(warnings[p][s.value] for s in Severity.levels)
240 for p in project_names
241 }
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800242
243 # total_by_severity[s] is number of warnings of severity s.
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800244 total_by_severity = {
245 s.value: sum(warnings[p][s.value] for p in project_names)
246 for s in Severity.levels
247 }
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800248
249 # emit table header
250 stats_header = ['Project']
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800251 for s in Severity.levels:
252 if total_by_severity[s.value]:
253 stats_header.append(
254 '<span style=\'background-color:{}\'>{}</span>'.format(
255 s.color, s.column_header))
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800256 stats_header.append('TOTAL')
257
258 # emit a row of warning counts per project, skip no-warning projects
259 total_all_projects = 0
260 stats_rows = []
261 for p in project_names:
262 if total_by_project[p]:
263 one_row = [p]
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800264 for s in Severity.levels:
265 if total_by_severity[s.value]:
266 one_row.append(warnings[p][s.value])
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800267 one_row.append(total_by_project[p])
268 stats_rows.append(one_row)
269 total_all_projects += total_by_project[p]
270
271 # emit a row of warning counts per severity
272 total_all_severities = 0
273 one_row = ['<b>TOTAL</b>']
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800274 for s in Severity.levels:
275 if total_by_severity[s.value]:
276 one_row.append(total_by_severity[s.value])
277 total_all_severities += total_by_severity[s.value]
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800278 one_row.append(total_all_projects)
279 stats_rows.append(one_row)
280 print('<script>')
281 emit_const_string_array('StatsHeader', stats_header)
282 emit_const_object_array('StatsRows', stats_rows)
283 print(draw_table_javascript)
284 print('</script>')
285
286
287def dump_stats():
288 """Dump some stats about total number of warnings and such."""
289 known = 0
290 skipped = 0
291 unknown = 0
292 sort_warnings()
293 for i in warn_patterns:
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800294 if i['severity'] == Severity.UNMATCHED:
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800295 unknown += len(i['members'])
296 elif i['severity'] == Severity.SKIP:
297 skipped += len(i['members'])
298 else:
299 known += len(i['members'])
300 print('Number of classified warnings: <b>' + str(known) + '</b><br>')
301 print('Number of skipped warnings: <b>' + str(skipped) + '</b><br>')
302 print('Number of unclassified warnings: <b>' + str(unknown) + '</b><br>')
303 total = unknown + known + skipped
304 extra_msg = ''
305 if total < 1000:
306 extra_msg = ' (low count may indicate incremental build)'
307 print('Total number of warnings: <b>' + str(total) + '</b>' + extra_msg)
308
309
310# New base table of warnings, [severity, warn_id, project, warning_message]
311# Need buttons to show warnings in different grouping options.
312# (1) Current, group by severity, id for each warning pattern
313# sort by severity, warn_id, warning_message
314# (2) Current --byproject, group by severity,
315# id for each warning pattern + project name
316# sort by severity, warn_id, project, warning_message
317# (3) New, group by project + severity,
318# id for each warning pattern
319# sort by project, severity, warn_id, warning_message
320def emit_buttons():
321 print('<button class="button" onclick="expandCollapse(1);">'
322 'Expand all warnings</button>\n'
323 '<button class="button" onclick="expandCollapse(0);">'
324 'Collapse all warnings</button>\n'
325 '<button class="button" onclick="groupBySeverity();">'
326 'Group warnings by severity</button>\n'
327 '<button class="button" onclick="groupByProject();">'
328 'Group warnings by project</button><br>')
329
330
331def all_patterns(category):
332 patterns = ''
333 for i in category['patterns']:
334 patterns += i
335 patterns += ' / '
336 return patterns
337
338
339def dump_fixed():
340 """Show which warnings no longer occur."""
341 anchor = 'fixed_warnings'
342 mark = anchor + '_mark'
343 print('\n<br><p style="background-color:lightblue"><b>'
344 '<button id="' + mark + '" '
345 'class="bt" onclick="expand(\'' + anchor + '\');">'
346 '&#x2295</button> Fixed warnings. '
347 'No more occurrences. Please consider turning these into '
348 'errors if possible, before they are reintroduced in to the build'
349 ':</b></p>')
350 print('<blockquote>')
351 fixed_patterns = []
352 for i in warn_patterns:
353 if not i['members']:
354 fixed_patterns.append(i['description'] + ' (' +
355 all_patterns(i) + ')')
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800356 fixed_patterns = sorted(fixed_patterns)
357 print('<div id="' + anchor + '" style="display:none;"><table>')
358 cur_row_class = 0
359 for text in fixed_patterns:
360 cur_row_class = 1 - cur_row_class
361 # remove last '\n'
362 t = text[:-1] if text[-1] == '\n' else text
363 print('<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>')
364 print('</table></div>')
365 print('</blockquote>')
366
367
368def find_project_index(line):
369 for p in range(len(project_patterns)):
370 if project_patterns[p].match(line):
371 return p
372 return -1
373
374
375def classify_one_warning(line, results):
376 """Classify one warning line."""
377 for i in range(len(warn_patterns)):
378 w = warn_patterns[i]
379 for cpat in w['compiled_patterns']:
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800380 # pytype: disable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800381 if cpat.match(line):
382 p = find_project_index(line)
383 results.append([line, i, p])
384 return
385 else:
386 # If we end up here, there was a problem parsing the log
387 # probably caused by 'make -j' mixing the output from
388 # 2 or more concurrent compiles
389 pass
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800390 # pytype: enable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800391
392
393def classify_warnings(lines):
394 results = []
395 for line in lines:
396 classify_one_warning(line, results)
397 # After the main work, ignore all other signals to a child process,
398 # to avoid bad warning/error messages from the exit clean-up process.
399 if args.processes > 1:
400 signal.signal(signal.SIGTERM, lambda *args: sys.exit(-signal.SIGTERM))
401 return results
402
403
404def parallel_classify_warnings(warning_lines, parallel_process):
405 """Classify all warning lines with num_cpu parallel processes."""
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800406 num_cpu = args.processes
407 if num_cpu > 1:
408 groups = [[] for x in range(num_cpu)]
409 i = 0
410 for x in warning_lines:
411 groups[i].append(x)
412 i = (i + 1) % num_cpu
413 group_results = parallel_process(num_cpu, classify_warnings, groups)
414 else:
415 group_results = [classify_warnings(warning_lines)]
416
417 for result in group_results:
418 for line, pattern_idx, project_idx in result:
419 pattern = warn_patterns[pattern_idx]
420 pattern['members'].append(line)
421 message_idx = len(warning_messages)
422 warning_messages.append(line)
423 warning_records.append([pattern_idx, project_idx, message_idx])
424 pname = '???' if project_idx < 0 else project_names[project_idx]
425 # Count warnings by project.
426 if pname in pattern['projects']:
427 pattern['projects'][pname] += 1
428 else:
429 pattern['projects'][pname] = 1
430
431
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800432def find_warn_py_and_android_root(path):
433 """Set and return android_root path if it is found."""
434 global android_root
435 parts = path.split('/')
436 for idx in reversed(range(2, len(parts))):
437 root_path = '/'.join(parts[:idx])
438 # Android root directory should contain this script.
439 if os.path.exists(root_path + '/build/make/tools/warn.py'):
440 android_root = root_path
441 return True
442 return False
443
444
445def find_android_root():
446 """Guess android_root from common prefix of file paths."""
447 # Use the longest common prefix of the absolute file paths
448 # of the first 10000 warning messages as the android_root.
449 global android_root
450 warning_lines = set()
451 warning_pattern = re.compile('^/[^ ]*/[^ ]*: warning: .*')
452 count = 0
453 infile = io.open(args.buildlog, mode='r', encoding='utf-8')
454 for line in infile:
455 if warning_pattern.match(line):
456 warning_lines.add(line)
457 count += 1
458 if count > 9999:
459 break
460 # Try to find warn.py and use its location to find
461 # the source tree root.
462 if count < 100:
463 path = os.path.normpath(re.sub(':.*$', '', line))
464 if find_warn_py_and_android_root(path):
465 return
466 # Do not use common prefix of a small number of paths.
467 if count > 10:
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800468 # pytype: disable=wrong-arg-types
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800469 root_path = os.path.commonprefix(warning_lines)
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800470 # pytype: enable=wrong-arg-types
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800471 if len(root_path) > 2 and root_path[len(root_path) - 1] == '/':
472 android_root = root_path[:-1]
473
474
475def remove_android_root_prefix(path):
476 """Remove android_root prefix from path if it is found."""
477 if path.startswith(android_root):
478 return path[1 + len(android_root):]
479 else:
480 return path
481
482
483def normalize_path(path):
484 """Normalize file path relative to android_root."""
485 # If path is not an absolute path, just normalize it.
486 path = os.path.normpath(path)
487 # Remove known prefix of root path and normalize the suffix.
488 if path[0] == '/' and android_root:
489 return remove_android_root_prefix(path)
490 return path
491
492
493def normalize_warning_line(line):
494 """Normalize file path relative to android_root in a warning line."""
495 # replace fancy quotes with plain ol' quotes
496 line = re.sub(u'[\u2018\u2019]', '\'', line)
497 # replace non-ASCII chars to spaces
498 line = re.sub(u'[^\x00-\x7f]', ' ', line)
499 line = line.strip()
500 first_column = line.find(':')
501 if first_column > 0:
502 return normalize_path(line[:first_column]) + line[first_column:]
503 else:
504 return line
505
506
507def parse_input_file(infile):
508 """Parse input file, collect parameters and warning lines."""
509 global android_root
510 global platform_version
511 global target_product
512 global target_variant
513 line_counter = 0
514
515 # rustc warning messages have two lines that should be combined:
516 # warning: description
517 # --> file_path:line_number:column_number
518 # Some warning messages have no file name:
519 # warning: macro replacement list ... [bugprone-macro-parentheses]
520 # Some makefile warning messages have no line number:
521 # some/path/file.mk: warning: description
522 # C/C++ compiler warning messages have line and column numbers:
523 # some/path/file.c:line_number:column_number: warning: description
524 warning_pattern = re.compile('(^[^ ]*/[^ ]*: warning: .*)|(^warning: .*)')
525 warning_without_file = re.compile('^warning: .*')
526 rustc_file_position = re.compile('^[ ]+--> [^ ]*/[^ ]*:[0-9]+:[0-9]+')
527
528 # Collect all warnings into the warning_lines set.
529 warning_lines = set()
530 prev_warning = ''
531 for line in infile:
532 if prev_warning:
533 if rustc_file_position.match(line):
534 # must be a rustc warning, combine 2 lines into one warning
535 line = line.strip().replace('--> ', '') + ': ' + prev_warning
536 warning_lines.add(normalize_warning_line(line))
537 prev_warning = ''
538 continue
539 # add prev_warning, and then process the current line
540 prev_warning = 'unknown_source_file: ' + prev_warning
541 warning_lines.add(normalize_warning_line(prev_warning))
542 prev_warning = ''
543 if warning_pattern.match(line):
544 if warning_without_file.match(line):
545 # save this line and combine it with the next line
546 prev_warning = line
547 else:
548 warning_lines.add(normalize_warning_line(line))
549 continue
550 if line_counter < 100:
551 # save a little bit of time by only doing this for the first few lines
552 line_counter += 1
553 m = re.search('(?<=^PLATFORM_VERSION=).*', line)
554 if m is not None:
555 platform_version = m.group(0)
556 m = re.search('(?<=^TARGET_PRODUCT=).*', line)
557 if m is not None:
558 target_product = m.group(0)
559 m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
560 if m is not None:
561 target_variant = m.group(0)
562 m = re.search('.* TOP=([^ ]*) .*', line)
563 if m is not None:
564 android_root = m.group(1)
565 return warning_lines
566
567
568# Return s with escaped backslash and quotation characters.
569def escape_string(s):
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800570 # pytype: disable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800571 return s.replace('\\', '\\\\').replace('"', '\\"')
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800572 # pytype: enable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800573
574
575# Return s without trailing '\n' and escape the quotation characters.
576def strip_escape_string(s):
577 if not s:
578 return s
579 s = s[:-1] if s[-1] == '\n' else s
580 return escape_string(s)
581
582
583def emit_warning_array(name):
584 print('var warning_{} = ['.format(name))
585 for i in range(len(warn_patterns)):
586 print('{},'.format(warn_patterns[i][name]))
587 print('];')
588
589
590def emit_warning_arrays():
591 emit_warning_array('severity')
592 print('var warning_description = [')
593 for i in range(len(warn_patterns)):
594 if warn_patterns[i]['members']:
595 print('"{}",'.format(escape_string(warn_patterns[i]['description'])))
596 else:
597 print('"",') # no such warning
598 print('];')
599
600
601scripts_for_warning_groups = """
602 function compareMessages(x1, x2) { // of the same warning type
603 return (WarningMessages[x1[2]] <= WarningMessages[x2[2]]) ? -1 : 1;
604 }
605 function byMessageCount(x1, x2) {
606 return x2[2] - x1[2]; // reversed order
607 }
608 function bySeverityMessageCount(x1, x2) {
609 // orer by severity first
610 if (x1[1] != x2[1])
611 return x1[1] - x2[1];
612 return byMessageCount(x1, x2);
613 }
614 const ParseLinePattern = /^([^ :]+):(\\d+):(.+)/;
615 function addURL(line) {
616 if (FlagURL == "") return line;
617 if (FlagSeparator == "") {
618 return line.replace(ParseLinePattern,
619 "<a target='_blank' href='" + FlagURL + "/$1'>$1</a>:$2:$3");
620 }
621 return line.replace(ParseLinePattern,
622 "<a target='_blank' href='" + FlagURL + "/$1" + FlagSeparator +
623 "$2'>$1:$2</a>:$3");
624 }
625 function createArrayOfDictionaries(n) {
626 var result = [];
627 for (var i=0; i<n; i++) result.push({});
628 return result;
629 }
630 function groupWarningsBySeverity() {
631 // groups is an array of dictionaries,
632 // each dictionary maps from warning type to array of warning messages.
633 var groups = createArrayOfDictionaries(SeverityColors.length);
634 for (var i=0; i<Warnings.length; i++) {
635 var w = Warnings[i][0];
636 var s = WarnPatternsSeverity[w];
637 var k = w.toString();
638 if (!(k in groups[s]))
639 groups[s][k] = [];
640 groups[s][k].push(Warnings[i]);
641 }
642 return groups;
643 }
644 function groupWarningsByProject() {
645 var groups = createArrayOfDictionaries(ProjectNames.length);
646 for (var i=0; i<Warnings.length; i++) {
647 var w = Warnings[i][0];
648 var p = Warnings[i][1];
649 var k = w.toString();
650 if (!(k in groups[p]))
651 groups[p][k] = [];
652 groups[p][k].push(Warnings[i]);
653 }
654 return groups;
655 }
656 var GlobalAnchor = 0;
657 function createWarningSection(header, color, group) {
658 var result = "";
659 var groupKeys = [];
660 var totalMessages = 0;
661 for (var k in group) {
662 totalMessages += group[k].length;
663 groupKeys.push([k, WarnPatternsSeverity[parseInt(k)], group[k].length]);
664 }
665 groupKeys.sort(bySeverityMessageCount);
666 for (var idx=0; idx<groupKeys.length; idx++) {
667 var k = groupKeys[idx][0];
668 var messages = group[k];
669 var w = parseInt(k);
670 var wcolor = SeverityColors[WarnPatternsSeverity[w]];
671 var description = WarnPatternsDescription[w];
672 if (description.length == 0)
673 description = "???";
674 GlobalAnchor += 1;
675 result += "<table class='t1'><tr bgcolor='" + wcolor + "'><td>" +
676 "<button class='bt' id='" + GlobalAnchor + "_mark" +
677 "' onclick='expand(\\"" + GlobalAnchor + "\\");'>" +
678 "&#x2295</button> " +
679 description + " (" + messages.length + ")</td></tr></table>";
680 result += "<div id='" + GlobalAnchor +
681 "' style='display:none;'><table class='t1'>";
682 var c = 0;
683 messages.sort(compareMessages);
684 for (var i=0; i<messages.length; i++) {
685 result += "<tr><td class='c" + c + "'>" +
686 addURL(WarningMessages[messages[i][2]]) + "</td></tr>";
687 c = 1 - c;
688 }
689 result += "</table></div>";
690 }
691 if (result.length > 0) {
692 return "<br><span style='background-color:" + color + "'><b>" +
693 header + ": " + totalMessages +
694 "</b></span><blockquote><table class='t1'>" +
695 result + "</table></blockquote>";
696
697 }
698 return ""; // empty section
699 }
700 function generateSectionsBySeverity() {
701 var result = "";
702 var groups = groupWarningsBySeverity();
703 for (s=0; s<SeverityColors.length; s++) {
704 result += createWarningSection(SeverityHeaders[s], SeverityColors[s], groups[s]);
705 }
706 return result;
707 }
708 function generateSectionsByProject() {
709 var result = "";
710 var groups = groupWarningsByProject();
711 for (i=0; i<groups.length; i++) {
712 result += createWarningSection(ProjectNames[i], 'lightgrey', groups[i]);
713 }
714 return result;
715 }
716 function groupWarnings(generator) {
717 GlobalAnchor = 0;
718 var e = document.getElementById("warning_groups");
719 e.innerHTML = generator();
720 }
721 function groupBySeverity() {
722 groupWarnings(generateSectionsBySeverity);
723 }
724 function groupByProject() {
725 groupWarnings(generateSectionsByProject);
726 }
727"""
728
729
730# Emit a JavaScript const string
731def emit_const_string(name, value):
732 print('const ' + name + ' = "' + escape_string(value) + '";')
733
734
735# Emit a JavaScript const integer array.
736def emit_const_int_array(name, array):
737 print('const ' + name + ' = [')
738 for n in array:
739 print(str(n) + ',')
740 print('];')
741
742
743# Emit a JavaScript const string array.
744def emit_const_string_array(name, array):
745 print('const ' + name + ' = [')
746 for s in array:
747 print('"' + strip_escape_string(s) + '",')
748 print('];')
749
750
751# Emit a JavaScript const string array for HTML.
752def emit_const_html_string_array(name, array):
753 print('const ' + name + ' = [')
754 for s in array:
755 # Not using html.escape yet, to work for both python 2 and 3,
756 # until all users switch to python 3.
757 # pylint:disable=deprecated-method
758 print('"' + cgi.escape(strip_escape_string(s)) + '",')
759 print('];')
760
761
762# Emit a JavaScript const object array.
763def emit_const_object_array(name, array):
764 print('const ' + name + ' = [')
765 for x in array:
766 print(str(x) + ',')
767 print('];')
768
769
770def emit_js_data():
771 """Dump dynamic HTML page's static JavaScript data."""
772 emit_const_string('FlagURL', args.url if args.url else '')
773 emit_const_string('FlagSeparator', args.separator if args.separator else '')
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800774 emit_const_string_array('SeverityColors', [s.color for s in Severity.levels])
775 emit_const_string_array('SeverityHeaders',
776 [s.header for s in Severity.levels])
777 emit_const_string_array('SeverityColumnHeaders',
778 [s.column_header for s in Severity.levels])
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800779 emit_const_string_array('ProjectNames', project_names)
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800780 # pytype: disable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800781 emit_const_int_array('WarnPatternsSeverity',
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800782 [w['severity'].value for w in warn_patterns])
783 # pytype: enable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800784 emit_const_html_string_array('WarnPatternsDescription',
785 [w['description'] for w in warn_patterns])
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800786 emit_const_html_string_array('WarningMessages', warning_messages)
787 emit_const_object_array('Warnings', warning_records)
788
789draw_table_javascript = """
790google.charts.load('current', {'packages':['table']});
791google.charts.setOnLoadCallback(drawTable);
792function drawTable() {
793 var data = new google.visualization.DataTable();
794 data.addColumn('string', StatsHeader[0]);
795 for (var i=1; i<StatsHeader.length; i++) {
796 data.addColumn('number', StatsHeader[i]);
797 }
798 data.addRows(StatsRows);
799 for (var i=0; i<StatsRows.length; i++) {
800 for (var j=0; j<StatsHeader.length; j++) {
801 data.setProperty(i, j, 'style', 'border:1px solid black;');
802 }
803 }
804 var table = new google.visualization.Table(document.getElementById('stats_table'));
805 table.draw(data, {allowHtml: true, alternatingRowStyle: true});
806}
807"""
808
809
810def dump_html():
811 """Dump the html output to stdout."""
812 dump_html_prologue('Warnings for ' + platform_version + ' - ' +
813 target_product + ' - ' + target_variant)
814 dump_stats()
815 print('<br><div id="stats_table"></div><br>')
816 print('\n<script>')
817 emit_js_data()
818 print(scripts_for_warning_groups)
819 print('</script>')
820 emit_buttons()
821 # Warning messages are grouped by severities or project names.
822 print('<br><div id="warning_groups"></div>')
823 if args.byproject:
824 print('<script>groupByProject();</script>')
825 else:
826 print('<script>groupBySeverity();</script>')
827 dump_fixed()
828 dump_html_epilogue()
829
830
831##### Functions to count warnings and dump csv file. #########################
832
833
834def description_for_csv(category):
835 if not category['description']:
836 return '?'
837 return category['description']
838
839
840def count_severity(writer, sev, kind):
841 """Count warnings of given severity."""
842 total = 0
843 for i in warn_patterns:
844 if i['severity'] == sev and i['members']:
845 n = len(i['members'])
846 total += n
847 warning = kind + ': ' + description_for_csv(i)
848 writer.writerow([n, '', warning])
849 # print number of warnings for each project, ordered by project name.
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800850 # pytype: disable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800851 projects = sorted(i['projects'].keys())
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800852 # pytype: enable=attribute-error
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800853 for p in projects:
854 writer.writerow([i['projects'][p], p, warning])
855 writer.writerow([total, '', kind + ' warnings'])
856
857 return total
858
859
860# dump number of warnings in csv format to stdout
861def dump_csv(writer):
862 """Dump number of warnings in csv format to stdout."""
863 sort_warnings()
864 total = 0
Chih-Hung Hsieh949205a2020-01-10 10:33:40 -0800865 for s in Severity.levels:
866 if s != Severity.SEVERITY_UNKNOWN:
867 total += count_severity(writer, s, s.column_header)
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -0800868 writer.writerow([total, '', 'All warnings'])
869
870
871def common_main(parallel_process):
872 """Real main function to classify warnings and generate .html file."""
873 find_android_root()
874 # We must use 'utf-8' codec to parse some non-ASCII code in warnings.
875 warning_lines = parse_input_file(
876 io.open(args.buildlog, mode='r', encoding='utf-8'))
877 parallel_classify_warnings(warning_lines, parallel_process)
878 # If a user pases a csv path, save the fileoutput to the path
879 # If the user also passed gencsv write the output to stdout
880 # If the user did not pass gencsv flag dump the html report to stdout.
881 if args.csvpath:
882 with open(args.csvpath, 'w') as f:
883 dump_csv(csv.writer(f, lineterminator='\n'))
884 if args.gencsv:
885 dump_csv(csv.writer(sys.stdout, lineterminator='\n'))
886 else:
887 dump_html()