1#!/usr/bin/python3 2# 3# Copyright (c) 2019 Collabora, Ltd. 4# 5# SPDX-License-Identifier: Apache-2.0 6# 7# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com> 8# 9# Purpose: This script checks some "business logic" in the XML registry. 10 11import re 12import sys 13from pathlib import Path 14from collections import defaultdict, deque, namedtuple 15 16from check_spec_links import VulkanEntityDatabase as OrigEntityDatabase 17from reg import Registry 18from spec_tools.consistency_tools import XMLChecker 19from spec_tools.util import findNamedElem, getElemName, getElemType 20from apiconventions import APIConventions 21 22# These are extensions which do not follow the usual naming conventions, 23# specifying the alternate convention they follow 24EXTENSION_ENUM_NAME_SPELLING_CHANGE = { 25 'VK_EXT_swapchain_colorspace': 'VK_EXT_SWAPCHAIN_COLOR_SPACE', 26} 27 28# These are extensions whose names *look* like they end in version numbers, 29# but do not 30EXTENSION_NAME_VERSION_EXCEPTIONS = ( 31 'VK_AMD_gpu_shader_int16', 32 'VK_EXT_index_type_uint8', 33 'VK_EXT_shader_image_atomic_int64', 34 'VK_KHR_video_decode_h264', 35 'VK_KHR_video_decode_h265', 36 'VK_EXT_video_encode_h264', 37 'VK_EXT_video_encode_h265', 38 'VK_KHR_external_fence_win32', 39 'VK_KHR_external_memory_win32', 40 'VK_KHR_external_semaphore_win32', 41 'VK_KHR_shader_atomic_int64', 42 'VK_KHR_shader_float16_int8', 43 'VK_KHR_spirv_1_4', 44 'VK_NV_external_memory_win32', 45 'VK_RESERVED_do_not_use_146', 46 'VK_RESERVED_do_not_use_94', 47) 48 49# Exceptions to pointer parameter naming rules 50# Keyed by (entity name, type, name). 51CHECK_PARAM_POINTER_NAME_EXCEPTIONS = { 52 ('vkGetDrmDisplayEXT', 'VkDisplayKHR', 'display') : None, 53} 54 55# Exceptions to pNext member requiring an optional attribute 56CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS = ( 57 'VkVideoEncodeInfoKHR', 58 'VkVideoEncodeRateControlLayerInfoKHR', 59) 60 61# Exceptions to VK_INCOMPLETE being required for, and only applicable to, array 62# enumeration functions 63CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS = ( 64 'vkGetDeviceFaultInfoEXT', 65) 66 67def get_extension_commands(reg): 68 extension_cmds = set() 69 for ext in reg.extensions: 70 for cmd in ext.findall('./require/command[@name]'): 71 extension_cmds.add(cmd.get('name')) 72 return extension_cmds 73 74 75def get_enum_value_names(reg, enum_type): 76 names = set() 77 result_elem = reg.groupdict[enum_type].elem 78 for val in result_elem.findall('./enum[@name]'): 79 names.add(val.get('name')) 80 return names 81 82 83# Regular expression matching an extension name ending in a (possible) version number 84EXTNAME_RE = re.compile(r'(?P<base>(\w+[A-Za-z]))(?P<version>\d+)') 85 86DESTROY_PREFIX = 'vkDestroy' 87TYPEENUM = 'VkStructureType' 88 89SPECIFICATION_DIR = Path(__file__).parent.parent 90REVISION_RE = re.compile(r' *[*] Revision (?P<num>[1-9][0-9]*),.*') 91 92 93def get_extension_source(extname): 94 fn = f'{extname}.adoc' 95 return str(SPECIFICATION_DIR / 'appendices' / fn) 96 97 98class EntityDatabase(OrigEntityDatabase): 99 100 # Override base class method to not exclude 'disabled' extensions 101 def getExclusionSet(self): 102 """Return a set of "support=" attribute strings that should not be included in the database. 103 104 Called only during construction.""" 105 106 return set(()) 107 108 def makeRegistry(self): 109 try: 110 import lxml.etree as etree 111 HAS_LXML = True 112 except ImportError: 113 HAS_LXML = False 114 if not HAS_LXML: 115 return super().makeRegistry() 116 117 registryFile = str(SPECIFICATION_DIR / 'xml/vk.xml') 118 registry = Registry() 119 registry.filename = registryFile 120 registry.loadElementTree(etree.parse(registryFile)) 121 return registry 122 123 124class Checker(XMLChecker): 125 def __init__(self): 126 manual_types_to_codes = { 127 # These are hard-coded "manual" return codes: 128 # the codes of the value (string, list, or tuple) 129 # are available for a command if-and-only-if 130 # the key type is passed as an input. 131 'VkFormat': 'VK_ERROR_FORMAT_NOT_SUPPORTED' 132 } 133 forward_only = { 134 # Like the above, but these are only valid in the 135 # "type implies return code" direction 136 } 137 reverse_only = { 138 # like the above, but these are only valid in the 139 # "return code implies type or its descendant" direction 140 # "XrDuration": "XR_TIMEOUT_EXPIRED" 141 } 142 # Some return codes are related in that only one of a set 143 # may be returned by a command 144 # (eg. XR_ERROR_SESSION_RUNNING and XR_ERROR_SESSION_NOT_RUNNING) 145 self.exclusive_return_code_sets = tuple( 146 # set(("XR_ERROR_SESSION_NOT_RUNNING", "XR_ERROR_SESSION_RUNNING")), 147 ) 148 149 # This is used to report collisions. 150 conventions = APIConventions() 151 db = EntityDatabase() 152 153 self.extension_cmds = get_extension_commands(db.registry) 154 self.return_codes = get_enum_value_names(db.registry, 'VkResult') 155 self.structure_types = get_enum_value_names(db.registry, TYPEENUM) 156 157 # Dict of entity name to a list of messages to suppress. (Exclude any context data and "Warning:"/"Error:") 158 # Keys are entity names, values are tuples or lists of message text to suppress. 159 suppressions = {} 160 161 # Structures explicitly allowed to have 'limittype' attributes 162 self.allowedStructs = set(( 163 'VkFormatProperties', 164 'VkFormatProperties2', 165 'VkPhysicalDeviceProperties', 166 'VkPhysicalDeviceProperties2', 167 'VkPhysicalDeviceLimits', 168 'VkQueueFamilyProperties', 169 'VkQueueFamilyProperties2', 170 'VkSparseImageFormatProperties', 171 'VkSparseImageFormatProperties2', 172 )) 173 174 # Substructures of allowed structures. This can be found by looking 175 # at tags, but there are so few cases that it is hardwired for now. 176 self.nestedStructs = set(( 177 'VkPhysicalDeviceLimits', 178 'VkPhysicalDeviceSparseProperties', 179 'VkPhysicalDeviceProperties', 180 'VkQueueFamilyProperties', 181 'VkSparseImageFormatProperties', 182 )) 183 184 # Structures all of whose (non pNext/sType) members are required to 185 # have 'limittype' attributes, as are their descendants 186 self.requiredStructs = set(( 187 'VkPhysicalDeviceProperties', 188 'VkPhysicalDeviceProperties2', 189 'VkPhysicalDeviceLimits', 190 'VkSparseImageFormatProperties', 191 'VkSparseImageFormatProperties2', 192 )) 193 194 # Structures which have already have their limittype attributes validated 195 self.validatedLimittype = set() 196 197 # Initialize superclass 198 super().__init__(entity_db=db, conventions=conventions, 199 manual_types_to_codes=manual_types_to_codes, 200 forward_only_types_to_codes=forward_only, 201 reverse_only_types_to_codes=reverse_only, 202 suppressions=suppressions) 203 204 def check(self): 205 """Extends base class behavior with additional checks""" 206 207 # This test is not run on a per-structure basis, but loops over 208 # specific structures 209 self.check_type_required_limittype() 210 211 super().check() 212 213 def check_command(self, name, info): 214 """Extends base class behavior with additional checks""" 215 216 if name[0:5] == 'vkCmd': 217 if info.elem.get('tasks') is None: 218 self.record_error(f'{name} is a vkCmd* command, but is missing a "tasks" attribute') 219 220 super().check_command(name, info) 221 222 def check_command_return_codes_basic(self, name, info, 223 successcodes, errorcodes): 224 """Check a command's return codes for consistency. 225 226 Called on every command.""" 227 # Check that all extension commands can return the code associated 228 # with trying to use an extension that was not enabled. 229 # if name in self.extension_cmds and UNSUPPORTED not in errorcodes: 230 # self.record_error('Missing expected return code', 231 # UNSUPPORTED, 232 # 'implied due to being an extension command') 233 234 codes = successcodes.union(errorcodes) 235 236 # Check that all return codes are recognized. 237 unrecognized = codes - self.return_codes 238 if unrecognized: 239 self.record_error('Unrecognized return code(s):', 240 unrecognized) 241 242 elem = info.elem 243 params = [(getElemName(elt), elt) for elt in elem.findall('param')] 244 245 def is_count_output(name, elt): 246 # Must end with Count or Size, 247 # not be const, 248 # and be a pointer (detected by naming convention) 249 return (name.endswith('Count') or name.endswith('Size')) \ 250 and (elt.tail is None or 'const' not in elt.tail) \ 251 and (name.startswith('p')) 252 253 countParams = [elt 254 for name, elt in params 255 if is_count_output(name, elt)] 256 if countParams: 257 assert(len(countParams) == 1) 258 if 'VK_INCOMPLETE' not in successcodes: 259 message = "Apparent enumeration of an array without VK_INCOMPLETE in successcodes for command {}.".format(name) 260 if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS: 261 self.record_warning('(Allowed exception)', message) 262 else: 263 self.record_error(message) 264 265 elif 'VK_INCOMPLETE' in successcodes: 266 message = "VK_INCOMPLETE in successcodes of command {} that is apparently not an array enumeration.".format(name) 267 if name in CHECK_ARRAY_ENUMERATION_RETURN_CODE_EXCEPTIONS: 268 self.record_warning('(Allowed exception)', message) 269 else: 270 self.record_error(message) 271 272 def check_param(self, param): 273 """Check a member of a struct or a param of a function. 274 275 Called from check_params.""" 276 super().check_param(param) 277 278 if not self.is_api_type(param): 279 return 280 281 param_text = ''.join(param.itertext()) 282 param_name = getElemName(param) 283 284 # Make sure the number of leading 'p' matches the pointer count. 285 pointercount = param.find('type').tail 286 if pointercount: 287 pointercount = pointercount.count('*') 288 if pointercount: 289 prefix = 'p' * pointercount 290 if not param_name.startswith(prefix): 291 param_type = param.find('type').text 292 message = "Apparently incorrect pointer-related name prefix for {} - expected it to start with '{}'".format( 293 param_text, prefix) 294 if (self.entity, param_type, param_name) in CHECK_PARAM_POINTER_NAME_EXCEPTIONS: 295 self.record_warning('(Allowed exception)', message, elem=param) 296 else: 297 self.record_error(message, elem=param) 298 299 # Make sure pNext members have optional="true" attributes 300 if param_name == self.conventions.nextpointer_member_name: 301 optional = param.get('optional') 302 if optional is None or optional != 'true': 303 message = f'{self.entity}.pNext member is missing \'optional="true"\' attribute' 304 if self.entity in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS: 305 self.record_warning('(Allowed exception)', message, elem=param) 306 else: 307 self.record_error(message, elem=param) 308 309 def check_type_stype(self, name, info, type_elts): 310 """Check a struct type's sType member""" 311 if len(type_elts) > 1: 312 self.record_error( 313 'Have more than one member of type', TYPEENUM) 314 else: 315 type_elt = type_elts[0] 316 val = type_elt.get('values') 317 if val and val not in self.structure_types: 318 self.record_error('Unknown structure type constant', val) 319 320 def check_type_pnext(self, name, info): 321 """Check a struct type's pNext member, if present""" 322 323 next_name = self.conventions.nextpointer_member_name 324 next_member = findNamedElem(info.elem.findall('member'), next_name) 325 if next_member is not None: 326 # Ensure that the 'optional' attribute is set to 'true' 327 optional = next_member.get('optional') 328 if optional is None or optional != 'true': 329 message = f'{name}.{next_name} member is missing \'optional="true"\' attribute' 330 if name in CHECK_MEMBER_PNEXT_OPTIONAL_EXCEPTIONS: 331 self.record_warning('(Allowed exception)', message) 332 else: 333 self.record_error(message) 334 335 def __isLimittypeStruct(self, name, info, allowedStructs): 336 """Check if a type element is a structure allowed to have 'limittype' attributes 337 name - name of a structure 338 info - corresponding TypeInfo object 339 allowedStructs - set of struct names explicitly allowed""" 340 341 # Is this an explicitly allowed struct? 342 if name in allowedStructs: 343 return True 344 345 # Is this a struct extending an explicitly allowed struct? 346 extends = info.elem.get('structextends') 347 if extends is not None: 348 # See if any name in the structextends attribute is an allowed 349 # struct 350 if len(set(extends.split(',')) & allowedStructs) > 0: 351 return True 352 353 return False 354 355 def __validateStructLimittypes(self, name, info, requiredLimittype): 356 """Validate 'limittype' attributes for a single struct. 357 info - TypeInfo for a struct <type> 358 requiredLimittype - True if members *must* have a limittype""" 359 360 # Do not re-check structures 361 if name in self.validatedLimittype: 362 return {} 363 self.validatedLimittype.add(name) 364 365 limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid']) 366 badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[])) 367 validLimittypes = { 'min', 'max', 'pot', 'mul', 'bits', 'bitmask', 'range', 'struct', 'exact', 'noauto' } 368 for member in info.getMembers(): 369 memberName = member.findtext('name') 370 if memberName in ['sType', 'pNext']: 371 continue 372 limittype = member.get('limittype') 373 if limittype is None: 374 # Do not tag this as missing if it is not required 375 if requiredLimittype: 376 badFields[info.elem.get('name')].missing.append(memberName) 377 elif limittype == 'struct': 378 typeName = member.findtext('type') 379 memberType = self.reg.typedict[typeName] 380 badFields.update(self.__validateStructLimittypes(typeName, memberType, requiredLimittype)) 381 else: 382 for value in limittype.split(','): 383 if value not in validLimittypes: 384 badFields[info.elem.get('name')].invalid.append(memberName) 385 386 return badFields 387 388 def check_type_disallowed_limittype(self, name, info): 389 """Check if a struct type's members cannot have the 'limittype' attribute""" 390 391 # If not allowed to have limittypes, verify this for each member 392 if not self.__isLimittypeStruct(name, info, self.allowedStructs.union(self.nestedStructs)): 393 for member in info.getMembers(): 394 if member.get('limittype') is not None: 395 memname = member.findtext('name') 396 self.record_error(f'{name} member {memname} has disallowed limittype attribute') 397 398 def check_type_required_limittype(self): 399 """Check struct type members which must have the 'limittype' attribute 400 401 Called from check.""" 402 403 for name in self.allowedStructs: 404 # Assume that only extending structs of structs explicitly 405 # requiring limittypes also require them 406 requiredLimittype = (name in self.requiredStructs) 407 408 info = self.reg.typedict[name] 409 410 self.set_error_context(entity=name, elem=info.elem) 411 412 badFields = self.__validateStructLimittypes(name, info, requiredLimittype) 413 for extendingStructName in self.reg.validextensionstructs[name]: 414 extendingStruct = self.reg.typedict[extendingStructName] 415 badFields.update(self.__validateStructLimittypes(extendingStructName, extendingStruct, requiredLimittype)) 416 417 if badFields: 418 for key in sorted(badFields.keys()): 419 diags = badFields[key] 420 if diags.missing: 421 self.record_error(f'{name} missing limittype for members {", ".join(badFields[key].missing)}') 422 if diags.invalid: 423 self.record_error(f'{name} has invalid limittype for members {", ".join(badFields[key].invalid)}') 424 425 def check_type(self, name, info, category): 426 """Check a type's XML data for consistency. 427 428 Called from check.""" 429 430 if category == 'struct': 431 type_elts = [elt 432 for elt in info.elem.findall('member') 433 if getElemType(elt) == TYPEENUM] 434 435 if type_elts: 436 self.check_type_stype(name, info, type_elts) 437 self.check_type_pnext(name, info) 438 439 # Check for disallowed limittypes on all structures 440 self.check_type_disallowed_limittype(name, info) 441 elif category == 'bitmask': 442 if 'Flags' in name: 443 expected_require = name.replace('Flags', 'FlagBits') 444 require = info.elem.get('require') 445 if require is not None and expected_require != require: 446 self.record_error('Unexpected require attribute value:', 447 'got', require, 448 'but expected', expected_require) 449 super().check_type(name, info, category) 450 451 def check_extension(self, name, info, supported): 452 """Check an extension's XML data for consistency. 453 454 Called from check.""" 455 456 elem = info.elem 457 enums = elem.findall('./require/enum[@name]') 458 459 # If extension name is not on the exception list and matches the 460 # versioned-extension pattern, map the extension name to the version 461 # name with the version as a separate word. Otherwise just map it to 462 # the upper-case version of the extension name. 463 464 matches = EXTNAME_RE.fullmatch(name) 465 ext_versioned_name = False 466 if name in EXTENSION_ENUM_NAME_SPELLING_CHANGE: 467 ext_enum_name = EXTENSION_ENUM_NAME_SPELLING_CHANGE.get(name) 468 elif matches is None or name in EXTENSION_NAME_VERSION_EXCEPTIONS: 469 # This is the usual case, either a name that does not look 470 # versioned, or one that does but is on the exception list. 471 ext_enum_name = name.upper() 472 else: 473 # This is a versioned extension name. 474 # Treat the version number as a separate word. 475 base = matches.group('base') 476 version = matches.group('version') 477 ext_enum_name = base.upper() + '_' + version 478 # Keep track of this case 479 ext_versioned_name = True 480 481 # Look for the expected SPEC_VERSION token name 482 version_name = f'{ext_enum_name}_SPEC_VERSION' 483 version_elem = findNamedElem(enums, version_name) 484 485 if version_elem is None: 486 # Did not find a SPEC_VERSION enum matching the extension name 487 if ext_versioned_name: 488 suffix = '\n\ 489 Make sure that trailing version numbers in extension names are treated\n\ 490 as separate words in extension enumerant names. If this is an extension\n\ 491 whose name ends in a number which is not a version, such as "...h264"\n\ 492 or "...int16", add it to EXTENSION_NAME_VERSION_EXCEPTIONS in\n\ 493 scripts/xml_consistency.py.' 494 else: 495 suffix = '' 496 self.record_error(f'Missing version enum {version_name}{suffix}') 497 elif supported: 498 # Skip unsupported / disabled extensions for these checks 499 500 fn = get_extension_source(name) 501 revisions = [] 502 with open(fn, 'r', encoding='utf-8') as fp: 503 for line in fp: 504 line = line.rstrip() 505 match = REVISION_RE.match(line) 506 if match: 507 revisions.append(int(match.group('num'))) 508 ver_from_xml = version_elem.get('value') 509 if revisions: 510 ver_from_text = str(max(revisions)) 511 if ver_from_xml != ver_from_text: 512 self.record_error('Version enum mismatch: spec text indicates', ver_from_text, 513 'but XML says', ver_from_xml) 514 else: 515 if ver_from_xml == '1': 516 self.record_warning( 517 "Cannot find version history in spec text - make sure it has lines starting exactly like '* Revision 1, ....'", 518 filename=fn) 519 else: 520 self.record_warning("Cannot find version history in spec text, but XML reports a non-1 version number", ver_from_xml, 521 " - make sure the spec text has lines starting exactly like '* Revision 1, ....'", 522 filename=fn) 523 524 name_define = f'{ext_enum_name}_EXTENSION_NAME' 525 name_elem = findNamedElem(enums, name_define) 526 if name_elem is None: 527 self.record_error('Missing name enum', name_define) 528 else: 529 # Note: etree handles the XML entities here and turns " back into " 530 expected_name = f'"{name}"' 531 name_val = name_elem.get('value') 532 if name_val != expected_name: 533 self.record_error('Incorrect name enum: expected', expected_name, 534 'got', name_val) 535 536 super().check_extension(name, info, supported) 537 538 def check_format(self): 539 """Check an extension's XML data for consistency. 540 541 Called from check.""" 542 543 astc3d_formats = [ 544 'VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT', 545 'VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT', 546 'VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT', 547 'VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT', 548 'VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT', 549 'VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT', 550 'VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT', 551 'VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT', 552 'VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT', 553 'VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT', 554 'VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT', 555 'VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT', 556 'VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT', 557 'VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT', 558 'VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT', 559 'VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT', 560 'VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT', 561 'VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT', 562 'VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT', 563 'VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT', 564 'VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT', 565 'VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT', 566 'VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT', 567 'VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT', 568 'VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT', 569 'VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT', 570 'VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT', 571 'VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT', 572 'VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT', 573 'VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT' 574 ] 575 576 # Need to build list of formats from rest of <enums> 577 enum_formats = [] 578 for enum in self.reg.groupdict['VkFormat'].elem: 579 if enum.get('alias') is None and enum.get('name') != 'VK_FORMAT_UNDEFINED': 580 enum_formats.append(enum.get('name')) 581 582 found_formats = [] 583 for name, info in self.reg.formatsdict.items(): 584 found_formats.append(name) 585 self.set_error_context(entity=name, elem=info.elem) 586 587 if name not in enum_formats: 588 self.record_error('The <format> has no matching <enum> for', name) 589 590 # Check never just 1 plane 591 plane_elems = info.elem.findall('plane') 592 if len(plane_elems) == 1: 593 self.record_error('The <format> has only 1 <plane> for', name) 594 595 valid_chroma = ['420', '422', '444'] 596 if info.elem.get('chroma') and info.elem.get('chroma') not in valid_chroma: 597 self.record_error('The <format> has chroma is not a valid value for', name) 598 599 # The formatsgenerator.py assumes only 1 <spirvimageformat> tag. 600 # If this changes in the future, remove this warning and update generator script 601 spirv_image_format = info.elem.findall('spirvimageformat') 602 if len(spirv_image_format) > 1: 603 self.record_error('More than 1 <spirvimageformat> but formatsgenerator.py is not updated, for format', name) 604 605 # Re-loop to check the other way if the <format> is missing 606 for enum in self.reg.groupdict['VkFormat'].elem: 607 name = enum.get('name') 608 if enum.get('alias') is None and name != 'VK_FORMAT_UNDEFINED': 609 if name not in found_formats and name not in astc3d_formats: 610 self.set_error_context(entity=name, elem=enum) 611 self.record_error('The <enum> has no matching <format> for ', name) 612 613 super().check_format() 614 615 616if __name__ == '__main__': 617 618 ckr = Checker() 619 ckr.check() 620 621 if ckr.fail: 622 sys.exit(1) 623