1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import os 8import re 9import sys 10from functools import total_ordering 11from generator import GeneratorOptions, OutputGenerator, regSortFeatures, write 12from parse_dependency import dependencyMarkup 13 14class ExtensionMetaDocGeneratorOptions(GeneratorOptions): 15 """ExtensionMetaDocGeneratorOptions - subclass of GeneratorOptions. 16 17 Represents options during extension metainformation generation for Asciidoc""" 18 def __init__(self, *args, **kwargs): 19 super().__init__(*args, **kwargs) 20 21@total_ordering 22class Extension: 23 def __init__(self, 24 generator, # needed for logging and API conventions 25 filename, 26 name, 27 number, 28 ext_type, 29 depends, 30 contact, 31 promotedTo, 32 deprecatedBy, 33 obsoletedBy, 34 provisional, 35 revision, 36 specialuse, 37 ratified 38 ): 39 self.generator = generator 40 self.conventions = generator.genOpts.conventions 41 self.filename = filename 42 self.name = name 43 self.number = number 44 self.ext_type = ext_type 45 self.depends = depends 46 self.contact = contact 47 self.promotedTo = promotedTo 48 self.deprecatedBy = deprecatedBy 49 self.obsoletedBy = obsoletedBy 50 self.provisional = provisional 51 self.revision = revision 52 self.specialuse = specialuse 53 self.ratified = ratified 54 55 self.deprecationType = None 56 self.supercedingAPIVersion = None 57 self.supercedingExtension = None 58 # This is a set containing names of extensions (if any) promoted 59 # *to* this extension. 60 # It is filled in after all the Extension objects are created, 61 # since it requires a reverse mapping step. 62 self.promotedFrom = set() 63 64 if self.promotedTo is not None and self.deprecatedBy is not None and self.obsoletedBy is not None: 65 self.generator.logMsg('warn', 'All \'promotedto\', \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\' and \'deprecatedby\'.') 66 elif self.promotedTo is not None and self.deprecatedBy is not None: 67 self.generator.logMsg('warn', 'Both \'promotedto\' and \'deprecatedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 68 elif self.promotedTo is not None and self.obsoletedBy is not None: 69 self.generator.logMsg('warn', 'Both \'promotedto\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\'.') 70 elif self.deprecatedBy is not None and self.obsoletedBy is not None: 71 self.generator.logMsg('warn', 'Both \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 72 73 supercededBy = None 74 if self.promotedTo is not None: 75 self.deprecationType = 'promotion' 76 supercededBy = promotedTo 77 elif self.deprecatedBy is not None: 78 self.deprecationType = 'deprecation' 79 supercededBy = deprecatedBy 80 elif self.obsoletedBy is not None: 81 self.deprecationType = 'obsoletion' 82 supercededBy = obsoletedBy 83 84 if supercededBy is not None: 85 if supercededBy == '' and not self.deprecationType == 'promotion': 86 pass # supercedingAPIVersion, supercedingExtension is None 87 elif supercededBy.startswith(self.conventions.api_version_prefix): 88 self.supercedingAPIVersion = supercededBy 89 elif supercededBy.startswith(self.conventions.api_prefix): 90 self.supercedingExtension = supercededBy 91 else: 92 self.generator.logMsg('error', 'Unrecognized ' + self.deprecationType + ' attribute value \'' + supercededBy + '\'!') 93 94 def __str__(self): 95 return self.name 96 def __eq__(self, other): 97 return self.name == other.name 98 def __ne__(self, other): 99 return self.name != other.name 100 101 def __lt__(self, other): 102 self_is_KHR = self.name.startswith(self.conventions.KHR_prefix) 103 self_is_EXT = self.name.startswith(self.conventions.EXT_prefix) 104 other_is_KHR = other.name.startswith(self.conventions.KHR_prefix) 105 other_is_EXT = other.name.startswith(self.conventions.EXT_prefix) 106 107 swap = False 108 if self_is_KHR and not other_is_KHR: 109 return not swap 110 if other_is_KHR and not self_is_KHR: 111 return swap 112 if self_is_EXT and not other_is_EXT: 113 return not swap 114 if other_is_EXT and not self_is_EXT: 115 return swap 116 117 return self.name < other.name 118 119 def typeToStr(self): 120 if self.ext_type == 'instance': 121 return 'Instance extension' 122 if self.ext_type == 'device': 123 return 'Device extension' 124 125 if self.ext_type is not None: 126 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).') 127 else: # should be unreachable 128 self.generator.logMsg('error', 'Logic error in typeToStr(): Missing type attribute!') 129 return None 130 131 def specLink(self, xrefName, xrefText, isRefpage = False): 132 """Generate a string containing a link to a specification anchor in 133 asciidoctor markup form. 134 135 - xrefName - anchor name in the spec 136 - xrefText - text to show for the link, or None 137 - isRefpage = True if generating a refpage include, False if 138 generating a specification extension appendix include""" 139 140 if isRefpage: 141 # Always link into API spec 142 specURL = self.conventions.specURL('api') 143 return 'link:{}#{}[{}^]'.format(specURL, xrefName, xrefText) 144 else: 145 return '<<' + xrefName + ', ' + xrefText + '>>' 146 147 def conditionalLinkCoreAPI(self, apiVersion, linkSuffix, isRefpage): 148 versionMatch = re.match(self.conventions.api_version_prefix + r'(\d+)_(\d+)', apiVersion) 149 major = versionMatch.group(1) 150 minor = versionMatch.group(2) 151 152 dottedVersion = major + '.' + minor 153 154 xrefName = 'versions-' + dottedVersion + linkSuffix 155 xrefText = self.conventions.api_name() + ' ' + dottedVersion 156 157 doc = 'ifdef::' + apiVersion + '[]\n' 158 doc += ' ' + self.specLink(xrefName, xrefText, isRefpage) + '\n' 159 doc += 'endif::' + apiVersion + '[]\n' 160 doc += 'ifndef::' + apiVersion + '[]\n' 161 doc += ' ' + self.conventions.api_name() + ' ' + dottedVersion + '\n' 162 doc += 'endif::' + apiVersion + '[]\n' 163 164 return doc 165 166 def conditionalLinkExt(self, extName, indent = ' '): 167 doc = 'ifdef::' + extName + '[]\n' 168 doc += indent + self.conventions.formatExtension(extName) + '\n' 169 doc += 'endif::' + extName + '[]\n' 170 doc += 'ifndef::' + extName + '[]\n' 171 doc += indent + '`' + extName + '`\n' 172 doc += 'endif::' + extName + '[]\n' 173 174 return doc 175 176 def resolveDeprecationChain(self, extensions, succeededBy, isRefpage, file): 177 if succeededBy not in extensions: 178 write(f' ** *NOTE* The extension `{succeededBy}` is not supported for the API specification being generated', file=file) 179 self.generator.logMsg('warn', f'resolveDeprecationChain: {self.name} defines a superseding interface {succeededBy} which is not in the supported extensions list') 180 return 181 182 ext = extensions[succeededBy] 183 184 if ext.deprecationType: 185 if ext.deprecationType == 'promotion': 186 if ext.supercedingAPIVersion: 187 write(' ** Which in turn was _promoted_ to\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-promotions', isRefpage), file=file) 188 else: # ext.supercedingExtension 189 write(' ** Which in turn was _promoted_ to extension\n' + ext.conditionalLinkExt(ext.supercedingExtension), file=file) 190 ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) 191 elif ext.deprecationType == 'deprecation': 192 if ext.supercedingAPIVersion: 193 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 194 elif ext.supercedingExtension: 195 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 196 ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) 197 else: 198 write(' ** Which in turn was _deprecated_ without replacement', file=file) 199 elif ext.deprecationType == 'obsoletion': 200 if ext.supercedingAPIVersion: 201 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 202 elif ext.supercedingExtension: 203 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 204 ext.resolveDeprecationChain(extensions, ext.supercedingExtension, file) 205 else: 206 write(' ** Which in turn was _obsoleted_ without replacement', file=file) 207 else: # should be unreachable 208 self.generator.logMsg('error', 'Logic error in resolveDeprecationChain(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 209 210 211 def writeTag(self, tag, value, isRefpage, fp): 212 """Write a tag and (if non-None) a tag value to a file. 213 214 If the value is None, just write the tag. 215 216 If the tag is None, just write the value (used for adding a value 217 to a just-written tag). 218 219 - tag - string tag name 220 - value - tag value, or None 221 - isRefpage - controls style in which the tag is marked up 222 - fp - open file pointer to write to""" 223 224 if isRefpage: 225 # Use subsection headers for the tag name 226 tagPrefix = '== ' 227 tagSuffix = '' 228 else: 229 # Use an bolded item list for the tag name 230 tagPrefix = '*' 231 tagSuffix = '*::' 232 233 if tag is not None: 234 write(tagPrefix + tag + tagSuffix, file=fp) 235 if value is not None: 236 write(value, file=fp) 237 238 if isRefpage: 239 write('', file=fp) 240 241 def makeMetafile(self, extensions, isRefpage = False): 242 """Generate a file containing extension metainformation in 243 asciidoctor markup form. 244 245 - extensions - dictionary of Extension objects for extensions spec 246 is being generated against 247 - isRefpage - True if generating a refpage include, False if 248 generating a specification extension appendix include""" 249 250 if isRefpage: 251 filename = self.filename.replace('meta/', 'meta/refpage.') 252 else: 253 filename = self.filename 254 255 fp = self.generator.newFile(filename) 256 257 if not isRefpage: 258 write('[[' + self.name + ']]', file=fp) 259 write('=== ' + self.name, file=fp) 260 write('', file=fp) 261 262 self.writeTag('Name String', '`' + self.name + '`', isRefpage, fp) 263 self.writeTag('Extension Type', self.typeToStr(), isRefpage, fp) 264 265 self.writeTag('Registered Extension Number', self.number, isRefpage, fp) 266 self.writeTag('Revision', self.revision, isRefpage, fp) 267 268 if self.conventions.xml_api_name in self.ratified.split(','): 269 ratstatus = 'Ratified' 270 else: 271 ratstatus = 'Not ratified' 272 self.writeTag('Ratification Status', ratstatus, isRefpage, fp) 273 274 # Only API extension dependencies are coded in XML, others are explicit 275 self.writeTag('Extension and Version Dependencies', None, isRefpage, fp) 276 277 # Transform the boolean 'depends' expression into equivalent 278 # human-readable asciidoc markup. 279 if self.depends is not None: 280 if isRefpage: 281 separator = '' 282 else: 283 separator = '+' 284 write(separator + '\n--\n' + 285 dependencyMarkup(self.depends) + 286 '--', file=fp) 287 else: 288 # Do not bother specifying the base Vulkan 1.0 API redundantly 289 True 290 291 if self.provisional == 'true' and self.conventions.provisional_extension_warning: 292 write(' * *This is a _provisional_ extension and must: be used with caution.', file=fp) 293 write(' See the ' + 294 self.specLink(xrefName = 'boilerplate-provisional-header', 295 xrefText = 'description', 296 isRefpage = isRefpage) + 297 ' of provisional header files for enablement and stability details.*', file=fp) 298 write('', file=fp) 299 300 if self.deprecationType: 301 self.writeTag('Deprecation State', None, isRefpage, fp) 302 303 if self.deprecationType == 'promotion': 304 if self.supercedingAPIVersion: 305 write(' * _Promoted_ to\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-promotions', isRefpage), file=fp) 306 else: # ext.supercedingExtension 307 write(' * _Promoted_ to\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension', file=fp) 308 self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) 309 elif self.deprecationType == 'deprecation': 310 if self.supercedingAPIVersion: 311 write(' * _Deprecated_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 312 elif self.supercedingExtension: 313 write(' * _Deprecated_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 314 self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) 315 else: 316 write(' * _Deprecated_ without replacement' , file=fp) 317 elif self.deprecationType == 'obsoletion': 318 if self.supercedingAPIVersion: 319 write(' * _Obsoleted_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 320 elif self.supercedingExtension: 321 write(' * _Obsoleted_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 322 self.resolveDeprecationChain(extensions, self.supercedingExtension, isRefpage, fp) 323 else: 324 # TODO: Does not make sense to retroactively ban use of extensions from 1.0. 325 # Needs some tweaks to the semantics and this message, when such extension(s) occur. 326 write(' * _Obsoleted_ without replacement' , file=fp) 327 else: # should be unreachable 328 self.generator.logMsg('error', 'Logic error in makeMetafile(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 329 write('', file=fp) 330 331 if self.specialuse is not None: 332 specialuses = self.specialuse.split(',') 333 if len(specialuses) > 1: 334 header = 'Special Uses' 335 else: 336 header = 'Special Use' 337 self.writeTag(header, None, isRefpage, fp) 338 339 for use in specialuses: 340 # Each specialuse attribute value expands an asciidoctor 341 # attribute of the same name, instead of using the shorter, 342 # and harder to understand attribute 343 write('* {}'.format( 344 self.specLink( 345 xrefName = self.conventions.special_use_section_anchor, 346 xrefText = '{' + use + '}', 347 isRefpage = isRefpage)), file=fp) 348 write('', file=fp) 349 350 if self.conventions.write_contacts: 351 self.writeTag('Contact', None, isRefpage, fp) 352 353 contacts = self.contact.split(',') 354 for contact in contacts: 355 contactWords = contact.strip().split() 356 name = ' '.join(contactWords[:-1]) 357 handle = contactWords[-1] 358 if handle.startswith('gitlab:'): 359 prettyHandle = 'icon:gitlab[alt=GitLab, role="red"]' + handle.replace('gitlab:@', '') 360 elif handle.startswith('@'): 361 issuePlaceholderText = '[' + self.name + '] ' + handle 362 issuePlaceholderText += '%0A*Here describe the issue or question you have about the ' + self.name + ' extension*' 363 trackerLink = 'link:++https://github.com/KhronosGroup/Vulkan-Docs/issues/new?body=' + issuePlaceholderText + '++' 364 prettyHandle = trackerLink + '[icon:github[alt=GitHub,role="black"]' + handle[1:] + ',window=_blank,opts=nofollow]' 365 else: 366 prettyHandle = handle 367 368 write(' * ' + name + ' ' + prettyHandle, file=fp) 369 write('', file=fp) 370 371 # Check if a proposal document for this extension exists in the 372 # current repository, and link to the same document (parameterized 373 # by a URL prefix attribute) if it does. 374 # The assumption is that a proposal document for an extension 375 # VK_name will be located in 'proposals/VK_name.adoc' relative 376 # to the repository root, and that this script will be invoked from 377 # the repository root. 378 # If a proposal for this extension does not exist, look for 379 # proposals for the extensions it is promoted from. 380 381 def checkProposal(extname): 382 """Check if a proposal document for an extension exists, 383 returning the path to that proposal or None otherwise.""" 384 385 path = 'proposals/{}.adoc'.format(extname) 386 if os.path.exists(path) and os.access(path, os.R_OK): 387 return path 388 else: 389 return None 390 391 # List of [ extname, proposal link ] 392 proposals = [] 393 394 path = checkProposal(self.name) 395 if path is not None: 396 proposals.append([self.name, path]) 397 else: 398 for name in self.promotedFrom: 399 path = checkProposal(name) 400 if path is not None: 401 proposals.append([name, path]) 402 403 if len(proposals) > 0: 404 tag = 'Extension Proposal' 405 for (name, path) in sorted(proposals): 406 self.writeTag(tag, 407 f'link:{{specRepositoryURL}}/{path}[{name}]', 408 isRefpage, fp) 409 # Setting tag = None so additional values will not get 410 # additional tag headers. 411 tag = None 412 413 # If this is metadata to be included in a refpage, adjust the 414 # leveloffset to account for the relative structure of the extension 415 # appendices vs. refpages. 416 if isRefpage and self.conventions.include_extension_appendix_in_refpage: 417 write(':leveloffset: -1', file=fp) 418 419 fp.close() 420 421class ExtensionMetaDocOutputGenerator(OutputGenerator): 422 """ExtensionMetaDocOutputGenerator - subclass of OutputGenerator. 423 424 Generates AsciiDoc includes with metainformation for the API extension 425 appendices. The fields used from <extension> tags in the API XML are: 426 427 - name extension name string 428 - number extension number (optional) 429 - contact name and GitHub login or email address (optional) 430 - type 'instance' | 'device' (optional) 431 - depends boolean expression of core version and extension names this depends on (optional) 432 - promotedTo extension or API version it was promoted to 433 - deprecatedBy extension or API version which deprecated this extension, 434 or empty string if deprecated without replacement 435 - obsoletedBy extension or API version which obsoleted this extension, 436 or empty string if obsoleted without replacement 437 - provisional 'true' if this extension is released provisionally""" 438 439 def __init__(self, *args, **kwargs): 440 super().__init__(*args, **kwargs) 441 self.extensions = {} 442 # List of strings containing all vendor tags 443 self.vendor_tags = [] 444 self.file_suffix = '' 445 446 def newFile(self, filename): 447 self.logMsg('diag', '# Generating include file:', filename) 448 fp = open(filename, 'w', encoding='utf-8') 449 write(self.genOpts.conventions.warning_comment, file=fp) 450 return fp 451 452 def beginFile(self, genOpts): 453 OutputGenerator.beginFile(self, genOpts) 454 455 self.directory = self.genOpts.directory 456 self.file_suffix = self.genOpts.conventions.file_suffix 457 458 # Iterate over all 'tag' Elements and add the names of all the valid vendor 459 # tags to the list 460 root = self.registry.tree.getroot() 461 for tag in root.findall('tags/tag'): 462 self.vendor_tags.append(tag.get('name')) 463 464 # Create subdirectory, if needed 465 self.makeDir(self.directory) 466 467 def conditionalExt(self, extName, content, ifdef = None, condition = None): 468 doc = '' 469 470 innerdoc = 'ifdef::' + extName + '[]\n' 471 innerdoc += content + '\n' 472 innerdoc += 'endif::' + extName + '[]\n' 473 474 if ifdef: 475 if ifdef == 'ifndef': 476 if condition: 477 doc += 'ifndef::' + condition + '[]\n' 478 doc += innerdoc 479 doc += 'endif::' + condition + '[]\n' 480 else: # no condition is as if condition is defined; "nothing" is always defined :p 481 pass # so no output 482 elif ifdef == 'ifdef': 483 if condition: 484 doc += 'ifdef::' + condition + '+' + extName + '[]\n' 485 doc += content + '\n' # does not include innerdoc; the ifdef was merged with the one above 486 doc += 'endif::' + condition + '+' + extName + '[]\n' 487 else: # no condition is as if condition is defined; "nothing" is always defined :p 488 doc += innerdoc 489 else: # should be unreachable 490 raise RuntimeError('Should be unreachable: ifdef is neither \'ifdef \' nor \'ifndef\'!') 491 else: 492 doc += innerdoc 493 494 return doc 495 496 def makeExtensionInclude(self, extname): 497 return self.conventions.extension_include_string(extname) 498 499 def endFile(self): 500 # Determine the extension an extension is promoted from, if any. 501 # This is used when attempting to locate a proposal document in 502 # makeMetafile() below. 503 for (extname, ext) in self.extensions.items(): 504 promotedTo = ext.promotedTo 505 if promotedTo is not None: 506 if promotedTo in self.extensions: 507 #print(f'{promotedTo} is promoted from {extname}') 508 self.extensions[promotedTo].promotedFrom.add(extname) 509 #print(f'setting self.extensions[{promotedTo}].promotedFrom = {self.extensions[promotedTo].promotedFrom}') 510 elif not self.conventions.is_api_version_name(promotedTo): 511 self.logMsg('warn', f'{extname} is promoted to {promotedTo} which is not in the extension map') 512 513 # Generate metadoc extension files, in refpage and non-refpage form 514 for ext in self.extensions.values(): 515 ext.makeMetafile(self.extensions, isRefpage = False) 516 if self.conventions.write_refpage_include: 517 ext.makeMetafile(self.extensions, isRefpage = True) 518 519 # Key to sort extensions alphabetically within 'KHR', 'EXT', vendor 520 # extension prefixes. 521 def makeSortKey(extname): 522 name = extname.lower() 523 prefixes = self.conventions.extension_index_prefixes 524 for i, prefix in enumerate(prefixes): 525 if extname.startswith(prefix): 526 return (i, name) 527 return (len(prefixes), name) 528 529 # Generate list of promoted extensions 530 promotedExtensions = {} 531 for ext in self.extensions.values(): 532 if ext.deprecationType == 'promotion' and ext.supercedingAPIVersion: 533 promotedExtensions.setdefault(ext.supercedingAPIVersion, []).append(ext.name) 534 535 for coreVersion, extensions in promotedExtensions.items(): 536 promoted_extensions_fp = self.newFile(self.directory + '/promoted_extensions_' + coreVersion + self.file_suffix) 537 538 for extname in sorted(extensions, key=makeSortKey): 539 indent = '' 540 write(' * {blank}\n+\n' + ext.conditionalLinkExt(extname, indent), file=promoted_extensions_fp) 541 542 promoted_extensions_fp.close() 543 544 # Generate include directives for the extensions appendix, grouping 545 # extensions by status (current, deprecated, provisional, etc.) 546 with self.newFile(self.directory + '/current_extensions_appendix' + self.file_suffix) as current_extensions_appendix_fp, \ 547 self.newFile(self.directory + '/deprecated_extensions_appendix' + self.file_suffix) as deprecated_extensions_appendix_fp, \ 548 self.newFile(self.directory + '/current_extension_appendices' + self.file_suffix) as current_extension_appendices_fp, \ 549 self.newFile(self.directory + '/current_extension_appendices_toc' + self.file_suffix) as current_extension_appendices_toc_fp, \ 550 self.newFile(self.directory + '/deprecated_extension_appendices' + self.file_suffix) as deprecated_extension_appendices_fp, \ 551 self.newFile(self.directory + '/deprecated_extension_appendices_toc' + self.file_suffix) as deprecated_extension_appendices_toc_fp, \ 552 self.newFile(self.directory + '/deprecated_extensions_guard_macro' + self.file_suffix) as deprecated_extensions_guard_macro_fp, \ 553 self.newFile(self.directory + '/provisional_extensions_appendix' + self.file_suffix) as provisional_extensions_appendix_fp, \ 554 self.newFile(self.directory + '/provisional_extension_appendices' + self.file_suffix) as provisional_extension_appendices_fp, \ 555 self.newFile(self.directory + '/provisional_extension_appendices_toc' + self.file_suffix) as provisional_extension_appendices_toc_fp, \ 556 self.newFile(self.directory + '/provisional_extensions_guard_macro' + self.file_suffix) as provisional_extensions_guard_macro_fp: 557 558 # Note: there is a hardwired assumption in creating the 559 # include:: directives below that all of these files are located 560 # in the 'meta/' subdirectory of the generated files directory. 561 # This is difficult to change, and it is very unlikely changing 562 # it will be needed. 563 564 write('', file=current_extensions_appendix_fp) 565 write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 566 write('', file=current_extensions_appendix_fp) 567 write('ifndef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 568 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 569 write('== List of Extensions', file=current_extensions_appendix_fp) 570 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 571 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 572 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 573 write('== List of Current Extensions', file=current_extensions_appendix_fp) 574 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 575 write('', file=current_extensions_appendix_fp) 576 write('include::{generated}/meta/current_extension_appendices_toc' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 577 write('\n<<<\n', file=current_extensions_appendix_fp) 578 write('include::{generated}/meta/current_extension_appendices' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 579 580 write('', file=deprecated_extensions_appendix_fp) 581 write('include::{generated}/meta/deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 582 write('', file=deprecated_extensions_appendix_fp) 583 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 584 write('[[deprecated-extension-appendices-list]]', file=deprecated_extensions_appendix_fp) 585 write('== List of Deprecated Extensions', file=deprecated_extensions_appendix_fp) 586 write('include::{generated}/meta/deprecated_extension_appendices_toc' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 587 write('\n<<<\n', file=deprecated_extensions_appendix_fp) 588 write('include::{generated}/meta/deprecated_extension_appendices' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 589 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 590 591 # add include guards to allow multiple includes 592 write('ifndef::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 593 write(':DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=deprecated_extensions_guard_macro_fp) 594 write('ifndef::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) 595 write(':PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=provisional_extensions_guard_macro_fp) 596 597 write('', file=provisional_extensions_appendix_fp) 598 write('include::{generated}/meta/provisional_extensions_guard_macro' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 599 write('', file=provisional_extensions_appendix_fp) 600 write('ifdef::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 601 write('[[provisional-extension-appendices-list]]', file=provisional_extensions_appendix_fp) 602 write('== List of Provisional Extensions', file=provisional_extensions_appendix_fp) 603 write('include::{generated}/meta/provisional_extension_appendices_toc' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 604 write('\n<<<\n', file=provisional_extensions_appendix_fp) 605 write('include::{generated}/meta/provisional_extension_appendices' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 606 write('endif::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 607 608 # Emit extensions in author ID order 609 sorted_keys = sorted(self.extensions.keys(), key=makeSortKey) 610 for name in sorted_keys: 611 ext = self.extensions[name] 612 613 include = self.makeExtensionInclude(ext.name) 614 link = ' * ' + self.conventions.formatExtension(ext.name) 615 if ext.provisional == 'true': 616 write(self.conditionalExt(ext.name, include), file=provisional_extension_appendices_fp) 617 write(self.conditionalExt(ext.name, link), file=provisional_extension_appendices_toc_fp) 618 write(self.conditionalExt(ext.name, ':HAS_PROVISIONAL_EXTENSIONS:'), file=provisional_extensions_guard_macro_fp) 619 elif ext.deprecationType is None: 620 write(self.conditionalExt(ext.name, include), file=current_extension_appendices_fp) 621 write(self.conditionalExt(ext.name, link), file=current_extension_appendices_toc_fp) 622 else: 623 condition = ext.supercedingAPIVersion if ext.supercedingAPIVersion else ext.supercedingExtension # potentially None too 624 625 write(self.conditionalExt(ext.name, include, 'ifndef', condition), file=current_extension_appendices_fp) 626 write(self.conditionalExt(ext.name, link, 'ifndef', condition), file=current_extension_appendices_toc_fp) 627 628 write(self.conditionalExt(ext.name, include, 'ifdef', condition), file=deprecated_extension_appendices_fp) 629 write(self.conditionalExt(ext.name, link, 'ifdef', condition), file=deprecated_extension_appendices_toc_fp) 630 631 write(self.conditionalExt(ext.name, ':HAS_DEPRECATED_EXTENSIONS:', 'ifdef', condition), file=deprecated_extensions_guard_macro_fp) 632 633 write('endif::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 634 write('endif::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) 635 636 OutputGenerator.endFile(self) 637 638 def beginFeature(self, interface, emit): 639 # Start processing in superclass 640 OutputGenerator.beginFeature(self, interface, emit) 641 642 if interface.tag != 'extension': 643 self.logMsg('diag', 'beginFeature: ignoring non-extension feature', self.featureName) 644 return 645 646 # These attributes must exist 647 name = self.featureName 648 number = self.getAttrib(interface, 'number') 649 ext_type = self.getAttrib(interface, 'type') 650 revision = self.getSpecVersion(interface, name) 651 652 # These attributes are optional 653 OPTIONAL = False 654 depends = self.getAttrib(interface, 'depends', OPTIONAL) # TODO should default to VK_VERSION_1_0? 655 contact = self.getAttrib(interface, 'contact', OPTIONAL) 656 promotedTo = self.getAttrib(interface, 'promotedto', OPTIONAL) 657 deprecatedBy = self.getAttrib(interface, 'deprecatedby', OPTIONAL) 658 obsoletedBy = self.getAttrib(interface, 'obsoletedby', OPTIONAL) 659 provisional = self.getAttrib(interface, 'provisional', OPTIONAL, 'false') 660 specialuse = self.getAttrib(interface, 'specialuse', OPTIONAL) 661 ratified = self.getAttrib(interface, 'ratified', OPTIONAL, '') 662 663 filename = self.directory + '/' + name + self.file_suffix 664 665 extdata = Extension( 666 generator = self, 667 filename = filename, 668 name = name, 669 number = number, 670 ext_type = ext_type, 671 depends = depends, 672 contact = contact, 673 promotedTo = promotedTo, 674 deprecatedBy = deprecatedBy, 675 obsoletedBy = obsoletedBy, 676 provisional = provisional, 677 revision = revision, 678 specialuse = specialuse, 679 ratified = ratified) 680 self.extensions[name] = extdata 681 682 def endFeature(self): 683 # Finish processing in superclass 684 OutputGenerator.endFeature(self) 685 686 def getAttrib(self, elem, attribute, required=True, default=None): 687 """Query an attribute from an element, or return a default value 688 689 - elem - element to query 690 - attribute - attribute name 691 - required - whether attribute must exist 692 - default - default value if attribute not present""" 693 attrib = elem.get(attribute, default) 694 if required and (attrib is None): 695 name = elem.get('name', 'UNKNOWN') 696 self.logMsg('error', 'While processing \'' + self.featureName + ', <' + elem.tag + '> \'' + name + '\' does not contain required attribute \'' + attribute + '\'') 697 return attrib 698 699 def numbersToWords(self, name): 700 allowlist = ['WIN32', 'INT16', 'D3D1'] 701 702 # temporarily replace allowlist items 703 for i, w in enumerate(allowlist): 704 name = re.sub(w, '{' + str(i) + '}', name) 705 706 name = re.sub(r'(?<=[A-Z])(\d+)(?![A-Z])', r'_\g<1>', name) 707 708 # undo allowlist substitution 709 for i, w in enumerate(allowlist): 710 name = re.sub('\\{' + str(i) + '}', w, name) 711 712 return name 713 714 def getSpecVersion(self, elem, extname, default=None): 715 """Determine the extension revision from the EXTENSION_NAME_SPEC_VERSION 716 enumerant. 717 718 - elem - <extension> element to query 719 - extname - extension name from the <extension> 'name' attribute 720 - default - default value if SPEC_VERSION token not present""" 721 # The literal enumerant name to match 722 versioningEnumName = self.numbersToWords(extname.upper()) + '_SPEC_VERSION' 723 724 for enum in elem.findall('./require/enum'): 725 enumName = self.getAttrib(enum, 'name') 726 if enumName == versioningEnumName: 727 return self.getAttrib(enum, 'value') 728 729 #if not found: 730 for enum in elem.findall('./require/enum'): 731 enumName = self.getAttrib(enum, 'name') 732 if enumName.find('SPEC_VERSION') != -1: 733 self.logMsg('diag', 'Missing ' + versioningEnumName + '! Potential misnamed candidate ' + enumName + '.') 734 return self.getAttrib(enum, 'value') 735 736 self.logMsg('error', 'Missing ' + versioningEnumName + '!') 737 return default 738