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