Merge vk-gl-cts/vulkan-cts-1.0.1 into vk-gl-cts/vulkan-cts-1.0.2
[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                         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))