143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
"""Find Kconfig identifieres that are referenced but not defined."""
|
||
|
|
||
|
# Copyright (C) 2014 Valentin Rothberg <valentinrothberg@gmail.com>
|
||
|
# Copyright (C) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify it
|
||
|
# under the terms and conditions of the GNU General Public License,
|
||
|
# version 2, as published by the Free Software Foundation.
|
||
|
#
|
||
|
# This program is distributed in the hope it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
# more details.
|
||
|
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
from subprocess import Popen, PIPE, STDOUT
|
||
|
|
||
|
# REGEX EXPRESSIONS
|
||
|
OPERATORS = r"&|\(|\)|\||\!"
|
||
|
FEATURE = r"\w*[A-Z]{1}\w*"
|
||
|
CONFIG_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
|
||
|
|
||
|
# REGEX OBJECTS
|
||
|
REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$")
|
||
|
REGEX_FEATURE = re.compile(r"(" + FEATURE + r")")
|
||
|
REGEX_SOURCE_FEATURE = re.compile(r"(?:D|\W|\b)+CONFIG_(" + FEATURE + r")")
|
||
|
REGEX_KCONFIG_DEF = re.compile(CONFIG_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 main():
|
||
|
"""Main function of this module."""
|
||
|
source_files = []
|
||
|
kconfig_files = []
|
||
|
defined_features = set()
|
||
|
referenced_features = dict()
|
||
|
|
||
|
# use 'git ls-files' to get the worklist
|
||
|
pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True)
|
||
|
(stdout, _) = pop.communicate() # wait until finished
|
||
|
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 \
|
||
|
os.path.isdir(gitfile):
|
||
|
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)
|
||
|
|
||
|
print "Undefined symbol used\tFile list"
|
||
|
for feature in sorted(referenced_features):
|
||
|
if feature not in defined_features:
|
||
|
if feature.endswith("_MODULE"):
|
||
|
# Avoid false positives for kernel modules
|
||
|
if feature[:-len("_MODULE")] in defined_features:
|
||
|
continue
|
||
|
if "FOO" in feature or "BAR" in feature:
|
||
|
continue
|
||
|
files = referenced_features.get(feature)
|
||
|
print "%s:\t%s" % (feature, ", ".join(files))
|
||
|
|
||
|
|
||
|
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
|
||
|
paths = referenced_features.get(feature, set())
|
||
|
paths.add(sfile)
|
||
|
referenced_features[feature] = paths
|
||
|
|
||
|
|
||
|
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 Kconfig 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()
|