]>
git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blob - scripts/checkkconfigsymbols.py
3 """Find Kconfig symbols that are referenced but not defined."""
5 # (c) 2014-2015 Valentin Rothberg <Valentin.Rothberg@lip6.fr>
6 # (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
8 # Licensed under the terms of the GNU GPL License version 2
14 from subprocess
import Popen
, PIPE
, STDOUT
15 from optparse
import OptionParser
19 OPERATORS
= r
"&|\(|\)|\||\!"
20 FEATURE
= r
"(?:\w*[A-Z0-9]\w*){2,}"
21 DEF
= r
"^\s*(?:menu){,1}config\s+(" + FEATURE
+ r
")\s*"
22 EXPR
= r
"(?:" + OPERATORS
+ r
"|\s|" + FEATURE
+ r
")+"
23 STMT
= r
"^\s*(?:if|select|depends\s+on)\s+" + EXPR
24 SOURCE_FEATURE
= r
"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE
+ r
")"
27 REGEX_FILE_KCONFIG
= re
.compile(r
".*Kconfig[\.\w+\-]*$")
28 REGEX_FEATURE
= re
.compile(r
"(" + FEATURE
+ r
")")
29 REGEX_SOURCE_FEATURE
= re
.compile(SOURCE_FEATURE
)
30 REGEX_KCONFIG_DEF
= re
.compile(DEF
)
31 REGEX_KCONFIG_EXPR
= re
.compile(EXPR
)
32 REGEX_KCONFIG_STMT
= re
.compile(STMT
)
33 REGEX_KCONFIG_HELP
= re
.compile(r
"^\s+(help|---help---)\s*$")
34 REGEX_FILTER_FEATURES
= re
.compile(r
"[A-Za-z0-9]$")
38 """The user interface of this module."""
39 usage
= "%prog [options]\n\n" \
40 "Run this tool to detect Kconfig symbols that are referenced but " \
41 "not defined in\nKconfig. The output of this tool has the " \
42 "format \'Undefined symbol\\tFile list\'\n\n" \
43 "If no option is specified, %prog will default to check your\n" \
44 "current tree. Please note that specifying commits will " \
45 "\'git reset --hard\'\nyour current tree! You may save " \
46 "uncommitted changes to avoid losing data."
48 parser
= OptionParser(usage
=usage
)
50 parser
.add_option('-c', '--commit', dest
='commit', action
='store',
52 help="Check if the specified commit (hash) introduces "
53 "undefined Kconfig symbols.")
55 parser
.add_option('-d', '--diff', dest
='diff', action
='store',
57 help="Diff undefined symbols between two commits. The "
58 "input format bases on Git log's "
59 "\'commmit1..commit2\'.")
61 parser
.add_option('', '--force', dest
='force', action
='store_true',
63 help="Reset current Git tree even when it's dirty.")
65 (opts
, _
) = parser
.parse_args()
67 if opts
.commit
and opts
.diff
:
68 sys
.exit("Please specify only one option at once.")
70 if opts
.diff
and not re
.match(r
"^[\w\-\.]+\.\.[\w\-\.]+$", opts
.diff
):
71 sys
.exit("Please specify valid input in the following format: "
72 "\'commmit1..commit2\'")
74 if opts
.commit
or opts
.diff
:
75 if not opts
.force
and tree_is_dirty():
76 sys
.exit("The current Git tree is dirty (see 'git status'). "
77 "Running this script may\ndelete important data since it "
78 "calls 'git reset --hard' for some performance\nreasons. "
79 " Please run this script in a clean Git tree or pass "
80 "'--force' if you\nwant to ignore this warning and "
87 """Main function of this module."""
88 opts
= parse_options()
90 if opts
.commit
or opts
.diff
:
97 commit_a
= opts
.commit
+ "~"
98 commit_b
= opts
.commit
100 split
= opts
.diff
.split("..")
106 # get undefined items before the commit
107 execute("git reset --hard %s" % commit_a
)
108 undefined_a
= check_symbols()
110 # get undefined items for the commit
111 execute("git reset --hard %s" % commit_b
)
112 undefined_b
= check_symbols()
114 # report cases that are present for the commit but not before
115 for feature
in undefined_b
:
116 # feature has not been undefined before
117 if not feature
in undefined_a
:
118 files
= undefined_b
.get(feature
)
119 print "%s\t%s" % (feature
, ", ".join(files
))
120 # check if there are new files that reference the undefined feature
122 files
= undefined_b
.get(feature
) - undefined_a
.get(feature
)
124 print "%s\t%s" % (feature
, ", ".join(files
))
127 execute("git reset --hard %s" % head
)
129 # default to check the entire tree
131 undefined
= check_symbols()
132 for feature
in undefined
:
133 files
= undefined
.get(feature
)
137 """Execute %cmd and return stdout. Exit in case of error."""
138 pop
= Popen(cmd
, stdout
=PIPE
, stderr
=STDOUT
, shell
=True)
139 (stdout
, _
) = pop
.communicate() # wait until finished
140 if pop
.returncode
!= 0:
146 """Return true if the current working tree is dirty (i.e., if any file has
147 been added, deleted, modified, renamed or copied but not committed)."""
148 stdout
= execute("git status --porcelain")
150 if re
.findall(r
"[URMADC]{1}", line
[:2]):
156 """Return commit hash of current HEAD."""
157 stdout
= execute("git rev-parse HEAD")
158 return stdout
.strip('\n')
162 """Find undefined Kconfig symbols and return a dict with the symbol as key
163 and a list of referencing files as value."""
166 defined_features
= set()
167 referenced_features
= dict() # {feature: [files]}
169 # use 'git ls-files' to get the worklist
170 stdout
= execute("git ls-files")
171 if len(stdout
) > 0 and stdout
[-1] == "\n":
174 for gitfile
in stdout
.rsplit("\n"):
175 if ".git" in gitfile
or "ChangeLog" in gitfile
or \
176 ".log" in gitfile
or os
.path
.isdir(gitfile
) or \
177 gitfile
.startswith("tools/"):
179 if REGEX_FILE_KCONFIG
.match(gitfile
):
180 kconfig_files
.append(gitfile
)
182 # all non-Kconfig files are checked for consistency
183 source_files
.append(gitfile
)
185 for sfile
in source_files
:
186 parse_source_file(sfile
, referenced_features
)
188 for kfile
in kconfig_files
:
189 parse_kconfig_file(kfile
, defined_features
, referenced_features
)
191 undefined
= {} # {feature: [files]}
192 for feature
in sorted(referenced_features
):
193 # filter some false positives
194 if feature
== "FOO" or feature
== "BAR" or \
195 feature
== "FOO_BAR" or feature
== "XXX":
197 if feature
not in defined_features
:
198 if feature
.endswith("_MODULE"):
199 # avoid false positives for kernel modules
200 if feature
[:-len("_MODULE")] in defined_features
:
202 undefined
[feature
] = referenced_features
.get(feature
)
206 def parse_source_file(sfile
, referenced_features
):
207 """Parse @sfile for referenced Kconfig features."""
209 with
open(sfile
, "r") as stream
:
210 lines
= stream
.readlines()
213 if not "CONFIG_" in line
:
215 features
= REGEX_SOURCE_FEATURE
.findall(line
)
216 for feature
in features
:
217 if not REGEX_FILTER_FEATURES
.search(feature
):
219 sfiles
= referenced_features
.get(feature
, set())
221 referenced_features
[feature
] = sfiles
224 def get_features_in_line(line
):
225 """Return mentioned Kconfig features in @line."""
226 return REGEX_FEATURE
.findall(line
)
229 def parse_kconfig_file(kfile
, defined_features
, referenced_features
):
230 """Parse @kfile and update feature definitions and references."""
234 with
open(kfile
, "r") as stream
:
235 lines
= stream
.readlines()
237 for i
in range(len(lines
)):
239 line
= line
.strip('\n')
240 line
= line
.split("#")[0] # ignore comments
242 if REGEX_KCONFIG_DEF
.match(line
):
243 feature_def
= REGEX_KCONFIG_DEF
.findall(line
)
244 defined_features
.add(feature_def
[0])
246 elif REGEX_KCONFIG_HELP
.match(line
):
249 # ignore content of help messages
251 elif REGEX_KCONFIG_STMT
.match(line
):
252 features
= get_features_in_line(line
)
253 # multi-line statements
254 while line
.endswith("\\"):
257 line
= line
.strip('\n')
258 features
.extend(get_features_in_line(line
))
259 for feature
in set(features
):
260 paths
= referenced_features
.get(feature
, set())
262 referenced_features
[feature
] = paths
265 if __name__
== "__main__":