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