diff options
Diffstat (limited to 'codegen/vulkan/scripts/Retired/checkLinks.py')
-rwxr-xr-x | codegen/vulkan/scripts/Retired/checkLinks.py | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/Retired/checkLinks.py b/codegen/vulkan/scripts/Retired/checkLinks.py new file mode 100755 index 00000000..35103a80 --- /dev/null +++ b/codegen/vulkan/scripts/Retired/checkLinks.py @@ -0,0 +1,353 @@ +#!/usr/bin/python3 +# +# Copyright 2015-2021 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# checkLinks.py - validate link/reference API constructs in files +# +# Usage: checkLinks.py [options] files > logfile +# +# Options: +# -follow attempt to follow include:: directives. This script isn't # an +# Asciidoctor processor, so only literal relative paths can # be followed. +# -info print some internal diagnostics. +# -paramcheck attempt to validate param: names against the surrounding +# context (the current structure/function being validated, for example). +# This generates many false positives, so is not enabled by default. +# -fatal unvalidatable links cause immediate error exit from the script. +# Otherwise, errors are accumulated and summarized at the end. +# +# Depends on vkapi.py, which is a Python representation of relevant parts +# of the Vulkan API. Only works when vkapi.py is generated for the full +# API, e.g. 'makeAllExts checklinks'; otherwise many false-flagged errors +# will occur. + +import copy, os, pdb, re, string, sys +from vkapi import * + +global curFile, curLine, sectionDepth +global errCount, warnCount, emittedPrefix, printInfo + +curFile = '???' +curLine = -1 +sectionDepth = 0 +emittedPrefix = {} +printInfo = False + +# Called before printing a warning or error. Only prints once prior +# to output for a given file. +def emitPrefix(): + global curFile, curLine, emittedPrefix + if (curFile not in emittedPrefix.keys()): + emittedPrefix[curFile] = None + print('Checking file:', curFile) + print('-------------------------------') + +def info(*args, **kwargs): + global curFile, curLine, printInfo + if (printInfo): + + emitPrefix() + print('INFO: %s line %d:' % (curFile, curLine), + ' '.join([str(arg) for arg in args])) + +# Print a validation warning found in a file +def warning(*args, **kwargs): + global curFile, curLine, warnCount + + warnCount = warnCount + 1 + emitPrefix() + print('WARNING: %s line %d:' % (curFile, curLine), + ' '.join([str(arg) for arg in args])) + +# Print a validation error found in a file +def error(*args, **kwargs): + global curFile, curLine, errCount + + errCount = errCount + 1 + emitPrefix() + print('ERROR: %s line %d:' % (curFile, curLine), + ' '.join([str(arg) for arg in args])) + +# See if a tag value exists in the specified dictionary and +# suggest it as an alternative if so. +def checkTag(tag, value, dict, dictName, tagName): + if (value in dict.keys()): + warning(value, 'exists in the API but not as a', + tag + ': .', 'Try using the', tagName + ': tag.') + +# Report an error due to an asciidoc tag which doesn't match +# a corresponding API entity. +def foundError(errType, tag, value, fatal): + global curFile, curLine + error('no such', errType, tag + ':' + value) + # Try some heuristics to detect likely problems such as missing vk + # prefixes or the wrong tag. + + # Look in all the dictionaries in vkapi.py to see if the tag + # is just wrong but the API entity actually exists. + checkTag(tag, value, flags, 'flags', 'tlink/tname') + checkTag(tag, value, enums, 'enums', 'elink') + checkTag(tag, value, structs, 'structs', 'slink/sname') + checkTag(tag, value, handles, 'handles', 'slink/sname') + checkTag(tag, value, defines, 'defines', 'slink/sname') + checkTag(tag, value, consts, 'consts', 'ename') + checkTag(tag, value, protos, 'protos', 'flink/fname') + checkTag(tag, value, funcpointers, 'funcpointers', 'tlink/tname') + + # Look for missing vk prefixes (quirky since it's case-dependent) + # NOT DONE YET + + if fatal: + print('ERROR: %s line %d:' % (curFile, curLine), + ' '.join(['no such', errType, tag + ':' + value]), file=sys.stderr) + sys.exit(1) + +# Look for param in the list of all parameters of the specified functions +# Returns True if found, False otherwise +def findParam(param, funclist): + for f in funclist: + if (param in protos[f]): + info('parameter:', param, 'found in function:', f) + return True + return False + +# Initialize tracking state for checking links/includes +def initChecks(): + global curFile, curLine, curFuncs, curStruct, accumFunc, sectionDepth + global errCount, warnCount + global incPat, linkPat, pathPat, sectionPat + + # Matches asciidoc single-line section tags + sectionPat = re.compile('^(=+) ') + + # Matches any asciidoc include:: directive + pathPat = re.compile('^include::([\w./_]+)\[\]') + + # Matches asciidoc include:: directives used in spec/ref pages (and also + # others such as validity). This is specific to the layout of the api/ + # includes and allows any path precding 'api/' followed by the category + # (protos, structs, enums, etc.) followed by the name of the proto, + # struct, etc. file. + incPat = re.compile('^.*api/(\w+)/(\w+)\.txt') + + # Lists of current /protos/ (functions) and /structs/ includes. There + # can be several protos contiguously for different forms of a command + curFuncs = [] + curStruct = None + + # Tag if we should accumulate funcs or start a new list. Any intervening + # pname: tags or struct includes will restart the list. + accumFunc = False + + # Matches all link names in the current spec/man pages. Assumes these + # macro names are not trailing subsets of other macros. Used to + # precede the regexp with [^A-Za-z], but this didn't catch macros + # at start of line. + linkPat = re.compile('([efpst](name|link)):(\w*)') + + # Total error/warning counters + errCount = 0 + warnCount = 0 + +# Validate asciidoc internal links in specified file. +# infile - filename to validate +# follow - if True, recursively follow include:: directives +# paramCheck - if True, try to verify pname: refers to valid +# parameter/member names. This generates many false flags currently +# included - if True, function was called recursively +# fatalExit - if True, validation errors cause an error exit immediately +# Links checked are: +# fname:vkBlah - Vulkan command name (generates internal link) +# flink:vkBlah - Vulkan command name +# sname:VkBlah - Vulkan struct name (generates internal link) +# slink:VkBlah - Vulkan struct name +# elink:VkEnumName - Vulkan enumeration ('enum') type name (generates internal link) +# ename:VK_BLAH - Vulkan enumerant token name +# pname:name - parameter name to a command or a struct member +# tlink:name - Other Vulkan type name (generates internal link) +# tname:name - Other Vulkan type name +def checkLinks(infile, follow = False, paramCheck = True, included = False, fatalExit = False): + global curFile, curLine, curFuncs, curStruct, accumFunc, sectionDepth + global errCount, warnCount + global incPat, linkPat, pathPat, sectionPat + + # Global state which gets saved and restored by this function + oldCurFile = curFile + oldCurLine = curLine + curFile = infile + curLine = 0 + + # N.b. dirname() returns an empty string for a path with no directories, + # unlike the shell dirname(1). + if (not os.path.exists(curFile)): + error('No such file', curFile, '- skipping check') + # Restore global state before exiting the function + curFile = oldCurFile + curLine = oldCurLine + return + + inPath = os.path.dirname(curFile) + fp = open(curFile, 'r', encoding='utf-8') + + for line in fp: + curLine = curLine + 1 + + # Track changes up and down section headers, and forget + # the current functions/structure when popping up a level + match = sectionPat.search(line) + if (match): + info('Match sectionPat for line:', line) + depth = len(match.group(1)) + if (depth < sectionDepth): + info('Resetting current function/structure for section:', line) + curFuncs = [] + curStruct = None + sectionDepth = depth + + match = pathPat.search(line) + if (match): + incpath = match.group(1) + info('Match pathPat for line:', line) + info(' incpath =', incpath) + # An include:: directive. First check if it looks like a + # function or struct include file, and modify the corresponding + # current function or struct state accordingly. + match = incPat.search(incpath) + if (match): + info('Match incPat for line:', line) + # For prototypes, if it is preceded by + # another include:: directive with no intervening link: tags, + # add to the current function list. Otherwise start a new list. + # There is only one current structure. + category = match.group(1) + tag = match.group(2) + # @ Validate tag! + # @ Arguably, any intervening text should shift to accumFuncs = False, + # e.g. only back-to-back includes separated by blank lines would be + # accumulated. + if (category == 'protos'): + if (tag in protos.keys()): + if (accumFunc): + curFuncs.append(tag) + else: + curFuncs = [ tag ] + # Restart accumulating functions + accumFunc = True + info('curFuncs =', curFuncs, 'accumFunc =', accumFunc) + else: + error('include of nonexistent function', tag) + elif (category == 'structs'): + if (tag in structs.keys()): + curStruct = tag + # Any /structs/ include means to stop accumulating /protos/ + accumFunc = False + info('curStruct =', curStruct) + else: + error('include of nonexistent struct', tag) + if (follow): + # Actually process the included file now, recursively + newpath = os.path.normpath(os.path.join(inPath, incpath)) + info(curFile, ': including file:', newpath) + checkLinks(newpath, follow, paramCheck, included = True, fatalExit = fatalExit) + + matches = linkPat.findall(line) + for match in matches: + # Start actual validation work. Depending on what the + # asciidoc tag name is, look up the value in the corresponding + # dictionary. + tag = match[0] + value = match[2] + if (tag == 'fname' or tag == 'flink'): + if (value not in protos.keys()): + foundError('function', tag, value, False) + elif (tag == 'sname' or tag == 'slink'): + if (value not in structs.keys() and + value not in handles.keys()): + foundError('aggregate/scalar/handle/define type', tag, value, False) + elif (tag == 'ename'): + if (value not in consts.keys() and value not in defines.keys()): + foundError('enumerant/constant', tag, value, False) + elif (tag == 'elink'): + if (value not in enums.keys() and value not in flags.keys()): + foundError('enum/bitflag type', tag, value, fatalExit) + # tname and tlink are the same except if the errors are treated as fatal + # They can be recombined once both are error-clean + elif (tag == 'tname'): + if (value not in funcpointers.keys() and value not in flags.keys()): + foundError('function pointer/other type', tag, value, fatalExit) + elif (tag == 'tlink'): + if (value not in funcpointers.keys() and value not in flags.keys()): + foundError('function pointer/other type', tag, value, False) + elif (tag == 'pname'): + # Any pname: tag means to stop accumulating /protos/ + accumFunc = False + # See if this parameter is in the current proto(s) and struct + foundParam = False + if (curStruct and value in structs[curStruct]): + info('parameter', value, 'found in struct', curStruct) + elif (curFuncs and findParam(value, curFuncs)): + True + else: + if paramCheck: + warning('parameter', value, 'not found. curStruct =', + curStruct, 'curFuncs =', curFuncs) + else: + # This is a logic error + error('unknown tag', tag + ':' + value) + fp.close() + + if (errCount > 0 or warnCount > 0): + if (not included): + print('Errors found:', errCount, 'Warnings found:', warnCount) + print('') + + if (included): + info('----- returning from:', infile, 'to parent file', '-----') + + # Don't generate any output for files without errors + # else: + # print(curFile + ': No errors found') + + # Restore global state before exiting the function + curFile = oldCurFile + curLine = oldCurLine + +if __name__ == '__main__': + follow = False + paramCheck = False + included = False + fatalExit = False + + totalErrCount = 0 + totalWarnCount = 0 + + if (len(sys.argv) > 1): + for file in sys.argv[1:]: + if (file == '-follow'): + follow = True + elif (file == '-info'): + printInfo = True + elif file == '-paramcheck': + paramCheck = True + elif (file == '-fatal'): + fatalExit = True + else: + initChecks() + checkLinks(file, + follow, + paramCheck = paramCheck, + included = included, + fatalExit = fatalExit) + totalErrCount = totalErrCount + errCount + totalWarnCount = totalWarnCount + warnCount + else: + print('Need arguments: [-follow] [-info] [-paramcheck] [-fatal] infile [infile...]', file=sys.stderr) + + if (totalErrCount > 0 or totalWarnCount > 0): + if (not included): + print('TOTAL Errors found:', totalErrCount, 'Warnings found:', + totalWarnCount) + if totalErrCount > 0: + sys.exit(1) |