Skip to content

Commit

Permalink
checkkconfigsymbols.py: make it Git aware
Browse files Browse the repository at this point in the history
The script now supports to check a specified commit or a specified range
of commits (i.e., commit1..commit2).  Developers and maintainers are
encouraged to use this functionality before sending or merging patches
to avoid potential bugs and to keep the code, documentation, etc. clean.

This patch adds the following options to the script:

 -c COMMIT, --commit=COMMIT
                  Check if the specified commit (hash) introduces
                  undefined Kconfig symbols.

 -d DIFF, --diff=DIFF
                  Diff undefined symbols between two commits.  The input
                  format bases on Git log's 'commmit1..commit2'.

  --force         Reset current Git tree even when it's dirty.

Note that the first two options require to 'git reset --hard' the user's
Git tree.  This hard reset is necessary to keep the script fast, but it
can lead to the loss of uncommitted data.  Hence, the script aborts in
case it is executed in a dirty tree.  It won't abort if '--force' is
passed.

If neither -c nor -d is specified, the script defaults to check the
entire local tree (i.e., the previous behavior).

Signed-off-by: Valentin Rothberg <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
vrothberg authored and gregkh committed Mar 16, 2015
1 parent da2ff52 commit b1a3f24
Showing 1 changed file with 132 additions and 6 deletions.
138 changes: 132 additions & 6 deletions scripts/checkkconfigsymbols.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

"""Find Kconfig identifiers that are referenced but not defined."""
"""Find Kconfig symbols that are referenced but not defined."""

# (c) 2014-2015 Valentin Rothberg <[email protected]>
# (c) 2014 Stefan Hengelein <[email protected]>
Expand All @@ -10,7 +10,9 @@

import os
import re
import sys
from subprocess import Popen, PIPE, STDOUT
from optparse import OptionParser


# regex expressions
Expand All @@ -32,16 +34,140 @@
REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$")


def parse_options():
"""The user interface of this module."""
usage = "%prog [options]\n\n" \
"Run this tool to detect Kconfig symbols that are referenced but " \
"not defined in\nKconfig. The output of this tool has the " \
"format \'Undefined symbol\\tFile list\'\n\n" \
"If no option is specified, %prog will default to check your\n" \
"current tree. Please note that specifying commits will " \
"\'git reset --hard\'\nyour current tree! You may save " \
"uncommitted changes to avoid losing data."

parser = OptionParser(usage=usage)

parser.add_option('-c', '--commit', dest='commit', action='store',
default="",
help="Check if the specified commit (hash) introduces "
"undefined Kconfig symbols.")

parser.add_option('-d', '--diff', dest='diff', action='store',
default="",
help="Diff undefined symbols between two commits. The "
"input format bases on Git log's "
"\'commmit1..commit2\'.")

parser.add_option('', '--force', dest='force', action='store_true',
default=False,
help="Reset current Git tree even when it's dirty.")

(opts, _) = parser.parse_args()

if opts.commit and opts.diff:
sys.exit("Please specify only one option at once.")

if opts.diff and not re.match(r"^[\w\-\.]+\.\.[\w\-\.]+$", opts.diff):
sys.exit("Please specify valid input in the following format: "
"\'commmit1..commit2\'")

if opts.commit or opts.diff:
if not opts.force and tree_is_dirty():
sys.exit("The current Git tree is dirty (see 'git status'). "
"Running this script may\ndelete important data since it "
"calls 'git reset --hard' for some performance\nreasons. "
" Please run this script in a clean Git tree or pass "
"'--force' if you\nwant to ignore this warning and "
"continue.")

return opts


def main():
"""Main function of this module."""
opts = parse_options()

if opts.commit or opts.diff:
head = get_head()

# get commit range
commit_a = None
commit_b = None
if opts.commit:
commit_a = opts.commit + "~"
commit_b = opts.commit
elif opts.diff:
split = opts.diff.split("..")
commit_a = split[0]
commit_b = split[1]
undefined_a = {}
undefined_b = {}

# get undefined items before the commit
execute("git reset --hard %s" % commit_a)
undefined_a = check_symbols()

# get undefined items for the commit
execute("git reset --hard %s" % commit_b)
undefined_b = check_symbols()

# report cases that are present for the commit but not before
for feature in undefined_b:
# feature has not been undefined before
if not feature in undefined_a:
files = undefined_b.get(feature)
print "%s\t%s" % (feature, ", ".join(files))
# check if there are new files that reference the undefined feature
else:
files = undefined_b.get(feature) - undefined_a.get(feature)
if files:
print "%s\t%s" % (feature, ", ".join(files))

# reset to head
execute("git reset --hard %s" % head)

# default to check the entire tree
else:
undefined = check_symbols()
for feature in undefined:
files = undefined.get(feature)


def execute(cmd):
"""Execute %cmd and return stdout. Exit in case of error."""
pop = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True)
(stdout, _) = pop.communicate() # wait until finished
if pop.returncode != 0:
sys.exit(stdout)
return stdout


def tree_is_dirty():
"""Return true if the current working tree is dirty (i.e., if any file has
been added, deleted, modified, renamed or copied but not committed)."""
stdout = execute("git status --porcelain")
for line in stdout:
if re.findall(r"[URMADC]{1}", line[:2]):
return True
return False


def get_head():
"""Return commit hash of current HEAD."""
stdout = execute("git rev-parse HEAD")
return stdout.strip('\n')


def check_symbols():
"""Find undefined Kconfig symbols and return a dict with the symbol as key
and a list of referencing files as value."""
source_files = []
kconfig_files = []
defined_features = set()
referenced_features = dict() # {feature: [files]}

# use 'git ls-files' to get the worklist
pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True)
(stdout, _) = pop.communicate() # wait until finished
stdout = execute("git ls-files")
if len(stdout) > 0 and stdout[-1] == "\n":
stdout = stdout[:-1]

Expand All @@ -62,7 +188,7 @@ def main():
for kfile in kconfig_files:
parse_kconfig_file(kfile, defined_features, referenced_features)

print "Undefined symbol used\tFile list"
undefined = {} # {feature: [files]}
for feature in sorted(referenced_features):
# filter some false positives
if feature == "FOO" or feature == "BAR" or \
Expand All @@ -73,8 +199,8 @@ def main():
# avoid false positives for kernel modules
if feature[:-len("_MODULE")] in defined_features:
continue
files = referenced_features.get(feature)
print "%s\t%s" % (feature, ", ".join(files))
undefined[feature] = referenced_features.get(feature)
return undefined


def parse_source_file(sfile, referenced_features):
Expand Down

0 comments on commit b1a3f24

Please sign in to comment.