diff options
Diffstat (limited to 'scripts/checkkconfigsymbols.py')
-rwxr-xr-x | scripts/checkkconfigsymbols.py | 138 |
1 files changed, 132 insertions, 6 deletions
diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index 6445693df669..ce9ca60808b8 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py | |||
@@ -1,6 +1,6 @@ | |||
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-2015 Valentin Rothberg <Valentin.Rothberg@lip6.fr> | 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> |
@@ -10,7 +10,9 @@ | |||
10 | 10 | ||
11 | import os | 11 | import os |
12 | import re | 12 | import re |
13 | import sys | ||
13 | from subprocess import Popen, PIPE, STDOUT | 14 | from subprocess import Popen, PIPE, STDOUT |
15 | from optparse import OptionParser | ||
14 | 16 | ||
15 | 17 | ||
16 | # regex expressions | 18 | # regex expressions |
@@ -32,16 +34,140 @@ REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") | |||
32 | REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") | 34 | REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") |
33 | 35 | ||
34 | 36 | ||
37 | def 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 | |||
35 | def main(): | 86 | def 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 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 | ||
121 | else: | ||
122 | files = undefined_b.get(feature) - undefined_a.get(feature) | ||
123 | if files: | ||
124 | print "%s\t%s" % (feature, ", ".join(files)) | ||
125 | |||
126 | # reset to head | ||
127 | execute("git reset --hard %s" % head) | ||
128 | |||
129 | # default to check the entire tree | ||
130 | else: | ||
131 | undefined = check_symbols() | ||
132 | for feature in undefined: | ||
133 | files = undefined.get(feature) | ||
134 | |||
135 | |||
136 | def execute(cmd): | ||
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: | ||
141 | sys.exit(stdout) | ||
142 | return stdout | ||
143 | |||
144 | |||
145 | def tree_is_dirty(): | ||
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") | ||
149 | for line in stdout: | ||
150 | if re.findall(r"[URMADC]{1}", line[:2]): | ||
151 | return True | ||
152 | return False | ||
153 | |||
154 | |||
155 | def get_head(): | ||
156 | """Return commit hash of current HEAD.""" | ||
157 | stdout = execute("git rev-parse HEAD") | ||
158 | return stdout.strip('\n') | ||
159 | |||
160 | |||
161 | def check_symbols(): | ||
162 | """Find undefined Kconfig symbols and return a dict with the symbol as key | ||
163 | and a list of referencing files as value.""" | ||
37 | source_files = [] | 164 | source_files = [] |
38 | kconfig_files = [] | 165 | kconfig_files = [] |
39 | defined_features = set() | 166 | defined_features = set() |
40 | referenced_features = dict() # {feature: [files]} | 167 | referenced_features = dict() # {feature: [files]} |
41 | 168 | ||
42 | # use 'git ls-files' to get the worklist | 169 | # use 'git ls-files' to get the worklist |
43 | pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True) | 170 | stdout = execute("git ls-files") |
44 | (stdout, _) = pop.communicate() # wait until finished | ||
45 | if len(stdout) > 0 and stdout[-1] == "\n": | 171 | if len(stdout) > 0 and stdout[-1] == "\n": |
46 | stdout = stdout[:-1] | 172 | stdout = stdout[:-1] |
47 | 173 | ||
@@ -62,7 +188,7 @@ def main(): | |||
62 | for kfile in kconfig_files: | 188 | for kfile in kconfig_files: |
63 | parse_kconfig_file(kfile, defined_features, referenced_features) | 189 | parse_kconfig_file(kfile, defined_features, referenced_features) |
64 | 190 | ||
65 | print "Undefined symbol used\tFile list" | 191 | undefined = {} # {feature: [files]} |
66 | for feature in sorted(referenced_features): | 192 | for feature in sorted(referenced_features): |
67 | # filter some false positives | 193 | # filter some false positives |
68 | if feature == "FOO" or feature == "BAR" or \ | 194 | if feature == "FOO" or feature == "BAR" or \ |
@@ -73,8 +199,8 @@ def main(): | |||
73 | # avoid false positives for kernel modules | 199 | # avoid false positives for kernel modules |
74 | if feature[:-len("_MODULE")] in defined_features: | 200 | if feature[:-len("_MODULE")] in defined_features: |
75 | continue | 201 | continue |
76 | files = referenced_features.get(feature) | 202 | undefined[feature] = referenced_features.get(feature) |
77 | print "%s\t%s" % (feature, ", ".join(files)) | 203 | return undefined |
78 | 204 | ||
79 | 205 | ||
80 | def parse_source_file(sfile, referenced_features): | 206 | def parse_source_file(sfile, referenced_features): |