| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| r''' | |
| Class describing lint configuration object | |
| ''' | |
| import logging as log | |
| from pathlib import Path | |
| from tabulate import tabulate | |
| from OneShotCfg import OneShotCfg | |
| from utils import subst_wildcards, check_bool | |
| from MsgBuckets import MsgBuckets | |
| class LintCfg(OneShotCfg): | |
| '''Derivative class for linting purposes.''' | |
| flow = 'lint' | |
| def __init__(self, flow_cfg_file, hjson_data, args, mk_config): | |
| # TODO: check whether this can be replaced with the subflow concept. | |
| # This determines whether the flow is for a style lint run. | |
| # Format: bool | |
| self.is_style_lint = '' | |
| # Determines which message severities to print into report summaries | |
| # Format: [str, ...] | |
| self.report_severities = [] | |
| # Determines which message severities lead to a pass/fail | |
| # Format: [str, ...] | |
| self.fail_severities = [] | |
| # Message bucket format configuration | |
| # Format: [{category: str, severity: str, label: str}, ...] | |
| self.message_buckets = [] | |
| super().__init__(flow_cfg_file, hjson_data, args, mk_config) | |
| if self.is_style_lint == '': | |
| self.is_style_lint = False | |
| else: | |
| self.is_style_lint = check_bool(self.is_style_lint) | |
| # Set the title for lint results. | |
| if self.is_style_lint: | |
| self.results_title = f'{self.name.upper()} Style Lint Results' | |
| else: | |
| self.results_title = f'{self.name.upper()} Lint Results' | |
| def gen_results_summary(self): | |
| ''' | |
| Gathers the aggregated results from all sub configs | |
| ''' | |
| # Generate results table for runs. | |
| log.info('Create summary of lint results') | |
| results_str = f'## {self.results_title} (Summary)\n\n' | |
| results_str += f'### {self.timestamp_long}\n' | |
| if self.revision: | |
| results_str += f'### {self.revision}\n' | |
| results_str += f'### Branch: {self.branch}\n' | |
| results_str += '\n' | |
| # Aggregate with all summaries | |
| self.totals = MsgBuckets(self.message_buckets) | |
| for cfg in self.cfgs: | |
| self.totals += cfg.result_summary | |
| # Construct Header | |
| labels = self.totals.get_labels(self.report_severities) | |
| header = ['Name'] + labels | |
| colalign = ('center', ) * len(header) | |
| table = [header] | |
| keys = self.totals.get_keys(self.report_severities) | |
| for cfg in self.cfgs: | |
| name_with_link = cfg._get_results_page_link( | |
| self.results_dir) | |
| row = [name_with_link] | |
| row += cfg.result_summary.get_counts_md(keys) | |
| table.append(row) | |
| if len(table) > 1: | |
| self.results_summary_md = results_str + tabulate( | |
| table, headers='firstrow', tablefmt='pipe', | |
| colalign=colalign) + '\n' | |
| else: | |
| self.results_summary_md = f'{results_str}\nNo results to display.\n' | |
| print(self.results_summary_md) | |
| # Return only the tables | |
| return self.results_summary_md | |
| # TODO(#9079): This way of parsing out messages into an intermediate | |
| # results.hjson file will be replaced by a native parser mechanism. | |
| def _gen_results(self, results): | |
| # ''' | |
| # The function is called after the regression has completed. It looks | |
| # for a results.hjson file with aggregated results from the lint run. | |
| # The hjson needs to have the following format: | |
| # | |
| # { | |
| # bucket_key: [str], | |
| # // other buckets according to message_buckets configuration | |
| # } | |
| # | |
| # Each bucket key points to a list of signatures (strings). | |
| # The bucket categories and severities are defined in the | |
| # message_buckets class variable, and can be set via Hjson Dvsim | |
| # config files. | |
| # | |
| # Note that if this is a primary config, the results will | |
| # be generated using the _gen_results_summary function | |
| # ''' | |
| # Generate results table for runs. | |
| results_str = f'## {self.results_title}\n\n' | |
| results_str += f'### {self.timestamp_long}\n' | |
| if self.revision: | |
| results_str += f'### {self.revision}\n' | |
| results_str += f'### Branch: {self.branch}\n' | |
| results_str += f'### Tool: {self.tool.upper()}\n\n' | |
| # Load all result files from all build modes and convert them to | |
| # message buckets. | |
| self.result = [] | |
| self.result_summary = MsgBuckets(self.message_buckets) | |
| for mode in self.build_modes: | |
| result_path = Path( | |
| subst_wildcards(self.build_dir, {'build_mode': mode.name}) + | |
| '/results.hjson') | |
| log.info('[results:hjson]: [%s]: [%s]', self.name, result_path) | |
| # TODO(#9079): replace this with native log parser | |
| msgs = MsgBuckets(self.message_buckets) | |
| msgs.load_hjson(result_path) | |
| self.result.append(msgs) | |
| # Aggregate with summary results | |
| self.result_summary += msgs | |
| # Construct Header | |
| labels = self.result_summary.get_labels(self.report_severities) | |
| header = ['Build Mode'] + labels | |
| colalign = ('center', ) * len(header) | |
| table = [header] | |
| fail_msgs = '' | |
| self.errors_seen = 0 | |
| keys = self.result_summary.get_keys(self.report_severities) | |
| for mode, res in zip(self.build_modes, self.result): | |
| row = [mode.name] + res.get_counts_md(keys) | |
| table.append(row) | |
| self.errors_seen += res.has_signatures(self.fail_severities) | |
| fail_msgs += f"\n### Messages for Build Mode `'{mode.name}'`\n" | |
| fail_msgs += res.print_signatures_md(self.report_severities, | |
| self.max_msg_count) | |
| if len(table) > 1: | |
| self.results_md = results_str + tabulate( | |
| table, headers='firstrow', tablefmt='pipe', | |
| colalign=colalign) + '\n' | |
| # Th published report will default to self.results_md if they are | |
| # empty. In case it needs need to be sanitized, override it and do | |
| # not append detailed messages. | |
| if self.sanitize_publish_results: | |
| self.publish_results_md = self.results_md | |
| # Locally generated result always contains all details. | |
| self.results_md += fail_msgs | |
| else: | |
| self.results_md = f'{results_str}\nNo results to display.\n' | |
| self.publish_results_md = self.results_md | |
| return self.results_md |