1#!/usr/bin/env python 2 3"""Find Kconfig identifiers that are referenced but not defined.""" 4 5# (c) 2014 Valentin Rothberg <valentinrothberg@gmail.com> 6# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 7# 8# Licensed under the terms of the GNU GPL License version 2 9 10 11import os 12import re 13from subprocess import Popen, PIPE, STDOUT 14 15 16# regex expressions 17OPERATORS = r"&|\(|\)|\||\!" 18FEATURE = r"(?:\w*[A-Z0-9]\w*){2,}" 19DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*" 20EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+" 21STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR 22SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")" 23 24# regex objects 25REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") 26REGEX_FEATURE = re.compile(r"(" + FEATURE + r")") 27REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE) 28REGEX_KCONFIG_DEF = re.compile(DEF) 29REGEX_KCONFIG_EXPR = re.compile(EXPR) 30REGEX_KCONFIG_STMT = re.compile(STMT) 31REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") 32REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") 33 34 35def main(): 36 """Main function of this module.""" 37 source_files = [] 38 kconfig_files = [] 39 defined_features = set() 40 referenced_features = dict() # {feature: [files]} 41 42 # use 'git ls-files' to get the worklist 43 pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True) 44 (stdout, _) = pop.communicate() # wait until finished 45 if len(stdout) > 0 and stdout[-1] == "\n": 46 stdout = stdout[:-1] 47 48 for gitfile in stdout.rsplit("\n"): 49 if ".git" in gitfile or "ChangeLog" in gitfile or \ 50 ".log" in gitfile or os.path.isdir(gitfile): 51 continue 52 if REGEX_FILE_KCONFIG.match(gitfile): 53 kconfig_files.append(gitfile) 54 else: 55 # all non-Kconfig files are checked for consistency 56 source_files.append(gitfile) 57 58 for sfile in source_files: 59 parse_source_file(sfile, referenced_features) 60 61 for kfile in kconfig_files: 62 parse_kconfig_file(kfile, defined_features, referenced_features) 63 64 print "Undefined symbol used\tFile list" 65 for feature in sorted(referenced_features): 66 # filter some false positives 67 if feature == "FOO" or feature == "BAR" or \ 68 feature == "FOO_BAR" or feature == "XXX": 69 continue 70 if feature not in defined_features: 71 if feature.endswith("_MODULE"): 72 # avoid false positives for kernel modules 73 if feature[:-len("_MODULE")] in defined_features: 74 continue 75 files = referenced_features.get(feature) 76 print "%s\t%s" % (feature, ", ".join(files)) 77 78 79def parse_source_file(sfile, referenced_features): 80 """Parse @sfile for referenced Kconfig features.""" 81 lines = [] 82 with open(sfile, "r") as stream: 83 lines = stream.readlines() 84 85 for line in lines: 86 if not "CONFIG_" in line: 87 continue 88 features = REGEX_SOURCE_FEATURE.findall(line) 89 for feature in features: 90 if not REGEX_FILTER_FEATURES.search(feature): 91 continue 92 sfiles = referenced_features.get(feature, set()) 93 sfiles.add(sfile) 94 referenced_features[feature] = sfiles 95 96 97def get_features_in_line(line): 98 """Return mentioned Kconfig features in @line.""" 99 return REGEX_FEATURE.findall(line) 100 101 102def parse_kconfig_file(kfile, defined_features, referenced_features): 103 """Parse @kfile and update feature definitions and references.""" 104 lines = [] 105 skip = False 106 107 with open(kfile, "r") as stream: 108 lines = stream.readlines() 109 110 for i in range(len(lines)): 111 line = lines[i] 112 line = line.strip('\n') 113 line = line.split("#")[0] # ignore comments 114 115 if REGEX_KCONFIG_DEF.match(line): 116 feature_def = REGEX_KCONFIG_DEF.findall(line) 117 defined_features.add(feature_def[0]) 118 skip = False 119 elif REGEX_KCONFIG_HELP.match(line): 120 skip = True 121 elif skip: 122 # ignore content of help messages 123 pass 124 elif REGEX_KCONFIG_STMT.match(line): 125 features = get_features_in_line(line) 126 # multi-line statements 127 while line.endswith("\\"): 128 i += 1 129 line = lines[i] 130 line = line.strip('\n') 131 features.extend(get_features_in_line(line)) 132 for feature in set(features): 133 paths = referenced_features.get(feature, set()) 134 paths.add(kfile) 135 referenced_features[feature] = paths 136 137 138if __name__ == "__main__": 139 main() 140