blob: 1b176766fc9441f7d21b9ca8a00ecd77f52b452e [file] [log] [blame]
Michael Schaffnera42582d2021-11-03 13:43:11 -07001#!/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
5r"""Parses cdc report and dump filtered messages in hjson format.
6"""
7import argparse
8import logging as log
9import re
10import sys
11import os
12import hjson
13
14from pathlib import Path
15from LintParser import LintParser
16
17
18def 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 Kim2b989a42022-07-15 13:34:28 -070040 r'^Summary of Policy: NEW((?:.|\n|\r\n)*)Rule Details of Policy: NEW',
Michael Schaffnera42582d2021-11-03 13:43:11 -070041 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 Kim2b989a42022-07-15 13:34:28 -070046 if re.match(r'^POLICY\s+NEW', line):
47 total = re.findall(r'^POLICY\s+NEW\s+([0-9]+)', line)
Michael Schaffnera42582d2021-11-03 13:43:11 -070048 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.
92class 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.
136def 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 Schaffnere97ff3f2021-12-06 17:00:33 -0800185 ("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 Schaffnera42582d2021-11-03 13:43:11 -0700190 # 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 Schaffnere97ff3f2021-12-06 17:00:33 -0800199 ("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 Schaffnera42582d2021-11-03 13:43:11 -0700210 ]
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 Kim2b989a42022-07-15 13:34:28 -0700220 args.repdir.joinpath('REPORT/vcdc.new.rpt'))
Michael Schaffnera42582d2021-11-03 13:43:11 -0700221
222 # Patterns for vcdc.rpt
223 parser_args.update({
Eunchan Kim2b989a42022-07-15 13:34:28 -0700224 args.repdir.joinpath('REPORT/vcdc.new.rpt'): cdc_rule_patterns
Michael Schaffnera42582d2021-11-03 13:43:11 -0700225 })
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
246if __name__ == "__main__":
247 main()