317 lines
13 KiB
Python
317 lines
13 KiB
Python
import sys
|
|
import os.path
|
|
import re
|
|
import time
|
|
import optparse
|
|
|
|
ExtensionsToCheck = [".cpp", ".h", ".hpp", ".c"]
|
|
|
|
IgnoreDirectories = ["created_chm", "devil", "zlib", "build", "dataacc", "drvtool", "forceinclude", "actiontree", "jpeg", "3rdParty"]
|
|
|
|
IgnoreNames = [r"resource\.h", r"stdafx\.h", r"forceinclude\.h", r"combined_.*\.cpp"]
|
|
|
|
RageDir = os.getenv("RAGE_ROOT_FOLDER", "C:/soft/rage")
|
|
|
|
RageSourceDirs = ["base/src", "suite/src", "migrate/src", "script/src", "base/samples", "suite/samples", "migrate/samples", "script/samples"]
|
|
|
|
def any(l):
|
|
for i in l:
|
|
if i: return True
|
|
return False
|
|
|
|
def getFilesWithExtension(dirList):
|
|
fileList = []
|
|
for i in dirList:
|
|
if os.path.isfile(i):
|
|
fileList.append(i)
|
|
continue
|
|
for root, dirs, files in os.walk(i):
|
|
# remove any subdirs that are in IgnoreDirectories
|
|
dirs[:] = [x for x in dirs if x not in IgnoreDirectories]
|
|
shortFileList = [x for x in files if not any([re.match(y, x) for y in IgnoreNames])]
|
|
fileList.extend([os.path.abspath(os.path.join(root, x)) for x in shortFileList if os.path.splitext(x)[1] in ExtensionsToCheck])
|
|
return fileList
|
|
|
|
def checkInRage(moduleName, fileName):
|
|
global RageDir
|
|
global RageSourceDirs
|
|
|
|
if not fileName.endswith(".h"):
|
|
return False
|
|
# match = re.match('#include (<|")(\w+)/(\w+)("|>)')
|
|
# if not match:
|
|
# return False
|
|
# modulePart = match.group(2)
|
|
# filePart = match.group(3)
|
|
for dir in RageSourceDirs:
|
|
if os.path.exists(os.path.join(RageDir, dir, moduleName, fileName)):
|
|
return True
|
|
return False
|
|
|
|
ErrorList = []
|
|
NumErrors = 0
|
|
|
|
def addError(file, line, string):
|
|
global ErrorList, NumErrors
|
|
ErrorList.append(file + "(" + str(line) + "): error: " + string)
|
|
NumErrors += 1
|
|
|
|
def addWarning(file, line, string):
|
|
global ErrorList, NumErrors
|
|
ErrorList.append(file + "(" + str(line) + "): warning: " + string)
|
|
NumErrors += 1
|
|
|
|
def addComment(string):
|
|
global ErrorList
|
|
ErrorList.append(string)
|
|
|
|
def main(args):
|
|
global ErrorList, NumErrors
|
|
global RageDir
|
|
|
|
sys.stderr.write("Counting files\n")
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-i", "--includeorder", dest="includeOrder", action="store_true", default=False, help="Check the order of the #includes")
|
|
parser.add_option("-d", "--ragedir", dest="rageDir", help="Specify the root of the rage tree")
|
|
parser.add_option("-r", "--release", dest="release", action="store", type="int", help="Warn when a comment containing 'RAGE_RELEASE_xxx' refers to an old release")
|
|
|
|
(options, globs) = parser.parse_args();
|
|
|
|
if options.rageDir:
|
|
RageDir = options.rageDir
|
|
|
|
fileList = getFilesWithExtension(globs)
|
|
|
|
yearStr = time.strftime("%Y", time.localtime())
|
|
|
|
sys.stderr.write("Checking " + str(len(fileList)) + " files")
|
|
|
|
for (fileNum, fileName) in enumerate(fileList):
|
|
if fileNum % 100 == 0:
|
|
sys.stderr.write(".")
|
|
sys.stderr.flush()
|
|
f = file(fileName, "r")
|
|
|
|
dirPart, filePart = os.path.split(fileName)
|
|
rootDirPart, parentDirPart = os.path.split(dirPart)
|
|
|
|
protectFile = filePart.upper().replace('.', "_")
|
|
# allow underscores between any characters in protectFile
|
|
protectFile = "_?".join(list(protectFile))
|
|
protectName = parentDirPart.upper() + "_" + protectFile
|
|
niceProtectName = parentDirPart.upper() + "_" + filePart.upper().replace('.', "_")
|
|
|
|
print "Checking ", parentDirPart, filePart
|
|
|
|
# The top N lines in a file must match all of these regexps in order.
|
|
topOfFileTests = [
|
|
r"//\s*$",
|
|
r"// " + parentDirPart + "/" + filePart + "\s*$",
|
|
r"//\s*$",
|
|
r"// Copyright \(C\) (199|200)\d-" + yearStr + " Rockstar Games\.\s+All Rights Reserved\.\s*$",
|
|
r"//\s*$"
|
|
r"\s*$"]
|
|
|
|
expectedLines = [
|
|
"//",
|
|
"// " + parentDirPart + "/" + filePart,
|
|
"//",
|
|
"// Copyright (C) <orig year>-" + yearStr + " Rockstar Games. All Rights Reserved.",
|
|
"//",
|
|
"<empty line>",
|
|
]
|
|
|
|
# Regexps for header files only. Each header has to match each of the regexps somewhere.
|
|
# Format is (regexp, error_msg, lineno) where lineno is the line that the code would normally be found on
|
|
headerTests = [
|
|
(r"^((//\s*RAGE_QA:.*\bNO_INCLUDE_GUARD\b.*)|(#ifndef " + protectName + r")|(#if !defined\(" + protectName + r"\)))\s*$", "Include guard not found or incorrect (should be #ifndef " + niceProtectName + ")", 7),
|
|
(r"^((//\s*RAGE_QA:.*\bNO_INCLUDE_GUARD\b.*)|(#define " + protectName + r"))\s*$", "Include guard not found or incorrect (should be #define " + niceProtectName + ")", 8),
|
|
(r"^((//\s*RAGE_QA:.*\bNO_NAMESPACE_RAGE\b.*)|(namespace rage\s*{?))\s*$", "\"namespace rage\" not found.", 10)]
|
|
|
|
# Regexps that cause a header file to fail.
|
|
# Format is (regexp_failure, regexp_mitigation, error_msg) where
|
|
# regexp_failure is a regexp that, if it matches, causes a file
|
|
# to fail, unless regexp_mitigation is found somewhere in that file
|
|
# Use None for regexp_mitigation if you the error should always happen
|
|
headerFailTests = [
|
|
(re.compile(r"\s*using\s+namespace\s+rage\s*;\s*"),
|
|
re.compile(r"//\s*RAGE_QA:.*\bUSING_NAMESPACE_RAGE\b.*"),
|
|
'"using namespace rage;" not recommended for rage header files'
|
|
)
|
|
]
|
|
|
|
numLines = len(topOfFileTests)
|
|
|
|
lines = f.readlines()
|
|
f.close()
|
|
|
|
fullFile = "".join(lines)
|
|
|
|
isHeaderFile = os.path.splitext(filePart)[1] in (".h", ".hpp")
|
|
|
|
if len(lines) < numLines:
|
|
addError(fileName, 0, "Too few lines in header")
|
|
continue
|
|
|
|
# the first N lines need to match, line for line, the regexs in topOfFileTests[]
|
|
for (line, test, lineNo) in zip(lines, topOfFileTests, range(numLines)):
|
|
if not re.match(test, line):
|
|
addError(fileName, lineNo+1, "Incorrect RAGE header format, expecting \"%s\"" % expectedLines[lineNo])
|
|
break
|
|
|
|
# regexp that checks for misformatted #includes
|
|
includeLineRE = re.compile('^#include\s+"(.*?)"')
|
|
|
|
# regexp that checks for a RAGE release comment
|
|
rageReleaseRE = re.compile('RAGE_RELEASE_(\d+)')
|
|
|
|
# do a line by line check in the file
|
|
for (lineNo, line) in enumerate(lines):
|
|
match = includeLineRE.match(line)
|
|
if match:
|
|
inclName = match.group(1)
|
|
# if not inclName.islower():
|
|
# if not ("RAGE_QA:" in line and "INCLUDE_OK" in line):
|
|
# addError(fileName, lineNo+1, "Uppercase chars found in #include")
|
|
if '\\' in inclName:
|
|
if not ("RAGE_QA:" in line and "INCLUDE_OK" in line):
|
|
addError(fileName, lineNo+1, "Backslashes found in #include path")
|
|
match = rageReleaseRE.search(line)
|
|
if match:
|
|
rageVersion = int(match.group(1))
|
|
if rageVersion <= options.release:
|
|
addWarning(fileName, lineNo+1, "Comment refers to an old release number")
|
|
if isHeaderFile:
|
|
for failTest in headerFailTests:
|
|
if re.match(failTest[0], line):
|
|
if not failTest[1] or not re.search(failTest[1], fullFile, re.MULTILINE):
|
|
addError(fileName, lineNo+1, failTest[2])
|
|
|
|
|
|
# header files need to have special tests, they need an include guard, a "namespace rage", and no "using namespace rage"
|
|
if isHeaderFile:
|
|
for test in headerTests:
|
|
if not re.search(test[0], fullFile, re.MULTILINE):
|
|
addError(fileName, test[2], test[1])
|
|
|
|
|
|
# check that #includes are in the correct order
|
|
|
|
# the order is:
|
|
# .cpp files include corresponding .h file
|
|
# .h files from the same module (alphabetical order, w/ no slashes)
|
|
# .h files from other rage modules (module, single slash, filename. with quotes)
|
|
# external #includes or system includes
|
|
|
|
includeOkRE = re.compile("RAGE_QA:.*INCLUDE_OK")
|
|
|
|
includes = [(x.strip(), num) for (num, x) in enumerate(lines) if (x.startswith("#include") and not includeOkRE.search(x))]
|
|
selfIncludes = []
|
|
moduleIncludes = []
|
|
rageIncludes = []
|
|
externalIncludes = []
|
|
parserIncludes = []
|
|
|
|
for (incl, lineNum) in includes:
|
|
inclModuleName = ""
|
|
inclFileName = ""
|
|
inclToken = ""
|
|
match = re.match('#include (<|")(\w+)/((\w|\.)+)("|>)', incl)
|
|
if match:
|
|
inclToken = match.group(1)
|
|
inclModuleName = match.group(2)
|
|
inclFileName = match.group(3)
|
|
|
|
match = re.match('#include (<|")((\w|\.)+)("|>)', incl)
|
|
if match:
|
|
inclModuleName = ""
|
|
inclToken = match.group(1)
|
|
inclFileName = match.group(2)
|
|
|
|
if inclModuleName == parentDirPart:
|
|
addError(fileName, lineNum, "#includes from the same module shouldn't contain the module name")
|
|
|
|
inclList = moduleIncludes
|
|
if inclFileName == filePart.replace(".cpp", ".h"):
|
|
inclList = selfIncludes
|
|
|
|
inclList.append(incl.replace(parentDirPart + "/", ""))
|
|
|
|
elif inclModuleName == "":
|
|
# either external or in current module
|
|
if os.path.exists(os.path.join(dirPart, inclFileName)) or checkInRage(parentDirPart, inclFileName):
|
|
|
|
# find out which list to add incl to
|
|
inclList = moduleIncludes
|
|
if inclFileName.lower() == filePart.lower().replace(".cpp", ".h"):
|
|
inclList = selfIncludes
|
|
elif inclFileName.lower() == filePart.lower().replace(".cpp", "_parser.h"):
|
|
inclList = parserIncludes
|
|
|
|
# add it to the list we found (changing <> to "" if necessary)
|
|
if inclToken == "<":
|
|
addError(fileName, lineNum, "#includes for rage modules should not use < and >")
|
|
inclList.append(re.sub('#include <([^>]+)>', '#include "\\1"', incl))
|
|
else:
|
|
inclList.append(incl)
|
|
else:
|
|
externalIncludes.append(incl)
|
|
|
|
else:
|
|
if checkInRage(inclModuleName, inclFileName):
|
|
if inclToken == "<":
|
|
addError(fileName, lineNum, "#includes for rage modules should not use < and >")
|
|
rageIncludes.append(re.sub('#include <([^>]+)>', '#include "\\1"', incl))
|
|
else:
|
|
rageIncludes.append(incl)
|
|
else:
|
|
externalIncludes.append(incl)
|
|
|
|
if len(selfIncludes) > 0:
|
|
try:
|
|
moduleIncludes.remove(selfIncludes[0])
|
|
except:
|
|
pass
|
|
|
|
moduleIncludes.sort(lambda x, y: cmp(x.lower(), y.lower()))
|
|
rageIncludes.sort(lambda x, y: cmp(x.lower(), y.lower()))
|
|
|
|
includeGroups = [selfIncludes, moduleIncludes, rageIncludes, externalIncludes, parserIncludes]
|
|
|
|
if options.includeOrder:
|
|
group = 0
|
|
item = 0
|
|
for (lineNo, line) in enumerate(lines):
|
|
while(group < len(includeGroups) and item >= len(includeGroups[group])):
|
|
group += 1
|
|
item = 0
|
|
if line.startswith("#include") and not includeOkRE.search(line):
|
|
if line.strip() != includeGroups[group][item]:
|
|
addError(fileName, lineNo+1, "#includes are not in the correct order or in an incorrect format")
|
|
addComment("Recommended include list is below:")
|
|
for groupList in includeGroups:
|
|
for itemStr in groupList:
|
|
addComment(itemStr)
|
|
if len(groupList) > 0:
|
|
addComment("")
|
|
break
|
|
item += 1
|
|
|
|
|
|
sys.stderr.write("Done\n")
|
|
if len(ErrorList) > 0:
|
|
for i in ErrorList:
|
|
print i
|
|
print "Finished."
|
|
if NumErrors > 0:
|
|
print NumErrors, "error(s) found"
|
|
sys.exit(1)
|
|
else:
|
|
print "All files are correct"
|
|
sys.exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv)
|
|
|