diff options
Diffstat (limited to 'scripts/checkkconfigsymbols.py')
-rwxr-xr-x[-rw-r--r--] | scripts/checkkconfigsymbols.py | 147 |
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 | ||
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,22 +34,149 @@ 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 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 | |||
138 | def 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 | |||
147 | def 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 | |||
157 | def get_head(): | ||
158 | """Return commit hash of current HEAD.""" | ||
159 | stdout = execute("git rev-parse HEAD") | ||
160 | return stdout.strip('\n') | ||
161 | |||
162 | |||
163 | def 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 | ||
79 | def parse_source_file(sfile, referenced_features): | 208 | def parse_source_file(sfile, referenced_features): |