Loading tools/warn.py +450 −397 Original line number Diff line number Diff line #!/usr/bin/env python #!/usr/bin/python # This file uses the following encoding: utf-8 """Grep warnings messages and output HTML tables or warning counts in CSV. Default is to output warnings in HTML tables grouped by warning severity. Use option --byproject to output tables grouped by source file projects. Use option --gencsv to output warning counts in CSV format. """ import argparse import re parser = argparse.ArgumentParser(description='Convert a build log into HTML') parser.add_argument('--gencsv', help='Generate a CSV file with number of various warnings', action="store_true", action='store_true', default=False) parser.add_argument('--byproject', help='Separate warnings in HTML output by project names', action="store_true", action='store_true', default=False) parser.add_argument('--url', help='Root URL of an Android source code tree prefixed ' Loading @@ -23,8 +30,10 @@ parser.add_argument(dest='buildlog', metavar='build.log', help='Path to build.log file') args = parser.parse_args() # if you add another level, don't forget to give it a color below class severity: class severity: # pylint:disable=invalid-name,old-style-class """Severity levels and attributes.""" UNKNOWN = 0 FIXMENOW = 1 HIGH = 2 Loading @@ -34,6 +43,7 @@ class severity: HARMLESS = 6 SKIP = 7 attributes = [ # pylint:disable=bad-whitespace ['lightblue', 'Unknown', 'Unknown warnings'], ['fuchsia', 'FixNow', 'Critical warnings, fix me now'], ['red', 'High', 'High severity warnings'], Loading @@ -44,12 +54,15 @@ class severity: ['grey', 'Unhandled', 'Unhandled warnings'] ] color = [a[0] for a in attributes] columnheader = [a[1] for a in attributes] column_headers = [a[1] for a in attributes] header = [a[2] for a in attributes] # order to dump by severity kinds = [FIXMENOW, HIGH, MEDIUM, LOW, TIDY, HARMLESS, UNKNOWN, SKIP] warnpatterns = [ warn_patterns = [ # TODO(chh): fix pylint space and indentation warnings # pylint:disable=bad-whitespace,bad-continuation, # pylint:disable=line-too-long,g-inconsistent-quotes { 'category':'make', 'severity':severity.MEDIUM, 'description':'make: overriding commands/ignoring old commands', 'patterns':[r".*: warning: overriding commands for target .+", Loading Loading @@ -1158,6 +1171,8 @@ warnpatterns = [ { 'category':'C/C++', 'severity':severity.MEDIUM, 'option':'-Wmissing-noreturn', 'description':'Missing noreturn', 'patterns':[r".*: warning: function '.*' could be declared with attribute 'noreturn'"] }, # pylint:disable=anomalous-backslash-in-string # TODO(chh): fix the backslash pylint warning. { 'category':'gcc', 'severity':severity.MEDIUM, 'description':'Invalid option for C file', 'patterns':[r".*: warning: command line option "".+"" is valid for C\+\+\/ObjC\+\+ but not for C"] }, Loading Loading @@ -1577,14 +1592,11 @@ warnpatterns = [ 'patterns':[r".*: warning: .+"] }, ] for w in warnpatterns: w['members'] = [] if 'option' not in w: w['option'] = '' # A list of [project_name, file_path_pattern]. # project_name should not contain comma, to be used in CSV output. projectlist = [ project_list = [ # pylint:disable=bad-whitespace,g-inconsistent-quotes,line-too-long ['art', r"(^|.*/)art/.*: warning:"], ['bionic', r"(^|.*/)bionic/.*: warning:"], ['bootable', r"(^|.*/)bootable/.*: warning:"], Loading Loading @@ -1619,24 +1631,37 @@ projectlist = [ ['other', r".*: warning:"], ] projectpatterns = [] for p in projectlist: projectpatterns.append({'description':p[0], 'members':[], 'pattern':re.compile(p[1])}) project_patterns = [] project_names = [] projectnames = [p[0] for p in projectlist] def initialize_arrays(): """Complete global arrays before they are used.""" global project_names project_names = [p[0] for p in project_list] for p in project_list: project_patterns.append({'description': p[0], 'members': [], 'pattern': re.compile(p[1])}) # Each warning pattern has 3 dictionaries: # (1) 'projects' maps a project name to number of warnings in that project. # (2) 'projectanchor' maps a project name to its anchor number for HTML. # (3) 'projectwarning' maps a project name to a list of warning of that project. for w in warnpatterns: # (2) 'project_anchor' maps a project name to its anchor number for HTML. # (3) 'project_warnings' maps a project name to a list of warning messages. for w in warn_patterns: w['members'] = [] if 'option' not in w: w['option'] = '' w['projects'] = {} w['projectanchor'] = {} w['projectwarning'] = {} w['project_anchor'] = {} w['project_warnings'] = {} platformversion = 'unknown' targetproduct = 'unknown' targetvariant = 'unknown' initialize_arrays() platform_version = 'unknown' target_product = 'unknown' target_variant = 'unknown' ##### Data and functions to dump html file. ################################## Loading Loading @@ -1682,93 +1707,97 @@ html_script_style = """\ def output(text): print text, def htmlbig(param): def html_big(param): return '<font size="+2">' + param + '</font>' def dumphtmlprologue(title): def dump_html_prologue(title): output('<html>\n<head>\n') output('<title>' + title + '</title>\n') output(html_script_style) output('</head>\n<body>\n') output(htmlbig(title)) output(html_big(title)) output('<p>\n') def dumphtmlepilogue(): def dump_html_epilogue(): output('</body>\n</head>\n</html>\n') def tablerow(text): def table_row(text): global cur_row_class cur_row_class = 1 - cur_row_class # remove last '\n' t = text[:-1] if text[-1] == '\n' else text output('<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>\n') def sortwarnings(): for i in warnpatterns: def sort_warnings(): for i in warn_patterns: i['members'] = sorted(set(i['members'])) # dump a table of warnings per project and severity def dumpstatsbyproject(): projects = set(projectnames) severities = set(severity.kinds) def dump_stats_by_project(): """Dump a table of warnings per project and severity.""" # warnings[p][s] is number of warnings in project p of severity s. warnings = {p:{s:0 for s in severity.kinds} for p in projectnames} for i in warnpatterns: warnings = {p: {s: 0 for s in severity.kinds} for p in project_names} for i in warn_patterns: s = i['severity'] for p in i['projects']: warnings[p][s] += i['projects'][p] # totalbyproject[p] is number of warnings in project p. totalbyproject = {p:sum(warnings[p][s] for s in severity.kinds) for p in projectnames} # total_by_project[p] is number of warnings in project p. total_by_project = {p: sum(warnings[p][s] for s in severity.kinds) for p in project_names} # totalbyseverity[s] is number of warnings of severity s. totalbyseverity = {s:sum(warnings[p][s] for p in projectnames) # total_by_severity[s] is number of warnings of severity s. total_by_severity = {s: sum(warnings[p][s] for p in project_names) for s in severity.kinds} # emit table header output('<blockquote><table border=1>\n<tr><th>Project</th>\n') for s in severity.kinds: if totalbyseverity[s]: if total_by_severity[s]: output('<th width="8%"><span style="background-color:{}">{}</span></th>'. format(severity.color[s], severity.columnheader[s])) format(severity.color[s], severity.column_headers[s])) output('<th>TOTAL</th></tr>\n') # emit a row of warning counts per project, skip no-warning projects totalallprojects = 0 for p in projectnames: if totalbyproject[p]: total_all_projects = 0 for p in project_names: if total_by_project[p]: output('<tr><td align="left">{}</td>'.format(p)) for s in severity.kinds: if totalbyseverity[s]: if total_by_severity[s]: output('<td align="right">{}</td>'.format(warnings[p][s])) output('<td align="right">{}</td>'.format(totalbyproject[p])) totalallprojects += totalbyproject[p] output('<td align="right">{}</td>'.format(total_by_project[p])) total_all_projects += total_by_project[p] output('</tr>\n') # emit a row of warning counts per severity totalallseverities = 0 total_all_severities = 0 output('<tr><td align="right">TOTAL</td>') for s in severity.kinds: if totalbyseverity[s]: output('<td align="right">{}</td>'.format(totalbyseverity[s])) totalallseverities += totalbyseverity[s] output('<td align="right">{}</td></tr>\n'.format(totalallprojects)) if total_by_severity[s]: output('<td align="right">{}</td>'.format(total_by_severity[s])) total_all_severities += total_by_severity[s] output('<td align="right">{}</td></tr>\n'.format(total_all_projects)) # at the end of table, verify total counts output('</table></blockquote><br>\n') if totalallprojects != totalallseverities: output('<h3>ERROR: Sum of warnings by project ' + if total_all_projects != total_all_severities: output('<h3>ERROR: Sum of warnings by project ' '!= Sum of warnings by severity.</h3>\n') # dump some stats about total number of warnings and such def dumpstats(): def dump_stats(): """Dump some stats about total number of warnings and such.""" known = 0 skipped = 0 unknown = 0 sortwarnings() for i in warnpatterns: sort_warnings() for i in warn_patterns: if i['severity'] == severity.UNKNOWN: unknown += len(i['members']) elif i['severity'] == severity.SKIP: Loading @@ -1784,140 +1813,155 @@ def dumpstats(): output('(low count may indicate incremental build)') output('<br><br>\n') def emitbuttons(): output('<button class="button" onclick="expand_collapse(1);">' + 'Expand all warnings</button>\n' + '<button class="button" onclick="expand_collapse(0);">' + def emit_buttons(): output('<button class="button" onclick="expand_collapse(1);">' 'Expand all warnings</button>\n' '<button class="button" onclick="expand_collapse(0);">' 'Collapse all warnings</button><br>\n') # dump everything for a given severity def dumpseverity(sev): def dump_severity(sev): """Dump everything for a given severity.""" global anchor total = 0 for i in warnpatterns: for i in warn_patterns: if i['severity'] == sev: total = total + len(i['members']) output('\n<br><span style="background-color:' + severity.color[sev] + '"><b>' + severity.header[sev] + ': ' + str(total) + '</b></span>\n') total += len(i['members']) output('\n<br><span style="background-color:' + severity.color[sev] + '"><b>' + severity.header[sev] + ': ' + str(total) + '</b></span>\n') output('<blockquote>\n') for i in warnpatterns: if i['severity'] == sev and len(i['members']) > 0: for i in warn_patterns: if i['severity'] == sev and i['members']: anchor += 1 i['anchor'] = str(anchor) if args.byproject: dumpcategorybyproject(sev, i) dump_category_by_project(sev, i) else: dumpcategory(sev, i) dump_category(sev, i) output('</blockquote>\n') # emit all skipped project anchors for expand_collapse. def dumpskippedanchors(): def dump_skipped_anchors(): """emit all skipped project anchors for expand_collapse.""" output('<div style="display:none;">\n') # hide these fake elements for i in warnpatterns: if i['severity'] == severity.SKIP and len(i['members']) > 0: projects = i['projectwarning'].keys() for i in warn_patterns: if i['severity'] == severity.SKIP and i['members']: projects = i['project_warnings'].keys() for p in projects: output('<div id="' + i['projectanchor'][p] + '"></div>' + '<div id="' + i['projectanchor'][p] + '_mark"></div>\n') output('<div id="' + i['project_anchor'][p] + '"></div>' + '<div id="' + i['project_anchor'][p] + '_mark"></div>\n') output('</div>\n') def allpatterns(cat): def all_patterns(cat): pats = '' for i in cat['patterns']: pats += i pats += ' / ' return pats def descriptionfor(cat): if cat['description'] != '': def description_for(cat): if cat['description']: return cat['description'] return allpatterns(cat) return all_patterns(cat) # show which warnings no longer occur def dumpfixed(): def dump_fixed(): """Show which warnings no longer occur.""" global anchor anchor += 1 mark = str(anchor) + '_mark' output('\n<br><p style="background-color:lightblue"><b>' + '<button id="' + mark + '" ' + 'class="bt" onclick="expand(' + str(anchor) + ');">' + '⊕</button> Fixed warnings. ' + 'No more occurences. Please consider turning these into ' + 'errors if possible, before they are reintroduced in to the build' + output('\n<br><p style="background-color:lightblue"><b>' '<button id="' + mark + '" ' 'class="bt" onclick="expand(' + str(anchor) + ');">' '⊕</button> Fixed warnings. ' 'No more occurrences. Please consider turning these into ' 'errors if possible, before they are reintroduced in to the build' ':</b></p>\n') output('<blockquote>\n') fixed_patterns = [] for i in warnpatterns: if len(i['members']) == 0: for i in warn_patterns: if not i['members']: fixed_patterns.append(i['description'] + ' (' + allpatterns(i) + ')') all_patterns(i) + ')') if i['option']: fixed_patterns.append(' ' + i['option']) fixed_patterns.sort() output('<div id="' + str(anchor) + '" style="display:none;"><table>\n') for i in fixed_patterns: tablerow(i) table_row(i) output('</table></div>\n') output('</blockquote>\n') def warningwithurl(line): def warning_with_url(line): """Returns a warning message line with HTML link to given args.url.""" if not args.url: return line m = re.search(r'^([^ :]+):(\d+):(.+)', line, re.M|re.I) if not m: return line filepath = m.group(1) linenumber = m.group(2) file_path = m.group(1) line_number = m.group(2) warning = m.group(3) prefix = '<a href="' + args.url + '/' + file_path if args.separator: return '<a href="' + args.url + '/' + filepath + args.separator + linenumber + '">' + filepath + ':' + linenumber + '</a>:' + warning return (prefix + args.separator + line_number + '">' + file_path + ':' + line_number + '</a>:' + warning) else: return '<a href="' + args.url + '/' + filepath + '">' + filepath + '</a>:' + linenumber + ':' + warning return prefix + '">' + file_path + '</a>:' + line_number + ':' + warning def dumpgroup(sev, anchor, description, warnings): mark = anchor + '_mark' def dump_group(sev, anchor_str, description, warnings): """Dump warnings of given severity, anchor_str, and description.""" mark = anchor_str + '_mark' output('\n<table class="t1">\n') output('<tr bgcolor="' + severity.color[sev] + '">' + '<td><button class="bt" id="' + mark + '" onclick="expand(\'' + anchor + '\');">' + '" onclick="expand(\'' + anchor_str + '\');">' + '⊕</button> ' + description + '</td></tr>\n') output('</table>\n') output('<div id="' + anchor + '" style="display:none;">') output('<div id="' + anchor_str + '" style="display:none;">') output('<table class="t1">\n') for i in warnings: tablerow(warningwithurl(i)) table_row(warning_with_url(i)) output('</table></div>\n') # dump warnings in a category def dumpcategory(sev, cat): description = descriptionfor(cat) + ' (' + str(len(cat['members'])) + ')' dumpgroup(sev, cat['anchor'], description, cat['members']) # similar to dumpcategory but output one table per project. def dumpcategorybyproject(sev, cat): warning = descriptionfor(cat) projects = cat['projectwarning'].keys() def dump_category(sev, cat): """Dump warnings in a category.""" description = description_for(cat) + ' (' + str(len(cat['members'])) + ')' dump_group(sev, cat['anchor'], description, cat['members']) def dump_category_by_project(sev, cat): """Similar to dump_category but output one table per project.""" warning = description_for(cat) projects = cat['project_warnings'].keys() projects.sort() for p in projects: anchor = cat['projectanchor'][p] projectwarnings = cat['projectwarning'][p] description = '{}, in {} ({})'.format(warning, p, len(projectwarnings)) dumpgroup(sev, anchor, description, projectwarnings) anchor_str = cat['project_anchor'][p] project_warnings = cat['project_warnings'][p] description = '{}, in {} ({})'.format(warning, p, len(project_warnings)) dump_group(sev, anchor_str, description, project_warnings) def findproject(line): for p in projectpatterns: def find_project(line): for p in project_patterns: if p['pattern'].match(line): return p['description'] return '???' def classifywarning(line): def classify_warning(line): global anchor for i in warnpatterns: for cpat in i['compiledpatterns']: for i in warn_patterns: for cpat in i['compiled_patterns']: if cpat.match(line): i['members'].append(line) pname = findproject(line) pname = find_project(line) # Count warnings by project. if pname in i['projects']: i['projects'][pname] += 1 Loading @@ -1925,13 +1969,13 @@ def classifywarning(line): i['projects'][pname] = 1 # Collect warnings by project. if args.byproject: if pname in i['projectwarning']: i['projectwarning'][pname].append(line) if pname in i['project_warnings']: i['project_warnings'][pname].append(line) else: i['projectwarning'][pname] = [line] if pname not in i['projectanchor']: i['project_warnings'][pname] = [line] if pname not in i['project_anchor']: anchor += 1 i['projectanchor'][pname] = str(anchor) i['project_anchor'][pname] = str(anchor) return else: # If we end up here, there was a problem parsing the log Loading @@ -1939,104 +1983,113 @@ def classifywarning(line): # 2 or more concurrent compiles pass # precompiling every pattern speeds up parsing by about 30x def compilepatterns(): for i in warnpatterns: i['compiledpatterns'] = [] def compile_patterns(): """Precompiling every pattern speeds up parsing by about 30x.""" for i in warn_patterns: i['compiled_patterns'] = [] for pat in i['patterns']: i['compiledpatterns'].append(re.compile(pat)) i['compiled_patterns'].append(re.compile(pat)) def parseinputfile(): global platformversion global targetproduct global targetvariant def parse_input_file(): """Parse input file, match warning lines.""" global platform_version global target_product global target_variant infile = open(args.buildlog, 'r') linecounter = 0 line_counter = 0 warningpattern = re.compile('.* warning:.*') compilepatterns() warning_pattern = re.compile('.* warning:.*') compile_patterns() # read the log file and classify all the warnings warninglines = set() warning_lines = set() for line in infile: # replace fancy quotes with plain ol' quotes line = line.replace("‘", "'"); line = line.replace("’", "'"); if warningpattern.match(line): if line not in warninglines: classifywarning(line) warninglines.add(line) line = line.replace('‘', "'") line = line.replace('’', "'") if warning_pattern.match(line): if line not in warning_lines: classify_warning(line) warning_lines.add(line) else: # save a little bit of time by only doing this for the first few lines if linecounter < 50: linecounter +=1 if line_counter < 50: line_counter += 1 m = re.search('(?<=^PLATFORM_VERSION=).*', line) if m != None: platformversion = m.group(0) if m is not None: platform_version = m.group(0) m = re.search('(?<=^TARGET_PRODUCT=).*', line) if m != None: targetproduct = m.group(0) if m is not None: target_product = m.group(0) m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line) if m != None: targetvariant = m.group(0) # dump the html output to stdout def dumphtml(): dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant) dumpstats() dumpstatsbyproject() emitbuttons() # sort table based on number of members once dumpstats has deduplicated the if m is not None: target_variant = m.group(0) def dump_html(): """Dump the html output to stdout.""" dump_html_prologue('Warnings for ' + platform_version + ' - ' + target_product + ' - ' + target_variant) dump_stats() dump_stats_by_project() emit_buttons() # sort table based on number of members once dump_stats has de-duplicated the # members. warnpatterns.sort(reverse=True, key=lambda i: len(i['members'])) # Dump warnings by severity. If severity.SKIP warnings are not dumpped, # the project anchors should be dumped through dumpskippedanchors. warn_patterns.sort(reverse=True, key=lambda i: len(i['members'])) # Dump warnings by severity. If severity.SKIP warnings are not dumped, # the project anchors should be dumped through dump_skipped_anchors. for s in severity.kinds: dumpseverity(s) dumpfixed() dumphtmlepilogue() dump_severity(s) dump_fixed() dump_html_epilogue() ##### Functions to count warnings and dump csv file. ######################### def descriptionforcsv(cat): if cat['description'] == '': def description_for_csv(cat): if not cat['description']: return '?' return cat['description'] def stringforcsv(s): def string_for_csv(s): if ',' in s: return '"{}"'.format(s) return s def countseverity(sev, kind): sum = 0 for i in warnpatterns: if i['severity'] == sev and len(i['members']) > 0: def count_severity(sev, kind): """Count warnings of given severity.""" total = 0 for i in warn_patterns: if i['severity'] == sev and i['members']: n = len(i['members']) sum += n warning = stringforcsv(kind + ': ' + descriptionforcsv(i)) total += n warning = string_for_csv(kind + ': ' + description_for_csv(i)) print '{},,{}'.format(n, warning) # print number of warnings for each project, ordered by project name. projects = i['projects'].keys() projects.sort() for p in projects: print '{},{},{}'.format(i['projects'][p], p, warning) print '{},,{}'.format(sum, kind + ' warnings') return sum print '{},,{}'.format(total, kind + ' warnings') return total # dump number of warnings in csv format to stdout def dumpcsv(): sortwarnings() def dump_csv(): """Dump number of warnings in csv format to stdout.""" sort_warnings() total = 0 for s in severity.kinds: total += countseverity(s, severity.columnheader[s]) total += count_severity(s, severity.column_headers[s]) print '{},,{}'.format(total, 'All warnings') parseinputfile() parse_input_file() if args.gencsv: dumpcsv() dump_csv() else: dumphtml() dump_html() Loading
tools/warn.py +450 −397 Original line number Diff line number Diff line #!/usr/bin/env python #!/usr/bin/python # This file uses the following encoding: utf-8 """Grep warnings messages and output HTML tables or warning counts in CSV. Default is to output warnings in HTML tables grouped by warning severity. Use option --byproject to output tables grouped by source file projects. Use option --gencsv to output warning counts in CSV format. """ import argparse import re parser = argparse.ArgumentParser(description='Convert a build log into HTML') parser.add_argument('--gencsv', help='Generate a CSV file with number of various warnings', action="store_true", action='store_true', default=False) parser.add_argument('--byproject', help='Separate warnings in HTML output by project names', action="store_true", action='store_true', default=False) parser.add_argument('--url', help='Root URL of an Android source code tree prefixed ' Loading @@ -23,8 +30,10 @@ parser.add_argument(dest='buildlog', metavar='build.log', help='Path to build.log file') args = parser.parse_args() # if you add another level, don't forget to give it a color below class severity: class severity: # pylint:disable=invalid-name,old-style-class """Severity levels and attributes.""" UNKNOWN = 0 FIXMENOW = 1 HIGH = 2 Loading @@ -34,6 +43,7 @@ class severity: HARMLESS = 6 SKIP = 7 attributes = [ # pylint:disable=bad-whitespace ['lightblue', 'Unknown', 'Unknown warnings'], ['fuchsia', 'FixNow', 'Critical warnings, fix me now'], ['red', 'High', 'High severity warnings'], Loading @@ -44,12 +54,15 @@ class severity: ['grey', 'Unhandled', 'Unhandled warnings'] ] color = [a[0] for a in attributes] columnheader = [a[1] for a in attributes] column_headers = [a[1] for a in attributes] header = [a[2] for a in attributes] # order to dump by severity kinds = [FIXMENOW, HIGH, MEDIUM, LOW, TIDY, HARMLESS, UNKNOWN, SKIP] warnpatterns = [ warn_patterns = [ # TODO(chh): fix pylint space and indentation warnings # pylint:disable=bad-whitespace,bad-continuation, # pylint:disable=line-too-long,g-inconsistent-quotes { 'category':'make', 'severity':severity.MEDIUM, 'description':'make: overriding commands/ignoring old commands', 'patterns':[r".*: warning: overriding commands for target .+", Loading Loading @@ -1158,6 +1171,8 @@ warnpatterns = [ { 'category':'C/C++', 'severity':severity.MEDIUM, 'option':'-Wmissing-noreturn', 'description':'Missing noreturn', 'patterns':[r".*: warning: function '.*' could be declared with attribute 'noreturn'"] }, # pylint:disable=anomalous-backslash-in-string # TODO(chh): fix the backslash pylint warning. { 'category':'gcc', 'severity':severity.MEDIUM, 'description':'Invalid option for C file', 'patterns':[r".*: warning: command line option "".+"" is valid for C\+\+\/ObjC\+\+ but not for C"] }, Loading Loading @@ -1577,14 +1592,11 @@ warnpatterns = [ 'patterns':[r".*: warning: .+"] }, ] for w in warnpatterns: w['members'] = [] if 'option' not in w: w['option'] = '' # A list of [project_name, file_path_pattern]. # project_name should not contain comma, to be used in CSV output. projectlist = [ project_list = [ # pylint:disable=bad-whitespace,g-inconsistent-quotes,line-too-long ['art', r"(^|.*/)art/.*: warning:"], ['bionic', r"(^|.*/)bionic/.*: warning:"], ['bootable', r"(^|.*/)bootable/.*: warning:"], Loading Loading @@ -1619,24 +1631,37 @@ projectlist = [ ['other', r".*: warning:"], ] projectpatterns = [] for p in projectlist: projectpatterns.append({'description':p[0], 'members':[], 'pattern':re.compile(p[1])}) project_patterns = [] project_names = [] projectnames = [p[0] for p in projectlist] def initialize_arrays(): """Complete global arrays before they are used.""" global project_names project_names = [p[0] for p in project_list] for p in project_list: project_patterns.append({'description': p[0], 'members': [], 'pattern': re.compile(p[1])}) # Each warning pattern has 3 dictionaries: # (1) 'projects' maps a project name to number of warnings in that project. # (2) 'projectanchor' maps a project name to its anchor number for HTML. # (3) 'projectwarning' maps a project name to a list of warning of that project. for w in warnpatterns: # (2) 'project_anchor' maps a project name to its anchor number for HTML. # (3) 'project_warnings' maps a project name to a list of warning messages. for w in warn_patterns: w['members'] = [] if 'option' not in w: w['option'] = '' w['projects'] = {} w['projectanchor'] = {} w['projectwarning'] = {} w['project_anchor'] = {} w['project_warnings'] = {} platformversion = 'unknown' targetproduct = 'unknown' targetvariant = 'unknown' initialize_arrays() platform_version = 'unknown' target_product = 'unknown' target_variant = 'unknown' ##### Data and functions to dump html file. ################################## Loading Loading @@ -1682,93 +1707,97 @@ html_script_style = """\ def output(text): print text, def htmlbig(param): def html_big(param): return '<font size="+2">' + param + '</font>' def dumphtmlprologue(title): def dump_html_prologue(title): output('<html>\n<head>\n') output('<title>' + title + '</title>\n') output(html_script_style) output('</head>\n<body>\n') output(htmlbig(title)) output(html_big(title)) output('<p>\n') def dumphtmlepilogue(): def dump_html_epilogue(): output('</body>\n</head>\n</html>\n') def tablerow(text): def table_row(text): global cur_row_class cur_row_class = 1 - cur_row_class # remove last '\n' t = text[:-1] if text[-1] == '\n' else text output('<tr><td class="c' + str(cur_row_class) + '">' + t + '</td></tr>\n') def sortwarnings(): for i in warnpatterns: def sort_warnings(): for i in warn_patterns: i['members'] = sorted(set(i['members'])) # dump a table of warnings per project and severity def dumpstatsbyproject(): projects = set(projectnames) severities = set(severity.kinds) def dump_stats_by_project(): """Dump a table of warnings per project and severity.""" # warnings[p][s] is number of warnings in project p of severity s. warnings = {p:{s:0 for s in severity.kinds} for p in projectnames} for i in warnpatterns: warnings = {p: {s: 0 for s in severity.kinds} for p in project_names} for i in warn_patterns: s = i['severity'] for p in i['projects']: warnings[p][s] += i['projects'][p] # totalbyproject[p] is number of warnings in project p. totalbyproject = {p:sum(warnings[p][s] for s in severity.kinds) for p in projectnames} # total_by_project[p] is number of warnings in project p. total_by_project = {p: sum(warnings[p][s] for s in severity.kinds) for p in project_names} # totalbyseverity[s] is number of warnings of severity s. totalbyseverity = {s:sum(warnings[p][s] for p in projectnames) # total_by_severity[s] is number of warnings of severity s. total_by_severity = {s: sum(warnings[p][s] for p in project_names) for s in severity.kinds} # emit table header output('<blockquote><table border=1>\n<tr><th>Project</th>\n') for s in severity.kinds: if totalbyseverity[s]: if total_by_severity[s]: output('<th width="8%"><span style="background-color:{}">{}</span></th>'. format(severity.color[s], severity.columnheader[s])) format(severity.color[s], severity.column_headers[s])) output('<th>TOTAL</th></tr>\n') # emit a row of warning counts per project, skip no-warning projects totalallprojects = 0 for p in projectnames: if totalbyproject[p]: total_all_projects = 0 for p in project_names: if total_by_project[p]: output('<tr><td align="left">{}</td>'.format(p)) for s in severity.kinds: if totalbyseverity[s]: if total_by_severity[s]: output('<td align="right">{}</td>'.format(warnings[p][s])) output('<td align="right">{}</td>'.format(totalbyproject[p])) totalallprojects += totalbyproject[p] output('<td align="right">{}</td>'.format(total_by_project[p])) total_all_projects += total_by_project[p] output('</tr>\n') # emit a row of warning counts per severity totalallseverities = 0 total_all_severities = 0 output('<tr><td align="right">TOTAL</td>') for s in severity.kinds: if totalbyseverity[s]: output('<td align="right">{}</td>'.format(totalbyseverity[s])) totalallseverities += totalbyseverity[s] output('<td align="right">{}</td></tr>\n'.format(totalallprojects)) if total_by_severity[s]: output('<td align="right">{}</td>'.format(total_by_severity[s])) total_all_severities += total_by_severity[s] output('<td align="right">{}</td></tr>\n'.format(total_all_projects)) # at the end of table, verify total counts output('</table></blockquote><br>\n') if totalallprojects != totalallseverities: output('<h3>ERROR: Sum of warnings by project ' + if total_all_projects != total_all_severities: output('<h3>ERROR: Sum of warnings by project ' '!= Sum of warnings by severity.</h3>\n') # dump some stats about total number of warnings and such def dumpstats(): def dump_stats(): """Dump some stats about total number of warnings and such.""" known = 0 skipped = 0 unknown = 0 sortwarnings() for i in warnpatterns: sort_warnings() for i in warn_patterns: if i['severity'] == severity.UNKNOWN: unknown += len(i['members']) elif i['severity'] == severity.SKIP: Loading @@ -1784,140 +1813,155 @@ def dumpstats(): output('(low count may indicate incremental build)') output('<br><br>\n') def emitbuttons(): output('<button class="button" onclick="expand_collapse(1);">' + 'Expand all warnings</button>\n' + '<button class="button" onclick="expand_collapse(0);">' + def emit_buttons(): output('<button class="button" onclick="expand_collapse(1);">' 'Expand all warnings</button>\n' '<button class="button" onclick="expand_collapse(0);">' 'Collapse all warnings</button><br>\n') # dump everything for a given severity def dumpseverity(sev): def dump_severity(sev): """Dump everything for a given severity.""" global anchor total = 0 for i in warnpatterns: for i in warn_patterns: if i['severity'] == sev: total = total + len(i['members']) output('\n<br><span style="background-color:' + severity.color[sev] + '"><b>' + severity.header[sev] + ': ' + str(total) + '</b></span>\n') total += len(i['members']) output('\n<br><span style="background-color:' + severity.color[sev] + '"><b>' + severity.header[sev] + ': ' + str(total) + '</b></span>\n') output('<blockquote>\n') for i in warnpatterns: if i['severity'] == sev and len(i['members']) > 0: for i in warn_patterns: if i['severity'] == sev and i['members']: anchor += 1 i['anchor'] = str(anchor) if args.byproject: dumpcategorybyproject(sev, i) dump_category_by_project(sev, i) else: dumpcategory(sev, i) dump_category(sev, i) output('</blockquote>\n') # emit all skipped project anchors for expand_collapse. def dumpskippedanchors(): def dump_skipped_anchors(): """emit all skipped project anchors for expand_collapse.""" output('<div style="display:none;">\n') # hide these fake elements for i in warnpatterns: if i['severity'] == severity.SKIP and len(i['members']) > 0: projects = i['projectwarning'].keys() for i in warn_patterns: if i['severity'] == severity.SKIP and i['members']: projects = i['project_warnings'].keys() for p in projects: output('<div id="' + i['projectanchor'][p] + '"></div>' + '<div id="' + i['projectanchor'][p] + '_mark"></div>\n') output('<div id="' + i['project_anchor'][p] + '"></div>' + '<div id="' + i['project_anchor'][p] + '_mark"></div>\n') output('</div>\n') def allpatterns(cat): def all_patterns(cat): pats = '' for i in cat['patterns']: pats += i pats += ' / ' return pats def descriptionfor(cat): if cat['description'] != '': def description_for(cat): if cat['description']: return cat['description'] return allpatterns(cat) return all_patterns(cat) # show which warnings no longer occur def dumpfixed(): def dump_fixed(): """Show which warnings no longer occur.""" global anchor anchor += 1 mark = str(anchor) + '_mark' output('\n<br><p style="background-color:lightblue"><b>' + '<button id="' + mark + '" ' + 'class="bt" onclick="expand(' + str(anchor) + ');">' + '⊕</button> Fixed warnings. ' + 'No more occurences. Please consider turning these into ' + 'errors if possible, before they are reintroduced in to the build' + output('\n<br><p style="background-color:lightblue"><b>' '<button id="' + mark + '" ' 'class="bt" onclick="expand(' + str(anchor) + ');">' '⊕</button> Fixed warnings. ' 'No more occurrences. Please consider turning these into ' 'errors if possible, before they are reintroduced in to the build' ':</b></p>\n') output('<blockquote>\n') fixed_patterns = [] for i in warnpatterns: if len(i['members']) == 0: for i in warn_patterns: if not i['members']: fixed_patterns.append(i['description'] + ' (' + allpatterns(i) + ')') all_patterns(i) + ')') if i['option']: fixed_patterns.append(' ' + i['option']) fixed_patterns.sort() output('<div id="' + str(anchor) + '" style="display:none;"><table>\n') for i in fixed_patterns: tablerow(i) table_row(i) output('</table></div>\n') output('</blockquote>\n') def warningwithurl(line): def warning_with_url(line): """Returns a warning message line with HTML link to given args.url.""" if not args.url: return line m = re.search(r'^([^ :]+):(\d+):(.+)', line, re.M|re.I) if not m: return line filepath = m.group(1) linenumber = m.group(2) file_path = m.group(1) line_number = m.group(2) warning = m.group(3) prefix = '<a href="' + args.url + '/' + file_path if args.separator: return '<a href="' + args.url + '/' + filepath + args.separator + linenumber + '">' + filepath + ':' + linenumber + '</a>:' + warning return (prefix + args.separator + line_number + '">' + file_path + ':' + line_number + '</a>:' + warning) else: return '<a href="' + args.url + '/' + filepath + '">' + filepath + '</a>:' + linenumber + ':' + warning return prefix + '">' + file_path + '</a>:' + line_number + ':' + warning def dumpgroup(sev, anchor, description, warnings): mark = anchor + '_mark' def dump_group(sev, anchor_str, description, warnings): """Dump warnings of given severity, anchor_str, and description.""" mark = anchor_str + '_mark' output('\n<table class="t1">\n') output('<tr bgcolor="' + severity.color[sev] + '">' + '<td><button class="bt" id="' + mark + '" onclick="expand(\'' + anchor + '\');">' + '" onclick="expand(\'' + anchor_str + '\');">' + '⊕</button> ' + description + '</td></tr>\n') output('</table>\n') output('<div id="' + anchor + '" style="display:none;">') output('<div id="' + anchor_str + '" style="display:none;">') output('<table class="t1">\n') for i in warnings: tablerow(warningwithurl(i)) table_row(warning_with_url(i)) output('</table></div>\n') # dump warnings in a category def dumpcategory(sev, cat): description = descriptionfor(cat) + ' (' + str(len(cat['members'])) + ')' dumpgroup(sev, cat['anchor'], description, cat['members']) # similar to dumpcategory but output one table per project. def dumpcategorybyproject(sev, cat): warning = descriptionfor(cat) projects = cat['projectwarning'].keys() def dump_category(sev, cat): """Dump warnings in a category.""" description = description_for(cat) + ' (' + str(len(cat['members'])) + ')' dump_group(sev, cat['anchor'], description, cat['members']) def dump_category_by_project(sev, cat): """Similar to dump_category but output one table per project.""" warning = description_for(cat) projects = cat['project_warnings'].keys() projects.sort() for p in projects: anchor = cat['projectanchor'][p] projectwarnings = cat['projectwarning'][p] description = '{}, in {} ({})'.format(warning, p, len(projectwarnings)) dumpgroup(sev, anchor, description, projectwarnings) anchor_str = cat['project_anchor'][p] project_warnings = cat['project_warnings'][p] description = '{}, in {} ({})'.format(warning, p, len(project_warnings)) dump_group(sev, anchor_str, description, project_warnings) def findproject(line): for p in projectpatterns: def find_project(line): for p in project_patterns: if p['pattern'].match(line): return p['description'] return '???' def classifywarning(line): def classify_warning(line): global anchor for i in warnpatterns: for cpat in i['compiledpatterns']: for i in warn_patterns: for cpat in i['compiled_patterns']: if cpat.match(line): i['members'].append(line) pname = findproject(line) pname = find_project(line) # Count warnings by project. if pname in i['projects']: i['projects'][pname] += 1 Loading @@ -1925,13 +1969,13 @@ def classifywarning(line): i['projects'][pname] = 1 # Collect warnings by project. if args.byproject: if pname in i['projectwarning']: i['projectwarning'][pname].append(line) if pname in i['project_warnings']: i['project_warnings'][pname].append(line) else: i['projectwarning'][pname] = [line] if pname not in i['projectanchor']: i['project_warnings'][pname] = [line] if pname not in i['project_anchor']: anchor += 1 i['projectanchor'][pname] = str(anchor) i['project_anchor'][pname] = str(anchor) return else: # If we end up here, there was a problem parsing the log Loading @@ -1939,104 +1983,113 @@ def classifywarning(line): # 2 or more concurrent compiles pass # precompiling every pattern speeds up parsing by about 30x def compilepatterns(): for i in warnpatterns: i['compiledpatterns'] = [] def compile_patterns(): """Precompiling every pattern speeds up parsing by about 30x.""" for i in warn_patterns: i['compiled_patterns'] = [] for pat in i['patterns']: i['compiledpatterns'].append(re.compile(pat)) i['compiled_patterns'].append(re.compile(pat)) def parseinputfile(): global platformversion global targetproduct global targetvariant def parse_input_file(): """Parse input file, match warning lines.""" global platform_version global target_product global target_variant infile = open(args.buildlog, 'r') linecounter = 0 line_counter = 0 warningpattern = re.compile('.* warning:.*') compilepatterns() warning_pattern = re.compile('.* warning:.*') compile_patterns() # read the log file and classify all the warnings warninglines = set() warning_lines = set() for line in infile: # replace fancy quotes with plain ol' quotes line = line.replace("‘", "'"); line = line.replace("’", "'"); if warningpattern.match(line): if line not in warninglines: classifywarning(line) warninglines.add(line) line = line.replace('‘', "'") line = line.replace('’', "'") if warning_pattern.match(line): if line not in warning_lines: classify_warning(line) warning_lines.add(line) else: # save a little bit of time by only doing this for the first few lines if linecounter < 50: linecounter +=1 if line_counter < 50: line_counter += 1 m = re.search('(?<=^PLATFORM_VERSION=).*', line) if m != None: platformversion = m.group(0) if m is not None: platform_version = m.group(0) m = re.search('(?<=^TARGET_PRODUCT=).*', line) if m != None: targetproduct = m.group(0) if m is not None: target_product = m.group(0) m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line) if m != None: targetvariant = m.group(0) # dump the html output to stdout def dumphtml(): dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant) dumpstats() dumpstatsbyproject() emitbuttons() # sort table based on number of members once dumpstats has deduplicated the if m is not None: target_variant = m.group(0) def dump_html(): """Dump the html output to stdout.""" dump_html_prologue('Warnings for ' + platform_version + ' - ' + target_product + ' - ' + target_variant) dump_stats() dump_stats_by_project() emit_buttons() # sort table based on number of members once dump_stats has de-duplicated the # members. warnpatterns.sort(reverse=True, key=lambda i: len(i['members'])) # Dump warnings by severity. If severity.SKIP warnings are not dumpped, # the project anchors should be dumped through dumpskippedanchors. warn_patterns.sort(reverse=True, key=lambda i: len(i['members'])) # Dump warnings by severity. If severity.SKIP warnings are not dumped, # the project anchors should be dumped through dump_skipped_anchors. for s in severity.kinds: dumpseverity(s) dumpfixed() dumphtmlepilogue() dump_severity(s) dump_fixed() dump_html_epilogue() ##### Functions to count warnings and dump csv file. ######################### def descriptionforcsv(cat): if cat['description'] == '': def description_for_csv(cat): if not cat['description']: return '?' return cat['description'] def stringforcsv(s): def string_for_csv(s): if ',' in s: return '"{}"'.format(s) return s def countseverity(sev, kind): sum = 0 for i in warnpatterns: if i['severity'] == sev and len(i['members']) > 0: def count_severity(sev, kind): """Count warnings of given severity.""" total = 0 for i in warn_patterns: if i['severity'] == sev and i['members']: n = len(i['members']) sum += n warning = stringforcsv(kind + ': ' + descriptionforcsv(i)) total += n warning = string_for_csv(kind + ': ' + description_for_csv(i)) print '{},,{}'.format(n, warning) # print number of warnings for each project, ordered by project name. projects = i['projects'].keys() projects.sort() for p in projects: print '{},{},{}'.format(i['projects'][p], p, warning) print '{},,{}'.format(sum, kind + ' warnings') return sum print '{},,{}'.format(total, kind + ' warnings') return total # dump number of warnings in csv format to stdout def dumpcsv(): sortwarnings() def dump_csv(): """Dump number of warnings in csv format to stdout.""" sort_warnings() total = 0 for s in severity.kinds: total += countseverity(s, severity.columnheader[s]) total += count_severity(s, severity.column_headers[s]) print '{},,{}'.format(total, 'All warnings') parseinputfile() parse_input_file() if args.gencsv: dumpcsv() dump_csv() else: dumphtml() dump_html()