1 # -*- coding: utf-8 -*-
3 #-------------------------------------------------------------------------
4 # drawElements Quality Program utilities
5 # --------------------------------------
7 # Copyright 2015 The Android Open Source Project
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 #-------------------------------------------------------------------------
23 import sys, logging, re
24 from lxml import etree
25 from collections import OrderedDict
26 from functools import wraps, partial
28 log = logging.getLogger(__name__)
34 def warnElem(elem, fmt, *args):
35 warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args)
38 def __init__(self, **kwargs):
39 self.__dict__.update(kwargs)
41 class Located(Object):
44 class Group(Located): pass
45 class Enum(Located): pass
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)
67 value=eEnum.get('value'),
68 type=eEnum.get('type'),
69 alias=eEnum.get('alias'))
71 class Param(Located): pass
73 class Command(Located):
82 class Interface(Object): pass
85 def __init__(self, items=[], **kwargs):
88 self.__dict__.update(kwargs)
91 def append(self, item):
92 keys = self.getkeys(item)
95 self.items.append(item)
97 def update(self, items):
102 return iter(self.items)
104 def nextkey(self, key):
107 def getkeys(self, item):
110 def __contains__(self, key):
111 return key in self.index
113 def __setitem__(self, key, item):
114 if key in self.index:
116 self.duplicateKey(key, item)
118 self.index[key] = item
120 def duplicateKey(self, key, item):
121 warning("Duplicate %s: %r", type(item).__name__.lower(), key)
123 def __getitem__(self, key):
127 return self.index[key]
130 key = self.nextkey(key)
132 item = self.missingKey(key)
136 def missingKey(self, key):
140 return len(self.items)
142 class ElemNameIndex(Index):
143 def getkeys(self, item):
144 return [item.get('name')]
146 def duplicateKey(self, key, item):
147 warnElem(item, "Duplicate key: %s", key)
149 class 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')]
155 class NameApiIndex(Index):
156 def getkeys(self, item):
157 return [(item.get('name'), item.get('api'))]
159 def nextkey(self, key):
160 if len(key) == 2 and key[1] is not None:
164 def duplicateKey(self, key, item):
165 warnElem(item, "Duplicate key: %s", key)
167 class TypeIndex(NameApiIndex):
168 def getkeys(self, item):
169 return [(item.get('name') or item.findtext('name'), item.get('api'))]
171 class 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 [])
178 def duplicateKey(self, nameapipair, item):
179 (name, api) = nameapipair
180 if name == item.get('alias'):
181 warnElem(item, "Alias already present: %s", name)
183 warnElem(item, "Already present")
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'))
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
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'))]
208 class NameIndex(Index):
212 def getkeys(self, item):
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)
222 def matchApi(api1, api2):
223 return api1 is None or api2 is None or api1 == api2
225 class Interface(Object):
228 def extractAlias(eCommand):
229 aliases = eCommand.xpath('alias/@name')
230 return aliases[0] if aliases else None
232 def getExtensionName(eExtension):
233 return eExtension.get('name')
235 def extensionSupports(eExtension, api, profile=None):
236 if api == 'gl' and profile == 'core':
237 needSupport = 'glcore'
240 supporteds = eExtension.get('supported').split('|')
241 return needSupport in supporteds
243 class InterfaceSpec(Object):
247 self.commands = set()
248 self.versions = set()
250 def addComponent(self, eComponent):
251 if eComponent.tag == 'require':
252 def modify(items, item): items.add(item)
254 assert eComponent.tag == 'remove'
255 def modify(items, item):
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)
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)
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')):
279 warnElem(eFeature, 'API %s is not supported', api)
280 self.addComponents(eFeature, api, profile)
281 self.versions.add(eFeature.get('name'))
283 def addExtension(self, eExtension, api=None, profile=None, force=False):
284 if not extensionSupports(eExtension, api, profile):
286 warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api))
287 self.addComponents(eExtension, api, profile)
289 def createInterface(registry, spec, api=None):
290 def parseType(eType):
292 #requires = eType.get('requires')
293 #if requires is not None:
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'))
302 def createType(name):
303 info('Add type %s', name)
305 return parseType(registry.types[name, api])
307 return Type(name=name)
309 def createEnum(enumName):
310 info('Add enum %s', enumName)
311 return parseEnum(registry.enums[enumName, api])
313 def extractPtype(elem):
314 ePtype = elem.find('ptype')
317 return types[ePtype.text]
319 def extractGroup(elem):
320 groupName = elem.get('group')
321 if groupName is None:
323 return groups[groupName]
325 def parseParam(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))
334 def createCommand(commandName):
335 info('Add command %s', commandName)
336 eCmd = registry.commands[commandName]
337 eProto = eCmd.find('proto')
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')))))
348 def createGroup(name):
349 info('Add group %s', name)
351 eGroup = registry.groups[name]
353 return Group(name=name)
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')
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
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
371 if item.location is None:
373 warning("Location not found for %s: %s", type(item).__name__.lower(), item.name)
376 sortedItems = sorted(items, key=lambda item: item.location)
379 return NameIndex(sortedItems)
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)
390 # This is a mess because the registry contains alias chains whose
391 # midpoints might not be included in the interface even though
393 for command in commands:
394 alias = command.alias
396 while alias is not None:
397 aliasCommand = registry.commands[alias]
398 alias = extractAlias(aliasCommand)
400 if aliasCommand is not None:
401 name = aliasCommand.findtext('proto/name')
403 command.alias = commands[name]
405 sortedTypes=sortedIndex(types)
406 sortedEnums=sortedIndex(enums)
407 sortedGroups=sortedIndex(groups)
408 sortedCommands=sortedIndex(commands)
414 commands=sortedCommands,
420 def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False):
421 available = set(protects)
422 spec = InterfaceSpec()
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
429 def check(v): return v <= version
431 # BOZA TODO: I suppose adding primitive types will remove a lot of warnings
432 # spec.addComponents(registry.types, api, profile)
434 for eFeature in registry.getFeatures(api, check):
435 spec.addFeature(eFeature, api, profile, force)
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)
444 spec.addExtension(eExtension, api, profile, force)
445 available.add(extName)
449 def interface(registry, api, **kwargs):
450 s = spec(registry, api, **kwargs)
451 return createInterface(registry, s, api)
454 return Registry(etree.parse(path))