Fix missing dependency on sparse binds
[platform/upstream/VK-GL-CTS.git] / scripts / khr_util / registry.py
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                         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
142 class 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
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')]
154
155 class 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
167 class TypeIndex(NameApiIndex):
168         def getkeys(self, item):
169                 return [(item.get('name') or item.findtext('name'), item.get('api'))]
170
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 [])
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
185 class 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
208 class 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
222 def matchApi(api1, api2):
223         return api1 is None or api2 is None or api1 == api2
224
225 class Interface(Object):
226         pass
227
228 def extractAlias(eCommand):
229         aliases = eCommand.xpath('alias/@name')
230         return aliases[0] if aliases else None
231
232 def getExtensionName(eExtension):
233         return eExtension.get('name')
234
235 def 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
243 class 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
289 def 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
420 def 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
449 def interface(registry, api, **kwargs):
450         s = spec(registry, api, **kwargs)
451         return createInterface(registry, s, api)
452
453 def parse(path):
454         return Registry(etree.parse(path))