scripts: kconfig: lint.py: Add check for missing CONFIG_ prefix
Add a new --check-missing-config-prefix check that looks for references like #if MACRO #if(n)def MACRO defined(MACRO) IS_ENABLED(MACRO) where MACRO is the name of a defined Kconfig symbol but doesn't have a CONFIG_ prefix. Could be a typo. Skip MACRO if it is #define'd somewhere, even if it looks like a Kconfig symbol. Found e.g. https://github.com/zephyrproject-rtos/zephyr/pull/22195. Piggyback skipping binary files when grepping for Kconfig symbol references. Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
parent
41a3ac6a35
commit
e2f647b08b
1 changed files with 70 additions and 5 deletions
|
@ -40,7 +40,8 @@ def main():
|
||||||
checks = (check_always_n,
|
checks = (check_always_n,
|
||||||
check_unused,
|
check_unused,
|
||||||
check_pointless_menuconfigs,
|
check_pointless_menuconfigs,
|
||||||
check_defconfig_only_definition)
|
check_defconfig_only_definition,
|
||||||
|
check_missing_config_prefix)
|
||||||
|
|
||||||
first = True
|
first = True
|
||||||
for check in checks:
|
for check in checks:
|
||||||
|
@ -99,6 +100,22 @@ files. A common base definition should probably be added
|
||||||
somewhere for such symbols, and the type declaration ('int',
|
somewhere for such symbols, and the type declaration ('int',
|
||||||
'hex', etc.) removed from Kconfig.defconfig.""")
|
'hex', etc.) removed from Kconfig.defconfig.""")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--check-missing-config-prefix",
|
||||||
|
action="append_const", dest="checks", const=check_missing_config_prefix,
|
||||||
|
help="""\
|
||||||
|
Look for references like
|
||||||
|
|
||||||
|
#if MACRO
|
||||||
|
#if(n)def MACRO
|
||||||
|
defined(MACRO)
|
||||||
|
IS_ENABLED(MACRO)
|
||||||
|
|
||||||
|
where MACRO is the name of a defined Kconfig symbol but
|
||||||
|
doesn't have a CONFIG_ prefix. Could be a typo.
|
||||||
|
|
||||||
|
Macros that are #define'd somewhere are not flagged.""")
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,6 +151,50 @@ def check_defconfig_only_definition():
|
||||||
print(name_and_locs(sym))
|
print(name_and_locs(sym))
|
||||||
|
|
||||||
|
|
||||||
|
def check_missing_config_prefix():
|
||||||
|
print_header("Symbol references that might be missing a CONFIG_ prefix")
|
||||||
|
|
||||||
|
# Paths to modules
|
||||||
|
modpaths = run(("west", "list", "-f{abspath}")).splitlines()
|
||||||
|
|
||||||
|
# Gather #define'd macros that might overlap with symbol names, so that
|
||||||
|
# they don't trigger false positives
|
||||||
|
defined = set()
|
||||||
|
for modpath in modpaths:
|
||||||
|
regex = r"#\s*define\s+([A-Z0-9_]+)\b"
|
||||||
|
defines = run(("git", "grep", "--extended-regexp", regex),
|
||||||
|
cwd=modpath, check=False)
|
||||||
|
# Could pass --only-matching to git grep as well, but it was added
|
||||||
|
# pretty recently (2018)
|
||||||
|
defined.update(re.findall(regex, defines))
|
||||||
|
|
||||||
|
# Filter out symbols whose names are #define'd too. Preserve definition
|
||||||
|
# order to make the output consistent.
|
||||||
|
syms = [sym for sym in kconf.unique_defined_syms
|
||||||
|
if sym.name not in defined]
|
||||||
|
|
||||||
|
# grep for symbol references in #ifdef/defined() that are missing a CONFIG_
|
||||||
|
# prefix. Work around an "argument list too long" error from 'git grep' by
|
||||||
|
# checking symbols in batches.
|
||||||
|
for batch in split_list(syms, 200):
|
||||||
|
# grep for '#if((n)def) <symbol>', 'defined(<symbol>', and
|
||||||
|
# 'IS_ENABLED(<symbol>', with a missing CONFIG_ prefix
|
||||||
|
regex = r"(?:#\s*if(?:n?def)\s+|\bdefined\s*\(\s*|IS_ENABLED\(\s*)(?:" + \
|
||||||
|
"|".join(sym.name for sym in batch) + r")\b"
|
||||||
|
cmd = ("git", "grep", "--line-number", "-I", "--perl-regexp", regex)
|
||||||
|
|
||||||
|
for modpath in modpaths:
|
||||||
|
print(run(cmd, cwd=modpath, check=False), end="")
|
||||||
|
|
||||||
|
|
||||||
|
def split_list(lst, batch_size):
|
||||||
|
# check_missing_config_prefix() helper generator that splits a list into
|
||||||
|
# equal-sized batches (possibly with a shorter batch at the end)
|
||||||
|
|
||||||
|
for i in range(0, len(lst), batch_size):
|
||||||
|
yield lst[i:i + batch_size]
|
||||||
|
|
||||||
|
|
||||||
def print_header(s):
|
def print_header(s):
|
||||||
print(s + "\n" + len(s)*"=")
|
print(s + "\n" + len(s)*"=")
|
||||||
|
|
||||||
|
@ -189,7 +250,7 @@ def referenced_outside_kconfig():
|
||||||
|
|
||||||
# 'git grep' all modules
|
# 'git grep' all modules
|
||||||
for modpath in run(("west", "list", "-f{abspath}")).splitlines():
|
for modpath in run(("west", "list", "-f{abspath}")).splitlines():
|
||||||
for line in run(("git", "grep", "-h", "--extended-regexp", regex),
|
for line in run(("git", "grep", "-h", "-I", "--extended-regexp", regex),
|
||||||
cwd=modpath).splitlines():
|
cwd=modpath).splitlines():
|
||||||
# Don't record lines starting with "CONFIG_FOO=" or "# CONFIG_FOO="
|
# Don't record lines starting with "CONFIG_FOO=" or "# CONFIG_FOO="
|
||||||
# as references, so that symbols that are only assigned in .config
|
# as references, so that symbols that are only assigned in .config
|
||||||
|
@ -229,9 +290,11 @@ def name_and_locs(sym):
|
||||||
", ".join("{0.filename}:{0.linenr}".format(node) for node in sym.nodes))
|
", ".join("{0.filename}:{0.linenr}".format(node) for node in sym.nodes))
|
||||||
|
|
||||||
|
|
||||||
def run(cmd, cwd=TOP_DIR):
|
def run(cmd, cwd=TOP_DIR, check=True):
|
||||||
# Runs 'cmd' with subprocess, returning the decoded stdout output. 'cwd' is
|
# Runs 'cmd' with subprocess, returning the decoded stdout output. 'cwd' is
|
||||||
# the working directory. It defaults to the top-level Zephyr directory.
|
# the working directory. It defaults to the top-level Zephyr directory.
|
||||||
|
# Exits with an error if the command exits with a non-zero return code if
|
||||||
|
# 'check' is True.
|
||||||
|
|
||||||
cmd_s = " ".join(shlex.quote(word) for word in cmd)
|
cmd_s = " ".join(shlex.quote(word) for word in cmd)
|
||||||
|
|
||||||
|
@ -242,9 +305,11 @@ def run(cmd, cwd=TOP_DIR):
|
||||||
err("Failed to run '{}': {}".format(cmd_s, e))
|
err("Failed to run '{}': {}".format(cmd_s, e))
|
||||||
|
|
||||||
stdout, stderr = process.communicate()
|
stdout, stderr = process.communicate()
|
||||||
stdout = stdout.decode("utf-8")
|
# errors="ignore" temporarily works around
|
||||||
|
# https://github.com/zephyrproject-rtos/esp-idf/pull/2
|
||||||
|
stdout = stdout.decode("utf-8", errors="ignore")
|
||||||
stderr = stderr.decode("utf-8")
|
stderr = stderr.decode("utf-8")
|
||||||
if process.returncode:
|
if check and process.returncode:
|
||||||
err("""\
|
err("""\
|
||||||
'{}' exited with status {}.
|
'{}' exited with status {}.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue