check-package 6.8 KB

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