lib_config.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # See utils/checkpackagelib/readme.txt before editing this file.
  2. # Kconfig generates errors if someone introduces a typo like "boool" instead of
  3. # "bool", so below check functions don't need to check for things already
  4. # checked by running "make menuconfig".
  5. import re
  6. from checkpackagelib.base import _CheckFunction
  7. from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
  8. from checkpackagelib.lib import EmptyLastLine # noqa: F401
  9. from checkpackagelib.lib import NewlineAtEof # noqa: F401
  10. from checkpackagelib.lib import TrailingSpace # noqa: F401
  11. from checkpackagelib.tool import NotExecutable # noqa: F401
  12. def _empty_or_comment(text):
  13. line = text.strip()
  14. # ignore empty lines and comment lines indented or not
  15. return line == "" or line.startswith("#")
  16. def _part_of_help_text(text):
  17. return text.startswith("\t ")
  18. # used in more than one check
  19. entries_that_should_not_be_indented = [
  20. "choice", "comment", "config", "endchoice", "endif", "endmenu", "if",
  21. "menu", "menuconfig", "source"]
  22. class AttributesOrder(_CheckFunction):
  23. attributes_order_convention = {
  24. "bool": 1, "prompt": 1, "string": 1, "default": 2, "depends": 3,
  25. "select": 4, "help": 5}
  26. def before(self):
  27. self.state = 0
  28. def check_line(self, lineno, text):
  29. if _empty_or_comment(text) or _part_of_help_text(text):
  30. return
  31. attribute = text.split()[0]
  32. if attribute in entries_that_should_not_be_indented:
  33. self.state = 0
  34. return
  35. if attribute not in self.attributes_order_convention.keys():
  36. return
  37. new_state = self.attributes_order_convention[attribute]
  38. wrong_order = self.state > new_state
  39. # save to process next line
  40. self.state = new_state
  41. if wrong_order:
  42. return ["{}:{}: attributes order: type, default, depends on,"
  43. " select, help ({}#_config_files)"
  44. .format(self.filename, lineno, self.url_to_manual),
  45. text]
  46. class CommentsMenusPackagesOrder(_CheckFunction):
  47. def before(self):
  48. self.level = 0
  49. self.menu_of_packages = ["The top level menu"]
  50. self.new_package = ""
  51. self.package = [""]
  52. self.print_package_warning = [True]
  53. self.state = ""
  54. def get_level(self):
  55. return len(self.state.split('-')) - 1
  56. def initialize_package_level_elements(self, text):
  57. try:
  58. self.menu_of_packages[self.level] = text[:-1]
  59. self.package[self.level] = ""
  60. self.print_package_warning[self.level] = True
  61. except IndexError:
  62. self.menu_of_packages.append(text[:-1])
  63. self.package.append("")
  64. self.print_package_warning.append(True)
  65. def initialize_level_elements(self, text):
  66. self.level = self.get_level()
  67. self.initialize_package_level_elements(text)
  68. def check_line(self, lineno, text):
  69. # We only want to force sorting for the top-level menus
  70. if self.filename not in ["fs/Config.in",
  71. "package/Config.in",
  72. "package/Config.in.host",
  73. "package/kodi/Config.in"]:
  74. return
  75. source_line = re.match(r'^\s*source ".*/([^/]*)/Config.in(.host)?"', text)
  76. if text.startswith("comment "):
  77. if not self.state.endswith("-comment"):
  78. self.state += "-comment"
  79. self.initialize_level_elements(text)
  80. elif text.startswith("if "):
  81. self.state += "-if"
  82. self.initialize_level_elements(text)
  83. elif text.startswith("menu "):
  84. if self.state.endswith("-comment"):
  85. self.state = self.state[:-8]
  86. self.state += "-menu"
  87. self.initialize_level_elements(text)
  88. elif text.startswith("endif") or text.startswith("endmenu"):
  89. if self.state.endswith("-comment"):
  90. self.state = self.state[:-8]
  91. if text.startswith("endif"):
  92. self.state = self.state[:-3]
  93. elif text.startswith("endmenu"):
  94. self.state = self.state[:-5]
  95. self.level = self.get_level()
  96. elif source_line:
  97. self.new_package = source_line.group(1)
  98. # We order _ before A, so replace it with .
  99. new_package_ord = self.new_package.replace('_', '.')
  100. if self.package[self.level] != "" and \
  101. self.print_package_warning[self.level] and \
  102. new_package_ord < self.package[self.level]:
  103. self.print_package_warning[self.level] = False
  104. prefix = "{}:{}: ".format(self.filename, lineno)
  105. spaces = " " * len(prefix)
  106. return ["{prefix}Packages in: {menu},\n"
  107. "{spaces}are not alphabetically ordered;\n"
  108. "{spaces}correct order: '-', '_', digits, capitals, lowercase;\n"
  109. "{spaces}first incorrect package: {package}"
  110. .format(prefix=prefix, spaces=spaces,
  111. menu=self.menu_of_packages[self.level],
  112. package=self.new_package),
  113. text]
  114. self.package[self.level] = new_package_ord
  115. class HelpText(_CheckFunction):
  116. HELP_TEXT_FORMAT = re.compile(r"^\t .{,62}$")
  117. HELP_TEXT_FORMAT_1 = re.compile(r"^\t \S.{,61}$")
  118. URL_ONLY = re.compile(r"^(http|https|git)://\S*$")
  119. def before(self):
  120. self.help_text = False
  121. def check_line(self, lineno, text):
  122. if _empty_or_comment(text):
  123. return
  124. entry = text.split()[0]
  125. if entry in entries_that_should_not_be_indented:
  126. self.help_text = False
  127. return
  128. if text.strip() == "help":
  129. self.help_text = True
  130. self.help_first_line = True
  131. return
  132. if not self.help_text:
  133. return
  134. if self.help_first_line:
  135. help_text_match = self.HELP_TEXT_FORMAT_1
  136. self.help_first_line = False
  137. else:
  138. help_text_match = self.HELP_TEXT_FORMAT
  139. if help_text_match.match(text.rstrip()):
  140. return
  141. if self.URL_ONLY.match(text.strip()):
  142. return
  143. return ["{}:{}: help text: <tab><2 spaces><62 chars>"
  144. " ({}#writing-rules-config-in)"
  145. .format(self.filename, lineno, self.url_to_manual),
  146. text,
  147. "\t " + "123456789 " * 6 + "12"]
  148. class Indent(_CheckFunction):
  149. ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
  150. entries_that_should_be_indented = [
  151. "bool", "default", "depends", "help", "prompt", "select", "string"]
  152. def before(self):
  153. self.backslash = False
  154. def check_line(self, lineno, text):
  155. if _empty_or_comment(text) or _part_of_help_text(text):
  156. self.backslash = False
  157. return
  158. entry = text.split()[0]
  159. last_line_ends_in_backslash = self.backslash
  160. # calculate for next line
  161. if self.ENDS_WITH_BACKSLASH.search(text):
  162. self.backslash = True
  163. else:
  164. self.backslash = False
  165. if last_line_ends_in_backslash:
  166. if text.startswith("\t"):
  167. return
  168. return ["{}:{}: continuation line should be indented using tabs"
  169. .format(self.filename, lineno),
  170. text]
  171. if entry in self.entries_that_should_be_indented:
  172. if not text.startswith("\t{}".format(entry)):
  173. return ["{}:{}: should be indented with one tab"
  174. " ({}#_config_files)"
  175. .format(self.filename, lineno, self.url_to_manual),
  176. text]
  177. elif entry in entries_that_should_not_be_indented:
  178. if not text.startswith(entry):
  179. # four Config.in files have a special but legitimate indentation rule
  180. if self.filename in ["package/Config.in",
  181. "package/Config.in.host",
  182. "package/kodi/Config.in",
  183. "package/x11r7/Config.in"]:
  184. return
  185. return ["{}:{}: should not be indented"
  186. .format(self.filename, lineno),
  187. text]
  188. class RedefinedConfig(_CheckFunction):
  189. CONFIG = re.compile(r"^\s*(menu|)config\s+(BR2_\w+)\b")
  190. IF = re.compile(r"^\s*if\s+([^#]*)\b")
  191. ENDIF = re.compile(r"^\s*endif\b")
  192. def before(self):
  193. self.configs = {}
  194. self.conditional = []
  195. def check_line(self, lineno, text):
  196. if _empty_or_comment(text) or _part_of_help_text(text):
  197. return
  198. m = self.IF.search(text)
  199. if m is not None:
  200. condition = m.group(1)
  201. self.conditional.append(condition)
  202. return
  203. m = self.ENDIF.search(text)
  204. if m is not None:
  205. self.conditional.pop()
  206. return
  207. m = self.CONFIG.search(text)
  208. if m is None:
  209. return
  210. config = m.group(2)
  211. key = (config, ' AND '.join(self.conditional))
  212. if key in self.configs.keys():
  213. previous_line = self.configs[key]
  214. return ["{}:{}: config {} redeclared (previous line: {})"
  215. .format(self.filename, lineno, config, previous_line),
  216. text]
  217. self.configs[key] = lineno