check-package 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #!/usr/bin/env python3
  2. # See utils/checkpackagelib/readme.txt before editing this file.
  3. import argparse
  4. import inspect
  5. import os
  6. import re
  7. import six
  8. import sys
  9. import checkpackagelib.base
  10. import checkpackagelib.lib_config
  11. import checkpackagelib.lib_hash
  12. import checkpackagelib.lib_mk
  13. import checkpackagelib.lib_patch
  14. import checkpackagelib.lib_sysv
  15. VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES = 3
  16. flags = None # Command line arguments.
  17. def get_ignored_parsers_per_file(intree_only, ignore_filename):
  18. ignored = dict()
  19. entry_base_dir = ''
  20. if not ignore_filename:
  21. return ignored
  22. filename = os.path.abspath(ignore_filename)
  23. entry_base_dir = os.path.join(os.path.dirname(filename))
  24. with open(filename, "r") as f:
  25. for line in f.readlines():
  26. filename, warnings_str = line.split(' ', 1)
  27. warnings = warnings_str.split()
  28. ignored[os.path.join(entry_base_dir, filename)] = warnings
  29. return ignored
  30. def parse_args():
  31. parser = argparse.ArgumentParser()
  32. # Do not use argparse.FileType("r") here because only files with known
  33. # format will be open based on the filename.
  34. parser.add_argument("files", metavar="F", type=str, nargs="*",
  35. help="list of files")
  36. parser.add_argument("--br2-external", "-b", dest='intree_only', action="store_false",
  37. help="do not apply the pathname filters used for intree files")
  38. parser.add_argument("--ignore-list", dest='ignore_filename', action="store",
  39. help='override the default list of ignored warnings')
  40. parser.add_argument("--manual-url", action="store",
  41. default="http://nightly.buildroot.org/",
  42. help="default: %(default)s")
  43. parser.add_argument("--verbose", "-v", action="count", default=0)
  44. parser.add_argument("--quiet", "-q", action="count", default=0)
  45. # Now the debug options in the order they are processed.
  46. parser.add_argument("--include-only", dest="include_list", action="append",
  47. help="run only the specified functions (debug)")
  48. parser.add_argument("--exclude", dest="exclude_list", action="append",
  49. help="do not run the specified functions (debug)")
  50. parser.add_argument("--dry-run", action="store_true", help="print the "
  51. "functions that would be called for each file (debug)")
  52. flags = parser.parse_args()
  53. flags.ignore_list = get_ignored_parsers_per_file(flags.intree_only, flags.ignore_filename)
  54. return flags
  55. CONFIG_IN_FILENAME = re.compile(r"Config\.\S*$")
  56. DO_CHECK_INTREE = re.compile(r"|".join([
  57. r"Config.in",
  58. r"arch/",
  59. r"boot/",
  60. r"fs/",
  61. r"linux/",
  62. r"package/",
  63. r"system/",
  64. r"toolchain/",
  65. ]))
  66. DO_NOT_CHECK_INTREE = re.compile(r"|".join([
  67. r"boot/barebox/barebox\.mk$",
  68. r"fs/common\.mk$",
  69. r"package/doc-asciidoc\.mk$",
  70. r"package/pkg-\S*\.mk$",
  71. r"toolchain/helpers\.mk$",
  72. r"toolchain/toolchain-external/pkg-toolchain-external\.mk$",
  73. ]))
  74. SYSV_INIT_SCRIPT_FILENAME = re.compile(r"/S\d\d[^/]+$")
  75. def get_lib_from_filename(fname):
  76. if flags.intree_only:
  77. if DO_CHECK_INTREE.match(fname) is None:
  78. return None
  79. if DO_NOT_CHECK_INTREE.match(fname):
  80. return None
  81. else:
  82. if os.path.basename(fname) == "external.mk" and \
  83. os.path.exists(fname[:-2] + "desc"):
  84. return None
  85. if CONFIG_IN_FILENAME.search(fname):
  86. return checkpackagelib.lib_config
  87. if fname.endswith(".hash"):
  88. return checkpackagelib.lib_hash
  89. if fname.endswith(".mk"):
  90. return checkpackagelib.lib_mk
  91. if fname.endswith(".patch"):
  92. return checkpackagelib.lib_patch
  93. if SYSV_INIT_SCRIPT_FILENAME.search(fname):
  94. return checkpackagelib.lib_sysv
  95. return None
  96. def common_inspect_rules(m):
  97. # do not call the base class
  98. if m.__name__.startswith("_"):
  99. return False
  100. if flags.include_list and m.__name__ not in flags.include_list:
  101. return False
  102. if flags.exclude_list and m.__name__ in flags.exclude_list:
  103. return False
  104. return True
  105. def is_a_check_function(m):
  106. if not inspect.isclass(m):
  107. return False
  108. if not issubclass(m, checkpackagelib.base._CheckFunction):
  109. return False
  110. return common_inspect_rules(m)
  111. def is_external_tool(m):
  112. if not inspect.isclass(m):
  113. return False
  114. if not issubclass(m, checkpackagelib.base._Tool):
  115. return False
  116. return common_inspect_rules(m)
  117. def print_warnings(warnings, xfail):
  118. # Avoid the need to use 'return []' at the end of every check function.
  119. if warnings is None:
  120. return 0, 0 # No warning generated.
  121. if xfail:
  122. return 0, 1 # Warning not generated, fail expected for this file.
  123. for level, message in enumerate(warnings):
  124. if flags.verbose >= level:
  125. print(message.replace("\t", "< tab >").rstrip())
  126. return 1, 1 # One more warning to count.
  127. def check_file_using_lib(fname):
  128. # Count number of warnings generated and lines processed.
  129. nwarnings = 0
  130. nlines = 0
  131. xfail = flags.ignore_list.get(os.path.abspath(fname), [])
  132. failed = set()
  133. lib = get_lib_from_filename(fname)
  134. if not lib:
  135. if flags.verbose >= VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES:
  136. print("{}: ignored".format(fname))
  137. return nwarnings, nlines
  138. internal_functions = inspect.getmembers(lib, is_a_check_function)
  139. external_tools = inspect.getmembers(lib, is_external_tool)
  140. all_checks = internal_functions + external_tools
  141. if flags.dry_run:
  142. functions_to_run = [c[0] for c in all_checks]
  143. print("{}: would run: {}".format(fname, functions_to_run))
  144. return nwarnings, nlines
  145. objects = [[c[0], c[1](fname, flags.manual_url)] for c in internal_functions]
  146. for name, cf in objects:
  147. warn, fail = print_warnings(cf.before(), name in xfail)
  148. if fail > 0:
  149. failed.add(name)
  150. nwarnings += warn
  151. if six.PY3:
  152. f = open(fname, "r", errors="surrogateescape")
  153. else:
  154. f = open(fname, "r")
  155. lastline = ""
  156. for lineno, text in enumerate(f.readlines()):
  157. nlines += 1
  158. for name, cf in objects:
  159. if cf.disable.search(lastline):
  160. continue
  161. warn, fail = print_warnings(cf.check_line(lineno + 1, text), name in xfail)
  162. if fail > 0:
  163. failed.add(name)
  164. nwarnings += warn
  165. lastline = text
  166. f.close()
  167. for name, cf in objects:
  168. warn, fail = print_warnings(cf.after(), name in xfail)
  169. if fail > 0:
  170. failed.add(name)
  171. nwarnings += warn
  172. tools = [[c[0], c[1](fname)] for c in external_tools]
  173. for name, tool in tools:
  174. warn, fail = print_warnings(tool.run(), name in xfail)
  175. if fail > 0:
  176. failed.add(name)
  177. nwarnings += warn
  178. for should_fail in xfail:
  179. if should_fail not in failed:
  180. print("{}:0: {} was expected to fail, did you fixed the file and forgot to update {}?"
  181. .format(fname, should_fail, flags.ignore_filename))
  182. nwarnings += 1
  183. return nwarnings, nlines
  184. def __main__():
  185. global flags
  186. flags = parse_args()
  187. if flags.intree_only:
  188. # change all paths received to be relative to the base dir
  189. base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  190. files_to_check = [os.path.relpath(os.path.abspath(f), base_dir) for f in flags.files]
  191. # move current dir so the script find the files
  192. os.chdir(base_dir)
  193. else:
  194. files_to_check = flags.files
  195. if len(files_to_check) == 0:
  196. print("No files to check style")
  197. sys.exit(1)
  198. # Accumulate number of warnings generated and lines processed.
  199. total_warnings = 0
  200. total_lines = 0
  201. for fname in files_to_check:
  202. nwarnings, nlines = check_file_using_lib(fname)
  203. total_warnings += nwarnings
  204. total_lines += nlines
  205. # The warning messages are printed to stdout and can be post-processed
  206. # (e.g. counted by 'wc'), so for stats use stderr. Wait all warnings are
  207. # printed, for the case there are many of them, before printing stats.
  208. sys.stdout.flush()
  209. if not flags.quiet:
  210. print("{} lines processed".format(total_lines), file=sys.stderr)
  211. print("{} warnings generated".format(total_warnings), file=sys.stderr)
  212. if total_warnings > 0:
  213. sys.exit(1)
  214. __main__()