File indexing completed on 2024-05-12 16:39:34

0001 #!/usr/bin/python -Qwarnall
0002 
0003 #
0004 # This script checks the source tree for errors on a file and directory level:
0005 #  * File level
0006 #     - Checks that the include guard is the same in the #ifndef and #define
0007 #       It does not check if the include guard is correct in relation to the filename.
0008 #  * Directory level:
0009 #     - Checks for duplicate include guards between different files
0010 #     - Checks for different export macros and export files in the same directory
0011 #
0012 
0013 import sys
0014 import os
0015 import string
0016 import getopt
0017 import re
0018 import fnmatch
0019 
0020 
0021 # Global variables
0022 dryrun = False    # Maybe the default should be True...
0023 pattern = "*"
0024 recursive = True
0025 verbose = False
0026 
0027 # Stores include guards in .h files.
0028 includeGuards = {}
0029 
0030 # Stores the export macros for each directory
0031 exportMacros = {}
0032 
0033 
0034 # ----------------------------------------------------------------
0035 #                         Individual actions
0036 
0037 
0038 def checkContents(dirName, filename, contents):
0039     global verbose
0040     global includeGuards
0041     global exportMacros
0042 
0043     # Compile a regex that matches an ifndef statement.
0044     ifndefPattern = '^(\\#ifndef\\s*)([^\\s]*)(\\s*)'
0045     ifndefRegex = re.compile(ifndefPattern)
0046 
0047     # Compile a regex that matches an define statement.
0048     definePattern = '^(\\#define\\s*)([^\\s]*)(\\s*)'
0049     defineRegex = re.compile(definePattern)
0050 
0051     # Compile a regex that matches a class definition with an export
0052     classPattern = '^(class\\s*)([a-zA-Z0-9_]*[Ee][Xx][Pp][Oo][Rr][Tt])?(\\s*)([a-zA-Z0-9_]*)(.*)'
0053     classRegex = re.compile(classPattern)
0054 
0055     lineNo = 1
0056     ifndefFound = False
0057     defineFound = False
0058     ifndefLine = -1
0059     defineLine = -1
0060     includeGuard = ""
0061     for line in contents:
0062         #print "input: ", line
0063 
0064         # ----------------------------------------------------------------
0065         # Check include guards
0066 
0067         # Check if this line is an #ifndef line. Only check the first one.
0068         # if it is, and it's the first one, assume that it's the include guard and store it.
0069         if not ifndefFound:
0070             ifndefs = ifndefRegex.findall(line)
0071             #print "ifndefs: ", ifndefs
0072             if len(ifndefs) > 0:
0073                 ifndefFound = True
0074                 ifndefLine = lineNo
0075 
0076                 ifndef = ifndefs[0]
0077                 # Here we know it's an include statement.  We should only have found one hit.
0078                 #  ifndef[0] = "#ifndef "
0079                 #  ifndef[1] = the symbol the ifndef statement
0080                 #  ifndef[2] = the rest of the line
0081                 includeGuard = ifndef[1]
0082         
0083         # Check if this line is a #define line. Only check the first one after the #ifndef is found
0084         if ifndefFound and not defineFound:
0085             defines = defineRegex.findall(line)
0086             #print "defines: ", defines
0087             if len(defines) > 0:
0088                 defineFound = True
0089                 defineLine = lineNo
0090 
0091                 define = defines[0]
0092                 # Here we know it's an include statement.  We should only have found one hit.
0093                 #  define[0] = "#define "
0094                 #  define[1] = the symbol the define statement
0095                 #  define[2] = the rest of the line
0096                 if define[1] == includeGuard and lineNo == ifndefLine + 1:
0097                     includeGuards[filename] = includeGuard
0098                 else:
0099                     sys.stderr.write(filename + ":" + str(ifndefLine) + ": Faulty include guard\n")
0100 
0101         # ----------------------------------------------------------------
0102         # Check exports
0103 
0104         classes = classRegex.findall(line)
0105         #print "defines: ", defines
0106         if len(classes) > 0:
0107             classLine = classes[0]
0108             # Here we know it's an include statement.  We should only have found one hit.
0109             #  classLine[0] = "class "
0110             #  classLine[1] = export macro
0111             #  classLine[2] = spaces
0112             #  classLine[3] = class name
0113             #  classLine[4] = rest of the line
0114             if classLine[1] != "":
0115                 #print classLine
0116                 exportMacro = classLine[1]
0117                 if exportMacros.has_key(dirName):
0118                     # Append the export macro name if it's not already there.
0119                     if not exportMacro in exportMacros[dirName]:
0120                         exportMacros[dirName].append(exportMacro)
0121                 else:
0122                     exportMacros[dirName] = [exportMacro]
0123 
0124         # end of the loop over the lines
0125         lineNo = lineNo + 1
0126 
0127     if not ifndefFound or not defineFound:
0128         sys.stderr.write(filename + ": No include guard\n")
0129 
0130     return
0131 
0132 
0133 # ----------------------------------------------------------------
0134 
0135 
0136 def handleFile(dir, name, actions):
0137     global dryrun, verbose
0138 
0139     # We only handle .h files for now.
0140     if name[-2:] != ".h":
0141         return
0142 
0143     if verbose:
0144         sys.stderr.write(name + ":\n")
0145 
0146     #print "doing: ", name
0147 
0148     # Read the contents of the file into a list, one item per line.
0149     infile = open(name)
0150     contents = infile.readlines()
0151     infile.close()
0152 
0153     checkContents(dir, name, contents)
0154 
0155 
0156 def traverseTree(dir, actions, names):
0157     global recursive, verbose, pattern
0158 
0159     # We could also use os.walk()
0160     for name in names:
0161         if dir == "":
0162             fullname = name
0163         else:
0164             fullname = dir + "/" + name
0165 
0166         if not os.path.exists(fullname):
0167             sys.stderr.write(fullname + ": unknown file or directory\n")
0168             continue
0169 
0170         if os.path.isdir(fullname):
0171             if recursive:
0172                 traverseTree(fullname, actions, os.listdir(fullname))
0173             # Ignore all directories if not in recursive mode
0174         else:
0175             if fnmatch.fnmatch(name, pattern):
0176                 handleFile(dir, fullname, actions)
0177 
0178 
0179 def report():
0180     global includeGuards
0181     global exportMacros
0182 
0183     print
0184     print "SUMMARY REPORT"
0185     globalProblems = False
0186 
0187     # Check for duplicate include guards.
0188     guardFiles = {}
0189     for file in includeGuards.keys():
0190         #print file
0191         guard = includeGuards[file]
0192         #print "guard:", guard
0193         if guardFiles.has_key(guard):
0194             guardFiles[guard].append(file)
0195         else:
0196             guardFiles[guard] = [file]
0197         #print guardFiles
0198 
0199     for guard in guardFiles.keys():
0200         if len(guardFiles[guard]) > 1:
0201             globalProblems = True
0202             sys.stderr.write('include guard "' + guard + '" is duplicated in the following files:\n')
0203             for file in guardFiles[guard]:
0204                 sys.stderr.write('  ' + file + '\n')
0205 
0206     #print exportMacros
0207     for key in exportMacros.keys():
0208         if len(exportMacros[key]) > 1:
0209             globalProblems = True
0210             sys.stderr.write('directory "' + key + '" has the following export macros:\n')
0211             for macro in exportMacros[key]:
0212                 sys.stderr.write('  ' + macro + '\n')
0213             
0214 
0215     if not globalProblems:
0216         print "  No global problems"
0217 
0218     return
0219 
0220 
0221 # ================================================================
0222 
0223 
0224 def usage(errormsg=""):
0225     if errormsg:
0226         print "Error:", sys.argv[0] + ":", errormsg, "\n"
0227     else:
0228         print "Check for errors in the source tree.\n"
0229 
0230     print """usage: chksrc.py [options] [files]
0231     options:
0232 
0233         -h --help       print this help and exit immediately
0234         -v --verbse     print verbose output
0235 
0236     files:
0237         directories to be checked
0238 
0239     examples:
0240         chksrc.py libs
0241         chksrc.py .
0242 """
0243     sys.exit(0)
0244 
0245 
0246 def main():
0247     global verbose
0248     allActions = ["includeguards", "export", "all"]
0249 
0250     try :
0251         opts, params = getopt.getopt(sys.argv[1:], "a:hv" ,
0252                                      ["actions=", "help", "verbose"])
0253     except getopt.GetoptError:
0254         usage("unknown options")
0255     #print "opts:", opts
0256     #print "params:", params
0257 
0258     #actions = []
0259     actions = ["all"]
0260     for opt, param in opts:
0261         #print opt, param
0262         if False and opt in ("-a" , "--actions"):  #disable -a for now
0263             actions = string.split(param, ",")
0264             #print "actions: ", actions
0265             for a in actions:
0266                 if not a in allActions:
0267                     usage("unknown action: " + a + "\n\n")
0268             if "all" in actions:
0269                 actions = allActions[:-1] # Remove "all", which is a meta action.
0270                 
0271         elif opt in ("-h" , "--help"):
0272             usage()
0273         elif opt in ("-v" , "--verbose"):
0274             verbose = True
0275 
0276     if actions == []:
0277         usage("no actions defined")
0278 
0279     # Do the actual work
0280     traverseTree("", actions, params)
0281     report()
0282 
0283     return 0
0284 
0285 if __name__ == '__main__':
0286     main()