mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 17:59:32 +00:00
![Valentin Rothberg](/assets/img/avatar_default.png)
Commit b1a3f24348
("checkkconfigsymbols.py: make it Git aware")
mistakenly removed to print undefined Kconfig symbols in alphabetical
order. Furthermore, the script does not print anything anymore when the
entire tree is checked (i.e., when no commit is specified).
This patch restores the sorted output and adds the missing print for the
default case. Additionally, the file lists are now sorted as well which
(a) makes it easier to read and (b) makes the output deterministic.
Signed-off-by: Valentin Rothberg <valentinrothberg@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
268 lines
9.3 KiB
Python
Executable file
268 lines
9.3 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
"""Find Kconfig symbols that are referenced but not defined."""
|
|
|
|
# (c) 2014-2015 Valentin Rothberg <Valentin.Rothberg@lip6.fr>
|
|
# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
|
|
#
|
|
# Licensed under the terms of the GNU GPL License version 2
|
|
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
from optparse import OptionParser
|
|
|
|
|
|
# regex expressions
|
|
OPERATORS = r"&|\(|\)|\||\!"
|
|
FEATURE = r"(?:\w*[A-Z0-9]\w*){2,}"
|
|
DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*"
|
|
EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+"
|
|
STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR
|
|
SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")"
|
|
|
|
# regex objects
|
|
REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$")
|
|
REGEX_FEATURE = re.compile(r"(" + FEATURE + r")")
|
|
REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE)
|
|
REGEX_KCONFIG_DEF = re.compile(DEF)
|
|
REGEX_KCONFIG_EXPR = re.compile(EXPR)
|
|
REGEX_KCONFIG_STMT = re.compile(STMT)
|
|
REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$")
|
|
REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$")
|
|
|
|
|
|
def parse_options():
|
|
"""The user interface of this module."""
|
|
usage = "%prog [options]\n\n" \
|
|
"Run this tool to detect Kconfig symbols that are referenced but " \
|
|
"not defined in\nKconfig. The output of this tool has the " \
|
|
"format \'Undefined symbol\\tFile list\'\n\n" \
|
|
"If no option is specified, %prog will default to check your\n" \
|
|
"current tree. Please note that specifying commits will " \
|
|
"\'git reset --hard\'\nyour current tree! You may save " \
|
|
"uncommitted changes to avoid losing data."
|
|
|
|
parser = OptionParser(usage=usage)
|
|
|
|
parser.add_option('-c', '--commit', dest='commit', action='store',
|
|
default="",
|
|
help="Check if the specified commit (hash) introduces "
|
|
"undefined Kconfig symbols.")
|
|
|
|
parser.add_option('-d', '--diff', dest='diff', action='store',
|
|
default="",
|
|
help="Diff undefined symbols between two commits. The "
|
|
"input format bases on Git log's "
|
|
"\'commmit1..commit2\'.")
|
|
|
|
parser.add_option('', '--force', dest='force', action='store_true',
|
|
default=False,
|
|
help="Reset current Git tree even when it's dirty.")
|
|
|
|
(opts, _) = parser.parse_args()
|
|
|
|
if opts.commit and opts.diff:
|
|
sys.exit("Please specify only one option at once.")
|
|
|
|
if opts.diff and not re.match(r"^[\w\-\.]+\.\.[\w\-\.]+$", opts.diff):
|
|
sys.exit("Please specify valid input in the following format: "
|
|
"\'commmit1..commit2\'")
|
|
|
|
if opts.commit or opts.diff:
|
|
if not opts.force and tree_is_dirty():
|
|
sys.exit("The current Git tree is dirty (see 'git status'). "
|
|
"Running this script may\ndelete important data since it "
|
|
"calls 'git reset --hard' for some performance\nreasons. "
|
|
" Please run this script in a clean Git tree or pass "
|
|
"'--force' if you\nwant to ignore this warning and "
|
|
"continue.")
|
|
|
|
return opts
|
|
|
|
|
|
def main():
|
|
"""Main function of this module."""
|
|
opts = parse_options()
|
|
|
|
if opts.commit or opts.diff:
|
|
head = get_head()
|
|
|
|
# get commit range
|
|
commit_a = None
|
|
commit_b = None
|
|
if opts.commit:
|
|
commit_a = opts.commit + "~"
|
|
commit_b = opts.commit
|
|
elif opts.diff:
|
|
split = opts.diff.split("..")
|
|
commit_a = split[0]
|
|
commit_b = split[1]
|
|
undefined_a = {}
|
|
undefined_b = {}
|
|
|
|
# get undefined items before the commit
|
|
execute("git reset --hard %s" % commit_a)
|
|
undefined_a = check_symbols()
|
|
|
|
# get undefined items for the commit
|
|
execute("git reset --hard %s" % commit_b)
|
|
undefined_b = check_symbols()
|
|
|
|
# report cases that are present for the commit but not before
|
|
for feature in sorted(undefined_b):
|
|
# feature has not been undefined before
|
|
if not feature in undefined_a:
|
|
files = sorted(undefined_b.get(feature))
|
|
print "%s\t%s" % (feature, ", ".join(files))
|
|
# check if there are new files that reference the undefined feature
|
|
else:
|
|
files = sorted(undefined_b.get(feature) -
|
|
undefined_a.get(feature))
|
|
if files:
|
|
print "%s\t%s" % (feature, ", ".join(files))
|
|
|
|
# reset to head
|
|
execute("git reset --hard %s" % head)
|
|
|
|
# default to check the entire tree
|
|
else:
|
|
undefined = check_symbols()
|
|
for feature in sorted(undefined):
|
|
files = sorted(undefined.get(feature))
|
|
print "%s\t%s" % (feature, ", ".join(files))
|
|
|
|
|
|
def execute(cmd):
|
|
"""Execute %cmd and return stdout. Exit in case of error."""
|
|
pop = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True)
|
|
(stdout, _) = pop.communicate() # wait until finished
|
|
if pop.returncode != 0:
|
|
sys.exit(stdout)
|
|
return stdout
|
|
|
|
|
|
def tree_is_dirty():
|
|
"""Return true if the current working tree is dirty (i.e., if any file has
|
|
been added, deleted, modified, renamed or copied but not committed)."""
|
|
stdout = execute("git status --porcelain")
|
|
for line in stdout:
|
|
if re.findall(r"[URMADC]{1}", line[:2]):
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_head():
|
|
"""Return commit hash of current HEAD."""
|
|
stdout = execute("git rev-parse HEAD")
|
|
return stdout.strip('\n')
|
|
|
|
|
|
def check_symbols():
|
|
"""Find undefined Kconfig symbols and return a dict with the symbol as key
|
|
and a list of referencing files as value."""
|
|
source_files = []
|
|
kconfig_files = []
|
|
defined_features = set()
|
|
referenced_features = dict() # {feature: [files]}
|
|
|
|
# use 'git ls-files' to get the worklist
|
|
stdout = execute("git ls-files")
|
|
if len(stdout) > 0 and stdout[-1] == "\n":
|
|
stdout = stdout[:-1]
|
|
|
|
for gitfile in stdout.rsplit("\n"):
|
|
if ".git" in gitfile or "ChangeLog" in gitfile or \
|
|
".log" in gitfile or os.path.isdir(gitfile) or \
|
|
gitfile.startswith("tools/"):
|
|
continue
|
|
if REGEX_FILE_KCONFIG.match(gitfile):
|
|
kconfig_files.append(gitfile)
|
|
else:
|
|
# all non-Kconfig files are checked for consistency
|
|
source_files.append(gitfile)
|
|
|
|
for sfile in source_files:
|
|
parse_source_file(sfile, referenced_features)
|
|
|
|
for kfile in kconfig_files:
|
|
parse_kconfig_file(kfile, defined_features, referenced_features)
|
|
|
|
undefined = {} # {feature: [files]}
|
|
for feature in sorted(referenced_features):
|
|
# filter some false positives
|
|
if feature == "FOO" or feature == "BAR" or \
|
|
feature == "FOO_BAR" or feature == "XXX":
|
|
continue
|
|
if feature not in defined_features:
|
|
if feature.endswith("_MODULE"):
|
|
# avoid false positives for kernel modules
|
|
if feature[:-len("_MODULE")] in defined_features:
|
|
continue
|
|
undefined[feature] = referenced_features.get(feature)
|
|
return undefined
|
|
|
|
|
|
def parse_source_file(sfile, referenced_features):
|
|
"""Parse @sfile for referenced Kconfig features."""
|
|
lines = []
|
|
with open(sfile, "r") as stream:
|
|
lines = stream.readlines()
|
|
|
|
for line in lines:
|
|
if not "CONFIG_" in line:
|
|
continue
|
|
features = REGEX_SOURCE_FEATURE.findall(line)
|
|
for feature in features:
|
|
if not REGEX_FILTER_FEATURES.search(feature):
|
|
continue
|
|
sfiles = referenced_features.get(feature, set())
|
|
sfiles.add(sfile)
|
|
referenced_features[feature] = sfiles
|
|
|
|
|
|
def get_features_in_line(line):
|
|
"""Return mentioned Kconfig features in @line."""
|
|
return REGEX_FEATURE.findall(line)
|
|
|
|
|
|
def parse_kconfig_file(kfile, defined_features, referenced_features):
|
|
"""Parse @kfile and update feature definitions and references."""
|
|
lines = []
|
|
skip = False
|
|
|
|
with open(kfile, "r") as stream:
|
|
lines = stream.readlines()
|
|
|
|
for i in range(len(lines)):
|
|
line = lines[i]
|
|
line = line.strip('\n')
|
|
line = line.split("#")[0] # ignore comments
|
|
|
|
if REGEX_KCONFIG_DEF.match(line):
|
|
feature_def = REGEX_KCONFIG_DEF.findall(line)
|
|
defined_features.add(feature_def[0])
|
|
skip = False
|
|
elif REGEX_KCONFIG_HELP.match(line):
|
|
skip = True
|
|
elif skip:
|
|
# ignore content of help messages
|
|
pass
|
|
elif REGEX_KCONFIG_STMT.match(line):
|
|
features = get_features_in_line(line)
|
|
# multi-line statements
|
|
while line.endswith("\\"):
|
|
i += 1
|
|
line = lines[i]
|
|
line = line.strip('\n')
|
|
features.extend(get_features_in_line(line))
|
|
for feature in set(features):
|
|
paths = referenced_features.get(feature, set())
|
|
paths.add(kfile)
|
|
referenced_features[feature] = paths
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|