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