blob: de4b80595f2d904d25fb08f7eed95156cb4de4b5 [file] [log] [blame]
Chih-Hung Hsieh888d1432019-12-09 19:32:03 -08001#
2# 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
27# severity.range [0, 1, ... last_severity_level]
28# severity.colors for header background
29# severity.column_headers for the warning count table
30# severity.headers for warning message tables
31# warn_patterns:
32# warn_patterns[w]['category'] tool that issued the warning, not used now
33# warn_patterns[w]['description'] table heading
34# warn_patterns[w]['members'] matched warnings from input
35# warn_patterns[w]['option'] compiler flag to control the warning
36# warn_patterns[w]['patterns'] regular expressions to match warnings
37# warn_patterns[w]['projects'][p] number of warnings of pattern w in p
38# warn_patterns[w]['severity'] severity level
39# project_list[p][0] project name
40# project_list[p][1] regular expression to match a project path
41# project_patterns[p] re.compile(project_list[p][1])
42# project_names[p] project_list[p][0]
43# warning_messages array of each warning message, without source url
44# warning_records array of [idx to warn_patterns,
45# idx to project_names,
46# idx to warning_messages]
47# android_root
48# platform_version
49# target_product
50# target_variant
51# compile_patterns, parse_input_file
52#
53# To emit html page of warning messages:
54# flags: --byproject, --url, --separator
55# Old stuff for static html components:
56# html_script_style: static html scripts and styles
57# htmlbig:
58# dump_stats, dump_html_prologue, dump_html_epilogue:
59# emit_buttons:
60# dump_fixed
61# sort_warnings:
62# emit_stats_by_project:
63# all_patterns,
64# findproject, classify_warning
65# dump_html
66#
67# New dynamic HTML page's static JavaScript data:
68# Some data are copied from Python to JavaScript, to generate HTML elements.
69# FlagURL args.url
70# FlagSeparator args.separator
71# SeverityColors: severity.colors
72# SeverityHeaders: severity.headers
73# SeverityColumnHeaders: severity.column_headers
74# ProjectNames: project_names, or project_list[*][0]
75# WarnPatternsSeverity: warn_patterns[*]['severity']
76# WarnPatternsDescription: warn_patterns[*]['description']
77# WarnPatternsOption: warn_patterns[*]['option']
78# WarningMessages: warning_messages
79# Warnings: warning_records
80# StatsHeader: warning count table header row
81# StatsRows: array of warning count table rows
82#
83# New dynamic HTML page's dynamic JavaScript data:
84#
85# New dynamic HTML related function to emit data:
86# escape_string, strip_escape_string, emit_warning_arrays
87# emit_js_data():
88
89from __future__ import print_function
90import argparse
91import cgi
92import csv
93import io
94import multiprocessing
95import os
96import re
97import signal
98import sys
99
100# pylint:disable=relative-beyond-top-level
101from . import cpp_warn_patterns
102from . import java_warn_patterns
103from . import make_warn_patterns
104from . import other_warn_patterns
105from . import tidy_warn_patterns
106from .android_project_list import project_list
107from .severity import Severity
108
109parser = argparse.ArgumentParser(description='Convert a build log into HTML')
110parser.add_argument('--csvpath',
111 help='Save CSV warning file to the passed absolute path',
112 default=None)
113parser.add_argument('--gencsv',
114 help='Generate a CSV file with number of various warnings',
115 action='store_true',
116 default=False)
117parser.add_argument('--byproject',
118 help='Separate warnings in HTML output by project names',
119 action='store_true',
120 default=False)
121parser.add_argument('--url',
122 help='Root URL of an Android source code tree prefixed '
123 'before files in warnings')
124parser.add_argument('--separator',
125 help='Separator between the end of a URL and the line '
126 'number argument. e.g. #')
127parser.add_argument('--processes',
128 type=int,
129 default=multiprocessing.cpu_count(),
130 help='Number of parallel processes to process warnings')
131parser.add_argument(dest='buildlog', metavar='build.log',
132 help='Path to build.log file')
133args = parser.parse_args()
134
135warn_patterns = make_warn_patterns.patterns
136warn_patterns.extend(cpp_warn_patterns.patterns)
137warn_patterns.extend(java_warn_patterns.patterns)
138warn_patterns.extend(tidy_warn_patterns.patterns)
139warn_patterns.extend(other_warn_patterns.patterns)
140
141project_patterns = []
142project_names = []
143warning_messages = []
144warning_records = []
145
146
147def initialize_arrays():
148 """Complete global arrays before they are used."""
149 global project_names, project_patterns
150 project_names = [p[0] for p in project_list]
151 project_patterns = [re.compile(p[1]) for p in project_list]
152 for w in warn_patterns:
153 w['members'] = []
154 if 'option' not in w:
155 w['option'] = ''
156 # Each warning pattern has a 'projects' dictionary, that
157 # maps a project name to number of warnings in that project.
158 w['projects'] = {}
159
160
161initialize_arrays()
162
163
164android_root = ''
165platform_version = 'unknown'
166target_product = 'unknown'
167target_variant = 'unknown'
168
169
170##### Data and functions to dump html file. ##################################
171
172html_head_scripts = """\
173 <script type="text/javascript">
174 function expand(id) {
175 var e = document.getElementById(id);
176 var f = document.getElementById(id + "_mark");
177 if (e.style.display == 'block') {
178 e.style.display = 'none';
179 f.innerHTML = '&#x2295';
180 }
181 else {
182 e.style.display = 'block';
183 f.innerHTML = '&#x2296';
184 }
185 };
186 function expandCollapse(show) {
187 for (var id = 1; ; id++) {
188 var e = document.getElementById(id + "");
189 var f = document.getElementById(id + "_mark");
190 if (!e || !f) break;
191 e.style.display = (show ? 'block' : 'none');
192 f.innerHTML = (show ? '&#x2296' : '&#x2295');
193 }
194 };
195 </script>
196 <style type="text/css">
197 th,td{border-collapse:collapse; border:1px solid black;}
198 .button{color:blue;font-size:110%;font-weight:bolder;}
199 .bt{color:black;background-color:transparent;border:none;outline:none;
200 font-size:140%;font-weight:bolder;}
201 .c0{background-color:#e0e0e0;}
202 .c1{background-color:#d0d0d0;}
203 .t1{border-collapse:collapse; width:100%; border:1px solid black;}
204 </style>
205 <script src="https://www.gstatic.com/charts/loader.js"></script>
206"""
207
208
209def html_big(param):
210 return '<font size="+2">' + param + '</font>'
211
212
213def dump_html_prologue(title):
214 print('<html>\n<head>')
215 print('<title>' + title + '</title>')
216 print(html_head_scripts)
217 emit_stats_by_project()
218 print('</head>\n<body>')
219 print(html_big(title))
220 print('<p>')
221
222
223def dump_html_epilogue():
224 print('</body>\n</head>\n</html>')
225
226
227def sort_warnings():
228 for i in warn_patterns:
229 i['members'] = sorted(set(i['members']))
230
231
232def emit_stats_by_project():
233 """Dump a google chart table of warnings per project and severity."""
234 # warnings[p][s] is number of warnings in project p of severity s.
235 # pylint:disable=g-complex-comprehension
236 warnings = {p: {s: 0 for s in Severity.range} for p in project_names}
237 for i in warn_patterns:
238 s = i['severity']
239 for p in i['projects']:
240 warnings[p][s] += i['projects'][p]
241
242 # total_by_project[p] is number of warnings in project p.
243 total_by_project = {p: sum(warnings[p][s] for s in Severity.range)
244 for p in project_names}
245
246 # total_by_severity[s] is number of warnings of severity s.
247 total_by_severity = {s: sum(warnings[p][s] for p in project_names)
248 for s in Severity.range}
249
250 # emit table header
251 stats_header = ['Project']
252 for s in Severity.range:
253 if total_by_severity[s]:
254 stats_header.append("<span style='background-color:{}'>{}</span>".
255 format(Severity.colors[s],
256 Severity.column_headers[s]))
257 stats_header.append('TOTAL')
258
259 # emit a row of warning counts per project, skip no-warning projects
260 total_all_projects = 0
261 stats_rows = []
262 for p in project_names:
263 if total_by_project[p]:
264 one_row = [p]
265 for s in Severity.range:
266 if total_by_severity[s]:
267 one_row.append(warnings[p][s])
268 one_row.append(total_by_project[p])
269 stats_rows.append(one_row)
270 total_all_projects += total_by_project[p]
271
272 # emit a row of warning counts per severity
273 total_all_severities = 0
274 one_row = ['<b>TOTAL</b>']
275 for s in Severity.range:
276 if total_by_severity[s]:
277 one_row.append(total_by_severity[s])
278 total_all_severities += total_by_severity[s]
279 one_row.append(total_all_projects)
280 stats_rows.append(one_row)
281 print('<script>')
282 emit_const_string_array('StatsHeader', stats_header)
283 emit_const_object_array('StatsRows', stats_rows)
284 print(draw_table_javascript)
285 print('</script>')
286
287
288def dump_stats():
289 """Dump some stats about total number of warnings and such."""
290 known = 0
291 skipped = 0
292 unknown = 0
293 sort_warnings()
294 for i in warn_patterns:
295 if i['severity'] == Severity.UNKNOWN:
296 unknown += len(i['members'])
297 elif i['severity'] == Severity.SKIP:
298 skipped += len(i['members'])
299 else:
300 known += len(i['members'])
301 print('Number of classified warnings: <b>' + str(known) + '</b><br>')
302 print('Number of skipped warnings: <b>' + str(skipped) + '</b><br>')
303 print('Number of unclassified warnings: <b>' + str(unknown) + '</b><br>')
304 total = unknown + known + skipped
305 extra_msg = ''
306 if total < 1000:
307 extra_msg = ' (low count may indicate incremental build)'
308 print('Total number of warnings: <b>' + str(total) + '</b>' + extra_msg)
309
310
311# New base table of warnings, [severity, warn_id, project, warning_message]
312# Need buttons to show warnings in different grouping options.
313# (1) Current, group by severity, id for each warning pattern
314# sort by severity, warn_id, warning_message
315# (2) Current --byproject, group by severity,
316# id for each warning pattern + project name
317# sort by severity, warn_id, project, warning_message
318# (3) New, group by project + severity,
319# id for each warning pattern
320# sort by project, severity, warn_id, warning_message
321def emit_buttons():
322 print('<button class="button" onclick="expandCollapse(1);">'
323 'Expand all warnings</button>\n'
324 '<button class="button" onclick="expandCollapse(0);">'
325 'Collapse all warnings</button>\n'
326 '<button class="button" onclick="groupBySeverity();">'
327 'Group warnings by severity</button>\n'
328 '<button class="button" onclick="groupByProject();">'
329 'Group warnings by project</button><br>')
330
331
332def all_patterns(category):
333 patterns = ''
334 for i in category['patterns']:
335 patterns += i
336 patterns += ' / '
337 return patterns
338
339
340def dump_fixed():
341 """Show which warnings no longer occur."""
342 anchor = 'fixed_warnings'
343 mark = anchor + '_mark'
344 print('\n<br><p style="background-color:lightblue"><b>'
345 '<button id="' + mark + '" '
346 'class="bt" onclick="expand(\'' + anchor + '\');">'
347 '&#x2295</button> Fixed warnings. '
348 'No more occurrences. Please consider turning these into '
349 'errors if possible, before they are reintroduced in to the build'
350 ':</b></p>')
351 print('<blockquote>')
352 fixed_patterns = []
353 for i in warn_patterns:
354 if not i['members']:
355 fixed_patterns.append(i['description'] + ' (' +
356 all_patterns(i) + ')')
357 if i['option']:
358 fixed_patterns.append(' ' + i['option'])
359 fixed_patterns = sorted(fixed_patterns)
360 print('<div id="' + anchor + '" style="display:none;"><table>')
361 cur_row_class = 0
362 for text in fixed_patterns:
363 cur_row_class = 1 - cur_row_class
364 # remove last '\n'
365 t = text[:-1] if text[-1] == '\n' else text
366 print('<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>')
367 print('</table></div>')
368 print('</blockquote>')
369
370
371def find_project_index(line):
372 for p in range(len(project_patterns)):
373 if project_patterns[p].match(line):
374 return p
375 return -1
376
377
378def classify_one_warning(line, results):
379 """Classify one warning line."""
380 for i in range(len(warn_patterns)):
381 w = warn_patterns[i]
382 for cpat in w['compiled_patterns']:
383 if cpat.match(line):
384 p = find_project_index(line)
385 results.append([line, i, p])
386 return
387 else:
388 # If we end up here, there was a problem parsing the log
389 # probably caused by 'make -j' mixing the output from
390 # 2 or more concurrent compiles
391 pass
392
393
394def classify_warnings(lines):
395 results = []
396 for line in lines:
397 classify_one_warning(line, results)
398 # After the main work, ignore all other signals to a child process,
399 # to avoid bad warning/error messages from the exit clean-up process.
400 if args.processes > 1:
401 signal.signal(signal.SIGTERM, lambda *args: sys.exit(-signal.SIGTERM))
402 return results
403
404
405def parallel_classify_warnings(warning_lines, parallel_process):
406 """Classify all warning lines with num_cpu parallel processes."""
407 compile_patterns()
408 num_cpu = args.processes
409 if num_cpu > 1:
410 groups = [[] for x in range(num_cpu)]
411 i = 0
412 for x in warning_lines:
413 groups[i].append(x)
414 i = (i + 1) % num_cpu
415 group_results = parallel_process(num_cpu, classify_warnings, groups)
416 else:
417 group_results = [classify_warnings(warning_lines)]
418
419 for result in group_results:
420 for line, pattern_idx, project_idx in result:
421 pattern = warn_patterns[pattern_idx]
422 pattern['members'].append(line)
423 message_idx = len(warning_messages)
424 warning_messages.append(line)
425 warning_records.append([pattern_idx, project_idx, message_idx])
426 pname = '???' if project_idx < 0 else project_names[project_idx]
427 # Count warnings by project.
428 if pname in pattern['projects']:
429 pattern['projects'][pname] += 1
430 else:
431 pattern['projects'][pname] = 1
432
433
434def compile_patterns():
435 """Precompiling every pattern speeds up parsing by about 30x."""
436 for i in warn_patterns:
437 i['compiled_patterns'] = []
438 for pat in i['patterns']:
439 i['compiled_patterns'].append(re.compile(pat))
440
441
442def find_warn_py_and_android_root(path):
443 """Set and return android_root path if it is found."""
444 global android_root
445 parts = path.split('/')
446 for idx in reversed(range(2, len(parts))):
447 root_path = '/'.join(parts[:idx])
448 # Android root directory should contain this script.
449 if os.path.exists(root_path + '/build/make/tools/warn.py'):
450 android_root = root_path
451 return True
452 return False
453
454
455def find_android_root():
456 """Guess android_root from common prefix of file paths."""
457 # Use the longest common prefix of the absolute file paths
458 # of the first 10000 warning messages as the android_root.
459 global android_root
460 warning_lines = set()
461 warning_pattern = re.compile('^/[^ ]*/[^ ]*: warning: .*')
462 count = 0
463 infile = io.open(args.buildlog, mode='r', encoding='utf-8')
464 for line in infile:
465 if warning_pattern.match(line):
466 warning_lines.add(line)
467 count += 1
468 if count > 9999:
469 break
470 # Try to find warn.py and use its location to find
471 # the source tree root.
472 if count < 100:
473 path = os.path.normpath(re.sub(':.*$', '', line))
474 if find_warn_py_and_android_root(path):
475 return
476 # Do not use common prefix of a small number of paths.
477 if count > 10:
478 root_path = os.path.commonprefix(warning_lines)
479 if len(root_path) > 2 and root_path[len(root_path) - 1] == '/':
480 android_root = root_path[:-1]
481
482
483def remove_android_root_prefix(path):
484 """Remove android_root prefix from path if it is found."""
485 if path.startswith(android_root):
486 return path[1 + len(android_root):]
487 else:
488 return path
489
490
491def normalize_path(path):
492 """Normalize file path relative to android_root."""
493 # If path is not an absolute path, just normalize it.
494 path = os.path.normpath(path)
495 # Remove known prefix of root path and normalize the suffix.
496 if path[0] == '/' and android_root:
497 return remove_android_root_prefix(path)
498 return path
499
500
501def normalize_warning_line(line):
502 """Normalize file path relative to android_root in a warning line."""
503 # replace fancy quotes with plain ol' quotes
504 line = re.sub(u'[\u2018\u2019]', '\'', line)
505 # replace non-ASCII chars to spaces
506 line = re.sub(u'[^\x00-\x7f]', ' ', line)
507 line = line.strip()
508 first_column = line.find(':')
509 if first_column > 0:
510 return normalize_path(line[:first_column]) + line[first_column:]
511 else:
512 return line
513
514
515def parse_input_file(infile):
516 """Parse input file, collect parameters and warning lines."""
517 global android_root
518 global platform_version
519 global target_product
520 global target_variant
521 line_counter = 0
522
523 # rustc warning messages have two lines that should be combined:
524 # warning: description
525 # --> file_path:line_number:column_number
526 # Some warning messages have no file name:
527 # warning: macro replacement list ... [bugprone-macro-parentheses]
528 # Some makefile warning messages have no line number:
529 # some/path/file.mk: warning: description
530 # C/C++ compiler warning messages have line and column numbers:
531 # some/path/file.c:line_number:column_number: warning: description
532 warning_pattern = re.compile('(^[^ ]*/[^ ]*: warning: .*)|(^warning: .*)')
533 warning_without_file = re.compile('^warning: .*')
534 rustc_file_position = re.compile('^[ ]+--> [^ ]*/[^ ]*:[0-9]+:[0-9]+')
535
536 # Collect all warnings into the warning_lines set.
537 warning_lines = set()
538 prev_warning = ''
539 for line in infile:
540 if prev_warning:
541 if rustc_file_position.match(line):
542 # must be a rustc warning, combine 2 lines into one warning
543 line = line.strip().replace('--> ', '') + ': ' + prev_warning
544 warning_lines.add(normalize_warning_line(line))
545 prev_warning = ''
546 continue
547 # add prev_warning, and then process the current line
548 prev_warning = 'unknown_source_file: ' + prev_warning
549 warning_lines.add(normalize_warning_line(prev_warning))
550 prev_warning = ''
551 if warning_pattern.match(line):
552 if warning_without_file.match(line):
553 # save this line and combine it with the next line
554 prev_warning = line
555 else:
556 warning_lines.add(normalize_warning_line(line))
557 continue
558 if line_counter < 100:
559 # save a little bit of time by only doing this for the first few lines
560 line_counter += 1
561 m = re.search('(?<=^PLATFORM_VERSION=).*', line)
562 if m is not None:
563 platform_version = m.group(0)
564 m = re.search('(?<=^TARGET_PRODUCT=).*', line)
565 if m is not None:
566 target_product = m.group(0)
567 m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
568 if m is not None:
569 target_variant = m.group(0)
570 m = re.search('.* TOP=([^ ]*) .*', line)
571 if m is not None:
572 android_root = m.group(1)
573 return warning_lines
574
575
576# Return s with escaped backslash and quotation characters.
577def escape_string(s):
578 return s.replace('\\', '\\\\').replace('"', '\\"')
579
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 '')
780 emit_const_string_array('SeverityColors', Severity.colors)
781 emit_const_string_array('SeverityHeaders', Severity.headers)
782 emit_const_string_array('SeverityColumnHeaders', Severity.column_headers)
783 emit_const_string_array('ProjectNames', project_names)
784 emit_const_int_array('WarnPatternsSeverity',
785 [w['severity'] for w in warn_patterns])
786 emit_const_html_string_array('WarnPatternsDescription',
787 [w['description'] for w in warn_patterns])
788 emit_const_html_string_array('WarnPatternsOption',
789 [w['option'] for w in warn_patterns])
790 emit_const_html_string_array('WarningMessages', warning_messages)
791 emit_const_object_array('Warnings', warning_records)
792
793draw_table_javascript = """
794google.charts.load('current', {'packages':['table']});
795google.charts.setOnLoadCallback(drawTable);
796function drawTable() {
797 var data = new google.visualization.DataTable();
798 data.addColumn('string', StatsHeader[0]);
799 for (var i=1; i<StatsHeader.length; i++) {
800 data.addColumn('number', StatsHeader[i]);
801 }
802 data.addRows(StatsRows);
803 for (var i=0; i<StatsRows.length; i++) {
804 for (var j=0; j<StatsHeader.length; j++) {
805 data.setProperty(i, j, 'style', 'border:1px solid black;');
806 }
807 }
808 var table = new google.visualization.Table(document.getElementById('stats_table'));
809 table.draw(data, {allowHtml: true, alternatingRowStyle: true});
810}
811"""
812
813
814def dump_html():
815 """Dump the html output to stdout."""
816 dump_html_prologue('Warnings for ' + platform_version + ' - ' +
817 target_product + ' - ' + target_variant)
818 dump_stats()
819 print('<br><div id="stats_table"></div><br>')
820 print('\n<script>')
821 emit_js_data()
822 print(scripts_for_warning_groups)
823 print('</script>')
824 emit_buttons()
825 # Warning messages are grouped by severities or project names.
826 print('<br><div id="warning_groups"></div>')
827 if args.byproject:
828 print('<script>groupByProject();</script>')
829 else:
830 print('<script>groupBySeverity();</script>')
831 dump_fixed()
832 dump_html_epilogue()
833
834
835##### Functions to count warnings and dump csv file. #########################
836
837
838def description_for_csv(category):
839 if not category['description']:
840 return '?'
841 return category['description']
842
843
844def count_severity(writer, sev, kind):
845 """Count warnings of given severity."""
846 total = 0
847 for i in warn_patterns:
848 if i['severity'] == sev and i['members']:
849 n = len(i['members'])
850 total += n
851 warning = kind + ': ' + description_for_csv(i)
852 writer.writerow([n, '', warning])
853 # print number of warnings for each project, ordered by project name.
854 projects = sorted(i['projects'].keys())
855 for p in projects:
856 writer.writerow([i['projects'][p], p, warning])
857 writer.writerow([total, '', kind + ' warnings'])
858
859 return total
860
861
862# dump number of warnings in csv format to stdout
863def dump_csv(writer):
864 """Dump number of warnings in csv format to stdout."""
865 sort_warnings()
866 total = 0
867 for s in Severity.range:
868 total += count_severity(writer, s, Severity.column_headers[s])
869 writer.writerow([total, '', 'All warnings'])
870
871
872def common_main(parallel_process):
873 """Real main function to classify warnings and generate .html file."""
874 find_android_root()
875 # We must use 'utf-8' codec to parse some non-ASCII code in warnings.
876 warning_lines = parse_input_file(
877 io.open(args.buildlog, mode='r', encoding='utf-8'))
878 parallel_classify_warnings(warning_lines, parallel_process)
879 # If a user pases a csv path, save the fileoutput to the path
880 # If the user also passed gencsv write the output to stdout
881 # If the user did not pass gencsv flag dump the html report to stdout.
882 if args.csvpath:
883 with open(args.csvpath, 'w') as f:
884 dump_csv(csv.writer(f, lineterminator='\n'))
885 if args.gencsv:
886 dump_csv(csv.writer(sys.stdout, lineterminator='\n'))
887 else:
888 dump_html()