xref: /aosp_15_r20/external/deqp/scripts/khr_util/registry.py (revision 35238bce31c2a825756842865a792f8cf7f89930)
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
23import sys, logging, re
24from lxml import etree
25from collections import OrderedDict
26from functools import wraps, partial
27
28log = logging.getLogger(__name__)
29
30debug = log.debug
31info = log.info
32warning = log.warning
33
34def warnElem(elem, fmt, *args):
35    warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args)
36
37class Object(object):
38    def __init__(self, **kwargs):
39        self.__dict__.update(kwargs)
40
41class Located(Object):
42    location = None
43
44class Group(Located): pass
45class Enum(Located): pass
46class Enums(Located):
47    name = None
48    comment = None
49    enums = None
50
51class Type(Located):
52    location = None
53    name=None
54    definition=None
55    api=None
56    requires=None
57
58def 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
64def parseEnum(eEnum):
65    return makeObject(
66        Enum, eEnum,
67        value=eEnum.get('value'),
68        type=eEnum.get('type'),
69        alias=eEnum.get('alias'))
70
71class Param(Located): pass
72
73class Command(Located):
74    name=None
75    declaration=None
76    type=None
77    ptype=None
78    group=None
79    params=None
80    alias=None
81
82class Interface(Object): pass
83
84class 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
142class 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
149class 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
155class 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
167class TypeIndex(NameApiIndex):
168    def getkeys(self, item):
169        return [(item.get('name') or item.findtext('name'), item.get('api'))]
170
171class 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
185class 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
208class 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
222def matchApi(api1, api2):
223    return api1 is None or api2 is None or api1 == api2
224
225class Interface(Object):
226    pass
227
228def extractAlias(eCommand):
229    aliases = eCommand.xpath('alias/@name')
230    return aliases[0] if aliases else None
231
232def getExtensionName(eExtension):
233    return eExtension.get('name')
234
235def 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
243class 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
289def 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
420def 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
449def interface(registry, api, **kwargs):
450    s = spec(registry, api, **kwargs)
451    return createInterface(registry, s, api)
452
453def parse(path):
454    return Registry(etree.parse(path))
455