| #!/usr/bin/python3 -i |
| # |
| # Copyright 2013-2023 The Khronos Group Inc. |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import os |
| import re |
| import sys |
| from functools import total_ordering |
| from generator import GeneratorOptions, OutputGenerator, regSortFeatures, write |
| from parse_dependency import dependencyMarkup |
| |
| class ExtensionMetaDocGeneratorOptions(GeneratorOptions): |
| """ExtensionMetaDocGeneratorOptions - subclass of GeneratorOptions. |
| |
| Represents options during extension metainformation generation for Asciidoc""" |
| def __init__(self, *args, **kwargs): |
| super().__init__(*args, **kwargs) |
| |
| @total_ordering |
| class Extension: |
| def __init__(self, |
| generator, # needed for logging and API conventions |
| filename, |
| name, |
| number, |
| ext_type, |
| depends, |
| contact, |
| promotedTo, |
| deprecatedBy, |
| obsoletedBy, |
| provisional, |
| revision, |
| specialuse, |
| ratified |
| ): |
| self.generator = generator |
| self.conventions = generator.genOpts.conventions |
| self.filename = filename |
| self.name = name |
| self.number = number |
| self.ext_type = ext_type |
| self.depends = depends |
| self.contact = contact |
| self.promotedTo = promotedTo |
| self.deprecatedBy = deprecatedBy |
| self.obsoletedBy = obsoletedBy |
| self.provisional = provisional |
| self.revision = revision |
| self.specialuse = specialuse |
| self.ratified = ratified |
| |
| self.deprecationType = None |
| self.supercedingAPIVersion = None |
| self.supercedingExtension = None |
| # This is a set containing names of extensions (if any) promoted |
| # *to* this extension. |
| # It is filled in after all the Extension objects are created, |
| # since it requires a reverse mapping step. |
| self.promotedFrom = set() |
| |
| if self.promotedTo is not None and self.deprecatedBy is not None and self.obsoletedBy is not None: |
| self.generator.logMsg('warn', 'All \'promotedto\', \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\' and \'deprecatedby\'.') |
| elif self.promotedTo is not None and self.deprecatedBy is not None: |
| self.generator.logMsg('warn', 'Both \'promotedto\' and \'deprecatedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') |
| elif self.promotedTo is not None and self.obsoletedBy is not None: |
| self.generator.logMsg('warn', 'Both \'promotedto\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\'.') |
| elif self.deprecatedBy is not None and self.obsoletedBy is not None: |
| self.generator.logMsg('warn', 'Both \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') |
| |
| supercededBy = None |
| if self.promotedTo is not None: |
| self.deprecationType = 'promotion' |
| supercededBy = promotedTo |
| elif self.deprecatedBy is not None: |
| self.deprecationType = 'deprecation' |
| supercededBy = deprecatedBy |
| elif self.obsoletedBy is not None: |
| self.deprecationType = 'obsoletion' |
| supercededBy = obsoletedBy |
| |
| if supercededBy is not None: |
| if supercededBy == '' and not self.deprecationType == 'promotion': |
| pass # supercedingAPIVersion, supercedingExtension is None |
| elif supercededBy.startswith(self.conventions.api_version_prefix): |
| self.supercedingAPIVersion = supercededBy |
| elif supercededBy.startswith(self.conventions.api_prefix): |
| self.supercedingExtension = supercededBy |
| else: |
| self.generator.logMsg('error', 'Unrecognized ' + self.deprecationType + ' attribute value \'' + supercededBy + '\'!') |
| |
| def __str__(self): |
| return self.name |
| def __eq__(self, other): |
| return self.name == other.name |
| def __ne__(self, other): |
| return self.name != other.name |
| |
| def __lt__(self, other): |
| self_is_KHR = self.name.startswith(self.conventions.KHR_prefix) |
| self_is_EXT = self.name.startswith(self.conventions.EXT_prefix) |
| other_is_KHR = other.name.startswith(self.conventions.KHR_prefix) |
| other_is_EXT = other.name.startswith(self.conventions.EXT_prefix) |
| |
| swap = False |
| if self_is_KHR and not other_is_KHR: |
| return not swap |
| if other_is_KHR and not self_is_KHR: |
| return swap |
| if self_is_EXT and not other_is_EXT: |
| return not swap |
| if other_is_EXT and not self_is_EXT: |
| return swap |
| |
| return self.name < other.name |
| |
| def typeToStr(self): |
| if self.ext_type == 'instance': |
| return 'Instance extension' |
| if self.ext_type == 'device': |
| return 'Device extension' |
| |
| if self.ext_type is not None: |
| self.generator.logMsg('warn', 'The type attribute of ' + self.name + ' extension is neither \'instance\' nor \'device\'. That is invalid (at the time this script was written).') |
| else: # should be unreachable |
| self.generator.logMsg('error', 'Logic error in typeToStr(): Missing type attribute!') |
| return None |
| |
| def specLink(self, xrefName, xrefText, isRefpage = False): |
| """Generate a string containing a link to a specification anchor in |
| asciidoctor markup form. |
| |
| - xrefName - anchor name in the spec |
| - xrefText - text to show for the link, or None |
| - isRefpage = True if generating a refpage include, False if |
| generating a specification extension appendix include""" |
| |
| if isRefpage: |
| # Always link into API spec |
| specURL = self.conventions.specURL('api') |
| return 'link:{}#{}[{}^]'.format(specURL, xrefName, xrefText) |
| else: |
| return '<<' + xrefName + ', ' + xrefText + '>>' |
| |
| def conditionalLinkCoreAPI(self, apiVersion, linkSuffix, isRefpage): |
| versionMatch = re.match(self.conventions.api_version_prefix + r'(\d+)_(\d+)', apiVersion) |
| major = versionMatch.group(1) |
| minor = versionMatch.group(2) |
| |
| dottedVersion = major + '.' + minor |
| |
| xrefName = 'versions-' + dottedVersion + linkSuffix |
| xrefText = self.conventions.api_name() + ' ' + dottedVersion |
| |
| doc = 'ifdef::' + apiVersion + '[]\n' |
| doc += ' ' + self.specLink(xrefName, xrefText, isRefpage) + '\n' |
| doc += 'endif::' + apiVersion + '[]\n' |
| doc += 'ifndef::' + apiVersion + '[]\n' |
| doc += ' ' + self.conventions.api_name() + ' ' + dottedVersion + '\n' |
| doc += 'endif::' + apiVersion + '[]\n' |
| |
| return doc |
| |
| def conditionalLinkExt(self, extName, indent = ' '): |
| doc = 'ifdef::' + extName + '[]\n' |
| doc += indent + self.conventions.formatExtension(extName) + '\n' |
| doc += 'endif::' + extName + '[]\n' |
| doc += 'ifndef::' + extName + '[]\n' |
| doc += indent + '`' + extName + '`\n' |
| doc += 'endif::' + extName + '[]\n' |
| |
| return doc |
| |
| def resolveDeprecationChain(self, extensions, succeededBy, isRefpage, file): |
| if succeededBy not in extensions: |
| write(f' ** *NOTE* The extension `{succeededBy}` is not supported for the API specification being generated', file=file) |
| self.generator.logMsg('warn', f'resolveDeprecationChain: {self.name} defines a superseding interface {succeededBy} which is not in the supported extensions list') |
| return |
| |
| ext = extensions[succeededBy] |
| |
| if ext.deprecationType: |
| if ext.deprecationType == 'promotion': |
| if ext.supercedingAPIVersion: |
| write(' ** Which in turn was _promoted_ to\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-promotions', isRefpage), file=file) |
| else: # ext.supercedingExtension |
| write(' ** Which in turn was _promoted_ to extension\n' + ext.conditionalLinkExt(ext.supercedingExtension), file=file) |
| ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) |
| elif ext.deprecationType == 'deprecation': |
| if ext.supercedingAPIVersion: |
| write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) |
| elif ext.supercedingExtension: |
| write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) |
| ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) |
| else: |
| write(' ** Which in turn was _deprecated_ without replacement', file=file) |
| elif ext.deprecationType == 'obsoletion': |
| if ext.supercedingAPIVersion: |
| write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) |
| elif ext.supercedingExtension: |
| write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) |
| ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) |
| else: |
| write(' ** Which in turn was _obsoleted_ without replacement', file=file) |
| else: # should be unreachable |
| self.generator.logMsg('error', 'Logic error in resolveDeprecationChain(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') |
| |
| |
| def writeTag(self, tag, value, isRefpage, fp): |
| """Write a tag and (if non-None) a tag value to a file. |
| |
| If the value is None, just write the tag. |
| |
| If the tag is None, just write the value (used for adding a value |
| to a just-written tag). |
| |
| - tag - string tag name |
| - value - tag value, or None |
| - isRefpage - controls style in which the tag is marked up |
| - fp - open file pointer to write to""" |
| |
| if isRefpage: |
| # Use subsection headers for the tag name |
| tagPrefix = '== ' |
| tagSuffix = '' |
| else: |
| # Use an bolded item list for the tag name |
| tagPrefix = '*' |
| tagSuffix = '*::' |
| |
| if tag is not None: |
| write(tagPrefix + tag + tagSuffix, file=fp) |
| if value is not None: |
| write(value, file=fp) |
| |
| if isRefpage: |
| write('', file=fp) |
| |
| def makeMetafile(self, extensions, isRefpage = False): |
| """Generate a file containing extension metainformation in |
| asciidoctor markup form. |
| |
| - extensions - dictionary of Extension objects for extensions spec |
| is being generated against |
| - isRefpage - True if generating a refpage include, False if |
| generating a specification extension appendix include""" |
| |
| if isRefpage: |
| filename = self.filename.replace('meta/', 'meta/refpage.') |
| else: |
| filename = self.filename |
| |
| fp = self.generator.newFile(filename) |
| |
| if not isRefpage: |
| write('[[' + self.name + ']]', file=fp) |
| write('=== ' + self.name, file=fp) |
| write('', file=fp) |
| |
| self.writeTag('Name String', '`' + self.name + '`', isRefpage, fp) |
| self.writeTag('Extension Type', self.typeToStr(), isRefpage, fp) |
| |
| self.writeTag('Registered Extension Number', self.number, isRefpage, fp) |
| self.writeTag('Revision', self.revision, isRefpage, fp) |
| |
| if self.conventions.xml_api_name in self.ratified.split(','): |
| ratstatus = 'Ratified' |
| else: |
| ratstatus = 'Not ratified' |
| self.writeTag('Ratification Status', ratstatus, isRefpage, fp) |
| |
| # Only API extension dependencies are coded in XML, others are explicit |
| self.writeTag('Extension and Version Dependencies', None, isRefpage, fp) |
| |
| # Transform the boolean 'depends' expression into equivalent |
| # human-readable asciidoc markup. |
| if self.depends is not None: |
| if isRefpage: |
| separator = '' |
| else: |
| separator = '+' |
| write(separator + '\n--\n' + |
| dependencyMarkup(self.depends) + |
| '--', file=fp) |
| else: |
| # Do not bother specifying the base Vulkan 1.0 API redundantly |
| True |
| |
| if self.provisional == 'true' and self.conventions.provisional_extension_warning: |
| write(' * *This is a _provisional_ extension and must: be used with caution.', file=fp) |
| write(' See the ' + |
| self.specLink(xrefName = 'boilerplate-provisional-header', |
| xrefText = 'description', |
| isRefpage = isRefpage) + |
| ' of provisional header files for enablement and stability details.*', file=fp) |
| write('', file=fp) |
| |
| if self.deprecationType: |
| self.writeTag('Deprecation State', None, isRefpage, fp) |
| |
| if self.deprecationType == 'promotion': |
| if self.supercedingAPIVersion: |
| write(' * _Promoted_ to\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-promotions', isRefpage), file=fp) |
| else: # ext.supercedingExtension |
| write(' * _Promoted_ to\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension', file=fp) |
| self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) |
| elif self.deprecationType == 'deprecation': |
| if self.supercedingAPIVersion: |
| write(' * _Deprecated_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) |
| elif self.supercedingExtension: |
| write(' * _Deprecated_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) |
| self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) |
| else: |
| write(' * _Deprecated_ without replacement' , file=fp) |
| elif self.deprecationType == 'obsoletion': |
| if self.supercedingAPIVersion: |
| write(' * _Obsoleted_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) |
| elif self.supercedingExtension: |
| write(' * _Obsoleted_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) |
| self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) |
| else: |
| # TODO: Does not make sense to retroactively ban use of extensions from 1.0. |
| # Needs some tweaks to the semantics and this message, when such extension(s) occur. |
| write(' * _Obsoleted_ without replacement' , file=fp) |
| else: # should be unreachable |
| self.generator.logMsg('error', 'Logic error in makeMetafile(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') |
| write('', file=fp) |
| |
| if self.specialuse is not None: |
| specialuses = self.specialuse.split(',') |
| if len(specialuses) > 1: |
| header = 'Special Uses' |
| else: |
| header = 'Special Use' |
| self.writeTag(header, None, isRefpage, fp) |
| |
| for use in specialuses: |
| # Each specialuse attribute value expands an asciidoctor |
| # attribute of the same name, instead of using the shorter, |
| # and harder to understand attribute |
| write('* {}'.format( |
| self.specLink( |
| xrefName = self.conventions.special_use_section_anchor, |
| xrefText = '{' + use + '}', |
| isRefpage = isRefpage)), file=fp) |
| write('', file=fp) |
| |
| if self.conventions.write_contacts: |
| self.writeTag('Contact', None, isRefpage, fp) |
| |
| contacts = self.contact.split(',') |
| for contact in contacts: |
| contactWords = contact.strip().split() |
| name = ' '.join(contactWords[:-1]) |
| handle = contactWords[-1] |
| if handle.startswith('gitlab:'): |
| prettyHandle = 'icon:gitlab[alt=GitLab, role="red"]' + handle.replace('gitlab:@', '') |
| elif handle.startswith('@'): |
| issuePlaceholderText = '[' + self.name + '] ' + handle |
| issuePlaceholderText += '%0A*Here describe the issue or question you have about the ' + self.name + ' extension*' |
| trackerLink = 'link:++https://github.com/KhronosGroup/Vulkan-Docs/issues/new?body=' + issuePlaceholderText + '++' |
| prettyHandle = trackerLink + '[icon:github[alt=GitHub,role="black"]' + handle[1:] + ',window=_blank,opts=nofollow]' |
| else: |
| prettyHandle = handle |
| |
| write(' * ' + name + ' ' + prettyHandle, file=fp) |
| write('', file=fp) |
| |
| # Check if a proposal document for this extension exists in the |
| # current repository, and link to the same document (parameterized |
| # by a URL prefix attribute) if it does. |
| # The assumption is that a proposal document for an extension |
| # VK_name will be located in 'proposals/VK_name.adoc' relative |
| # to the repository root, and that this script will be invoked from |
| # the repository root. |
| # If a proposal for this extension does not exist, look for |
| # proposals for the extensions it is promoted from. |
| |
| def checkProposal(extname): |
| """Check if a proposal document for an extension exists, |
| returning the path to that proposal or None otherwise.""" |
| |
| path = 'proposals/{}.adoc'.format(extname) |
| if os.path.exists(path) and os.access(path, os.R_OK): |
| return path |
| else: |
| return None |
| |
| # List of [ extname, proposal link ] |
| proposals = [] |
| |
| path = checkProposal(self.name) |
| if path is not None: |
| proposals.append([self.name, path]) |
| else: |
| for name in self.promotedFrom: |
| path = checkProposal(name) |
| if path is not None: |
| proposals.append([name, path]) |
| |
| if len(proposals) > 0: |
| tag = 'Extension Proposal' |
| for (name, path) in sorted(proposals): |
| self.writeTag(tag, |
| f'link:{{specRepositoryURL}}/{path}[{name}]', |
| isRefpage, fp) |
| # Setting tag = None so additional values will not get |
| # additional tag headers. |
| tag = None |
| |
| # If this is metadata to be included in a refpage, adjust the |
| # leveloffset to account for the relative structure of the extension |
| # appendices vs. refpages. |
| if isRefpage and self.conventions.include_extension_appendix_in_refpage: |
| write(':leveloffset: -1', file=fp) |
| |
| fp.close() |
| |
| class ExtensionMetaDocOutputGenerator(OutputGenerator): |
| """ExtensionMetaDocOutputGenerator - subclass of OutputGenerator. |
| |
| Generates AsciiDoc includes with metainformation for the API extension |
| appendices. The fields used from <extension> tags in the API XML are: |
| |
| - name extension name string |
| - number extension number (optional) |
| - contact name and GitHub login or email address (optional) |
| - type 'instance' | 'device' (optional) |
| - depends boolean expression of core version and extension names this depends on (optional) |
| - promotedTo extension or API version it was promoted to |
| - deprecatedBy extension or API version which deprecated this extension, |
| or empty string if deprecated without replacement |
| - obsoletedBy extension or API version which obsoleted this extension, |
| or empty string if obsoleted without replacement |
| - provisional 'true' if this extension is released provisionally""" |
| |
| def __init__(self, *args, **kwargs): |
| super().__init__(*args, **kwargs) |
| self.extensions = {} |
| # List of strings containing all vendor tags |
| self.vendor_tags = [] |
| self.file_suffix = '' |
| |
| def newFile(self, filename): |
| self.logMsg('diag', '# Generating include file:', filename) |
| fp = open(filename, 'w', encoding='utf-8') |
| write(self.genOpts.conventions.warning_comment, file=fp) |
| return fp |
| |
| def beginFile(self, genOpts): |
| OutputGenerator.beginFile(self, genOpts) |
| |
| self.directory = self.genOpts.directory |
| self.file_suffix = self.genOpts.conventions.file_suffix |
| |
| # Iterate over all 'tag' Elements and add the names of all the valid vendor |
| # tags to the list |
| root = self.registry.tree.getroot() |
| for tag in root.findall('tags/tag'): |
| self.vendor_tags.append(tag.get('name')) |
| |
| # Create subdirectory, if needed |
| self.makeDir(self.directory) |
| |
| def conditionalExt(self, extName, content, ifdef = None, condition = None): |
| doc = '' |
| |
| innerdoc = 'ifdef::' + extName + '[]\n' |
| innerdoc += content + '\n' |
| innerdoc += 'endif::' + extName + '[]\n' |
| |
| if ifdef: |
| if ifdef == 'ifndef': |
| if condition: |
| doc += 'ifndef::' + condition + '[]\n' |
| doc += innerdoc |
| doc += 'endif::' + condition + '[]\n' |
| else: # no condition is as if condition is defined; "nothing" is always defined :p |
| pass # so no output |
| elif ifdef == 'ifdef': |
| if condition: |
| doc += 'ifdef::' + condition + '+' + extName + '[]\n' |
| doc += content + '\n' # does not include innerdoc; the ifdef was merged with the one above |
| doc += 'endif::' + condition + '+' + extName + '[]\n' |
| else: # no condition is as if condition is defined; "nothing" is always defined :p |
| doc += innerdoc |
| else: # should be unreachable |
| raise RuntimeError('Should be unreachable: ifdef is neither \'ifdef \' nor \'ifndef\'!') |
| else: |
| doc += innerdoc |
| |
| return doc |
| |
| def makeExtensionInclude(self, extname): |
| return self.conventions.extension_include_string(extname) |
| |
| def endFile(self): |
| # Determine the extension an extension is promoted from, if any. |
| # This is used when attempting to locate a proposal document in |
| # makeMetafile() below. |
| for (extname, ext) in self.extensions.items(): |
| promotedTo = ext.promotedTo |
| if promotedTo is not None: |
| if promotedTo in self.extensions: |
| #print(f'{promotedTo} is promoted from {extname}') |
| self.extensions[promotedTo].promotedFrom.add(extname) |
| #print(f'setting self.extensions[{promotedTo}].promotedFrom = {self.extensions[promotedTo].promotedFrom}') |
| elif not self.conventions.is_api_version_name(promotedTo): |
| self.logMsg('warn', f'{extname} is promoted to {promotedTo} which is not in the extension map') |
| |
| # Generate metadoc extension files, in refpage and non-refpage form |
| for ext in self.extensions.values(): |
| ext.makeMetafile(self.extensions, isRefpage = False) |
| if self.conventions.write_refpage_include: |
| ext.makeMetafile(self.extensions, isRefpage = True) |
| |
| # Key to sort extensions alphabetically within 'KHR', 'EXT', vendor |
| # extension prefixes. |
| def makeSortKey(extname): |
| name = extname.lower() |
| prefixes = self.conventions.extension_index_prefixes |
| for i, prefix in enumerate(prefixes): |
| if extname.startswith(prefix): |
| return (i, name) |
| return (len(prefixes), name) |
| |
| # Generate list of promoted extensions |
| promotedExtensions = {} |
| for ext in self.extensions.values(): |
| if ext.deprecationType == 'promotion' and ext.supercedingAPIVersion: |
| promotedExtensions.setdefault(ext.supercedingAPIVersion, []).append(ext.name) |
| |
| for coreVersion, extensions in promotedExtensions.items(): |
| promoted_extensions_fp = self.newFile(self.directory + '/promoted_extensions_' + coreVersion + self.file_suffix) |
| |
| for extname in sorted(extensions, key=makeSortKey): |
| indent = '' |
| write(' * {blank}\n+\n' + ext.conditionalLinkExt(extname, indent), file=promoted_extensions_fp) |
| |
| promoted_extensions_fp.close() |
| |
| # Generate include directives for the extensions appendix, grouping |
| # extensions by status (current, deprecated, provisional, etc.) |
| with self.newFile(self.directory + '/current_extensions_appendix' + self.file_suffix) as current_extensions_appendix_fp, \ |
| self.newFile(self.directory + '/deprecated_extensions_appendix' + self.file_suffix) as deprecated_extensions_appendix_fp, \ |
| self.newFile(self.directory + '/current_extension_appendices' + self.file_suffix) as current_extension_appendices_fp, \ |
| self.newFile(self.directory + '/current_extension_appendices_toc' + self.file_suffix) as current_extension_appendices_toc_fp, \ |
| self.newFile(self.directory + '/deprecated_extension_appendices' + self.file_suffix) as deprecated_extension_appendices_fp, \ |
| self.newFile(self.directory + '/deprecated_extension_appendices_toc' + self.file_suffix) as deprecated_extension_appendices_toc_fp, \ |
| self.newFile(self.directory + '/deprecated_extensions_guard_macro' + self.file_suffix) as deprecated_extensions_guard_macro_fp, \ |
| self.newFile(self.directory + '/provisional_extensions_appendix' + self.file_suffix) as provisional_extensions_appendix_fp, \ |
| self.newFile(self.directory + '/provisional_extension_appendices' + self.file_suffix) as provisional_extension_appendices_fp, \ |
| self.newFile(self.directory + '/provisional_extension_appendices_toc' + self.file_suffix) as provisional_extension_appendices_toc_fp, \ |
| self.newFile(self.directory + '/provisional_extensions_guard_macro' + self.file_suffix) as provisional_extensions_guard_macro_fp: |
| |
| # Note: there is a hardwired assumption in creating the |
| # include:: directives below that all of these files are located |
| # in the 'meta/' subdirectory of the generated files directory. |
| # This is difficult to change, and it is very unlikely changing |
| # it will be needed. |
| |
| write('', file=current_extensions_appendix_fp) |
| write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=current_extensions_appendix_fp) |
| write('', file=current_extensions_appendix_fp) |
| write('ifndef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) |
| write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) |
| write('== List of Extensions', file=current_extensions_appendix_fp) |
| write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) |
| write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) |
| write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) |
| write('== List of Current Extensions', file=current_extensions_appendix_fp) |
| write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) |
| write('', file=current_extensions_appendix_fp) |
| write('include::{generated}/meta/current_extension_appendices_toc' + self.file_suffix + '[]', file=current_extensions_appendix_fp) |
| write('\n<<<\n', file=current_extensions_appendix_fp) |
| write('include::{generated}/meta/current_extension_appendices' + self.file_suffix + '[]', file=current_extensions_appendix_fp) |
| |
| write('', file=deprecated_extensions_appendix_fp) |
| write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) |
| write('', file=deprecated_extensions_appendix_fp) |
| write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) |
| write('[[deprecated-extension-appendices-list]]', file=deprecated_extensions_appendix_fp) |
| write('== List of Deprecated Extensions', file=deprecated_extensions_appendix_fp) |
| write('include::{generated}/meta/deprecated_extension_appendices_toc' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) |
| write('\n<<<\n', file=deprecated_extensions_appendix_fp) |
| write('include::{generated}/meta/deprecated_extension_appendices' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) |
| write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) |
| |
| # add include guards to allow multiple includes |
| write('ifndef::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) |
| write(':DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=deprecated_extensions_guard_macro_fp) |
| write('ifndef::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) |
| write(':PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=provisional_extensions_guard_macro_fp) |
| |
| write('', file=provisional_extensions_appendix_fp) |
| write('include::{generated}/meta/provisional_extensions_guard_macro' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) |
| write('', file=provisional_extensions_appendix_fp) |
| write('ifdef::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) |
| write('[[provisional-extension-appendices-list]]', file=provisional_extensions_appendix_fp) |
| write('== List of Provisional Extensions', file=provisional_extensions_appendix_fp) |
| write('include::{generated}/meta/provisional_extension_appendices_toc' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) |
| write('\n<<<\n', file=provisional_extensions_appendix_fp) |
| write('include::{generated}/meta/provisional_extension_appendices' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) |
| write('endif::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) |
| |
| # Emit extensions in author ID order |
| sorted_keys = sorted(self.extensions.keys(), key=makeSortKey) |
| for name in sorted_keys: |
| ext = self.extensions[name] |
| |
| include = self.makeExtensionInclude(ext.name) |
| link = ' * ' + self.conventions.formatExtension(ext.name) |
| if ext.provisional == 'true': |
| write(self.conditionalExt(ext.name, include), file=provisional_extension_appendices_fp) |
| write(self.conditionalExt(ext.name, link), file=provisional_extension_appendices_toc_fp) |
| write(self.conditionalExt(ext.name, ':HAS_PROVISIONAL_EXTENSIONS:'), file=provisional_extensions_guard_macro_fp) |
| elif ext.deprecationType is None: |
| write(self.conditionalExt(ext.name, include), file=current_extension_appendices_fp) |
| write(self.conditionalExt(ext.name, link), file=current_extension_appendices_toc_fp) |
| else: |
| condition = ext.supercedingAPIVersion if ext.supercedingAPIVersion else ext.supercedingExtension # potentially None too |
| |
| write(self.conditionalExt(ext.name, include, 'ifndef', condition), file=current_extension_appendices_fp) |
| write(self.conditionalExt(ext.name, link, 'ifndef', condition), file=current_extension_appendices_toc_fp) |
| |
| write(self.conditionalExt(ext.name, include, 'ifdef', condition), file=deprecated_extension_appendices_fp) |
| write(self.conditionalExt(ext.name, link, 'ifdef', condition), file=deprecated_extension_appendices_toc_fp) |
| |
| write(self.conditionalExt(ext.name, ':HAS_DEPRECATED_EXTENSIONS:', 'ifdef', condition), file=deprecated_extensions_guard_macro_fp) |
| |
| write('endif::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) |
| write('endif::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) |
| |
| OutputGenerator.endFile(self) |
| |
| def beginFeature(self, interface, emit): |
| # Start processing in superclass |
| OutputGenerator.beginFeature(self, interface, emit) |
| |
| if interface.tag != 'extension': |
| self.logMsg('diag', 'beginFeature: ignoring non-extension feature', self.featureName) |
| return |
| |
| # These attributes must exist |
| name = self.featureName |
| number = self.getAttrib(interface, 'number') |
| ext_type = self.getAttrib(interface, 'type') |
| revision = self.getSpecVersion(interface, name) |
| |
| # These attributes are optional |
| OPTIONAL = False |
| depends = self.getAttrib(interface, 'depends', OPTIONAL) # TODO should default to VK_VERSION_1_0? |
| contact = self.getAttrib(interface, 'contact', OPTIONAL) |
| promotedTo = self.getAttrib(interface, 'promotedto', OPTIONAL) |
| deprecatedBy = self.getAttrib(interface, 'deprecatedby', OPTIONAL) |
| obsoletedBy = self.getAttrib(interface, 'obsoletedby', OPTIONAL) |
| provisional = self.getAttrib(interface, 'provisional', OPTIONAL, 'false') |
| specialuse = self.getAttrib(interface, 'specialuse', OPTIONAL) |
| ratified = self.getAttrib(interface, 'ratified', OPTIONAL, '') |
| |
| filename = self.directory + '/' + name + self.file_suffix |
| |
| extdata = Extension( |
| generator = self, |
| filename = filename, |
| name = name, |
| number = number, |
| ext_type = ext_type, |
| depends = depends, |
| contact = contact, |
| promotedTo = promotedTo, |
| deprecatedBy = deprecatedBy, |
| obsoletedBy = obsoletedBy, |
| provisional = provisional, |
| revision = revision, |
| specialuse = specialuse, |
| ratified = ratified) |
| self.extensions[name] = extdata |
| |
| def endFeature(self): |
| # Finish processing in superclass |
| OutputGenerator.endFeature(self) |
| |
| def getAttrib(self, elem, attribute, required=True, default=None): |
| """Query an attribute from an element, or return a default value |
| |
| - elem - element to query |
| - attribute - attribute name |
| - required - whether attribute must exist |
| - default - default value if attribute not present""" |
| attrib = elem.get(attribute, default) |
| if required and (attrib is None): |
| name = elem.get('name', 'UNKNOWN') |
| self.logMsg('error', 'While processing \'' + self.featureName + ', <' + elem.tag + '> \'' + name + '\' does not contain required attribute \'' + attribute + '\'') |
| return attrib |
| |
| def numbersToWords(self, name): |
| allowlist = ['WIN32', 'INT16', 'D3D1'] |
| |
| # temporarily replace allowlist items |
| for i, w in enumerate(allowlist): |
| name = re.sub(w, '{' + str(i) + '}', name) |
| |
| name = re.sub(r'(?<=[A-Z])(\d+)(?![A-Z])', r'_\g<1>', name) |
| |
| # undo allowlist substitution |
| for i, w in enumerate(allowlist): |
| name = re.sub('\\{' + str(i) + '}', w, name) |
| |
| return name |
| |
| def getSpecVersion(self, elem, extname, default=None): |
| """Determine the extension revision from the EXTENSION_NAME_SPEC_VERSION |
| enumerant. |
| |
| - elem - <extension> element to query |
| - extname - extension name from the <extension> 'name' attribute |
| - default - default value if SPEC_VERSION token not present""" |
| # The literal enumerant name to match |
| versioningEnumName = self.numbersToWords(extname.upper()) + '_SPEC_VERSION' |
| |
| for enum in elem.findall('./require/enum'): |
| enumName = self.getAttrib(enum, 'name') |
| if enumName == versioningEnumName: |
| return self.getAttrib(enum, 'value') |
| |
| #if not found: |
| for enum in elem.findall('./require/enum'): |
| enumName = self.getAttrib(enum, 'name') |
| if enumName.find('SPEC_VERSION') != -1: |
| self.logMsg('diag', 'Missing ' + versioningEnumName + '! Potential misnamed candidate ' + enumName + '.') |
| return self.getAttrib(enum, 'value') |
| |
| self.logMsg('error', 'Missing ' + versioningEnumName + '!') |
| return default |