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 23 import sys, logging, re 24 from lxml import etree 25 from collections import OrderedDict 26 from functools import wraps, partial 27 28 log = logging.getLogger(__name__) 29 30 debug = log.debug 31 info = log.info 32 warning = log.warning 33 34 def warnElem(elem, fmt, *args): 35 warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args) 36 37 class Object(object): 38 def __init__(self, **kwargs): 39 self.__dict__.update(kwargs) 40 41 class Located(Object): 42 location = None 43 44 class Group(Located): pass 45 class Enum(Located): pass 46 class Enums(Located): 47 name = None 48 comment = None 49 enums = None 50 51 class Type(Located): 52 location = None 53 name=None 54 definition=None 55 api=None 56 requires=None 57 58 def 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 64 def parseEnum(eEnum): 65 return makeObject( 66 Enum, eEnum, 67 value=eEnum.get('value'), 68 type=eEnum.get('type'), 69 alias=eEnum.get('alias')) 70 71 class Param(Located): pass 72 73 class Command(Located): 74 name=None 75 declaration=None 76 type=None 77 ptype=None 78 group=None 79 params=None 80 alias=None 81 82 class Interface(Object): pass 83 84 class 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 self.duplicateKey(key, item) 116 else: 117 self.index[key] = item 118 119 def duplicateKey(self, key, item): 120 warning("Duplicate %s: %r", type(item).__name__.lower(), key) 121 122 def __getitem__(self, key): 123 try: 124 while True: 125 try: 126 return self.index[key] 127 except KeyError: 128 pass 129 key = self.nextkey(key) 130 except KeyError: 131 item = self.missingKey(key) 132 self.append(item) 133 return item 134 135 def missingKey(self, key): 136 raise KeyError(key) 137 138 def __len__(self): 139 return len(self.items) 140 141 class ElemNameIndex(Index): 142 def getkeys(self, item): 143 return [item.get('name')] 144 145 def duplicateKey(self, key, item): 146 warnElem(item, "Duplicate key: %s", key) 147 148 class CommandIndex(Index): 149 def getkeys(self, item): 150 return [item.findtext('proto/name'), item.findtext('alias')] 151 152 class NameApiIndex(Index): 153 def getkeys(self, item): 154 return [(item.get('name'), item.get('api'))] 155 156 def nextkey(self, key): 157 if len(key) == 2 and key[1] is not None: 158 return key[0], None 159 raise KeyError 160 161 def duplicateKey(self, key, item): 162 warnElem(item, "Duplicate key: %s", key) 163 164 class TypeIndex(NameApiIndex): 165 def getkeys(self, item): 166 return [(item.get('name') or item.findtext('name'), item.get('api'))] 167 168 class EnumIndex(NameApiIndex): 169 def getkeys(self, item): 170 name, api, alias = (item.get(attrib) for attrib in ['name', 'api', 'alias']) 171 return [(name, api)] + ([(alias, api)] if alias is not None else []) 172 173 def duplicateKey(self, (name, api), item): 174 if name == item.get('alias'): 175 warnElem(item, "Alias already present: %s", name) 176 else: 177 warnElem(item, "Already present") 178 179 class Registry: 180 def __init__(self, eRegistry): 181 self.types = TypeIndex(eRegistry.findall('types/type')) 182 self.groups = ElemNameIndex(eRegistry.findall('groups/group')) 183 self.enums = EnumIndex(eRegistry.findall('enums/enum')) 184 for eEnum in self.enums: 185 groupName = eEnum.get('group') 186 if groupName is not None: 187 self.groups[groupName] = eEnum 188 self.commands = CommandIndex(eRegistry.findall('commands/command')) 189 self.features = ElemNameIndex(eRegistry.findall('feature')) 190 self.apis = {} 191 for eFeature in self.features: 192 self.apis.setdefault(eFeature.get('api'), []).append(eFeature) 193 for apiFeatures in self.apis.itervalues(): 194 apiFeatures.sort(key=lambda eFeature: eFeature.get('number')) 195 self.extensions = ElemNameIndex(eRegistry.findall('extensions/extension')) 196 self.element = eRegistry 197 198 def getFeatures(self, api, checkVersion=None): 199 return [eFeature for eFeature in self.apis[api] 200 if checkVersion is None or checkVersion(eFeature.get('number'))] 201 202 class NameIndex(Index): 203 createMissing = None 204 kind = "item" 205 206 def getkeys(self, item): 207 return [item.name] 208 209 def missingKey(self, key): 210 if self.createMissing: 211 warning("Reference to implicit %s: %r", self.kind, key) 212 return self.createMissing(name=key) 213 else: 214 raise KeyError 215 216 def matchApi(api1, api2): 217 return api1 is None or api2 is None or api1 == api2 218 219 class Interface(Object): 220 pass 221 222 def extractAlias(eCommand): 223 aliases = eCommand.xpath('alias/@name') 224 return aliases[0] if aliases else None 225 226 def getExtensionName(eExtension): 227 return eExtension.get('name') 228 229 def extensionSupports(eExtension, api, profile=None): 230 if api == 'gl' and profile == 'core': 231 needSupport = 'glcore' 232 else: 233 needSupport = api 234 supporteds = eExtension.get('supported').split('|') 235 return needSupport in supporteds 236 237 class InterfaceSpec(Object): 238 def __init__(self): 239 self.enums = set() 240 self.types = set() 241 self.commands = set() 242 243 def addComponent(self, eComponent): 244 if eComponent.tag == 'require': 245 def modify(items, item): items.add(item) 246 else: 247 assert eComponent.tag == 'remove' 248 def modify(items, item): 249 try: 250 items.remove(item) 251 except KeyError: 252 warning("Tried to remove absent item: %s", item) 253 for typeName in eComponent.xpath('type/@name'): 254 modify(self.types, typeName) 255 for enumName in eComponent.xpath('enum/@name'): 256 modify(self.enums, enumName) 257 for commandName in eComponent.xpath('command/@name'): 258 modify(self.commands, commandName) 259 260 def addComponents(self, elem, api, profile=None): 261 for eComponent in elem.xpath('require|remove'): 262 cApi = eComponent.get('api') 263 cProfile = eComponent.get('profile') 264 if (matchApi(api, eComponent.get('api')) and 265 matchApi(profile, eComponent.get('profile'))): 266 self.addComponent(eComponent) 267 268 def addFeature(self, eFeature, api=None, profile=None, force=False): 269 info('Feature %s', eFeature.get('name')) 270 if not matchApi(api, eFeature.get('api')): 271 if not force: return 272 warnElem(eFeature, 'API %s is not supported', api) 273 self.addComponents(eFeature, api, profile) 274 275 def addExtension(self, eExtension, api=None, profile=None, force=False): 276 if not extensionSupports(eExtension, api, profile): 277 if not force: return 278 warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api)) 279 self.addComponents(eExtension, api, profile) 280 281 def createInterface(registry, spec, api=None): 282 def parseType(eType): 283 # todo: apientry 284 #requires = eType.get('requires') 285 #if requires is not None: 286 # types[requires] 287 return makeObject( 288 Type, eType, 289 name=eType.get('name') or eType.findtext('name'), 290 definition=''.join(eType.xpath('.//text()')), 291 api=eType.get('api'), 292 requires=eType.get('requires')) 293 294 def createType(name): 295 info('Add type %s', name) 296 try: 297 return parseType(registry.types[name, api]) 298 except KeyError: 299 return Type(name=name) 300 301 def createEnum(enumName): 302 info('Add enum %s', enumName) 303 return parseEnum(registry.enums[enumName, api]) 304 305 def extractPtype(elem): 306 ePtype = elem.find('ptype') 307 if ePtype is None: 308 return None 309 return types[ePtype.text] 310 311 def extractGroup(elem): 312 groupName = elem.get('group') 313 if groupName is None: 314 return None 315 return groups[groupName] 316 317 def parseParam(eParam): 318 return makeObject( 319 Param, eParam, 320 name=eParam.get('name') or eParam.findtext('name'), 321 declaration=''.join(eParam.xpath('.//text()')).strip(), 322 type=''.join(eParam.xpath('(.|ptype)/text()')).strip(), 323 ptype=extractPtype(eParam), 324 group=extractGroup(eParam)) 325 326 def createCommand(commandName): 327 info('Add command %s', commandName) 328 eCmd = registry.commands[commandName] 329 eProto = eCmd.find('proto') 330 return makeObject( 331 Command, eCmd, 332 name=eCmd.findtext('proto/name'), 333 declaration=''.join(eProto.xpath('.//text()')).strip(), 334 type=''.join(eProto.xpath('(.|ptype)/text()')).strip(), 335 ptype=extractPtype(eProto), 336 group=extractGroup(eProto), 337 alias=extractAlias(eCmd), 338 params=NameIndex(map(parseParam, eCmd.findall('param')))) 339 340 def createGroup(name): 341 info('Add group %s', name) 342 try: 343 eGroup = registry.groups[name] 344 except KeyError: 345 return Group(name=name) 346 return makeObject( 347 Group, eGroup, 348 # Missing enums are often from exotic extensions. Don't create dummy entries, 349 # just filter them out. 350 enums=NameIndex(enums[name] for name in eGroup.xpath('enum/@name') 351 if name in enums)) 352 353 def sortedIndex(items): 354 return NameIndex(sorted(items, key=lambda item: item.location)) 355 356 groups = NameIndex(createMissing=createGroup, kind="group") 357 types = NameIndex(map(createType, spec.types), 358 createMissing=createType, kind="type") 359 enums = NameIndex(map(createEnum, spec.enums), 360 createMissing=Enum, kind="enum") 361 commands = NameIndex(map(createCommand, spec.commands), 362 createMissing=Command, kind="command") 363 364 # This is a mess because the registry contains alias chains whose 365 # midpoints might not be included in the interface even though 366 # endpoints are. 367 for command in commands: 368 alias = command.alias 369 aliasCommand = None 370 while alias is not None: 371 aliasCommand = registry.commands[alias] 372 alias = extractAlias(aliasCommand) 373 command.alias = None 374 if aliasCommand is not None: 375 name = aliasCommand.findtext('proto/name') 376 if name in commands: 377 command.alias = commands[name] 378 379 return Interface( 380 types=sortedIndex(types), 381 enums=sortedIndex(enums), 382 groups=sortedIndex(groups), 383 commands=sortedIndex(commands)) 384 385 386 def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False): 387 available = set(protects) 388 spec = InterfaceSpec() 389 390 if version is None or version is False: 391 def check(v): return False 392 elif version is True: 393 def check(v): return True 394 else: 395 def check(v): return v <= version 396 397 for eFeature in registry.getFeatures(api, check): 398 spec.addFeature(eFeature, api, profile, force) 399 400 for extName in extensionNames: 401 eExtension = registry.extensions[extName] 402 protect = eExtension.get('protect') 403 if protect is not None and protect not in available: 404 warnElem(eExtension, "Unavailable dependency %s", protect) 405 if not force: 406 continue 407 spec.addExtension(eExtension, api, profile, force) 408 available.add(extName) 409 410 return spec 411 412 def interface(registry, api, **kwargs): 413 s = spec(registry, api, **kwargs) 414 return createInterface(registry, s, api) 415 416 def parse(path): 417 return Registry(etree.parse(path)) 418