# -*- coding: utf-8 -*- #------------------------------------------------------------------------- # drawElements Quality Program utilities # -------------------------------------- # # Copyright 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #------------------------------------------------------------------------- import sys, logging, re from lxml import etree from collections import OrderedDict from functools import wraps, partial log = logging.getLogger(__name__) debug = log.debug info = log.info warning = log.warning def warnElem(elem, fmt, *args): warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args) class Object(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) class Located(Object): location = None class Group(Located): pass class Enum(Located): pass class Enums(Located): name = None comment = None enums = None class Type(Located): location = None name=None definition=None api=None requires=None def makeObject(cls, elem, **kwargs): kwargs.setdefault('name', elem.get('name')) kwargs.setdefault('comment', elem.get('comment')) kwargs['location'] = (elem.base, elem.sourceline) return cls(**kwargs) def parseEnum(eEnum): return makeObject( Enum, eEnum, value=eEnum.get('value'), type=eEnum.get('type'), alias=eEnum.get('alias')) class Param(Located): pass class Command(Located): name=None declaration=None type=None ptype=None group=None params=None alias=None class Interface(Object): pass class Index: def __init__(self, items=[], **kwargs): self.index = {} self.items = [] self.__dict__.update(kwargs) self.update(items) def append(self, item): keys = self.getkeys(item) for key in keys: self[key] = item self.items.append(item) def update(self, items): for item in items: self.append(item) def __iter__(self): return iter(self.items) def nextkey(self, key): raise KeyError def getkeys(self, item): return [] def __contains__(self, key): return key in self.index def __setitem__(self, key, item): if key in self.index: self.duplicateKey(key, item) else: self.index[key] = item def duplicateKey(self, key, item): warning("Duplicate %s: %r", type(item).__name__.lower(), key) def __getitem__(self, key): try: while True: try: return self.index[key] except KeyError: pass key = self.nextkey(key) except KeyError: item = self.missingKey(key) self.append(item) return item def missingKey(self, key): raise KeyError(key) def __len__(self): return len(self.items) class ElemNameIndex(Index): def getkeys(self, item): return [item.get('name')] def duplicateKey(self, key, item): warnElem(item, "Duplicate key: %s", key) class CommandIndex(Index): def getkeys(self, item): return [item.findtext('proto/name'), item.findtext('alias')] class NameApiIndex(Index): def getkeys(self, item): return [(item.get('name'), item.get('api'))] def nextkey(self, key): if len(key) == 2 and key[1] is not None: return key[0], None raise KeyError def duplicateKey(self, key, item): warnElem(item, "Duplicate key: %s", key) class TypeIndex(NameApiIndex): def getkeys(self, item): return [(item.get('name') or item.findtext('name'), item.get('api'))] class EnumIndex(NameApiIndex): def getkeys(self, item): name, api, alias = (item.get(attrib) for attrib in ['name', 'api', 'alias']) return [(name, api)] + ([(alias, api)] if alias is not None else []) def duplicateKey(self, (name, api), item): if name == item.get('alias'): warnElem(item, "Alias already present: %s", name) else: warnElem(item, "Already present") class Registry: def __init__(self, eRegistry): self.types = TypeIndex(eRegistry.findall('types/type')) self.groups = ElemNameIndex(eRegistry.findall('groups/group')) self.enums = EnumIndex(eRegistry.findall('enums/enum')) for eEnum in self.enums: groupName = eEnum.get('group') if groupName is not None: self.groups[groupName] = eEnum self.commands = CommandIndex(eRegistry.findall('commands/command')) self.features = ElemNameIndex(eRegistry.findall('feature')) self.apis = {} for eFeature in self.features: self.apis.setdefault(eFeature.get('api'), []).append(eFeature) for apiFeatures in self.apis.itervalues(): apiFeatures.sort(key=lambda eFeature: eFeature.get('number')) self.extensions = ElemNameIndex(eRegistry.findall('extensions/extension')) self.element = eRegistry def getFeatures(self, api, checkVersion=None): return [eFeature for eFeature in self.apis[api] if checkVersion is None or checkVersion(eFeature.get('number'))] class NameIndex(Index): createMissing = None kind = "item" def getkeys(self, item): return [item.name] def missingKey(self, key): if self.createMissing: warning("Reference to implicit %s: %r", self.kind, key) return self.createMissing(name=key) else: raise KeyError def matchApi(api1, api2): return api1 is None or api2 is None or api1 == api2 class Interface(Object): pass def extractAlias(eCommand): aliases = eCommand.xpath('alias/@name') return aliases[0] if aliases else None def getExtensionName(eExtension): return eExtension.get('name') def extensionSupports(eExtension, api, profile=None): if api == 'gl' and profile == 'core': needSupport = 'glcore' else: needSupport = api supporteds = eExtension.get('supported').split('|') return needSupport in supporteds class InterfaceSpec(Object): def __init__(self): self.enums = set() self.types = set() self.commands = set() def addComponent(self, eComponent): if eComponent.tag == 'require': def modify(items, item): items.add(item) else: assert eComponent.tag == 'remove' def modify(items, item): try: items.remove(item) except KeyError: warning("Tried to remove absent item: %s", item) for typeName in eComponent.xpath('type/@name'): modify(self.types, typeName) for enumName in eComponent.xpath('enum/@name'): modify(self.enums, enumName) for commandName in eComponent.xpath('command/@name'): modify(self.commands, commandName) def addComponents(self, elem, api, profile=None): for eComponent in elem.xpath('require|remove'): cApi = eComponent.get('api') cProfile = eComponent.get('profile') if (matchApi(api, eComponent.get('api')) and matchApi(profile, eComponent.get('profile'))): self.addComponent(eComponent) def addFeature(self, eFeature, api=None, profile=None, force=False): info('Feature %s', eFeature.get('name')) if not matchApi(api, eFeature.get('api')): if not force: return warnElem(eFeature, 'API %s is not supported', api) self.addComponents(eFeature, api, profile) def addExtension(self, eExtension, api=None, profile=None, force=False): if not extensionSupports(eExtension, api, profile): if not force: return warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api)) self.addComponents(eExtension, api, profile) def createInterface(registry, spec, api=None): def parseType(eType): # todo: apientry #requires = eType.get('requires') #if requires is not None: # types[requires] return makeObject( Type, eType, name=eType.get('name') or eType.findtext('name'), definition=''.join(eType.xpath('.//text()')), api=eType.get('api'), requires=eType.get('requires')) def createType(name): info('Add type %s', name) try: return parseType(registry.types[name, api]) except KeyError: return Type(name=name) def createEnum(enumName): info('Add enum %s', enumName) return parseEnum(registry.enums[enumName, api]) def extractPtype(elem): ePtype = elem.find('ptype') if ePtype is None: return None return types[ePtype.text] def extractGroup(elem): groupName = elem.get('group') if groupName is None: return None return groups[groupName] def parseParam(eParam): return makeObject( Param, eParam, name=eParam.get('name') or eParam.findtext('name'), declaration=''.join(eParam.xpath('.//text()')).strip(), type=''.join(eParam.xpath('(.|ptype)/text()')).strip(), ptype=extractPtype(eParam), group=extractGroup(eParam)) def createCommand(commandName): info('Add command %s', commandName) eCmd = registry.commands[commandName] eProto = eCmd.find('proto') return makeObject( Command, eCmd, name=eCmd.findtext('proto/name'), declaration=''.join(eProto.xpath('.//text()')).strip(), type=''.join(eProto.xpath('(.|ptype)/text()')).strip(), ptype=extractPtype(eProto), group=extractGroup(eProto), alias=extractAlias(eCmd), params=NameIndex(map(parseParam, eCmd.findall('param')))) def createGroup(name): info('Add group %s', name) try: eGroup = registry.groups[name] except KeyError: return Group(name=name) return makeObject( Group, eGroup, # Missing enums are often from exotic extensions. Don't create dummy entries, # just filter them out. enums=NameIndex(enums[name] for name in eGroup.xpath('enum/@name') if name in enums)) def sortedIndex(items): return NameIndex(sorted(items, key=lambda item: item.location)) groups = NameIndex(createMissing=createGroup, kind="group") types = NameIndex(map(createType, spec.types), createMissing=createType, kind="type") enums = NameIndex(map(createEnum, spec.enums), createMissing=Enum, kind="enum") commands = NameIndex(map(createCommand, spec.commands), createMissing=Command, kind="command") # This is a mess because the registry contains alias chains whose # midpoints might not be included in the interface even though # endpoints are. for command in commands: alias = command.alias aliasCommand = None while alias is not None: aliasCommand = registry.commands[alias] alias = extractAlias(aliasCommand) command.alias = None if aliasCommand is not None: name = aliasCommand.findtext('proto/name') if name in commands: command.alias = commands[name] return Interface( types=sortedIndex(types), enums=sortedIndex(enums), groups=sortedIndex(groups), commands=sortedIndex(commands)) def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False): available = set(protects) spec = InterfaceSpec() if version is None or version is False: def check(v): return False elif version is True: def check(v): return True else: def check(v): return v <= version for eFeature in registry.getFeatures(api, check): spec.addFeature(eFeature, api, profile, force) for extName in extensionNames: eExtension = registry.extensions[extName] protect = eExtension.get('protect') if protect is not None and protect not in available: warnElem(eExtension, "Unavailable dependency %s", protect) if not force: continue spec.addExtension(eExtension, api, profile, force) available.add(extName) return spec def interface(registry, api, **kwargs): s = spec(registry, api, **kwargs) return createInterface(registry, s, api) def parse(path): return Registry(etree.parse(path))