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