1# -*- coding: utf-8 -*- 2 3#------------------------------------------------------------------------- 4# drawElements Quality Program utilities 5# -------------------------------------- 6# 7# Copyright 2015 The Android Open Source Project 8# 9# Licensed under the Apache License, Version 2.0 (the "License"); 10# you may not use this file except in compliance with the License. 11# You may obtain a copy of the License at 12# 13# http://www.apache.org/licenses/LICENSE-2.0 14# 15# Unless required by applicable law or agreed to in writing, software 16# distributed under the License is distributed on an "AS IS" BASIS, 17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18# See the License for the specific language governing permissions and 19# limitations under the License. 20# 21#------------------------------------------------------------------------- 22 23import sys, logging, re 24from lxml import etree 25from collections import OrderedDict 26from functools import wraps, partial 27 28log = logging.getLogger(__name__) 29 30debug = log.debug 31info = log.info 32warning = log.warning 33 34def warnElem(elem, fmt, *args): 35 warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args) 36 37class Object(object): 38 def __init__(self, **kwargs): 39 self.__dict__.update(kwargs) 40 41class Located(Object): 42 location = None 43 44class Group(Located): pass 45class Enum(Located): pass 46class Enums(Located): 47 name = None 48 comment = None 49 enums = None 50 51class Type(Located): 52 location = None 53 name=None 54 definition=None 55 api=None 56 requires=None 57 58def makeObject(cls, elem, **kwargs): 59 kwargs.setdefault('name', elem.get('name')) 60 kwargs.setdefault('comment', elem.get('comment')) 61 kwargs['location'] = (elem.base, elem.sourceline) 62 return cls(**kwargs) 63 64def parseEnum(eEnum): 65 return makeObject( 66 Enum, eEnum, 67 value=eEnum.get('value'), 68 type=eEnum.get('type'), 69 alias=eEnum.get('alias')) 70 71class Param(Located): pass 72 73class Command(Located): 74 name=None 75 declaration=None 76 type=None 77 ptype=None 78 group=None 79 params=None 80 alias=None 81 82class Interface(Object): pass 83 84class Index: 85 def __init__(self, items=[], **kwargs): 86 self.index = {} 87 self.items = [] 88 self.__dict__.update(kwargs) 89 self.update(items) 90 91 def append(self, item): 92 keys = self.getkeys(item) 93 for key in keys: 94 self[key] = item 95 self.items.append(item) 96 97 def update(self, items): 98 for item in items: 99 self.append(item) 100 101 def __iter__(self): 102 return iter(self.items) 103 104 def nextkey(self, key): 105 raise KeyError 106 107 def getkeys(self, item): 108 return [] 109 110 def __contains__(self, key): 111 return key in self.index 112 113 def __setitem__(self, key, item): 114 if key in self.index: 115 if key is not None: 116 self.duplicateKey(key, item) 117 else: 118 self.index[key] = item 119 120 def duplicateKey(self, key, item): 121 warning("Duplicate %s: %r", type(item).__name__.lower(), key) 122 123 def __getitem__(self, key): 124 try: 125 while True: 126 try: 127 return self.index[key] 128 except KeyError: 129 pass 130 key = self.nextkey(key) 131 except KeyError: 132 item = self.missingKey(key) 133 self.append(item) 134 return item 135 136 def missingKey(self, key): 137 raise KeyError(key) 138 139 def __len__(self): 140 return len(self.items) 141 142class ElemNameIndex(Index): 143 def getkeys(self, item): 144 return [item.get('name')] 145 146 def duplicateKey(self, key, item): 147 warnElem(item, "Duplicate key: %s", key) 148 149class CommandIndex(Index): 150 def getkeys(self, item): 151 #BOZA: No reason to add alias: it has its own entry in enums in xml file 152 #return [(name, api)] + ([(alias, api)] if alias is not None else []) 153 return [item.findtext('proto/name')] 154 155class NameApiIndex(Index): 156 def getkeys(self, item): 157 return [(item.get('name'), item.get('api'))] 158 159 def nextkey(self, key): 160 if len(key) == 2 and key[1] is not None: 161 return key[0], None 162 raise KeyError 163 164 def duplicateKey(self, key, item): 165 warnElem(item, "Duplicate key: %s", key) 166 167class TypeIndex(NameApiIndex): 168 def getkeys(self, item): 169 return [(item.get('name') or item.findtext('name'), item.get('api'))] 170 171class EnumIndex(NameApiIndex): 172 def getkeys(self, item): 173 name, api, alias = (item.get(attrib) for attrib in ['name', 'api', 'alias']) 174 #BOZA: No reason to add alias: it has its own entry in enums 175 #return [(name, api)] + ([(alias, api)] if alias is not None else []) 176 return [(name, api)] 177 178 def duplicateKey(self, nameapipair, item): 179 (name, api) = nameapipair 180 if name == item.get('alias'): 181 warnElem(item, "Alias already present: %s", name) 182 else: 183 warnElem(item, "Already present") 184 185class Registry: 186 def __init__(self, eRegistry): 187 self.types = TypeIndex(eRegistry.findall('types/type')) 188 self.groups = ElemNameIndex(eRegistry.findall('groups/group')) 189 self.enums = EnumIndex(eRegistry.findall('enums/enum')) 190 for eEnum in self.enums: 191 groupName = eEnum.get('group') 192 if groupName is not None: 193 self.groups[groupName] = eEnum 194 self.commands = CommandIndex(eRegistry.findall('commands/command')) 195 self.features = ElemNameIndex(eRegistry.findall('feature')) 196 self.apis = {} 197 for eFeature in self.features: 198 self.apis.setdefault(eFeature.get('api'), []).append(eFeature) 199 for apiFeatures in self.apis.values(): 200 apiFeatures.sort(key=lambda eFeature: eFeature.get('number')) 201 self.extensions = ElemNameIndex(eRegistry.findall('extensions/extension')) 202 self.element = eRegistry 203 204 def getFeatures(self, api, checkVersion=None): 205 return [eFeature for eFeature in self.apis[api] 206 if checkVersion is None or checkVersion(eFeature.get('number'))] 207 208class NameIndex(Index): 209 createMissing = None 210 kind = "item" 211 212 def getkeys(self, item): 213 return [item.name] 214 215 def missingKey(self, key): 216 if self.createMissing: 217 warning("Reference to implicit %s: %r", self.kind, key) 218 return self.createMissing(name=key) 219 else: 220 raise KeyError 221 222def matchApi(api1, api2): 223 return api1 is None or api2 is None or api1 == api2 224 225class Interface(Object): 226 pass 227 228def extractAlias(eCommand): 229 aliases = eCommand.xpath('alias/@name') 230 return aliases[0] if aliases else None 231 232def getExtensionName(eExtension): 233 return eExtension.get('name') 234 235def extensionSupports(eExtension, api, profile=None): 236 if api == 'gl' and profile == 'core': 237 needSupport = 'glcore' 238 else: 239 needSupport = api 240 supporteds = eExtension.get('supported').split('|') 241 return needSupport in supporteds 242 243class InterfaceSpec(Object): 244 def __init__(self): 245 self.enums = set() 246 self.types = set() 247 self.commands = set() 248 self.versions = set() 249 250 def addComponent(self, eComponent): 251 if eComponent.tag == 'require': 252 def modify(items, item): items.add(item) 253 else: 254 assert eComponent.tag == 'remove' 255 def modify(items, item): 256 try: 257 items.remove(item) 258 except KeyError: 259 warning("Tried to remove absent item: %s", item) 260 for typeName in eComponent.xpath('type/@name'): 261 modify(self.types, typeName) 262 for enumName in eComponent.xpath('enum/@name'): 263 modify(self.enums, enumName) 264 for commandName in eComponent.xpath('command/@name'): 265 modify(self.commands, commandName) 266 267 def addComponents(self, elem, api, profile=None): 268 for eComponent in elem.xpath('require|remove'): 269 cApi = eComponent.get('api') 270 cProfile = eComponent.get('profile') 271 if (matchApi(api, eComponent.get('api')) and 272 matchApi(profile, eComponent.get('profile'))): 273 self.addComponent(eComponent) 274 275 def addFeature(self, eFeature, api=None, profile=None, force=False): 276 info('Feature %s', eFeature.get('name')) 277 if not matchApi(api, eFeature.get('api')): 278 if not force: return 279 warnElem(eFeature, 'API %s is not supported', api) 280 self.addComponents(eFeature, api, profile) 281 self.versions.add(eFeature.get('name')) 282 283 def addExtension(self, eExtension, api=None, profile=None, force=False): 284 if not extensionSupports(eExtension, api, profile): 285 if not force: return 286 warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api)) 287 self.addComponents(eExtension, api, profile) 288 289def createInterface(registry, spec, api=None): 290 def parseType(eType): 291 # todo: apientry 292 #requires = eType.get('requires') 293 #if requires is not None: 294 # types[requires] 295 return makeObject( 296 Type, eType, 297 name=eType.get('name') or eType.findtext('name'), 298 definition=''.join(eType.xpath('.//text()')), 299 api=eType.get('api'), 300 requires=eType.get('requires')) 301 302 def createType(name): 303 info('Add type %s', name) 304 try: 305 return parseType(registry.types[name, api]) 306 except KeyError: 307 return Type(name=name) 308 309 def createEnum(enumName): 310 info('Add enum %s', enumName) 311 return parseEnum(registry.enums[enumName, api]) 312 313 def extractPtype(elem): 314 ePtype = elem.find('ptype') 315 if ePtype is None: 316 return None 317 return types[ePtype.text] 318 319 def extractGroup(elem): 320 groupName = elem.get('group') 321 if groupName is None: 322 return None 323 return groups[groupName] 324 325 def parseParam(eParam): 326 return makeObject( 327 Param, eParam, 328 name=eParam.get('name') or eParam.findtext('name'), 329 declaration=''.join(eParam.xpath('.//text()')).strip(), 330 type=''.join(eParam.xpath('(.|ptype)/text()')).strip(), 331 ptype=extractPtype(eParam), 332 group=extractGroup(eParam)) 333 334 def createCommand(commandName): 335 info('Add command %s', commandName) 336 eCmd = registry.commands[commandName] 337 eProto = eCmd.find('proto') 338 return makeObject( 339 Command, eCmd, 340 name=eCmd.findtext('proto/name'), 341 declaration=''.join(eProto.xpath('.//text()')).strip(), 342 type=''.join(eProto.xpath('(.|ptype)/text()')).strip(), 343 ptype=extractPtype(eProto), 344 group=extractGroup(eProto), 345 alias=extractAlias(eCmd), 346 params=NameIndex(list(map(parseParam, eCmd.findall('param'))))) 347 348 def createGroup(name): 349 info('Add group %s', name) 350 try: 351 eGroup = registry.groups[name] 352 except KeyError: 353 return Group(name=name) 354 return makeObject( 355 Group, eGroup, 356 # Missing enums are often from exotic extensions. Don't create dummy entries, 357 # just filter them out. 358 enums=NameIndex(enums[name] for name in eGroup.xpath('enum/@name') 359 if name in enums)) 360 361 def sortedIndex(items): 362 # Some groups have no location set, due to it is absent in gl.xml file 363 # for example glGetFenceivNV uses group FenceNV which is not declared 364 # <command> 365 # <proto>void <name>glGetFenceivNV</name></proto> 366 # <param group="FenceNV"><ptype>GLuint</ptype> <name>fence</name></param> 367 # Python 2 ignores it. Avoid sorting to allow Python 3 to continue 368 369 enableSort=True 370 for item in items: 371 if item.location is None: 372 enableSort=False 373 warning("Location not found for %s: %s", type(item).__name__.lower(), item.name) 374 375 if enableSort: 376 sortedItems = sorted(items, key=lambda item: item.location) 377 else: 378 sortedItems = items 379 return NameIndex(sortedItems) 380 381 groups = NameIndex(createMissing=createGroup, kind="group") 382 types = NameIndex(list(map(createType, spec.types)), 383 createMissing=createType, kind="type") 384 enums = NameIndex(list(map(createEnum, spec.enums)), 385 createMissing=Enum, kind="enum") 386 commands = NameIndex(list(map(createCommand, spec.commands)), 387 createMissing=Command, kind="command") 388 versions = sorted(spec.versions) 389 390 # This is a mess because the registry contains alias chains whose 391 # midpoints might not be included in the interface even though 392 # endpoints are. 393 for command in commands: 394 alias = command.alias 395 aliasCommand = None 396 while alias is not None: 397 aliasCommand = registry.commands[alias] 398 alias = extractAlias(aliasCommand) 399 command.alias = None 400 if aliasCommand is not None: 401 name = aliasCommand.findtext('proto/name') 402 if name in commands: 403 command.alias = commands[name] 404 405 sortedTypes=sortedIndex(types) 406 sortedEnums=sortedIndex(enums) 407 sortedGroups=sortedIndex(groups) 408 sortedCommands=sortedIndex(commands) 409 410 ifc=Interface( 411 types=sortedTypes, 412 enums=sortedEnums, 413 groups=sortedGroups, 414 commands=sortedCommands, 415 versions=versions) 416 417 return ifc 418 419 420def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False): 421 available = set(protects) 422 spec = InterfaceSpec() 423 424 if version is None or version is False: 425 def check(v): return False 426 elif version is True: 427 def check(v): return True 428 else: 429 def check(v): return v <= version 430 431# BOZA TODO: I suppose adding primitive types will remove a lot of warnings 432# spec.addComponents(registry.types, api, profile) 433 434 for eFeature in registry.getFeatures(api, check): 435 spec.addFeature(eFeature, api, profile, force) 436 437 for extName in extensionNames: 438 eExtension = registry.extensions[extName] 439 protect = eExtension.get('protect') 440 if protect is not None and protect not in available: 441 warnElem(eExtension, "Unavailable dependency %s", protect) 442 if not force: 443 continue 444 spec.addExtension(eExtension, api, profile, force) 445 available.add(extName) 446 447 return spec 448 449def interface(registry, api, **kwargs): 450 s = spec(registry, api, **kwargs) 451 return createInterface(registry, s, api) 452 453def parse(path): 454 return Registry(etree.parse(path)) 455