Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright lowRISC contributors. |
| 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | r"""Parses cdc report and dump filtered messages in hjson format. |
| 6 | """ |
| 7 | import argparse |
| 8 | import logging as log |
| 9 | import re |
| 10 | import sys |
| 11 | import os |
| 12 | import hjson |
| 13 | |
| 14 | from pathlib import Path |
| 15 | from LintParser import LintParser |
| 16 | |
| 17 | |
| 18 | def extract_rule_patterns(file_path: Path): |
| 19 | ''' |
| 20 | This parses the CDC summary table to get the message totals, |
| 21 | rule names and corresponding severities. |
| 22 | ''' |
| 23 | |
| 24 | rule_patterns = [] |
| 25 | full_file = '' |
| 26 | try: |
| 27 | with Path(file_path).open() as f: |
| 28 | full_file = f.read() |
| 29 | except IOError: |
| 30 | # We will attempt read this file again in a second pass to parse out |
| 31 | # the details, this error will get caught and reported. |
| 32 | pass |
| 33 | |
| 34 | category = '' |
| 35 | severity = '' |
| 36 | known_rule_names = {} |
| 37 | total_msgs = 0 |
| 38 | # extract the summary table |
| 39 | m = re.findall( |
Eunchan Kim | 2b989a4 | 2022-07-15 13:34:28 -0700 | [diff] [blame] | 40 | r'^Summary of Policy: NEW((?:.|\n|\r\n)*)Rule Details of Policy: NEW', |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 41 | full_file, flags=re.MULTILINE) |
| 42 | if m: |
| 43 | # step through the table and identify rule names and their |
| 44 | # category and severity |
| 45 | for line in m[0].split('\n'): |
Eunchan Kim | 2b989a4 | 2022-07-15 13:34:28 -0700 | [diff] [blame] | 46 | if re.match(r'^POLICY\s+NEW', line): |
| 47 | total = re.findall(r'^POLICY\s+NEW\s+([0-9]+)', line) |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 48 | total_msgs = int(total[0]) |
| 49 | elif re.match(r'^ GROUP\s+SDC_ENV_LINT', line): |
| 50 | category = 'sdc' |
| 51 | elif re.match(r'^ GROUP\s+VCDC_SETUP_CHECKS', line): |
| 52 | category = 'setup' |
| 53 | elif re.match(r'^ GROUP\s+VCDC_ANALYSIS_CHECKS', line): |
| 54 | category = 'cdc' |
| 55 | elif re.match(r'^ GROUP\s+ERROR', line): |
| 56 | severity = 'error' |
| 57 | elif re.match(r'^ GROUP\s+WARNING', line): |
| 58 | severity = 'warning' |
| 59 | elif re.match(r'^ GROUP\s+INFO', line): |
| 60 | severity = 'info' |
| 61 | elif re.match(r'^ GROUP\s+REVIEW', line): |
| 62 | severity = 'review' |
| 63 | elif re.match(r'^ INSTANCE', line): |
| 64 | # we've found a new rule. convert it to a known rule pattern |
| 65 | # with the correct category and severity |
| 66 | rule = re.findall( |
| 67 | r'^ INSTANCE\s+([a-zA-Z0-9\_]+)\s+([0-9\_]+)', line) |
| 68 | name = rule[0][0] |
| 69 | count = int(rule[0][1]) |
| 70 | # a few rules produce messages with different severities but |
| 71 | # the same rule labels. for simplicity, we promote messages |
| 72 | # from lower severity buckets to the severity bucket where |
| 73 | # this rule name has first been encountered. since higher |
| 74 | # severity messages are listed first in this summary table, it |
| 75 | # is straightforward to check whether the rule name has |
| 76 | # already appeared in a higher severity bucket. |
| 77 | if name in known_rule_names: |
| 78 | msg_group = known_rule_names[name] |
| 79 | log.warning('Rule {} is reported in multiple severity ' |
| 80 | 'classes. All messages of this rule are ' |
| 81 | 'promoted to {}'.format(name, msg_group)) |
| 82 | |
| 83 | else: |
| 84 | msg_group = category + '_' + severity |
| 85 | known_rule_names.update({name: msg_group}) |
| 86 | rule_patterns.append((msg_group, r'^{}:.*'.format(name))) |
| 87 | |
| 88 | return rule_patterns |
| 89 | |
| 90 | |
| 91 | # Reuse the lint parser, but add more buckets. |
| 92 | class CdcParser(LintParser): |
| 93 | |
| 94 | def __init__(self) -> None: |
| 95 | self.buckets = { |
| 96 | 'flow_info': [], |
| 97 | 'flow_warning': [], |
| 98 | 'flow_error': [], |
| 99 | 'sdc_info': [], |
| 100 | 'sdc_review': [], |
| 101 | 'sdc_warning': [], |
| 102 | 'sdc_error': [], |
| 103 | 'setup_info': [], |
| 104 | 'setup_review': [], |
| 105 | 'setup_warning': [], |
| 106 | 'setup_error': [], |
| 107 | 'cdc_info': [], |
| 108 | 'cdc_review': [], |
| 109 | 'cdc_warning': [], |
| 110 | 'cdc_error': [], |
| 111 | # this bucket is temporary and will be removed at the end of the |
| 112 | # parsing pass. |
| 113 | 'fusesoc-error': [] |
| 114 | } |
| 115 | self.severities = { |
| 116 | 'flow_info': 'info', |
| 117 | 'flow_warning': 'warning', |
| 118 | 'flow_error': 'error', |
| 119 | 'sdc_info': 'info', |
| 120 | 'sdc_review': 'warning', |
| 121 | 'sdc_warning': 'warning', |
| 122 | 'sdc_error': 'error', |
| 123 | 'setup_info': 'info', |
| 124 | 'setup_review': 'warning', |
| 125 | 'setup_warning': 'warning', |
| 126 | 'setup_error': 'error', |
| 127 | 'cdc_info': 'info', |
| 128 | 'cdc_review': 'warning', |
| 129 | 'cdc_warning': 'warning', |
| 130 | 'cdc_error': 'error' |
| 131 | } |
| 132 | |
| 133 | |
| 134 | # TODO(#9079): this script will be removed long term once the |
| 135 | # parser has been merged with the Dvsim core code. |
| 136 | def main(): |
| 137 | parser = argparse.ArgumentParser( |
| 138 | description="""This script parses AscentLint log and report files from |
| 139 | a lint run, filters the messages and creates an aggregated result |
| 140 | .hjson file with lint messages and their severities. |
| 141 | |
| 142 | The script returns nonzero status if any warnings or errors are |
| 143 | present. |
| 144 | """) |
| 145 | parser.add_argument('--repdir', |
| 146 | type=lambda p: Path(p).resolve(), |
| 147 | default="./", |
| 148 | help="""The script searches the 'vcdc.log' and |
| 149 | 'vcdc.rpt' files in this directory. |
| 150 | Defaults to './'""") |
| 151 | |
| 152 | parser.add_argument('--outfile', |
| 153 | type=lambda p: Path(p).resolve(), |
| 154 | default="./results.hjson", |
| 155 | help="""Path to the results Hjson file. |
| 156 | Defaults to './results.hjson'""") |
| 157 | |
| 158 | args = parser.parse_args() |
| 159 | |
| 160 | # Define warning/error patterns for each logfile |
| 161 | parser_args = {} |
| 162 | |
| 163 | # Patterns for lint.log |
| 164 | parser_args.update({ |
| 165 | args.repdir.joinpath('build.log'): [ |
| 166 | # If lint warnings have been found, the lint tool will exit |
| 167 | # with a nonzero status code and fusesoc will always spit out |
| 168 | # an error like |
| 169 | # |
| 170 | # ERROR: Failed to build ip:core:name:0.1 : 'make' exited with an error code |
| 171 | # |
| 172 | # If we found any other warnings or errors, there's no point in |
| 173 | # listing this too. BUT we want to make sure we *do* see this |
| 174 | # error if there are no other errors or warnings, since that |
| 175 | # shows something has come unstuck. (Probably the lint tool |
| 176 | # spat out a warning that we don't understand) |
| 177 | ("fusesoc-error", |
| 178 | r"^ERROR: Failed to build .* : 'make' exited with an error code") |
| 179 | ] |
| 180 | }) |
| 181 | |
| 182 | # Patterns for vcdc.log |
| 183 | parser_args.update({ |
| 184 | args.repdir.joinpath('syn-icarus/vcdc.log'): [ |
Michael Schaffner | e97ff3f | 2021-12-06 17:00:33 -0800 | [diff] [blame] | 185 | ("flow_error", r"^FlexNet Licensing error.*"), |
| 186 | ("flow_error", r"^Error: .*"), |
| 187 | ("flow_error", r"^ERROR.*"), |
| 188 | ("flow_error", r"^ ERR .*"), |
| 189 | ("flow_warning", r"^Warning: .*"), |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 190 | # We ignore several messages here: |
| 191 | # #25010: unused signals |
| 192 | # #25011: unused signals |
| 193 | # #25012: unused port |
| 194 | # #25013: unused signals |
| 195 | # #26038: unused or RTL constant |
| 196 | # #39035: parameter becomes local |
| 197 | # #39122: non-positive repeat |
| 198 | # #39491: parameter in package |
Michael Schaffner | e97ff3f | 2021-12-06 17:00:33 -0800 | [diff] [blame] | 199 | ("flow_warning", r"^ " |
| 200 | "(?!WARN \[#25010\])" |
| 201 | "(?!WARN \[#25011\])" |
| 202 | "(?!WARN \[#25012\])" |
| 203 | "(?!WARN \[#25013\])" |
| 204 | "(?!WARN \[#26038\])" |
| 205 | "(?!WARN \[#39035\])" |
| 206 | "(?!WARN \[#39122\])" |
| 207 | "(?!WARN \[#39491\])" |
| 208 | "WARN .*"), |
| 209 | ("flow_info", r"^ INFO .*") |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 210 | ] |
| 211 | }) |
| 212 | |
| 213 | # The CDC messages are a bit more involved to parse out, since we |
| 214 | # need to know the names and associated severities to do this. |
| 215 | # The tool prints out an overview table in the report, which we are |
| 216 | # going to parse first in order to get this information. |
| 217 | # This is then used to construct the regex patterns to look for |
| 218 | # in a second pass to get the actual CDC messages. |
| 219 | cdc_rule_patterns = extract_rule_patterns( |
Eunchan Kim | 2b989a4 | 2022-07-15 13:34:28 -0700 | [diff] [blame] | 220 | args.repdir.joinpath('REPORT/vcdc.new.rpt')) |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 221 | |
| 222 | # Patterns for vcdc.rpt |
| 223 | parser_args.update({ |
Eunchan Kim | 2b989a4 | 2022-07-15 13:34:28 -0700 | [diff] [blame] | 224 | args.repdir.joinpath('REPORT/vcdc.new.rpt'): cdc_rule_patterns |
Michael Schaffner | a42582d | 2021-11-03 13:43:11 -0700 | [diff] [blame] | 225 | }) |
| 226 | |
| 227 | # Parse logs |
| 228 | parser = CdcParser() |
| 229 | num_messages = parser.get_results(parser_args) |
| 230 | |
| 231 | # Write out results file |
| 232 | parser.write_results_as_hjson(args.outfile) |
| 233 | |
| 234 | # return nonzero status if any warnings or errors are present |
| 235 | # lint infos do not count as failures |
| 236 | if num_messages['error'] > 0 or num_messages['warning'] > 0: |
| 237 | log.info("Found %d lint errors and %d lint warnings", |
| 238 | num_messages['error'], |
| 239 | num_messages['warning']) |
| 240 | sys.exit(1) |
| 241 | |
| 242 | log.info("Lint logfile parsed succesfully") |
| 243 | sys.exit(0) |
| 244 | |
| 245 | |
| 246 | if __name__ == "__main__": |
| 247 | main() |