#!/usr/bin/env python """Find Kconfig identifieres that are referenced but not defined.""" # Copyright (C) 2014 Valentin Rothberg # Copyright (C) 2014 Stefan Hengelein # # This program is free software; you can redistribute it and/or modify it # under the terms and conditions of the GNU General Public License, # version 2, as published by the Free Software Foundation. # # This program is distributed in the hope it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. import os import re from subprocess import Popen, PIPE, STDOUT # REGEX EXPRESSIONS OPERATORS = r"&|\(|\)|\||\!" FEATURE = r"\w*[A-Z]{1}\w*" CONFIG_DEF = r"^\s*(?:menu){,1}config\s+(" + FEATURE + r")\s*" EXPR = r"(?:" + OPERATORS + r"|\s|" + FEATURE + r")+" STMT = r"^\s*(?:if|select|depends\s+on)\s+" + EXPR # REGEX OBJECTS REGEX_FILE_KCONFIG = re.compile(r".*Kconfig[\.\w+\-]*$") REGEX_FEATURE = re.compile(r"(" + FEATURE + r")") REGEX_SOURCE_FEATURE = re.compile(r"(?:D|\W|\b)+CONFIG_(" + FEATURE + r")") REGEX_KCONFIG_DEF = re.compile(CONFIG_DEF) REGEX_KCONFIG_EXPR = re.compile(EXPR) REGEX_KCONFIG_STMT = re.compile(STMT) REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") def main(): """Main function of this module.""" source_files = [] kconfig_files = [] defined_features = set() referenced_features = dict() # 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 if len(stdout) > 0 and stdout[-1] == "\n": stdout = stdout[:-1] for gitfile in stdout.rsplit("\n"): if ".git" in gitfile or "ChangeLog" in gitfile or \ os.path.isdir(gitfile): continue if REGEX_FILE_KCONFIG.match(gitfile): kconfig_files.append(gitfile) else: # All non-Kconfig files are checked for consistency source_files.append(gitfile) for sfile in source_files: parse_source_file(sfile, referenced_features) for kfile in kconfig_files: parse_kconfig_file(kfile, defined_features, referenced_features) print "Undefined symbol used\tFile list" for feature in sorted(referenced_features): if feature not in defined_features: if feature.endswith("_MODULE"): # Avoid false positives for kernel modules if feature[:-len("_MODULE")] in defined_features: continue if "FOO" in feature or "BAR" in feature: continue files = referenced_features.get(feature) print "%s:\t%s" % (feature, ", ".join(files)) def parse_source_file(sfile, referenced_features): """Parse @sfile for referenced Kconfig features.""" lines = [] with open(sfile, "r") as stream: lines = stream.readlines() for line in lines: if not "CONFIG_" in line: continue features = REGEX_SOURCE_FEATURE.findall(line) for feature in features: if not REGEX_FILTER_FEATURES.search(feature): continue paths = referenced_features.get(feature, set()) paths.add(sfile) referenced_features[feature] = paths def get_features_in_line(line): """Return mentioned Kconfig features in @line.""" return REGEX_FEATURE.findall(line) def parse_kconfig_file(kfile, defined_features, referenced_features): """Parse @kfile and update feature definitions and references.""" lines = [] skip = False with open(kfile, "r") as stream: lines = stream.readlines() for i in range(len(lines)): line = lines[i] line = line.strip('\n') line = line.split("#")[0] # Ignore Kconfig comments if REGEX_KCONFIG_DEF.match(line): feature_def = REGEX_KCONFIG_DEF.findall(line) defined_features.add(feature_def[0]) skip = False elif REGEX_KCONFIG_HELP.match(line): skip = True elif skip: # Ignore content of help messages pass elif REGEX_KCONFIG_STMT.match(line): features = get_features_in_line(line) # Multi-line statements while line.endswith("\\"): i += 1 line = lines[i] line = line.strip('\n') features.extend(get_features_in_line(line)) for feature in set(features): paths = referenced_features.get(feature, set()) paths.add(kfile) referenced_features[feature] = paths if __name__ == "__main__": main()