| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| r"""Parses lint report and dump filtered messages in hjson format. |
| """ |
| import argparse |
| import logging as log |
| import re |
| import sys |
| from pathlib import Path |
| |
| import hjson |
| |
| |
| def extract_messages(full_file, patterns, results): |
| """ |
| This extracts messages from the sting buffer full_file. |
| The argument patterns needs to be a list of tuples with |
| (<error_severity>, <pattern_to_match_for>). |
| """ |
| for severity, pattern in patterns: |
| results[severity] += re.findall(pattern, full_file, flags=re.MULTILINE) |
| |
| return results |
| |
| |
| def get_results(resdir): |
| """ |
| Parse report and corresponding logfiles and extract error, warning |
| and info messages for each IP present in the result folder |
| """ |
| results = { |
| "tool": "ascentlint", |
| "fusesoc-error": [], |
| "errors": [], |
| "warnings": [], |
| "lint_errors": [], |
| "lint_warnings": [], |
| "lint_infos": [] |
| } |
| |
| log_files = [ |
| 'ascentlint.log', 'lint-ascentlint/ascentlint.log', |
| 'lint-ascentlint/ascentlint.rpt' |
| ] |
| log_file_contents = [] |
| # Open all log files |
| for name in log_files: |
| try: |
| with Path(resdir).joinpath(name).open() as f: |
| log_file_contents.append(f.read()) |
| except IOError as err: |
| results["errors"] += ["IOError: %s" % err] |
| |
| # Define warning/error patterns for each logfile |
| err_warn_patterns = [] |
| |
| # Patterns for lint.log |
| err_warn_patterns.append([ |
| # If lint warnings have been found, the lint tool will exit |
| # with a nonzero status code and fusesoc will always spit out |
| # an error like |
| # |
| # ERROR: Failed to build ip:core:name:0.1 : 'make' exited with an error code |
| # |
| # If we found any other warnings or errors, there's no point in |
| # listing this too. BUT we want to make sure we *do* see this |
| # error if there are no other errors or warnings, since that |
| # shows something has come unstuck. (Probably the lint tool |
| # spat out a warning that we don't understand) |
| ("fusesoc-error", |
| r"^ERROR: Failed to build .* : 'make' exited with an error code") |
| ]) |
| |
| # Patterns for ascentlint.log |
| err_warn_patterns.append([ |
| ("errors", r"^FlexNet Licensing error.*"), |
| ("errors", r"^Error: .*"), |
| ("errors", r"^ERROR.*"), |
| ("errors", r"^ ERR .*"), |
| ("warnings", r"^Warning: .*"), |
| # TODO: struct assignment labels within concatenation |
| # not supported. check with newer ascentlint version. |
| ("warnings", r"^ (?!WARN \[#39024\])WARN .*") |
| ]) |
| |
| # Patterns for ascentlint.rpt |
| err_warn_patterns.append([("lint_errors", r"^E .*"), |
| ("lint_warnings", r"^W .*"), |
| ("lint_infos", r"^I .*")]) |
| |
| # Go parse the logs |
| for patterns, logs in zip(err_warn_patterns, log_file_contents): |
| extract_messages(logs, patterns, results) |
| |
| # If there are no errors or warnings, add the "fusesoc-error" field to |
| # "errors" (which will be reported as tooling errors). Remove the |
| # "fusesoc-error" field either way. |
| if not (results['errors'] or results['warnings']): |
| results['errors'] = results['fusesoc-error'] |
| del results['fusesoc-error'] |
| |
| return results |
| |
| |
| def main(): |
| |
| parser = argparse.ArgumentParser( |
| description="""This script parses AscentLint log and report files from |
| a lint run, filters the messages and creates an aggregated result |
| .hjson file with the following fields: |
| |
| {"tool": "ascentlint", |
| "errors" : [], |
| "warnings" : [], |
| "lint_errors" : [], |
| "lint_warnings" : [], |
| "lint_infos" : []} |
| |
| The fields 'errors' and 'warnings' contain file IO messages or |
| messages output by the tool itself, whereas the fields prefixed with |
| 'lint_' contain lint-related messages. |
| |
| The script returns nonzero status if any warnings or errors are present. |
| """) |
| parser.add_argument('--repdir', |
| type=str, |
| default="./", |
| help="""The script searches the 'ascentlint.log' and |
| 'ascentlint.rpt' files in this directory. |
| Defaults to './'""") |
| |
| parser.add_argument('--outdir', |
| type=str, |
| default="./", |
| help="""Output directory for the 'results.hjson' file. |
| Defaults to './'""") |
| |
| args = parser.parse_args() |
| results = get_results(args.repdir) |
| |
| with Path(args.outdir).joinpath("results.hjson").open("w") as results_file: |
| hjson.dump(results, |
| results_file, |
| ensure_ascii=False, |
| for_json=True, |
| use_decimal=True) |
| |
| # return nonzero status if any warnings or errors are present |
| # lint infos do not count as failures |
| n_errors = len(results["errors"]) + len(results["lint_errors"]) |
| n_warnings = len(results["warnings"]) + len(results["lint_warnings"]) |
| if n_errors > 0 or n_warnings > 0: |
| log.info("Found %d lint errors and %d lint warnings", n_errors, |
| n_warnings) |
| sys.exit(1) |
| |
| log.info("Lint logfile parsed succesfully") |
| sys.exit(0) |
| |
| |
| if __name__ == "__main__": |
| main() |