Commit | Line | Data |
---|---|---|
24fe1f03 VR |
1 | #!/usr/bin/env python |
2 | ||
cc641d55 | 3 | """Find Kconfig identifiers that are referenced but not defined.""" |
24fe1f03 | 4 | |
cc641d55 VR |
5 | # (c) 2014 Valentin Rothberg <valentinrothberg@gmail.com> |
6 | # (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> | |
24fe1f03 | 7 | # |
cc641d55 | 8 | # Licensed under the terms of the GNU GPL License version 2 |
24fe1f03 VR |
9 | |
10 | ||
11 | import os | |
12 | import re | |
13 | from subprocess import Popen, PIPE, STDOUT | |
14 | ||
cc641d55 VR |
15 | |
16 | # regex expressions | |
24fe1f03 | 17 | OPERATORS = r"&|\(|\)|\||\!" |
cc641d55 VR |
18 | FEATURE = r"(?:\w*[A-Z0-9]\w*){2,}" |
19 | DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*" | |
24fe1f03 VR |
20 | EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+" |
21 | STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR | |
cc641d55 | 22 | SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")" |
24fe1f03 | 23 | |
cc641d55 | 24 | # regex objects |
24fe1f03 VR |
25 | REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") |
26 | REGEX_FEATURE = re.compile(r"(" + FEATURE + r")") | |
cc641d55 VR |
27 | REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE) |
28 | REGEX_KCONFIG_DEF = re.compile(DEF) | |
24fe1f03 VR |
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() | |
cc641d55 | 40 | referenced_features = dict() # {feature: [files]} |
24fe1f03 VR |
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 \ | |
cc641d55 | 50 | ".log" in gitfile or os.path.isdir(gitfile): |
24fe1f03 VR |
51 | continue |
52 | if REGEX_FILE_KCONFIG.match(gitfile): | |
53 | kconfig_files.append(gitfile) | |
54 | else: | |
cc641d55 | 55 | # all non-Kconfig files are checked for consistency |
24fe1f03 VR |
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): | |
cc641d55 VR |
66 | # filter some false positives |
67 | if feature == "FOO" or feature == "BAR" or \ | |
68 | feature == "FOO_BAR" or feature == "XXX": | |
69 | continue | |
24fe1f03 VR |
70 | if feature not in defined_features: |
71 | if feature.endswith("_MODULE"): | |
cc641d55 | 72 | # avoid false positives for kernel modules |
24fe1f03 VR |
73 | if feature[:-len("_MODULE")] in defined_features: |
74 | continue | |
24fe1f03 | 75 | files = referenced_features.get(feature) |
cc641d55 | 76 | print "%s\t%s" % (feature, ", ".join(files)) |
24fe1f03 VR |
77 | |
78 | ||
79 | def 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 | |
cc641d55 VR |
92 | sfiles = referenced_features.get(feature, set()) |
93 | sfiles.add(sfile) | |
94 | referenced_features[feature] = sfiles | |
24fe1f03 VR |
95 | |
96 | ||
97 | def get_features_in_line(line): | |
98 | """Return mentioned Kconfig features in @line.""" | |
99 | return REGEX_FEATURE.findall(line) | |
100 | ||
101 | ||
102 | def 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') | |
cc641d55 | 113 | line = line.split("#")[0] # ignore comments |
24fe1f03 VR |
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: | |
cc641d55 | 122 | # ignore content of help messages |
24fe1f03 VR |
123 | pass |
124 | elif REGEX_KCONFIG_STMT.match(line): | |
125 | features = get_features_in_line(line) | |
cc641d55 | 126 | # multi-line statements |
24fe1f03 VR |
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 | ||
138 | if __name__ == "__main__": | |
139 | main() |