diff options
Diffstat (limited to 'scripts')
| -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): |
