diff options
author | Valentin Rothberg <valentinrothberg@gmail.com> | 2015-10-15 04:37:47 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-12-14 13:54:23 -0500 |
commit | e2042a8a80ab598bbb8d06cd7f3078923c7c77e3 (patch) | |
tree | 5823b1d95bd59f22228645fc09f0ccae04431ab8 /scripts/checkkconfigsymbols.py | |
parent | 9f9499ae8e6415cefc4fe0a96ad0e27864353c89 (diff) |
checkkconfigsymbols.py: multiprocessing of files
Distribute the parsing of source and Kconfig files on all available
cores to speed up processing.
Signed-off-by: Valentin Rothberg <valentinrothberg@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'scripts/checkkconfigsymbols.py')
-rwxr-xr-x | scripts/checkkconfigsymbols.py | 128 |
1 files changed, 102 insertions, 26 deletions
diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index 2f4b7ffd5570..cfe397b61c48 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py | |||
@@ -10,9 +10,11 @@ | |||
10 | 10 | ||
11 | import os | 11 | import os |
12 | import re | 12 | import re |
13 | import signal | ||
13 | import sys | 14 | import sys |
14 | from subprocess import Popen, PIPE, STDOUT | 15 | from multiprocessing import Pool, cpu_count |
15 | from optparse import OptionParser | 16 | from optparse import OptionParser |
17 | from subprocess import Popen, PIPE, STDOUT | ||
16 | 18 | ||
17 | 19 | ||
18 | # regex expressions | 20 | # regex expressions |
@@ -26,7 +28,7 @@ SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")" | |||
26 | 28 | ||
27 | # regex objects | 29 | # regex objects |
28 | REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") | 30 | REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") |
29 | REGEX_FEATURE = re.compile(r'(?!\B"[^"]*)' + FEATURE + r'(?![^"]*"\B)') | 31 | REGEX_FEATURE = re.compile(r'(?!\B)' + FEATURE + r'(?!\B)') |
30 | REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE) | 32 | REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE) |
31 | REGEX_KCONFIG_DEF = re.compile(DEF) | 33 | REGEX_KCONFIG_DEF = re.compile(DEF) |
32 | REGEX_KCONFIG_EXPR = re.compile(EXPR) | 34 | REGEX_KCONFIG_EXPR = re.compile(EXPR) |
@@ -34,6 +36,7 @@ REGEX_KCONFIG_STMT = re.compile(STMT) | |||
34 | REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") | 36 | REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") |
35 | REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") | 37 | REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") |
36 | REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") | 38 | REGEX_NUMERIC = re.compile(r"0[xX][0-9a-fA-F]+|[0-9]+") |
39 | REGEX_QUOTES = re.compile("(\"(.*?)\")") | ||
37 | 40 | ||
38 | 41 | ||
39 | def parse_options(): | 42 | def parse_options(): |
@@ -209,14 +212,36 @@ def get_head(): | |||
209 | return stdout.strip('\n') | 212 | return stdout.strip('\n') |
210 | 213 | ||
211 | 214 | ||
215 | def partition(lst, size): | ||
216 | """Partition list @lst into eveni-sized lists of size @size.""" | ||
217 | return [lst[i::size] for i in xrange(size)] | ||
218 | |||
219 | |||
220 | def init_worker(): | ||
221 | """Set signal handler to ignore SIGINT.""" | ||
222 | signal.signal(signal.SIGINT, signal.SIG_IGN) | ||
223 | |||
224 | |||
212 | def check_symbols(ignore): | 225 | def check_symbols(ignore): |
213 | """Find undefined Kconfig symbols and return a dict with the symbol as key | 226 | """Find undefined Kconfig symbols and return a dict with the symbol as key |
214 | and a list of referencing files as value. Files matching %ignore are not | 227 | and a list of referencing files as value. Files matching %ignore are not |
215 | checked for undefined symbols.""" | 228 | checked for undefined symbols.""" |
229 | pool = Pool(cpu_count(), init_worker) | ||
230 | try: | ||
231 | return check_symbols_helper(pool, ignore) | ||
232 | except KeyboardInterrupt: | ||
233 | pool.terminate() | ||
234 | pool.join() | ||
235 | sys.exit(1) | ||
236 | |||
237 | |||
238 | def check_symbols_helper(pool, ignore): | ||
239 | """Helper method for check_symbols(). Used to catch keyboard interrupts in | ||
240 | check_symbols() in order to properly terminate running worker processes.""" | ||
216 | source_files = [] | 241 | source_files = [] |
217 | kconfig_files = [] | 242 | kconfig_files = [] |
218 | defined_features = set() | 243 | defined_features = [] |
219 | referenced_features = dict() # {feature: [files]} | 244 | referenced_features = dict() # {file: [features]} |
220 | 245 | ||
221 | # use 'git ls-files' to get the worklist | 246 | # use 'git ls-files' to get the worklist |
222 | stdout = execute("git ls-files") | 247 | stdout = execute("git ls-files") |
@@ -231,21 +256,33 @@ def check_symbols(ignore): | |||
231 | if REGEX_FILE_KCONFIG.match(gitfile): | 256 | if REGEX_FILE_KCONFIG.match(gitfile): |
232 | kconfig_files.append(gitfile) | 257 | kconfig_files.append(gitfile) |
233 | else: | 258 | else: |
234 | # all non-Kconfig files are checked for consistency | 259 | if ignore and not re.match(ignore, gitfile): |
260 | continue | ||
261 | # add source files that do not match the ignore pattern | ||
235 | source_files.append(gitfile) | 262 | source_files.append(gitfile) |
236 | 263 | ||
237 | for sfile in source_files: | 264 | # parse source files |
238 | if ignore and re.match(ignore, sfile): | 265 | arglist = partition(source_files, cpu_count()) |
239 | # do not check files matching %ignore | 266 | for res in pool.map(parse_source_files, arglist): |
240 | continue | 267 | referenced_features.update(res) |
241 | parse_source_file(sfile, referenced_features) | ||
242 | 268 | ||
243 | for kfile in kconfig_files: | 269 | |
244 | if ignore and re.match(ignore, kfile): | 270 | # parse kconfig files |
245 | # do not collect references for files matching %ignore | 271 | arglist = [] |
246 | parse_kconfig_file(kfile, defined_features, dict()) | 272 | for part in partition(kconfig_files, cpu_count()): |
247 | else: | 273 | arglist.append((part, ignore)) |
248 | parse_kconfig_file(kfile, defined_features, referenced_features) | 274 | for res in pool.map(parse_kconfig_files, arglist): |
275 | defined_features.extend(res[0]) | ||
276 | referenced_features.update(res[1]) | ||
277 | defined_features = set(defined_features) | ||
278 | |||
279 | # inverse mapping of referenced_features to dict(feature: [files]) | ||
280 | inv_map = dict() | ||
281 | for _file, features in referenced_features.iteritems(): | ||
282 | for feature in features: | ||
283 | inv_map[feature] = inv_map.get(feature, set()) | ||
284 | inv_map[feature].add(_file) | ||
285 | referenced_features = inv_map | ||
249 | 286 | ||
250 | undefined = {} # {feature: [files]} | 287 | undefined = {} # {feature: [files]} |
251 | for feature in sorted(referenced_features): | 288 | for feature in sorted(referenced_features): |
@@ -262,9 +299,23 @@ def check_symbols(ignore): | |||
262 | return undefined | 299 | return undefined |
263 | 300 | ||
264 | 301 | ||
265 | def parse_source_file(sfile, referenced_features): | 302 | def parse_source_files(source_files): |
266 | """Parse @sfile for referenced Kconfig features.""" | 303 | """Parse each source file in @source_files and return dictionary with source |
304 | files as keys and lists of references Kconfig symbols as values.""" | ||
305 | referenced_features = dict() | ||
306 | for sfile in source_files: | ||
307 | referenced_features[sfile] = parse_source_file(sfile) | ||
308 | return referenced_features | ||
309 | |||
310 | |||
311 | def parse_source_file(sfile): | ||
312 | """Parse @sfile and return a list of referenced Kconfig features.""" | ||
267 | lines = [] | 313 | lines = [] |
314 | references = [] | ||
315 | |||
316 | if not os.path.exists(sfile): | ||
317 | return references | ||
318 | |||
268 | with open(sfile, "r") as stream: | 319 | with open(sfile, "r") as stream: |
269 | lines = stream.readlines() | 320 | lines = stream.readlines() |
270 | 321 | ||
@@ -275,9 +326,9 @@ def parse_source_file(sfile, referenced_features): | |||
275 | for feature in features: | 326 | for feature in features: |
276 | if not REGEX_FILTER_FEATURES.search(feature): | 327 | if not REGEX_FILTER_FEATURES.search(feature): |
277 | continue | 328 | continue |
278 | sfiles = referenced_features.get(feature, set()) | 329 | references.append(feature) |
279 | sfiles.add(sfile) | 330 | |
280 | referenced_features[feature] = sfiles | 331 | return references |
281 | 332 | ||
282 | 333 | ||
283 | def get_features_in_line(line): | 334 | def get_features_in_line(line): |
@@ -285,11 +336,35 @@ def get_features_in_line(line): | |||
285 | return REGEX_FEATURE.findall(line) | 336 | return REGEX_FEATURE.findall(line) |
286 | 337 | ||
287 | 338 | ||
288 | def parse_kconfig_file(kfile, defined_features, referenced_features): | 339 | def parse_kconfig_files(args): |
340 | """Parse kconfig files and return tuple of defined and references Kconfig | ||
341 | symbols. Note, @args is a tuple of a list of files and the @ignore | ||
342 | pattern.""" | ||
343 | kconfig_files = args[0] | ||
344 | ignore = args[1] | ||
345 | defined_features = [] | ||
346 | referenced_features = dict() | ||
347 | |||
348 | for kfile in kconfig_files: | ||
349 | defined, references = parse_kconfig_file(kfile) | ||
350 | defined_features.extend(defined) | ||
351 | if ignore and re.match(ignore, kfile): | ||
352 | # do not collect references for files that match the ignore pattern | ||
353 | continue | ||
354 | referenced_features[kfile] = references | ||
355 | return (defined_features, referenced_features) | ||
356 | |||
357 | |||
358 | def parse_kconfig_file(kfile): | ||
289 | """Parse @kfile and update feature definitions and references.""" | 359 | """Parse @kfile and update feature definitions and references.""" |
290 | lines = [] | 360 | lines = [] |
361 | defined = [] | ||
362 | references = [] | ||
291 | skip = False | 363 | skip = False |
292 | 364 | ||
365 | if not os.path.exists(kfile): | ||
366 | return defined, references | ||
367 | |||
293 | with open(kfile, "r") as stream: | 368 | with open(kfile, "r") as stream: |
294 | lines = stream.readlines() | 369 | lines = stream.readlines() |
295 | 370 | ||
@@ -300,7 +375,7 @@ def parse_kconfig_file(kfile, defined_features, referenced_features): | |||
300 | 375 | ||
301 | if REGEX_KCONFIG_DEF.match(line): | 376 | if REGEX_KCONFIG_DEF.match(line): |
302 | feature_def = REGEX_KCONFIG_DEF.findall(line) | 377 | feature_def = REGEX_KCONFIG_DEF.findall(line) |
303 | defined_features.add(feature_def[0]) | 378 | defined.append(feature_def[0]) |
304 | skip = False | 379 | skip = False |
305 | elif REGEX_KCONFIG_HELP.match(line): | 380 | elif REGEX_KCONFIG_HELP.match(line): |
306 | skip = True | 381 | skip = True |
@@ -308,6 +383,7 @@ def parse_kconfig_file(kfile, defined_features, referenced_features): | |||
308 | # ignore content of help messages | 383 | # ignore content of help messages |
309 | pass | 384 | pass |
310 | elif REGEX_KCONFIG_STMT.match(line): | 385 | elif REGEX_KCONFIG_STMT.match(line): |
386 | line = REGEX_QUOTES.sub("", line) | ||
311 | features = get_features_in_line(line) | 387 | features = get_features_in_line(line) |
312 | # multi-line statements | 388 | # multi-line statements |
313 | while line.endswith("\\"): | 389 | while line.endswith("\\"): |
@@ -319,9 +395,9 @@ def parse_kconfig_file(kfile, defined_features, referenced_features): | |||
319 | if REGEX_NUMERIC.match(feature): | 395 | if REGEX_NUMERIC.match(feature): |
320 | # ignore numeric values | 396 | # ignore numeric values |
321 | continue | 397 | continue |
322 | paths = referenced_features.get(feature, set()) | 398 | references.append(feature) |
323 | paths.add(kfile) | 399 | |
324 | referenced_features[feature] = paths | 400 | return defined, references |
325 | 401 | ||
326 | 402 | ||
327 | if __name__ == "__main__": | 403 | if __name__ == "__main__": |