1 #!/usr/bin/python3 -i 2 # 3 # Copyright (c) 2013-2015 The Khronos Group Inc. 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a 6 # copy of this software and/or associated documentation files (the 7 # "Materials"), to deal in the Materials without restriction, including 8 # without limitation the rights to use, copy, modify, merge, publish, 9 # distribute, sublicense, and/or sell copies of the Materials, and to 10 # permit persons to whom the Materials are furnished to do so, subject to 11 # the following conditions: 12 # 13 # The above copyright notice and this permission notice shall be included 14 # in all copies or substantial portions of the Materials. 15 # 16 # THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 # MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 23 24 import io,os,re,string,sys 25 from lxml import etree 26 27 # matchAPIProfile - returns whether an API and profile 28 # being generated matches an element's profile 29 # api - string naming the API to match 30 # profile - string naming the profile to match 31 # elem - Element which (may) have 'api' and 'profile' 32 # attributes to match to. 33 # If a tag is not present in the Element, the corresponding API 34 # or profile always matches. 35 # Otherwise, the tag must exactly match the API or profile. 36 # Thus, if 'profile' = core: 37 # <remove> with no attribute will match 38 # <remove profile='core'> will match 39 # <remove profile='compatibility'> will not match 40 # Possible match conditions: 41 # Requested Element 42 # Profile Profile 43 # --------- -------- 44 # None None Always matches 45 # 'string' None Always matches 46 # None 'string' Does not match. Can't generate multiple APIs 47 # or profiles, so if an API/profile constraint 48 # is present, it must be asked for explicitly. 49 # 'string' 'string' Strings must match 50 # 51 # ** In the future, we will allow regexes for the attributes, 52 # not just strings, so that api="^(gl|gles2)" will match. Even 53 # this isn't really quite enough, we might prefer something 54 # like "gl(core)|gles1(common-lite)". 55 def matchAPIProfile(api, profile, elem): 56 """Match a requested API & profile name to a api & profile attributes of an Element""" 57 match = True 58 # Match 'api', if present 59 if ('api' in elem.attrib): 60 if (api == None): 61 raise UserWarning("No API requested, but 'api' attribute is present with value '" + 62 elem.get('api') + "'") 63 elif (api != elem.get('api')): 64 # Requested API doesn't match attribute 65 return False 66 if ('profile' in elem.attrib): 67 if (profile == None): 68 raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + 69 elem.get('profile') + "'") 70 elif (profile != elem.get('profile')): 71 # Requested profile doesn't match attribute 72 return False 73 return True 74 75 # BaseInfo - base class for information about a registry feature 76 # (type/group/enum/command/API/extension). 77 # required - should this feature be defined during header generation 78 # (has it been removed by a profile or version)? 79 # declared - has this feature been defined already? 80 # elem - lxml.etree Element for this feature 81 # resetState() - reset required/declared to initial values. Used 82 # prior to generating a new API interface. 83 class BaseInfo: 84 """Represents the state of a registry feature, used during API generation""" 85 def __init__(self, elem): 86 self.required = False 87 self.declared = False 88 self.elem = elem 89 def resetState(self): 90 self.required = False 91 self.declared = False 92 93 # TypeInfo - registry information about a type. No additional state 94 # beyond BaseInfo is required. 95 class TypeInfo(BaseInfo): 96 """Represents the state of a registry type""" 97 def __init__(self, elem): 98 BaseInfo.__init__(self, elem) 99 100 # GroupInfo - registry information about a group of related enums 101 # in an <enums> block, generally corresponding to a C "enum" type. 102 class GroupInfo(BaseInfo): 103 """Represents the state of a registry <enums> group""" 104 def __init__(self, elem): 105 BaseInfo.__init__(self, elem) 106 107 # EnumInfo - registry information about an enum 108 # type - numeric type of the value of the <enum> tag 109 # ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) 110 class EnumInfo(BaseInfo): 111 """Represents the state of a registry enum""" 112 def __init__(self, elem): 113 BaseInfo.__init__(self, elem) 114 self.type = elem.get('type') 115 if (self.type == None): 116 self.type = '' 117 118 # CmdInfo - registry information about a command 119 class CmdInfo(BaseInfo): 120 """Represents the state of a registry command""" 121 def __init__(self, elem): 122 BaseInfo.__init__(self, elem) 123 124 # FeatureInfo - registry information about an API <feature> 125 # or <extension> 126 # name - feature name string (e.g. 'vk_ext_khr_surface') 127 # version - feature version number (e.g. 1.2). <extension> 128 # features are unversioned and assigned version number 0. 129 # ** This is confusingly taken from the 'number' attribute of <feature>. 130 # Needs fixing. 131 # number - extension number, used for ordering and for 132 # assigning enumerant offsets. <feature> features do 133 # not have extension numbers and are assigned number 0. 134 # category - category, e.g. VERSION or khr/vendor tag 135 # emit - has this feature been defined already? 136 class FeatureInfo(BaseInfo): 137 """Represents the state of an API feature (version/extension)""" 138 def __init__(self, elem): 139 BaseInfo.__init__(self, elem) 140 self.name = elem.get('name') 141 # Determine element category (vendor). Only works 142 # for <extension> elements. 143 if (elem.tag == 'feature'): 144 self.category = 'VERSION' 145 self.version = elem.get('number') 146 self.number = "0" 147 else: 148 self.category = self.name.split('_', 2)[1] 149 self.version = "0" 150 self.number = elem.get('number') 151 self.emit = False 152 153 from generator import write, GeneratorOptions, OutputGenerator 154 155 # Registry - object representing an API registry, loaded from an XML file 156 # Members 157 # tree - ElementTree containing the root <registry> 158 # typedict - dictionary of TypeInfo objects keyed by type name 159 # groupdict - dictionary of GroupInfo objects keyed by group name 160 # enumdict - dictionary of EnumInfo objects keyed by enum name 161 # cmddict - dictionary of CmdInfo objects keyed by command name 162 # apidict - dictionary of <api> Elements keyed by API name 163 # extensions - list of <extension> Elements 164 # extdict - dictionary of <extension> Elements keyed by extension name 165 # gen - OutputGenerator object used to write headers / messages 166 # genOpts - GeneratorOptions object used to control which 167 # fetures to write and how to format them 168 # emitFeatures - True to actually emit features for a version / extension, 169 # or False to just treat them as emitted 170 # Public methods 171 # loadElementTree(etree) - load registry from specified ElementTree 172 # loadFile(filename) - load registry from XML file 173 # setGenerator(gen) - OutputGenerator to use 174 # parseTree() - parse the registry once loaded & create dictionaries 175 # dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries 176 # to specified file handle (default stdout). Truncates type / 177 # enum / command elements to maxlen characters (default 80) 178 # generator(g) - specify the output generator object 179 # apiGen(apiname, genOpts) - generate API headers for the API type 180 # and profile specified in genOpts, but only for the versions and 181 # extensions specified there. 182 # apiReset() - call between calls to apiGen() to reset internal state 183 # Private methods 184 # addElementInfo(elem,info,infoName,dictionary) - add feature info to dict 185 # lookupElementInfo(fname,dictionary) - lookup feature info in dict 186 class Registry: 187 """Represents an API registry loaded from XML""" 188 def __init__(self): 189 self.tree = None 190 self.typedict = {} 191 self.groupdict = {} 192 self.enumdict = {} 193 self.cmddict = {} 194 self.apidict = {} 195 self.extensions = [] 196 self.extdict = {} 197 # A default output generator, so commands prior to apiGen can report 198 # errors via the generator object. 199 self.gen = OutputGenerator() 200 self.genOpts = None 201 self.emitFeatures = False 202 def loadElementTree(self, tree): 203 """Load ElementTree into a Registry object and parse it""" 204 self.tree = tree 205 self.parseTree() 206 def loadFile(self, file): 207 """Load an API registry XML file into a Registry object and parse it""" 208 self.tree = etree.parse(file) 209 self.parseTree() 210 def setGenerator(self, gen): 211 """Specify output generator object. None restores the default generator""" 212 self.gen = gen 213 self.gen.setRegistry(self.tree) 214 215 # addElementInfo - add information about an element to the 216 # corresponding dictionary 217 # elem - <type>/<enums>/<enum>/<command>/<feature>/<extension> Element 218 # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object 219 # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' 220 # dictionary - self.{type|group|enum|cmd|api|ext}dict 221 # If the Element has an 'api' attribute, the dictionary key is the 222 # tuple (name,api). If not, the key is the name. 'name' is an 223 # attribute of the Element 224 def addElementInfo(self, elem, info, infoName, dictionary): 225 if ('api' in elem.attrib): 226 key = (elem.get('name'),elem.get('api')) 227 else: 228 key = elem.get('name') 229 if key in dictionary: 230 self.gen.logMsg('warn', '*** Attempt to redefine', 231 infoName, 'with key:', key) 232 else: 233 dictionary[key] = info 234 # 235 # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. 236 # If an object qualified by API name exists, use that. 237 # fname - name of type / enum / command 238 # dictionary - self.{type|enum|cmd}dict 239 def lookupElementInfo(self, fname, dictionary): 240 key = (fname, self.genOpts.apiname) 241 if (key in dictionary): 242 # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) 243 return dictionary[key] 244 elif (fname in dictionary): 245 # self.gen.logMsg('diag', 'Found generic element for feature', fname) 246 return dictionary[fname] 247 else: 248 return None 249 def parseTree(self): 250 """Parse the registry Element, once created""" 251 # This must be the Element for the root <registry> 252 self.reg = self.tree.getroot() 253 # 254 # Create dictionary of registry types from toplevel <types> tags 255 # and add 'name' attribute to each <type> tag (where missing) 256 # based on its <name> element. 257 # 258 # There's usually one <types> block; more are OK 259 # Required <type> attributes: 'name' or nested <name> tag contents 260 self.typedict = {} 261 for type in self.reg.findall('types/type'): 262 # If the <type> doesn't already have a 'name' attribute, set 263 # it from contents of its <name> tag. 264 if (type.get('name') == None): 265 type.attrib['name'] = type.find('name').text 266 self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) 267 # 268 # Create dictionary of registry enum groups from <enums> tags. 269 # 270 # Required <enums> attributes: 'name'. If no name is given, one is 271 # generated, but that group can't be identified and turned into an 272 # enum type definition - it's just a container for <enum> tags. 273 self.groupdict = {} 274 for group in self.reg.findall('enums'): 275 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 276 # 277 # Create dictionary of registry enums from <enum> tags 278 # 279 # <enums> tags usually define different namespaces for the values 280 # defined in those tags, but the actual names all share the 281 # same dictionary. 282 # Required <enum> attributes: 'name', 'value' 283 # For containing <enums> which have type="enum" or type="bitmask", 284 # tag all contained <enum>s are required. This is a stopgap until 285 # a better scheme for tagging core and extension enums is created. 286 self.enumdict = {} 287 for enums in self.reg.findall('enums'): 288 required = (enums.get('type') != None) 289 for enum in enums.findall('enum'): 290 enumInfo = EnumInfo(enum) 291 enumInfo.required = required 292 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 293 # 294 # Create dictionary of registry commands from <command> tags 295 # and add 'name' attribute to each <command> tag (where missing) 296 # based on its <proto><name> element. 297 # 298 # There's usually only one <commands> block; more are OK. 299 # Required <command> attributes: 'name' or <proto><name> tag contents 300 self.cmddict = {} 301 for cmd in self.reg.findall('commands/command'): 302 # If the <command> doesn't already have a 'name' attribute, set 303 # it from contents of its <proto><name> tag. 304 if (cmd.get('name') == None): 305 cmd.attrib['name'] = cmd.find('proto/name').text 306 ci = CmdInfo(cmd) 307 self.addElementInfo(cmd, ci, 'command', self.cmddict) 308 # 309 # Create dictionaries of API and extension interfaces 310 # from toplevel <api> and <extension> tags. 311 # 312 self.apidict = {} 313 for feature in self.reg.findall('feature'): 314 featureInfo = FeatureInfo(feature) 315 self.addElementInfo(feature, featureInfo, 'feature', self.apidict) 316 self.extensions = self.reg.findall('extensions/extension') 317 self.extdict = {} 318 for feature in self.extensions: 319 featureInfo = FeatureInfo(feature) 320 self.addElementInfo(feature, featureInfo, 'extension', self.extdict) 321 322 # Add additional enums defined only in <extension> tags 323 # to the corresponding core type. 324 # When seen here, a copy, processed to contain the numeric enum 325 # value, is added to the corresponding <enums> element, as well 326 # as adding to the enum dictionary. Also add a 'extnumber' 327 # attribute containing the extension number. 328 # 329 # For <enum> tags which are actually just constants, if there's 330 # no 'extends' tag but there is a 'value' or 'bitpos' tag, just 331 # add an EnumInfo record to the dictionary. That works because 332 # output generation of constants is purely dependency-based, and 333 # doesn't need to iterate through the XML tags. 334 # 335 # Something like this will need to be done for 'feature's up 336 # above, if we use the same mechanism for adding to the core 337 # API in 1.1. 338 for enum in feature.findall('require/enum'): 339 addEnumInfo = False 340 groupName = enum.get('extends') 341 if (groupName != None): 342 # self.gen.logMsg('diag', '*** Found extension enum', 343 # enum.get('name')) 344 # Add extension number attribute to the <enum> element 345 enum.attrib['extnumber'] = featureInfo.number 346 # Look up the GroupInfo with matching groupName 347 if (groupName in self.groupdict.keys()): 348 # self.gen.logMsg('diag', '*** Matching group', 349 # groupName, 'found, adding element...') 350 gi = self.groupdict[groupName] 351 gi.elem.append(enum) 352 else: 353 self.gen.logMsg('warn', '*** NO matching group', 354 groupName, 'for enum', enum.get('name'), 'found.') 355 addEnumInfo = True 356 elif (enum.get('value') or enum.get('bitpos')): 357 # self.gen.logMsg('diag', '*** Adding extension constant "enum"', 358 # enum.get('name')) 359 addEnumInfo = True 360 if (addEnumInfo): 361 enumInfo = EnumInfo(enum) 362 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 363 def dumpReg(self, maxlen = 40, filehandle = sys.stdout): 364 """Dump all the dictionaries constructed from the Registry object""" 365 write('***************************************', file=filehandle) 366 write(' ** Dumping Registry contents **', file=filehandle) 367 write('***************************************', file=filehandle) 368 write('// Types', file=filehandle) 369 for name in self.typedict: 370 tobj = self.typedict[name] 371 write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) 372 write('// Groups', file=filehandle) 373 for name in self.groupdict: 374 gobj = self.groupdict[name] 375 write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) 376 write('// Enums', file=filehandle) 377 for name in self.enumdict: 378 eobj = self.enumdict[name] 379 write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) 380 write('// Commands', file=filehandle) 381 for name in self.cmddict: 382 cobj = self.cmddict[name] 383 write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) 384 write('// APIs', file=filehandle) 385 for key in self.apidict: 386 write(' API Version ', key, '->', 387 etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) 388 write('// Extensions', file=filehandle) 389 for key in self.extdict: 390 write(' Extension', key, '->', 391 etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) 392 # write('***************************************', file=filehandle) 393 # write(' ** Dumping XML ElementTree **', file=filehandle) 394 # write('***************************************', file=filehandle) 395 # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) 396 # 397 # typename - name of type 398 # required - boolean (to tag features as required or not) 399 def markTypeRequired(self, typename, required): 400 """Require (along with its dependencies) or remove (but not its dependencies) a type""" 401 self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) 402 # Get TypeInfo object for <type> tag corresponding to typename 403 type = self.lookupElementInfo(typename, self.typedict) 404 if (type != None): 405 if (required): 406 # Tag type dependencies in 'required' attributes as 407 # required. This DOES NOT un-tag dependencies in a <remove> 408 # tag. See comments in markRequired() below for the reason. 409 if ('requires' in type.elem.attrib): 410 depType = type.elem.get('requires') 411 self.gen.logMsg('diag', '*** Generating dependent type', 412 depType, 'for type', typename) 413 self.markTypeRequired(depType, required) 414 # Tag types used in defining this type (e.g. in nested 415 # <type> tags) 416 # Look for <type> in entire <command> tree, 417 # not just immediate children 418 for subtype in type.elem.findall('.//type'): 419 self.gen.logMsg('diag', '*** markRequired: type requires dependent <type>', subtype.text) 420 self.markTypeRequired(subtype.text, required) 421 # Tag enums used in defining this type, for example in 422 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 423 for subenum in type.elem.findall('.//enum'): 424 self.gen.logMsg('diag', '*** markRequired: type requires dependent <enum>', subenum.text) 425 self.markEnumRequired(subenum.text, required) 426 type.required = required 427 else: 428 self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') 429 # 430 # enumname - name of enum 431 # required - boolean (to tag features as required or not) 432 def markEnumRequired(self, enumname, required): 433 self.gen.logMsg('diag', '*** tagging enum:', enumname, '-> required =', required) 434 enum = self.lookupElementInfo(enumname, self.enumdict) 435 if (enum != None): 436 enum.required = required 437 else: 438 self.gen.logMsg('warn', '*** enum:', enumname , 'IS NOT DEFINED') 439 # 440 # features - Element for <require> or <remove> tag 441 # required - boolean (to tag features as required or not) 442 def markRequired(self, features, required): 443 """Require or remove features specified in the Element""" 444 self.gen.logMsg('diag', '*** markRequired (features = <too long to print>, required =', required, ')') 445 # Loop over types, enums, and commands in the tag 446 # @@ It would be possible to respect 'api' and 'profile' attributes 447 # in individual features, but that's not done yet. 448 for typeElem in features.findall('type'): 449 self.markTypeRequired(typeElem.get('name'), required) 450 for enumElem in features.findall('enum'): 451 self.markEnumRequired(enumElem.get('name'), required) 452 for cmdElem in features.findall('command'): 453 name = cmdElem.get('name') 454 self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) 455 cmd = self.lookupElementInfo(name, self.cmddict) 456 if (cmd != None): 457 cmd.required = required 458 # Tag all parameter types of this command as required. 459 # This DOES NOT remove types of commands in a <remove> 460 # tag, because many other commands may use the same type. 461 # We could be more clever and reference count types, 462 # instead of using a boolean. 463 if (required): 464 # Look for <type> in entire <command> tree, 465 # not just immediate children 466 for type in cmd.elem.findall('.//type'): 467 self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', type.text) 468 self.markTypeRequired(type.text, required) 469 else: 470 self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') 471 # 472 # interface - Element for <version> or <extension>, containing 473 # <require> and <remove> tags 474 # api - string specifying API name being generated 475 # profile - string specifying API profile being generated 476 def requireAndRemoveFeatures(self, interface, api, profile): 477 """Process <recquire> and <remove> tags for a <version> or <extension>""" 478 # <require> marks things that are required by this version/profile 479 for feature in interface.findall('require'): 480 if (matchAPIProfile(api, profile, feature)): 481 self.markRequired(feature,True) 482 # <remove> marks things that are removed by this version/profile 483 for feature in interface.findall('remove'): 484 if (matchAPIProfile(api, profile, feature)): 485 self.markRequired(feature,False) 486 # 487 # generateFeature - generate a single type / enum group / enum / command, 488 # and all its dependencies as needed. 489 # fname - name of feature (<type>/<enum>/<command>) 490 # ftype - type of feature, 'type' | 'enum' | 'command' 491 # dictionary - of *Info objects - self.{type|enum|cmd}dict 492 def generateFeature(self, fname, ftype, dictionary): 493 f = self.lookupElementInfo(fname, dictionary) 494 if (f == None): 495 # No such feature. This is an error, but reported earlier 496 self.gen.logMsg('diag', '*** No entry found for feature', fname, 497 'returning!') 498 return 499 # 500 # If feature isn't required, or has already been declared, return 501 if (not f.required): 502 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') 503 return 504 if (f.declared): 505 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') 506 return 507 # Always mark feature declared, as though actually emitted 508 f.declared = True 509 # 510 # Pull in dependent declaration(s) of the feature. 511 # For types, there may be one type in the 'required' attribute of 512 # the element, as well as many in imbedded <type> and <enum> tags 513 # within the element. 514 # For commands, there may be many in <type> tags within the element. 515 # For enums, no dependencies are allowed (though perhaps if you 516 # have a uint64 enum, it should require GLuint64). 517 genProc = None 518 if (ftype == 'type'): 519 genProc = self.gen.genType 520 if ('requires' in f.elem.attrib): 521 depname = f.elem.get('requires') 522 self.gen.logMsg('diag', '*** Generating required dependent type', 523 depname) 524 self.generateFeature(depname, 'type', self.typedict) 525 for subtype in f.elem.findall('.//type'): 526 self.gen.logMsg('diag', '*** Generating required dependent <type>', 527 subtype.text) 528 self.generateFeature(subtype.text, 'type', self.typedict) 529 for subtype in f.elem.findall('.//enum'): 530 self.gen.logMsg('diag', '*** Generating required dependent <enum>', 531 subtype.text) 532 self.generateFeature(subtype.text, 'enum', self.enumdict) 533 # If the type is an enum group, look up the corresponding 534 # group in the group dictionary and generate that instead. 535 if (f.elem.get('category') == 'enum'): 536 self.gen.logMsg('diag', '*** Type', fname, 'is an enum group, so generate that instead') 537 group = self.lookupElementInfo(fname, self.groupdict) 538 if (group == None): 539 # Unless this is tested for, it's probably fatal to call below 540 genProc = None 541 self.logMsg('warn', '*** NO MATCHING ENUM GROUP FOUND!!!') 542 else: 543 genProc = self.gen.genGroup 544 f = group 545 elif (ftype == 'command'): 546 genProc = self.gen.genCmd 547 for type in f.elem.findall('.//type'): 548 depname = type.text 549 self.gen.logMsg('diag', '*** Generating required parameter type', 550 depname) 551 self.generateFeature(depname, 'type', self.typedict) 552 elif (ftype == 'enum'): 553 genProc = self.gen.genEnum 554 # Actually generate the type only if emitting declarations 555 if self.emitFeatures: 556 self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) 557 genProc(f, fname) 558 else: 559 self.gen.logMsg('diag', '*** Skipping', ftype, fname, 560 '(not emitting this feature)') 561 # 562 # generateRequiredInterface - generate all interfaces required 563 # by an API version or extension 564 # interface - Element for <version> or <extension> 565 def generateRequiredInterface(self, interface): 566 """Generate required C interface for specified API version/extension""" 567 # 568 # Loop over all features inside all <require> tags. 569 # <remove> tags are ignored (handled in pass 1). 570 for features in interface.findall('require'): 571 for t in features.findall('type'): 572 self.generateFeature(t.get('name'), 'type', self.typedict) 573 for e in features.findall('enum'): 574 self.generateFeature(e.get('name'), 'enum', self.enumdict) 575 for c in features.findall('command'): 576 self.generateFeature(c.get('name'), 'command', self.cmddict) 577 # 578 # apiGen(genOpts) - generate interface for specified versions 579 # genOpts - GeneratorOptions object with parameters used 580 # by the Generator object. 581 def apiGen(self, genOpts): 582 """Generate interfaces for the specified API type and range of versions""" 583 # 584 self.gen.logMsg('diag', '*******************************************') 585 self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, 586 'api:', genOpts.apiname, 587 'profile:', genOpts.profile) 588 self.gen.logMsg('diag', '*******************************************') 589 # 590 self.genOpts = genOpts 591 # Reset required/declared flags for all features 592 self.apiReset() 593 # 594 # Compile regexps used to select versions & extensions 595 regVersions = re.compile(self.genOpts.versions) 596 regEmitVersions = re.compile(self.genOpts.emitversions) 597 regAddExtensions = re.compile(self.genOpts.addExtensions) 598 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 599 # 600 # Get all matching API versions & add to list of FeatureInfo 601 features = [] 602 apiMatch = False 603 for key in self.apidict: 604 fi = self.apidict[key] 605 api = fi.elem.get('api') 606 if (api == self.genOpts.apiname): 607 apiMatch = True 608 if (regVersions.match(fi.version)): 609 # Matches API & version #s being generated. Mark for 610 # emission and add to the features[] list . 611 # @@ Could use 'declared' instead of 'emit'? 612 fi.emit = (regEmitVersions.match(fi.version) != None) 613 features.append(fi) 614 if (not fi.emit): 615 self.gen.logMsg('diag', '*** NOT tagging feature api =', api, 616 'name =', fi.name, 'version =', fi.version, 617 'for emission (does not match emitversions pattern)') 618 else: 619 self.gen.logMsg('diag', '*** NOT including feature api =', api, 620 'name =', fi.name, 'version =', fi.version, 621 '(does not match requested versions)') 622 else: 623 self.gen.logMsg('diag', '*** NOT including feature api =', api, 624 'name =', fi.name, 625 '(does not match requested API)') 626 if (not apiMatch): 627 self.gen.logMsg('warn', '*** No matching API versions found!') 628 # 629 # Get all matching extensions, in order by their extension number, 630 # and add to the list of features. 631 # Start with extensions tagged with 'api' pattern matching the API 632 # being generated. Add extensions matching the pattern specified in 633 # regExtensions, then remove extensions matching the pattern 634 # specified in regRemoveExtensions 635 for (extName,ei) in sorted(self.extdict.items(),key = lambda x : x[1].number): 636 extName = ei.name 637 include = False 638 # 639 # Include extension if defaultExtensions is not None and if the 640 # 'supported' attribute matches defaultExtensions. The regexp in 641 # 'supported' must exactly match defaultExtensions, so bracket 642 # it with ^(pat)$. 643 pat = '^(' + ei.elem.get('supported') + ')$' 644 if (self.genOpts.defaultExtensions and 645 re.match(pat, self.genOpts.defaultExtensions)): 646 self.gen.logMsg('diag', '*** Including extension', 647 extName, "(defaultExtensions matches the 'supported' attribute)") 648 include = True 649 # 650 # Include additional extensions if the extension name matches 651 # the regexp specified in the generator options. This allows 652 # forcing extensions into an interface even if they're not 653 # tagged appropriately in the registry. 654 if (regAddExtensions.match(extName) != None): 655 self.gen.logMsg('diag', '*** Including extension', 656 extName, '(matches explicitly requested extensions to add)') 657 include = True 658 # Remove extensions if the name matches the regexp specified 659 # in generator options. This allows forcing removal of 660 # extensions from an interface even if they're tagged that 661 # way in the registry. 662 if (regRemoveExtensions.match(extName) != None): 663 self.gen.logMsg('diag', '*** Removing extension', 664 extName, '(matches explicitly requested extensions to remove)') 665 include = False 666 # 667 # If the extension is to be included, add it to the 668 # extension features list. 669 if (include): 670 ei.emit = True 671 features.append(ei) 672 else: 673 self.gen.logMsg('diag', '*** NOT including extension', 674 extName, '(does not match api attribute or explicitly requested extensions)') 675 # 676 # Sort the extension features list, if a sort procedure is defined 677 if (self.genOpts.sortProcedure): 678 self.genOpts.sortProcedure(features) 679 # 680 # Pass 1: loop over requested API versions and extensions tagging 681 # types/commands/features as required (in an <require> block) or no 682 # longer required (in an <remove> block). It is possible to remove 683 # a feature in one version and restore it later by requiring it in 684 # a later version. 685 # If a profile other than 'None' is being generated, it must 686 # match the profile attribute (if any) of the <require> and 687 # <remove> tags. 688 self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') 689 for f in features: 690 self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', 691 f.name) 692 self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) 693 # 694 # Pass 2: loop over specified API versions and extensions printing 695 # declarations for required things which haven't already been 696 # generated. 697 self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') 698 self.gen.beginFile(self.genOpts) 699 for f in features: 700 self.gen.logMsg('diag', '*** PASS 2: Generating interface for', 701 f.name) 702 emit = self.emitFeatures = f.emit 703 if (not emit): 704 self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', 705 f.elem.get('name'), 'because it is not tagged for emission') 706 # Generate the interface (or just tag its elements as having been 707 # emitted, if they haven't been). 708 self.gen.beginFeature(f.elem, emit) 709 self.generateRequiredInterface(f.elem) 710 self.gen.endFeature() 711 self.gen.endFile() 712 # 713 # apiReset - use between apiGen() calls to reset internal state 714 # 715 def apiReset(self): 716 """Reset type/enum/command dictionaries before generating another API""" 717 for type in self.typedict: 718 self.typedict[type].resetState() 719 for enum in self.enumdict: 720 self.enumdict[enum].resetState() 721 for cmd in self.cmddict: 722 self.cmddict[cmd].resetState() 723 for cmd in self.apidict: 724 self.apidict[cmd].resetState() 725 # 726 # validateGroups - check that group= attributes match actual groups 727 # 728 def validateGroups(self): 729 """Validate group= attributes on <param> and <proto> tags""" 730 # Keep track of group names not in <group> tags 731 badGroup = {} 732 self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') 733 for cmd in self.reg.findall('commands/command'): 734 proto = cmd.find('proto') 735 funcname = cmd.find('proto/name').text 736 if ('group' in proto.attrib.keys()): 737 group = proto.get('group') 738 # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) 739 if (group not in self.groupdict.keys()): 740 # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) 741 if (group not in badGroup.keys()): 742 badGroup[group] = 1 743 else: 744 badGroup[group] = badGroup[group] + 1 745 for param in cmd.findall('param'): 746 pname = param.find('name') 747 if (pname != None): 748 pname = pname.text 749 else: 750 pname = type.get('name') 751 if ('group' in param.attrib.keys()): 752 group = param.get('group') 753 if (group not in self.groupdict.keys()): 754 # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) 755 if (group not in badGroup.keys()): 756 badGroup[group] = 1 757 else: 758 badGroup[group] = badGroup[group] + 1 759 if (len(badGroup.keys()) > 0): 760 self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') 761 for key in sorted(badGroup.keys()): 762 self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') 763