1#!/usr/bin/python3 -i 2# 3# Copyright 2020-2024 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7# Description: 8# ----------- 9# This script generates a full schema definition from the vk.xml. 10 11import os 12import re 13from generator import (GeneratorOptions, OutputGenerator, noneStr, 14 regSortFeatures, write) 15 16 17headerString = "\ 18{\n\ 19\"$schema\": \"http://json-schema.org/draft-04/schema#\", \n\ 20\"id\": \"https://schema.khronos.org/vulkan/vk.json#\",\n\ 21\"title\": \"JSON schema for Vulkan SC\",\n\ 22\"description\": \"Schema for representing entire vk.xml as a schema.\",\n\ 23\"type\": \"object\",\n\ 24\"additionalProperties\": true,\n\ 25\"definitions\": {\ 26" 27 28basetypeString = "\ 29 \"$schema\": {\"type\": \"string\", \"format\": \"uri\"},\n\ 30 \"uint8_t\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255},\n\ 31 \"int32_t\": {\"type\": \"integer\", \"minimum\": -2147483648, \"maximum\": 2147483647},\n\ 32 \"uint32_t\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 4294967295},\n\ 33 \"uint64_t\": {\"oneOf\": [{\"enum\": [\"\"]},{\"type\": \"integer\"}]},\n\ 34 \"char\": {\"type\": \"string\"},\n\ 35 \"float\": {\"type\": \"number\"},\n\ 36 \"size_t\": {\"$ref\": \"#/definitions/uint32_t\"},\n\ 37 \"enum\": {\"type\": \"string\"},\n\ 38 \"void\": {\"enum\": [\"NULL\", \"\"]},\ 39" 40 41class SchemaGeneratorOptions(GeneratorOptions): 42 """SchemaGeneratorOptions - subclass of GeneratorOptions. 43 44 Adds options used by SchemaOutputGenerator objects during C language header 45 generation.""" 46 47 def __init__(self, 48 prefixText="", 49 genFuncPointers=True, 50 protectFile=True, 51 protectFeature=True, 52 protectProto=None, 53 protectProtoStr=None, 54 apicall='', 55 apientry='', 56 apientryp='', 57 indentFuncProto=True, 58 indentFuncPointer=False, 59 alignFuncParam=0, 60 genEnumBeginEndRange=False, 61 genAliasMacro=False, 62 aliasMacro='', 63 **kwargs 64 ): 65 66 GeneratorOptions.__init__(self, **kwargs) 67 68 self.prefixText = prefixText 69 """list of strings to prefix generated header with (usually a copyright statement + calling convention macros).""" 70 71 self.genFuncPointers = genFuncPointers 72 """True if function pointer typedefs should be generated""" 73 74 self.protectFile = protectFile 75 """True if multiple inclusion protection should be generated (based on the filename) around the entire header.""" 76 77 self.protectFeature = protectFeature 78 """True if #ifndef..#endif protection should be generated around a feature interface in the header file.""" 79 80 self.protectProto = protectProto 81 """If conditional protection should be generated around prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectProtoStr) or '#ifndef' to require opt-out (#ifndef protectProtoStr). Otherwise set to None.""" 82 83 self.protectProtoStr = protectProtoStr 84 """#ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set""" 85 86 self.apicall = apicall 87 """string to use for the function declaration prefix, such as APICALL on Windows.""" 88 89 self.apientry = apientry 90 """string to use for the calling convention macro, in typedefs, such as APIENTRY.""" 91 92 self.apientryp = apientryp 93 """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP.""" 94 95 self.indentFuncProto = indentFuncProto 96 """True if prototype declarations should put each parameter on a separate line""" 97 98 self.indentFuncPointer = indentFuncPointer 99 """True if typedefed function pointers should put each parameter on a separate line""" 100 101 self.alignFuncParam = alignFuncParam 102 """if nonzero and parameters are being put on a separate line, align parameter names at the specified column""" 103 104 self.genEnumBeginEndRange = genEnumBeginEndRange 105 """True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types""" 106 107 self.genAliasMacro = genAliasMacro 108 """True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful)""" 109 110 self.aliasMacro = aliasMacro 111 """alias macro to inject when genAliasMacro is True""" 112 113 self.codeGenerator = True 114 """True if this generator makes compilable code""" 115 116 117class SchemaOutputGenerator(OutputGenerator): 118 # This is an ordered list of sections in the header file. 119 TYPE_SECTIONS = ['basetype', 'handle', 'enum', 'group', 'bitmask', 'struct'] 120 ALL_SECTIONS = TYPE_SECTIONS 121 122 def __init__(self, *args, **kwargs): 123 super().__init__(*args, **kwargs) 124 # Internal state - accumulators for different inner block text 125 self.sections = {section: [] for section in self.ALL_SECTIONS} 126 self.feature_not_empty = False 127 self.may_alias = None 128 129 def beginFile(self, genOpts): 130 OutputGenerator.beginFile(self, genOpts) 131 132 # Write schema header 133 write(headerString, file=self.outFile) 134 write(basetypeString, file=self.outFile) 135 136 def endFile(self): 137 write(" \"VkLastStructure\": {", file=self.outFile) 138 write(" }", file=self.outFile) 139 write(" }", file=self.outFile) 140 write("}", file=self.outFile) 141 142 # Finish processing in superclass 143 OutputGenerator.endFile(self) 144 145 def beginFeature(self, interface, emit): 146 OutputGenerator.beginFeature(self, interface, emit) 147 self.sections = {section: [] for section in self.ALL_SECTIONS} 148 self.feature_not_empty = False 149 150 def endFeature(self): 151 if self.emit: 152 if self.feature_not_empty: 153 if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename): 154 155 for section in self.TYPE_SECTIONS: 156 contents = self.sections[section] 157 if contents: 158 write('\n'.join(contents), file=self.outFile) 159 160 # Finish processing in superclass 161 OutputGenerator.endFeature(self) 162 163 def appendSection(self, section, text): 164 self.sections[section].append(text) 165 self.feature_not_empty = True 166 167 def genType(self, typeinfo, name, alias): 168 OutputGenerator.genType(self, typeinfo, name, alias) 169 typeElem = typeinfo.elem 170 body = "" 171 172 category = typeElem.get('category') 173 if category == 'funcpointer': 174 section = 'struct' 175 else: 176 section = category 177 178 if category in ('struct', 'union'): 179 self.genStruct(typeinfo, name, alias) 180 181 elif category == 'handle': 182 for elem in typeElem: 183 if elem.tag == 'name': 184 body += " \"" + elem.text + "\": {\"$ref\": \"#/definitions/uint64_t" + "\"}," 185 186 elif category in ('bitmask','basetype'): 187 storeType = "" 188 section = 'bitmask' 189 190 for elem in typeElem: 191 if elem.tag == 'type': 192 storeType = elem.text 193 194 if elem.tag == 'name': 195 if elem.text == "VkBool32": 196 body += " \"" + elem.text + "\": {\"oneOf\": [{\"$ref\": \"#/definitions/" + storeType + "\"},{\"enum\": [\"VK_TRUE\", \"VK_FALSE\"]}]}," 197 elif elem.text == "VkFlags": 198 body += " \"" + elem.text + "\": {\"oneOf\": [{\"$ref\": \"#/definitions/" + storeType + "\"},{\"$ref\": \"#/definitions/enum\"}]}," 199 else: 200 body += " \"" + elem.text + "\": {\"$ref\": \"#/definitions/" + storeType + "\"}," 201 202 if body: 203 self.appendSection(section, body) 204 205 def genMemberSchema(self, structure, param): 206 paramdecl = "" 207 storeType = "" 208 isArr = param.get('len') not in (None, "null-terminated") 209 isPtr = False 210 211 for elem in param: 212 text = noneStr(elem.text) 213 tail = noneStr(elem.tail) 214 215 #TODO: In the actual json data, we inline the pNext structs by checking what they point to, at runtime. 216 # This is however not possible to be represented in the schema. So, the plan is to not represent the 217 # pNext structs altogether or to indicate that these would be represented at runtime. 218 #if elem.text != 'pNext' and elem.text != 'sType' and elem.text != 'VkStructureType' and elem.text != 'void' and elem.text != 'const': 219 if 1: 220 if elem.tag == 'type': 221 storeType = text 222 if '*' in tail: 223 isPtr = True 224 225 if elem.tag == 'name': 226 paramdecl += " \"" + text + "\": " 227 if isPtr and text != "pNext": 228 paramdecl += "{\"oneOf\": [{\"$ref\": \"#/definitions/void\"}," 229 230 if isArr or tail.startswith('['): 231 # TODO: How to get maxCount here? 232 paramdecl += " {\"type\": \"array\", \"minItems\": 0, \"maxItems\": 255, \"items\": {\"$ref\": \"#/definitions/" 233 if (structure == "VkPipelineLayoutCreateInfo" and text == "pSetLayouts") or \ 234 (structure == "VkDescriptorSetLayoutBinding" and text == "pImmutableSamplers") or \ 235 (structure == "VkSamplerYcbcrConversionInfo" and text == "conversion"): 236 paramdecl += "char\"}}" 237 elif (storeType == "void"): 238 # void* data can be NULL, an array of uint8_t data, or a Base64-encoded string 239 paramdecl += "uint8_t\"}}, {\"type\": \"string\"}" 240 else: 241 paramdecl += storeType + "\"}}" 242 else: 243 paramdecl += "{\"$ref\": \"#/definitions/" 244 paramdecl += storeType + "\"}" 245 246 if isPtr and text != "pNext": 247 paramdecl += "]}" 248 isPtr = False 249 else: 250 return "" 251 252 return paramdecl 253 254 def genStruct(self, typeinfo, typeName, alias): 255 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 256 body = "" 257 typeElem = typeinfo.elem 258 259 if alias: 260 return 261 else: 262 body = '' 263 body += " \"" + typeName + "\": {\n" 264 body += " \"type\": \"object\",\n" 265 body += " \"additionalProperties\": false,\n" 266 body += " \"properties\": {\n" 267 268 count = 0 269 numMembers = len(typeElem.findall('.//member')) 270 271 for member in typeElem.findall('.//member'): 272 count = count + 1 273 274 genText = self.genMemberSchema(typeName, member) 275 body += genText 276 277 if count < numMembers and genText != "": 278 body += ',' 279 body += '\n' 280 body += "\n }\n" 281 body += " },\n" 282 283 self.appendSection('struct', body) 284 285 def genGroup(self, groupinfo, groupName, alias=None): 286 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 287 groupElem = groupinfo.elem 288 body = "" 289 290 section = 'enum' 291 body += " \"" + groupName + "\": {\"$ref\": \"#/definitions/enum"+ "\"}," 292 293 self.appendSection(section, body) 294 295