/ tools / ci / check_build_warnings.py
check_build_warnings.py
  1  #!/usr/bin/env python
  2  # coding=utf-8
  3  #
  4  # CI script to check build logs for warnings.
  5  # Reads the list of builds, in the format produced by find_apps.py or build_apps.py, and finds warnings in the
  6  # log files for every build.
  7  # Exits with a non-zero exit code if any warning is found.
  8  
  9  import argparse
 10  import logging
 11  import os
 12  import re
 13  import sys
 14  
 15  try:
 16      from find_build_apps import BuildItem, setup_logging
 17  except ImportError:
 18      sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
 19      from find_build_apps import BuildItem, setup_logging
 20  
 21  WARNING_REGEX = re.compile(r"(?:error|warning)[^\w]", re.MULTILINE | re.IGNORECASE)
 22  
 23  IGNORE_WARNS = [
 24      re.compile(r_str) for r_str in [
 25          r"library/error\.o",
 26          r".*error.*\.c\.obj",
 27          r"-Werror",
 28          r"error\.d",
 29          r"reassigning to symbol",
 30          r"changes choice state",
 31          r"crosstool_version_check\.cmake",
 32          r"CryptographyDeprecationWarning",
 33      ]
 34  ]
 35  
 36  
 37  def line_has_warnings(line):  # type: (str) -> bool
 38      if not WARNING_REGEX.search(line):
 39          return False
 40  
 41      has_warnings = True
 42      for ignored in IGNORE_WARNS:
 43          if re.search(ignored, line):
 44              has_warnings = False
 45              break
 46  
 47      return has_warnings
 48  
 49  
 50  def main():
 51      parser = argparse.ArgumentParser(description="ESP-IDF app builder")
 52      parser.add_argument(
 53          "-v",
 54          "--verbose",
 55          action="count",
 56          help="Increase the logging level of the script. Can be specified multiple times.",
 57      )
 58      parser.add_argument(
 59          "--log-file",
 60          type=argparse.FileType("w"),
 61          help="Write the script log to the specified file, instead of stderr",
 62      )
 63      parser.add_argument(
 64          "build_list",
 65          type=argparse.FileType("r"),
 66          nargs="?",
 67          default=sys.stdin,
 68          help="Name of the file to read the list of builds from. If not specified, read from stdin.",
 69      )
 70      args = parser.parse_args()
 71      setup_logging(args)
 72  
 73      build_items = [BuildItem.from_json(line) for line in args.build_list]
 74      if not build_items:
 75          logging.warning("Empty build list")
 76          SystemExit(0)
 77  
 78      found_warnings = 0
 79      for build_item in build_items:
 80          if not build_item.build_log_path:
 81              logging.debug("No log file for {}".format(build_item.work_dir))
 82              continue
 83          with open(build_item.build_log_path, "r") as log_file:
 84              for line_no, line in enumerate(log_file):
 85                  if line_has_warnings(line):
 86                      logging.error("Issue in app {}, config {}:".format(build_item.app_dir, build_item.config_name))
 87                      logging.error(line.rstrip("\n"))
 88                      logging.error("See {}:{} for details".format(os.path.basename(build_item.build_log_path),
 89                                                                   line_no + 1))
 90                      found_warnings += 1
 91                      break
 92  
 93      if found_warnings:
 94          logging.error("Checked {} builds, found {} warnings".format(len(build_items), found_warnings))
 95          raise SystemExit(1)
 96  
 97      logging.info("No warnings found")
 98  
 99  
100  if __name__ == "__main__":
101      main()