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