Files
GTASource/rage/qa/checkheaders.py

317 lines
13 KiB
Python
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
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)