Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 3 | """Find Kconfig identifiers that are referenced but not defined.""" |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 4 | |
Valentin Rothberg | 208d511 | 2015-02-25 15:15:23 +0100 | [diff] [blame^] | 5 | # (c) 2014-2015 Valentin Rothberg <Valentin.Rothberg@lip6.fr> |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 6 | # (c) 2014 Stefan Hengelein <stefan.hengelein@fau.de> |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 7 | # |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 8 | # Licensed under the terms of the GNU GPL License version 2 |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 9 | |
| 10 | |
| 11 | import os |
| 12 | import re |
| 13 | from subprocess import Popen, PIPE, STDOUT |
| 14 | |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 15 | |
| 16 | # regex expressions |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 17 | OPERATORS = r"&|\(|\)|\||\!" |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 18 | FEATURE = r"(?:\w*[A-Z0-9]\w*){2,}" |
| 19 | DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*" |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 20 | EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+" |
| 21 | STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 22 | SOURCE_FEATURE = r"(?:\W|\b)+[D]{,1}CONFIG_(" + FEATURE + r")" |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 23 | |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 24 | # regex objects |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 25 | REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") |
| 26 | REGEX_FEATURE = re.compile(r"(" + FEATURE + r")") |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 27 | REGEX_SOURCE_FEATURE = re.compile(SOURCE_FEATURE) |
| 28 | REGEX_KCONFIG_DEF = re.compile(DEF) |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 29 | REGEX_KCONFIG_EXPR = re.compile(EXPR) |
| 30 | REGEX_KCONFIG_STMT = re.compile(STMT) |
| 31 | REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") |
| 32 | REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") |
| 33 | |
| 34 | |
| 35 | def main(): |
| 36 | """Main function of this module.""" |
| 37 | source_files = [] |
| 38 | kconfig_files = [] |
| 39 | defined_features = set() |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 40 | referenced_features = dict() # {feature: [files]} |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 41 | |
| 42 | # use 'git ls-files' to get the worklist |
| 43 | pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True) |
| 44 | (stdout, _) = pop.communicate() # wait until finished |
| 45 | if len(stdout) > 0 and stdout[-1] == "\n": |
| 46 | stdout = stdout[:-1] |
| 47 | |
| 48 | for gitfile in stdout.rsplit("\n"): |
Valentin Rothberg | 208d511 | 2015-02-25 15:15:23 +0100 | [diff] [blame^] | 49 | if ".git" in gitfile or "ChangeLog" in gitfile or \ |
| 50 | ".log" in gitfile or os.path.isdir(gitfile) or \ |
| 51 | gitfile.startswith("tools/"): |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 52 | continue |
| 53 | if REGEX_FILE_KCONFIG.match(gitfile): |
| 54 | kconfig_files.append(gitfile) |
| 55 | else: |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 56 | # all non-Kconfig files are checked for consistency |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 57 | source_files.append(gitfile) |
| 58 | |
| 59 | for sfile in source_files: |
| 60 | parse_source_file(sfile, referenced_features) |
| 61 | |
| 62 | for kfile in kconfig_files: |
| 63 | parse_kconfig_file(kfile, defined_features, referenced_features) |
| 64 | |
| 65 | print "Undefined symbol used\tFile list" |
| 66 | for feature in sorted(referenced_features): |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 67 | # filter some false positives |
| 68 | if feature == "FOO" or feature == "BAR" or \ |
| 69 | feature == "FOO_BAR" or feature == "XXX": |
| 70 | continue |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 71 | if feature not in defined_features: |
| 72 | if feature.endswith("_MODULE"): |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 73 | # avoid false positives for kernel modules |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 74 | if feature[:-len("_MODULE")] in defined_features: |
| 75 | continue |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 76 | files = referenced_features.get(feature) |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 77 | print "%s\t%s" % (feature, ", ".join(files)) |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 78 | |
| 79 | |
| 80 | def parse_source_file(sfile, referenced_features): |
| 81 | """Parse @sfile for referenced Kconfig features.""" |
| 82 | lines = [] |
| 83 | with open(sfile, "r") as stream: |
| 84 | lines = stream.readlines() |
| 85 | |
| 86 | for line in lines: |
| 87 | if not "CONFIG_" in line: |
| 88 | continue |
| 89 | features = REGEX_SOURCE_FEATURE.findall(line) |
| 90 | for feature in features: |
| 91 | if not REGEX_FILTER_FEATURES.search(feature): |
| 92 | continue |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 93 | sfiles = referenced_features.get(feature, set()) |
| 94 | sfiles.add(sfile) |
| 95 | referenced_features[feature] = sfiles |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 96 | |
| 97 | |
| 98 | def get_features_in_line(line): |
| 99 | """Return mentioned Kconfig features in @line.""" |
| 100 | return REGEX_FEATURE.findall(line) |
| 101 | |
| 102 | |
| 103 | def parse_kconfig_file(kfile, defined_features, referenced_features): |
| 104 | """Parse @kfile and update feature definitions and references.""" |
| 105 | lines = [] |
| 106 | skip = False |
| 107 | |
| 108 | with open(kfile, "r") as stream: |
| 109 | lines = stream.readlines() |
| 110 | |
| 111 | for i in range(len(lines)): |
| 112 | line = lines[i] |
| 113 | line = line.strip('\n') |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 114 | line = line.split("#")[0] # ignore comments |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 115 | |
| 116 | if REGEX_KCONFIG_DEF.match(line): |
| 117 | feature_def = REGEX_KCONFIG_DEF.findall(line) |
| 118 | defined_features.add(feature_def[0]) |
| 119 | skip = False |
| 120 | elif REGEX_KCONFIG_HELP.match(line): |
| 121 | skip = True |
| 122 | elif skip: |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 123 | # ignore content of help messages |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 124 | pass |
| 125 | elif REGEX_KCONFIG_STMT.match(line): |
| 126 | features = get_features_in_line(line) |
Valentin Rothberg | cc641d5 | 2014-11-08 20:56:35 +0100 | [diff] [blame] | 127 | # multi-line statements |
Valentin Rothberg | 24fe1f0 | 2014-09-27 16:30:45 +0200 | [diff] [blame] | 128 | while line.endswith("\\"): |
| 129 | i += 1 |
| 130 | line = lines[i] |
| 131 | line = line.strip('\n') |
| 132 | features.extend(get_features_in_line(line)) |
| 133 | for feature in set(features): |
| 134 | paths = referenced_features.get(feature, set()) |
| 135 | paths.add(kfile) |
| 136 | referenced_features[feature] = paths |
| 137 | |
| 138 | |
| 139 | if __name__ == "__main__": |
| 140 | main() |