4bbda72743
Warn the developer in the case the same config is declared more than once in the same Config.in file. But take into account the conditional code that lets the config be visible and warn only when it is declared more than once in the same conditions. For instance, do not warn for: if BR2_PACKAGE_BUSYBOX config BR2_PACKAGE_BUSYBOX_SHOW_OTHERS endif if !BR2_PACKAGE_BUSYBOX # kconfig doesn't support else config BR2_PACKAGE_BUSYBOX_SHOW_OTHERS endif Signed-off-by: Ricardo Martincoski <ricardo.martincoski@gmail.com> Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
274 lines
9.1 KiB
Python
274 lines
9.1 KiB
Python
# See utils/checkpackagelib/readme.txt before editing this file.
|
|
# Kconfig generates errors if someone introduces a typo like "boool" instead of
|
|
# "bool", so below check functions don't need to check for things already
|
|
# checked by running "make menuconfig".
|
|
|
|
import re
|
|
|
|
from checkpackagelib.base import _CheckFunction
|
|
from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
|
|
from checkpackagelib.lib import EmptyLastLine # noqa: F401
|
|
from checkpackagelib.lib import NewlineAtEof # noqa: F401
|
|
from checkpackagelib.lib import TrailingSpace # noqa: F401
|
|
from checkpackagelib.tool import NotExecutable # noqa: F401
|
|
|
|
|
|
def _empty_or_comment(text):
|
|
line = text.strip()
|
|
# ignore empty lines and comment lines indented or not
|
|
return line == "" or line.startswith("#")
|
|
|
|
|
|
def _part_of_help_text(text):
|
|
return text.startswith("\t ")
|
|
|
|
|
|
# used in more than one check
|
|
entries_that_should_not_be_indented = [
|
|
"choice", "comment", "config", "endchoice", "endif", "endmenu", "if",
|
|
"menu", "menuconfig", "source"]
|
|
|
|
|
|
class AttributesOrder(_CheckFunction):
|
|
attributes_order_convention = {
|
|
"bool": 1, "prompt": 1, "string": 1, "default": 2, "depends": 3,
|
|
"select": 4, "help": 5}
|
|
|
|
def before(self):
|
|
self.state = 0
|
|
|
|
def check_line(self, lineno, text):
|
|
if _empty_or_comment(text) or _part_of_help_text(text):
|
|
return
|
|
|
|
attribute = text.split()[0]
|
|
|
|
if attribute in entries_that_should_not_be_indented:
|
|
self.state = 0
|
|
return
|
|
if attribute not in self.attributes_order_convention.keys():
|
|
return
|
|
new_state = self.attributes_order_convention[attribute]
|
|
wrong_order = self.state > new_state
|
|
|
|
# save to process next line
|
|
self.state = new_state
|
|
|
|
if wrong_order:
|
|
return ["{}:{}: attributes order: type, default, depends on,"
|
|
" select, help ({}#_config_files)"
|
|
.format(self.filename, lineno, self.url_to_manual),
|
|
text]
|
|
|
|
|
|
class CommentsMenusPackagesOrder(_CheckFunction):
|
|
def before(self):
|
|
self.level = 0
|
|
self.menu_of_packages = ["The top level menu"]
|
|
self.new_package = ""
|
|
self.package = [""]
|
|
self.print_package_warning = [True]
|
|
self.state = ""
|
|
|
|
def get_level(self):
|
|
return len(self.state.split('-')) - 1
|
|
|
|
def initialize_package_level_elements(self, text):
|
|
try:
|
|
self.menu_of_packages[self.level] = text[:-1]
|
|
self.package[self.level] = ""
|
|
self.print_package_warning[self.level] = True
|
|
except IndexError:
|
|
self.menu_of_packages.append(text[:-1])
|
|
self.package.append("")
|
|
self.print_package_warning.append(True)
|
|
|
|
def initialize_level_elements(self, text):
|
|
self.level = self.get_level()
|
|
self.initialize_package_level_elements(text)
|
|
|
|
def check_line(self, lineno, text):
|
|
# We only want to force sorting for the top-level menus
|
|
if self.filename not in ["fs/Config.in",
|
|
"package/Config.in",
|
|
"package/Config.in.host",
|
|
"package/kodi/Config.in"]:
|
|
return
|
|
|
|
source_line = re.match(r'^\s*source ".*/([^/]*)/Config.in(.host)?"', text)
|
|
|
|
if text.startswith("comment "):
|
|
if not self.state.endswith("-comment"):
|
|
self.state += "-comment"
|
|
|
|
self.initialize_level_elements(text)
|
|
|
|
elif text.startswith("if "):
|
|
self.state += "-if"
|
|
|
|
self.initialize_level_elements(text)
|
|
|
|
elif text.startswith("menu "):
|
|
if self.state.endswith("-comment"):
|
|
self.state = self.state[:-8]
|
|
|
|
self.state += "-menu"
|
|
|
|
self.initialize_level_elements(text)
|
|
|
|
elif text.startswith("endif") or text.startswith("endmenu"):
|
|
if self.state.endswith("-comment"):
|
|
self.state = self.state[:-8]
|
|
|
|
if text.startswith("endif"):
|
|
self.state = self.state[:-3]
|
|
|
|
elif text.startswith("endmenu"):
|
|
self.state = self.state[:-5]
|
|
|
|
self.level = self.get_level()
|
|
|
|
elif source_line:
|
|
self.new_package = source_line.group(1)
|
|
|
|
# We order _ before A, so replace it with .
|
|
new_package_ord = self.new_package.replace('_', '.')
|
|
|
|
if self.package[self.level] != "" and \
|
|
self.print_package_warning[self.level] and \
|
|
new_package_ord < self.package[self.level]:
|
|
self.print_package_warning[self.level] = False
|
|
prefix = "{}:{}: ".format(self.filename, lineno)
|
|
spaces = " " * len(prefix)
|
|
return ["{prefix}Packages in: {menu},\n"
|
|
"{spaces}are not alphabetically ordered;\n"
|
|
"{spaces}correct order: '-', '_', digits, capitals, lowercase;\n"
|
|
"{spaces}first incorrect package: {package}"
|
|
.format(prefix=prefix, spaces=spaces,
|
|
menu=self.menu_of_packages[self.level],
|
|
package=self.new_package),
|
|
text]
|
|
|
|
self.package[self.level] = new_package_ord
|
|
|
|
|
|
class HelpText(_CheckFunction):
|
|
HELP_TEXT_FORMAT = re.compile(r"^\t .{,62}$")
|
|
URL_ONLY = re.compile(r"^(http|https|git)://\S*$")
|
|
|
|
def before(self):
|
|
self.help_text = False
|
|
|
|
def check_line(self, lineno, text):
|
|
if _empty_or_comment(text):
|
|
return
|
|
|
|
entry = text.split()[0]
|
|
|
|
if entry in entries_that_should_not_be_indented:
|
|
self.help_text = False
|
|
return
|
|
if text.strip() == "help":
|
|
self.help_text = True
|
|
return
|
|
|
|
if not self.help_text:
|
|
return
|
|
|
|
if self.HELP_TEXT_FORMAT.match(text.rstrip()):
|
|
return
|
|
if self.URL_ONLY.match(text.strip()):
|
|
return
|
|
return ["{}:{}: help text: <tab><2 spaces><62 chars>"
|
|
" ({}#writing-rules-config-in)"
|
|
.format(self.filename, lineno, self.url_to_manual),
|
|
text,
|
|
"\t " + "123456789 " * 6 + "12"]
|
|
|
|
|
|
class Indent(_CheckFunction):
|
|
ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
|
|
entries_that_should_be_indented = [
|
|
"bool", "default", "depends", "help", "prompt", "select", "string"]
|
|
|
|
def before(self):
|
|
self.backslash = False
|
|
|
|
def check_line(self, lineno, text):
|
|
if _empty_or_comment(text) or _part_of_help_text(text):
|
|
self.backslash = False
|
|
return
|
|
|
|
entry = text.split()[0]
|
|
|
|
last_line_ends_in_backslash = self.backslash
|
|
|
|
# calculate for next line
|
|
if self.ENDS_WITH_BACKSLASH.search(text):
|
|
self.backslash = True
|
|
else:
|
|
self.backslash = False
|
|
|
|
if last_line_ends_in_backslash:
|
|
if text.startswith("\t"):
|
|
return
|
|
return ["{}:{}: continuation line should be indented using tabs"
|
|
.format(self.filename, lineno),
|
|
text]
|
|
|
|
if entry in self.entries_that_should_be_indented:
|
|
if not text.startswith("\t{}".format(entry)):
|
|
return ["{}:{}: should be indented with one tab"
|
|
" ({}#_config_files)"
|
|
.format(self.filename, lineno, self.url_to_manual),
|
|
text]
|
|
elif entry in entries_that_should_not_be_indented:
|
|
if not text.startswith(entry):
|
|
# four Config.in files have a special but legitimate indentation rule
|
|
if self.filename in ["package/Config.in",
|
|
"package/Config.in.host",
|
|
"package/kodi/Config.in",
|
|
"package/x11r7/Config.in"]:
|
|
return
|
|
return ["{}:{}: should not be indented"
|
|
.format(self.filename, lineno),
|
|
text]
|
|
|
|
|
|
class RedefinedConfig(_CheckFunction):
|
|
CONFIG = re.compile(r"^\s*(menu|)config\s+(BR2_\w+)\b")
|
|
IF = re.compile(r"^\s*if\s+([^#]*)\b")
|
|
ENDIF = re.compile(r"^\s*endif\b")
|
|
|
|
def before(self):
|
|
self.configs = {}
|
|
self.conditional = []
|
|
|
|
def check_line(self, lineno, text):
|
|
if _empty_or_comment(text) or _part_of_help_text(text):
|
|
return
|
|
|
|
m = self.IF.search(text)
|
|
if m is not None:
|
|
condition = m.group(1)
|
|
self.conditional.append(condition)
|
|
return
|
|
|
|
m = self.ENDIF.search(text)
|
|
if m is not None:
|
|
self.conditional.pop()
|
|
return
|
|
|
|
m = self.CONFIG.search(text)
|
|
if m is None:
|
|
return
|
|
config = m.group(2)
|
|
|
|
key = (config, ' AND '.join(self.conditional))
|
|
if key in self.configs.keys():
|
|
previous_line = self.configs[key]
|
|
return ["{}:{}: config {} redeclared (previous line: {})"
|
|
.format(self.filename, lineno, config, previous_line),
|
|
text]
|
|
self.configs[key] = lineno
|