555 lines
18 KiB
Python
Executable File
555 lines
18 KiB
Python
Executable File
#!/usr/bin/python3
|
|
#
|
|
# Copyright (c) 2016 The Khronos Group Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# genRef.py - create Vulkan ref pages from spec source files
|
|
#
|
|
#
|
|
# Usage: genRef.py files
|
|
|
|
from reflib import *
|
|
from vkapi import *
|
|
import argparse, copy, os, pdb, re, string, sys
|
|
|
|
# Return True if name is a Vulkan extension name (ends with an upper-case
|
|
# author ID). This assumes that author IDs are at least two characters.
|
|
def isextension(name):
|
|
return name[-2:].isalpha() and name[-2:].isupper()
|
|
|
|
# Print Khronos CC-BY copyright notice on open file fp. If comment is
|
|
# True, print as an asciidoc comment block, which copyrights the source
|
|
# file. Otherwise print as an asciidoc include of the copyright in markup,
|
|
# which copyrights the outputs.
|
|
def printCopyrightBlock(fp, comment=True):
|
|
if (comment):
|
|
print('// Copyright (c) 2014-2016 Khronos Group. This work is licensed under a', file=fp)
|
|
print('// Creative Commons Attribution 4.0 International License; see', file=fp)
|
|
print('// http://creativecommons.org/licenses/by/4.0/', file=fp)
|
|
print('', file=fp)
|
|
else:
|
|
print('include::footer.txt[]', file=fp)
|
|
print('', file=fp)
|
|
|
|
# Add a spec asciidoc macro prefix to a Vulkan name, depending on its type
|
|
# (protos, structs, enums, etc.)
|
|
def macroPrefix(name):
|
|
if (name in basetypes.keys()):
|
|
return 'basetype:' + name
|
|
elif (name in defines.keys()):
|
|
return 'slink:' + name
|
|
elif (name in enums.keys()):
|
|
return 'elink:' + name
|
|
elif (name in flags.keys()):
|
|
return 'elink:' + name
|
|
elif (name in funcpointers.keys()):
|
|
return 'tlink:' + name
|
|
elif (name in handles.keys()):
|
|
return 'slink:' + name
|
|
elif (name in protos.keys()):
|
|
return 'flink:' + name
|
|
elif (name in structs.keys()):
|
|
return 'slink:' + name
|
|
elif (name == 'TBD'):
|
|
return 'No cross-references are available'
|
|
else:
|
|
return 'UNKNOWN:' + name
|
|
|
|
# Return an asciidoc string with a list of 'See Also' references for the
|
|
# Vulkan entity 'name', based on the relationship mapping in vkapi.py and
|
|
# the additional references in explicitRefs. If no relationships are
|
|
# available, return None.
|
|
def seeAlsoList(apiName, explicitRefs = None):
|
|
refs = {}
|
|
|
|
# Add all the implicit references to refs
|
|
if (apiName in mapDict.keys()):
|
|
for name in sorted(mapDict[apiName].keys()):
|
|
refs[name] = None
|
|
|
|
# Add all the explicit references
|
|
if (explicitRefs != None):
|
|
for name in explicitRefs.split():
|
|
refs[name] = None
|
|
|
|
names = [macroPrefix(name) for name in sorted(refs.keys())]
|
|
if (len(names) > 0):
|
|
return ', '.join(names) + '\n'
|
|
else:
|
|
return None
|
|
|
|
# Remap include directives in a list of lines so they can be extracted to a
|
|
# different directory. Returns remapped lines.
|
|
#
|
|
# lines - text to remap
|
|
# baseDir - target directory
|
|
# specDir - source directory
|
|
def remapIncludes(lines, baseDir, specDir):
|
|
# This should be compiled only once
|
|
includePat = re.compile('^include::(?P<path>.*)\[\]')
|
|
|
|
newLines = []
|
|
for line in lines:
|
|
matches = includePat.search(line)
|
|
if (matches != None):
|
|
path = matches.group('path')
|
|
|
|
# Relative path to include file from here
|
|
incPath = specDir + '/' + path
|
|
# Remap to be relative to baseDir
|
|
newPath = os.path.relpath(incPath, baseDir)
|
|
newLine = 'include::' + newPath + '[]\n'
|
|
logDiag('remapIncludes: remapping from:\n\t', line, 'to:\n\t', newLine)
|
|
newLines.append(newLine)
|
|
else:
|
|
newLines.append(line)
|
|
return newLines
|
|
|
|
# Generate header of a reference page
|
|
# pageName - string name of the page
|
|
# pageDesc - string short description of the page
|
|
# specText - string that goes in the "C Specification" section
|
|
# fieldName - string heading an additional section following specText, if not None
|
|
# fieldText - string that goes in the additional section
|
|
# descText - string that goes in the "Description" section
|
|
# fp - file to write to
|
|
def refPageHead(pageName, pageDesc, specText, fieldName, fieldText, descText, fp):
|
|
printCopyrightBlock(fp, comment=True)
|
|
|
|
s = pageName + '(3)'
|
|
print(s,
|
|
''.ljust(len(s), '='),
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
if (pageDesc.strip() == ''):
|
|
pageDesc = 'NO SHORT DESCRIPTION PROVIDED'
|
|
logWarn('refPageHead: no short description provided for', pageName)
|
|
|
|
print('Name',
|
|
'----',
|
|
pageName + ' - ' + pageDesc,
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
print('C Specification',
|
|
'---------------',
|
|
'',
|
|
specText,
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
if (fieldName != None):
|
|
print(fieldName,
|
|
''.ljust(len(fieldName), '-'),
|
|
'',
|
|
fieldText,
|
|
sep='\n', file=fp)
|
|
|
|
print('Description',
|
|
'-----------',
|
|
'',
|
|
descText,
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
def refPageTail(pageName, seeAlso, fp, auto = False):
|
|
# This is difficult to get working properly in asciidoc
|
|
# specURL = 'link:{vkspecpath}/vkspec.html'
|
|
|
|
# This needs to have the current repository branch path installed in
|
|
# place of '1.0'
|
|
specURL = 'https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html'
|
|
|
|
if (seeAlso == None):
|
|
seeAlso = 'No cross-references are available\n'
|
|
|
|
notes = [
|
|
'For more information, see the Vulkan Specification at URL',
|
|
'',
|
|
specURL + '#' + pageName,
|
|
'',
|
|
]
|
|
|
|
if (auto):
|
|
notes.extend([
|
|
'This page is a generated document.',
|
|
'Fixes and changes should be made to the generator scripts,'
|
|
'not directly.',
|
|
])
|
|
else:
|
|
notes.extend([
|
|
'This page is extracted from the Vulkan Specification.',
|
|
'Fixes and changes should be made to the Specification,'
|
|
'not directly.',
|
|
])
|
|
|
|
print('See Also',
|
|
'--------',
|
|
'',
|
|
seeAlso,
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
print('Document Notes',
|
|
'--------------',
|
|
'',
|
|
'\n'.join(notes),
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
printCopyrightBlock(fp, comment=False)
|
|
|
|
# Extract a single reference page into baseDir
|
|
# baseDir - base directory to emit page into
|
|
# specDir - directory extracted page source came from
|
|
# pi - pageInfo for this page relative to file
|
|
# file - list of strings making up the file, indexed by pi
|
|
def emitPage(baseDir, specDir, pi, file):
|
|
pageName = baseDir + '/' + pi.name + '.txt'
|
|
fp = open(pageName, 'w')
|
|
|
|
# Add a dictionary entry for this page
|
|
global genDict
|
|
genDict[pi.name] = None
|
|
logDiag('emitPage:', pageName)
|
|
|
|
# Short description
|
|
if (pi.desc == None):
|
|
pi.desc = '(no short description available)'
|
|
|
|
# Specification text
|
|
lines = remapIncludes(file[pi.begin:pi.include+1], baseDir, specDir)
|
|
specText = ''.join(lines)
|
|
|
|
# Member/parameter list, if there is one
|
|
field = None
|
|
fieldText = None
|
|
if (pi.param != None):
|
|
if (pi.type == 'structs'):
|
|
field = 'Members'
|
|
elif (pi.type in ['protos', 'funcpointers']):
|
|
field = 'Parameters'
|
|
else:
|
|
logWarn('emitPage: unknown field type:', pi.type,
|
|
'for', pi.name)
|
|
lines = remapIncludes(file[pi.param:pi.body], baseDir, specDir)
|
|
fieldText = ''.join(lines)
|
|
|
|
# Description text
|
|
lines = remapIncludes(file[pi.body:pi.end+1], baseDir, specDir)
|
|
descText = ''.join(lines)
|
|
|
|
refPageHead(pi.name,
|
|
pi.desc,
|
|
specText,
|
|
field, fieldText,
|
|
descText,
|
|
fp)
|
|
refPageTail(pi.name, seeAlsoList(pi.name, pi.refs), fp, auto = False)
|
|
fp.close()
|
|
|
|
# Autogenerate a single reference page in baseDir
|
|
# Script only knows how to do this for /enums/ pages, at present
|
|
# baseDir - base directory to emit page into
|
|
# pi - pageInfo for this page relative to file
|
|
# file - list of strings making up the file, indexed by pi
|
|
def autoGenEnumsPage(baseDir, pi, file):
|
|
pageName = baseDir + '/' + pi.name + '.txt'
|
|
fp = open(pageName, 'w')
|
|
|
|
# Add a dictionary entry for this page
|
|
global genDict
|
|
genDict[pi.name] = None
|
|
logDiag('autoGenEnumsPage:', pageName)
|
|
|
|
# Short description
|
|
if (pi.desc == None):
|
|
pi.desc = '(no short description available)'
|
|
|
|
# Description text. Allow for the case where an enum definition
|
|
# is not embedded.
|
|
if (not pi.embed):
|
|
embedRef = ''
|
|
else:
|
|
embedRef = ''.join([
|
|
' * The reference page for ',
|
|
macroPrefix(pi.embed),
|
|
', where this interface is defined.\n' ])
|
|
|
|
txt = ''.join([
|
|
'For more information, see:\n\n',
|
|
embedRef,
|
|
' * The See Also section for other reference pages using this type.\n',
|
|
' * The Vulkan Specification.\n' ])
|
|
|
|
refPageHead(pi.name,
|
|
pi.desc,
|
|
''.join(file[pi.begin:pi.include+1]),
|
|
None, None,
|
|
txt,
|
|
fp)
|
|
refPageTail(pi.name, seeAlsoList(pi.name, pi.refs), fp, auto = True)
|
|
fp.close()
|
|
|
|
# Pattern to break apart a Vk*Flags{authorID} name, used in autoGenFlagsPage.
|
|
flagNamePat = re.compile('(?P<name>\w+)Flags(?P<author>[A-Z]*)')
|
|
|
|
# Autogenerate a single reference page in baseDir for a Vk*Flags type
|
|
# baseDir - base directory to emit page into
|
|
# flagName - Vk*Flags name
|
|
def autoGenFlagsPage(baseDir, flagName):
|
|
pageName = baseDir + '/' + flagName + '.txt'
|
|
fp = open(pageName, 'w')
|
|
|
|
# Add a dictionary entry for this page
|
|
global genDict
|
|
genDict[flagName] = None
|
|
logDiag('autoGenFlagsPage:', pageName)
|
|
|
|
# Short description
|
|
matches = flagNamePat.search(flagName)
|
|
if (matches != None):
|
|
name = matches.group('name')
|
|
author = matches.group('author')
|
|
logDiag('autoGenFlagsPage: split name into', name, 'Flags', author)
|
|
flagBits = name + 'FlagBits' + author
|
|
desc = 'Bitmask of ' + flagBits
|
|
else:
|
|
logWarn('autoGenFlagsPage:', pageName, 'does not not end in "Flags{author ID}". Cannot infer FlagBits type.')
|
|
flagBits = None
|
|
desc = 'Unknown Vulkan flags type'
|
|
|
|
# Description text
|
|
if (flagBits != None):
|
|
txt = ''.join([
|
|
'etext:' + flagName,
|
|
' is a mask of zero or more elink:' + flagBits + '.\n',
|
|
'It is used as a member and/or parameter of the structures and commands\n',
|
|
'in the See Also section below.\n' ])
|
|
else:
|
|
txt = ''.join([
|
|
'etext:' + flagName,
|
|
' is an unknown Vulkan type, assumed to be a bitmask.\n' ])
|
|
|
|
refPageHead(flagName,
|
|
desc,
|
|
'include::../api/flags/' + flagName + '.txt[]\n',
|
|
None, None,
|
|
txt,
|
|
fp)
|
|
refPageTail(flagName, seeAlsoList(flagName), fp, auto = True)
|
|
fp.close()
|
|
|
|
# Autogenerate a single handle page in baseDir for a Vk* handle type
|
|
# baseDir - base directory to emit page into
|
|
# handleName - Vk* handle name
|
|
# @@ Need to determine creation function & add handles/ include for the
|
|
# @@ interface in generator.py.
|
|
def autoGenHandlePage(baseDir, handleName):
|
|
pageName = baseDir + '/' + handleName + '.txt'
|
|
fp = open(pageName, 'w')
|
|
|
|
# Add a dictionary entry for this page
|
|
global genDict
|
|
genDict[handleName] = None
|
|
logDiag('autoGenHandlePage:', pageName)
|
|
|
|
# Short description
|
|
desc = 'Vulkan object handle'
|
|
|
|
descText = ''.join([
|
|
'sname:' + handleName,
|
|
' is an object handle type, referring to an object used\n',
|
|
'by the Vulkan implementation. These handles are created or allocated\n',
|
|
'by the vk @@ TBD @@ function, and used by other Vulkan structures\n',
|
|
'and commands in the See Also section below.\n' ])
|
|
|
|
refPageHead(handleName,
|
|
desc,
|
|
'include::../api/handles/' + handleName + '.txt[]\n',
|
|
None, None,
|
|
descText,
|
|
fp)
|
|
refPageTail(handleName, seeAlsoList(handleName), fp, auto = True)
|
|
fp.close()
|
|
|
|
# Extract reference pages from a spec asciidoc source file
|
|
# specFile - filename to extract from
|
|
# baseDir - output directory to generate page in
|
|
#
|
|
def genRef(specFile, baseDir):
|
|
file = loadFile(specFile)
|
|
if (file == None):
|
|
return
|
|
|
|
# Save the path to this file for later use in rewriting relative includes
|
|
specDir = os.path.dirname(os.path.abspath(specFile))
|
|
|
|
pageMap = findRefs(file)
|
|
logDiag(specFile + ': found', len(pageMap.keys()), 'potential pages')
|
|
|
|
sys.stderr.flush()
|
|
|
|
# Fix up references in pageMap
|
|
fixupRefs(pageMap, specFile, file)
|
|
|
|
# Create each page, if possible
|
|
|
|
for name in sorted(pageMap.keys()):
|
|
pi = pageMap[name]
|
|
|
|
printPageInfo(pi, file)
|
|
|
|
if (pi.Warning):
|
|
logDiag('genRef:', pi.name + ':', pi.Warning)
|
|
|
|
if (pi.extractPage):
|
|
emitPage(baseDir, specDir, pi, file)
|
|
elif (pi.type == 'enums'):
|
|
autoGenEnumsPage(baseDir, pi, file)
|
|
elif (pi.type == 'flags'):
|
|
autoGenFlagsPage(baseDir, pi.name)
|
|
else:
|
|
# Don't extract this page
|
|
logWarn('genRef: Cannot extract or autogenerate:', pi.name)
|
|
|
|
# Generate baseDir/apispec.txt, the single-page version of the ref pages.
|
|
# This assumes there's a page for everything in the vkapi.py dictionaries.
|
|
# Extensions (KHR, EXT, etc.) are currently skipped
|
|
def genSinglePageRef(baseDir):
|
|
pageName = baseDir + '/apispec.txt'
|
|
fp = open(pageName, 'w')
|
|
|
|
printCopyrightBlock(fp, comment=True)
|
|
|
|
print('Vulkan API Reference Pages',
|
|
'==========================',
|
|
'include::../specversion.txt[]',
|
|
'',
|
|
':doctype: book',
|
|
':numbered!:',
|
|
':toc2:',
|
|
':max-width: 200',
|
|
':numbered:',
|
|
':doctype: book',
|
|
':data-uri:',
|
|
':asciimath:',
|
|
':toclevels: 4',
|
|
'',
|
|
sep='\n', file=fp)
|
|
|
|
print('include::khronoscopyright.txt[]', file=fp)
|
|
print('', file=fp)
|
|
|
|
# Inject the table of contents. Asciidoc really ought to be generating
|
|
# this for us.
|
|
|
|
sections = [
|
|
[ protos, 'protos', 'Vulkan Commands' ],
|
|
[ handles, 'handles', 'Object Handles' ],
|
|
[ structs, 'structs', 'Structures' ],
|
|
[ enums, 'enums', 'Enumerations' ],
|
|
[ flags, 'flags', 'Flags' ],
|
|
[ funcpointers, 'funcpointers', 'Function Pointer Types' ],
|
|
[ basetypes, 'basetypes', 'Vulkan Scalar types' ],
|
|
[ defines, 'defines', 'C Macro Definitions' ] ]
|
|
|
|
print('Table of Contents', file=fp)
|
|
print('-----------------', file=fp)
|
|
for (apiDict,label,title) in sections:
|
|
print(' * <<' + label + ',' + title + '>>', file=fp)
|
|
print('', file=fp)
|
|
|
|
for (apiDict,label,title) in sections:
|
|
anchor = '[[' + label + ',' + title + ']]'
|
|
print(anchor,
|
|
title,
|
|
''.ljust(len(title), '-'),
|
|
'',
|
|
':leveloffset: 2',
|
|
'',
|
|
sep='\n', file=fp)
|
|
for refPage in sorted(apiDict.keys()):
|
|
if (apiDict == defines or not isextension(refPage)):
|
|
print('include::' + refPage + '.txt[]', file=fp)
|
|
else:
|
|
print('// not including ' + refPage, file=fp)
|
|
print('\n' + ':leveloffset: 0' + '\n', file=fp)
|
|
|
|
# An index of pages could be generated here
|
|
|
|
if __name__ == '__main__':
|
|
global genDict
|
|
genDict = {}
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('-diag', action='store', dest='diagFile',
|
|
help='Set the diagnostic file')
|
|
parser.add_argument('-warn', action='store', dest='warnFile',
|
|
help='Set the warning file')
|
|
parser.add_argument('-log', action='store', dest='logFile',
|
|
help='Set the log file for both diagnostics and warnings')
|
|
parser.add_argument('-basedir', action='store', dest='baseDir',
|
|
default='man',
|
|
help='Set the base directory in which pages are generated')
|
|
parser.add_argument('files', metavar='filename', nargs='*',
|
|
help='a filename to extract ref pages from')
|
|
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
|
|
|
|
results = parser.parse_args()
|
|
|
|
setLogFile(True, True, results.logFile)
|
|
setLogFile(True, False, results.diagFile)
|
|
setLogFile(False, True, results.warnFile)
|
|
|
|
baseDir = results.baseDir
|
|
|
|
for file in results.files:
|
|
genRef(file, baseDir)
|
|
|
|
# Now figure out which pages *weren't* generated from the spec.
|
|
# This relies on the dictionaries of API constructs in vkapi.py.
|
|
|
|
# For Flags (e.g. Vk*Flags types), it's easy to autogenerate pages.
|
|
for page in flags.keys():
|
|
if not (page in genDict.keys()):
|
|
logWarn('Autogenerating flags page:', page, 'which should be included in the spec')
|
|
autoGenFlagsPage(baseDir, page)
|
|
|
|
# autoGenHandlePage is no longer needed because they are added to
|
|
# the spec sources now.
|
|
# for page in structs.keys():
|
|
# if (typeCategory[page] == 'handle'):
|
|
# autoGenHandlePage(baseDir, page)
|
|
|
|
sections = [
|
|
[ enums, 'Enumerated Types' ],
|
|
[ structs, 'Structures' ],
|
|
[ protos, 'Prototypes' ],
|
|
[ funcpointers, 'Function Pointers' ],
|
|
[ basetypes, 'Vulkan Scalar Types' ] ]
|
|
|
|
for (apiDict,title) in sections:
|
|
flagged = False
|
|
for page in apiDict.keys():
|
|
if not (page in genDict.keys()):
|
|
if (not flagged):
|
|
logWarn(title, 'with no ref page generated:')
|
|
flagged = True
|
|
logWarn(' ', page)
|
|
|
|
genSinglePageRef(baseDir)
|