aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/checkkconfigsymbols.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/checkkconfigsymbols.py')
-rwxr-xr-x[-rw-r--r--]scripts/checkkconfigsymbols.py147
1 files changed, 138 insertions, 9 deletions
diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py
index e9cc689033fe..74086a583d8d 100644..100755
--- a/scripts/checkkconfigsymbols.py
+++ b/scripts/checkkconfigsymbols.py
@@ -1,8 +1,8 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2 2
3"""Find Kconfig identifiers that are referenced but not defined.""" 3"""Find Kconfig symbols that are referenced but not defined."""
4 4
5# (c) 2014 Valentin Rothberg <valentinrothberg@gmail.com> 5# (c) 2014-2015 Valentin Rothberg <Valentin.Rothberg@lip6.fr>
6# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> 6# (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de>
7# 7#
8# Licensed under the terms of the GNU GPL License version 2 8# Licensed under the terms of the GNU GPL License version 2
@@ -10,7 +10,9 @@
10 10
11import os 11import os
12import re 12import re
13import sys
13from subprocess import Popen, PIPE, STDOUT 14from subprocess import Popen, PIPE, STDOUT
15from optparse import OptionParser
14 16
15 17
16# regex expressions 18# regex expressions
@@ -32,22 +34,149 @@ REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$")
32REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") 34REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$")
33 35
34 36
37def parse_options():
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."
47
48 parser = OptionParser(usage=usage)
49
50 parser.add_option('-c', '--commit', dest='commit', action='store',
51 default="",
52 help="Check if the specified commit (hash) introduces "
53 "undefined Kconfig symbols.")
54
55 parser.add_option('-d', '--diff', dest='diff', action='store',
56 default="",
57 help="Diff undefined symbols between two commits. The "
58 "input format bases on Git log's "
59 "\'commmit1..commit2\'.")
60
61 parser.add_option('', '--force', dest='force', action='store_true',
62 default=False,
63 help="Reset current Git tree even when it's dirty.")
64
65 (opts, _) = parser.parse_args()
66
67 if opts.commit and opts.diff:
68 sys.exit("Please specify only one option at once.")
69
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\'")
73
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 "
81 "continue.")
82
83 return opts
84
85
35def main(): 86def main():
36 """Main function of this module.""" 87 """Main function of this module."""
88 opts = parse_options()
89
90 if opts.commit or opts.diff:
91 head = get_head()
92
93 # get commit range
94 commit_a = None
95 commit_b = None
96 if opts.commit:
97 commit_a = opts.commit + "~"
98 commit_b = opts.commit
99 elif opts.diff:
100 split = opts.diff.split("..")
101 commit_a = split[0]
102 commit_b = split[1]
103 undefined_a = {}
104 undefined_b = {}
105
106 # get undefined items before the commit
107 execute("git reset --hard %s" % commit_a)
108 undefined_a = check_symbols()
109
110 # get undefined items for the commit
111 execute("git reset --hard %s" % commit_b)
112 undefined_b = check_symbols()
113
114 # report cases that are present for the commit but not before
115 for feature in sorted(undefined_b):
116 # feature has not been undefined before
117 if not feature in undefined_a:
118 files = sorted(undefined_b.get(feature))
119 print "%s\t%s" % (feature, ", ".join(files))
120 # check if there are new files that reference the undefined feature
121 else:
122 files = sorted(undefined_b.get(feature) -
123 undefined_a.get(feature))
124 if files:
125 print "%s\t%s" % (feature, ", ".join(files))
126
127 # reset to head
128 execute("git reset --hard %s" % head)
129
130 # default to check the entire tree
131 else:
132 undefined = check_symbols()
133 for feature in sorted(undefined):
134 files = sorted(undefined.get(feature))
135 print "%s\t%s" % (feature, ", ".join(files))
136
137
138def execute(cmd):
139 """Execute %cmd and return stdout. Exit in case of error."""
140 pop = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True)
141 (stdout, _) = pop.communicate() # wait until finished
142 if pop.returncode != 0:
143 sys.exit(stdout)
144 return stdout
145
146
147def tree_is_dirty():
148 """Return true if the current working tree is dirty (i.e., if any file has
149 been added, deleted, modified, renamed or copied but not committed)."""
150 stdout = execute("git status --porcelain")
151 for line in stdout:
152 if re.findall(r"[URMADC]{1}", line[:2]):
153 return True
154 return False
155
156
157def get_head():
158 """Return commit hash of current HEAD."""
159 stdout = execute("git rev-parse HEAD")
160 return stdout.strip('\n')
161
162
163def check_symbols():
164 """Find undefined Kconfig symbols and return a dict with the symbol as key
165 and a list of referencing files as value."""
37 source_files = [] 166 source_files = []
38 kconfig_files = [] 167 kconfig_files = []
39 defined_features = set() 168 defined_features = set()
40 referenced_features = dict() # {feature: [files]} 169 referenced_features = dict() # {feature: [files]}
41 170
42 # use 'git ls-files' to get the worklist 171 # use 'git ls-files' to get the worklist
43 pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True) 172 stdout = execute("git ls-files")
44 (stdout, _) = pop.communicate() # wait until finished
45 if len(stdout) > 0 and stdout[-1] == "\n": 173 if len(stdout) > 0 and stdout[-1] == "\n":
46 stdout = stdout[:-1] 174 stdout = stdout[:-1]
47 175
48 for gitfile in stdout.rsplit("\n"): 176 for gitfile in stdout.rsplit("\n"):
49 if ".git" in gitfile or "ChangeLog" in gitfile or \ 177 if ".git" in gitfile or "ChangeLog" in gitfile or \
50 ".log" in gitfile or os.path.isdir(gitfile): 178 ".log" in gitfile or os.path.isdir(gitfile) or \
179 gitfile.startswith("tools/"):
51 continue 180 continue
52 if REGEX_FILE_KCONFIG.match(gitfile): 181 if REGEX_FILE_KCONFIG.match(gitfile):
53 kconfig_files.append(gitfile) 182 kconfig_files.append(gitfile)
@@ -61,7 +190,7 @@ def main():
61 for kfile in kconfig_files: 190 for kfile in kconfig_files:
62 parse_kconfig_file(kfile, defined_features, referenced_features) 191 parse_kconfig_file(kfile, defined_features, referenced_features)
63 192
64 print "Undefined symbol used\tFile list" 193 undefined = {} # {feature: [files]}
65 for feature in sorted(referenced_features): 194 for feature in sorted(referenced_features):
66 # filter some false positives 195 # filter some false positives
67 if feature == "FOO" or feature == "BAR" or \ 196 if feature == "FOO" or feature == "BAR" or \
@@ -72,8 +201,8 @@ def main():
72 # avoid false positives for kernel modules 201 # avoid false positives for kernel modules
73 if feature[:-len("_MODULE")] in defined_features: 202 if feature[:-len("_MODULE")] in defined_features:
74 continue 203 continue
75 files = referenced_features.get(feature) 204 undefined[feature] = referenced_features.get(feature)
76 print "%s\t%s" % (feature, ", ".join(files)) 205 return undefined
77 206
78 207
79def parse_source_file(sfile, referenced_features): 208def parse_source_file(sfile, referenced_features):