1e5c31af7Sopenharmony_ci#!/usr/bin/env python3
2e5c31af7Sopenharmony_ci#
3e5c31af7Sopenharmony_ci# Copyright 2017-2024 The Khronos Group Inc.
4e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0
5e5c31af7Sopenharmony_ci
6e5c31af7Sopenharmony_ci"""Generate a mapping of extension name -> all required extension names for
7e5c31af7Sopenharmony_ci   that extension, from dependencies in the API XML."""
8e5c31af7Sopenharmony_ci
9e5c31af7Sopenharmony_ciimport argparse
10e5c31af7Sopenharmony_ciimport errno
11e5c31af7Sopenharmony_ciimport xml.etree.ElementTree as etree
12e5c31af7Sopenharmony_cifrom pathlib import Path
13e5c31af7Sopenharmony_ci
14e5c31af7Sopenharmony_cifrom apiconventions import APIConventions
15e5c31af7Sopenharmony_cifrom parse_dependency import dependencyNames
16e5c31af7Sopenharmony_ci
17e5c31af7Sopenharmony_ciclass DiGraph:
18e5c31af7Sopenharmony_ci    """A directed graph.
19e5c31af7Sopenharmony_ci
20e5c31af7Sopenharmony_ci    The implementation and API mimic that of networkx.DiGraph in networkx-1.11.
21e5c31af7Sopenharmony_ci    networkx implements graphs as nested dicts; it uses dicts all the way
22e5c31af7Sopenharmony_ci    down, no lists.
23e5c31af7Sopenharmony_ci
24e5c31af7Sopenharmony_ci    Some major differences between this implementation and that of
25e5c31af7Sopenharmony_ci    networkx-1.11 are:
26e5c31af7Sopenharmony_ci
27e5c31af7Sopenharmony_ci        * This omits edge and node attribute data, because we never use them
28e5c31af7Sopenharmony_ci          yet they add additional code complexity.
29e5c31af7Sopenharmony_ci
30e5c31af7Sopenharmony_ci        * This returns iterator objects when possible instead of collection
31e5c31af7Sopenharmony_ci          objects, because it simplifies the implementation and should provide
32e5c31af7Sopenharmony_ci          better performance.
33e5c31af7Sopenharmony_ci    """
34e5c31af7Sopenharmony_ci
35e5c31af7Sopenharmony_ci    def __init__(self):
36e5c31af7Sopenharmony_ci        self.__nodes = {}
37e5c31af7Sopenharmony_ci
38e5c31af7Sopenharmony_ci    def add_node(self, node):
39e5c31af7Sopenharmony_ci        if node not in self.__nodes:
40e5c31af7Sopenharmony_ci            self.__nodes[node] = DiGraphNode()
41e5c31af7Sopenharmony_ci
42e5c31af7Sopenharmony_ci    def add_edge(self, src, dest):
43e5c31af7Sopenharmony_ci        self.add_node(src)
44e5c31af7Sopenharmony_ci        self.add_node(dest)
45e5c31af7Sopenharmony_ci        self.__nodes[src].adj.add(dest)
46e5c31af7Sopenharmony_ci
47e5c31af7Sopenharmony_ci    def nodes(self):
48e5c31af7Sopenharmony_ci        """Iterate over the nodes in the graph."""
49e5c31af7Sopenharmony_ci        return self.__nodes.keys()
50e5c31af7Sopenharmony_ci
51e5c31af7Sopenharmony_ci    def descendants(self, node):
52e5c31af7Sopenharmony_ci        """
53e5c31af7Sopenharmony_ci        Iterate over the nodes reachable from the given start node, excluding
54e5c31af7Sopenharmony_ci        the start node itself. Each node in the graph is yielded at most once.
55e5c31af7Sopenharmony_ci        """
56e5c31af7Sopenharmony_ci
57e5c31af7Sopenharmony_ci        # Implementation detail: Do a breadth-first traversal because it is
58e5c31af7Sopenharmony_ci        # easier than depth-first.
59e5c31af7Sopenharmony_ci
60e5c31af7Sopenharmony_ci        # All nodes seen during traversal.
61e5c31af7Sopenharmony_ci        seen = set()
62e5c31af7Sopenharmony_ci
63e5c31af7Sopenharmony_ci        # The stack of nodes that need visiting.
64e5c31af7Sopenharmony_ci        visit_me = []
65e5c31af7Sopenharmony_ci
66e5c31af7Sopenharmony_ci        # Bootstrap the traversal.
67e5c31af7Sopenharmony_ci        seen.add(node)
68e5c31af7Sopenharmony_ci        for x in self.__nodes[node].adj:
69e5c31af7Sopenharmony_ci            if x not in seen:
70e5c31af7Sopenharmony_ci                seen.add(x)
71e5c31af7Sopenharmony_ci                visit_me.append(x)
72e5c31af7Sopenharmony_ci
73e5c31af7Sopenharmony_ci        while visit_me:
74e5c31af7Sopenharmony_ci            x = visit_me.pop()
75e5c31af7Sopenharmony_ci            assert x in seen
76e5c31af7Sopenharmony_ci            yield x
77e5c31af7Sopenharmony_ci
78e5c31af7Sopenharmony_ci            for y in self.__nodes[x].adj:
79e5c31af7Sopenharmony_ci                if y not in seen:
80e5c31af7Sopenharmony_ci                    seen.add(y)
81e5c31af7Sopenharmony_ci                    visit_me.append(y)
82e5c31af7Sopenharmony_ci
83e5c31af7Sopenharmony_ciclass DiGraphNode:
84e5c31af7Sopenharmony_ci    def __init__(self):
85e5c31af7Sopenharmony_ci        # Set of adjacent of nodes.
86e5c31af7Sopenharmony_ci        self.adj = set()
87e5c31af7Sopenharmony_ci
88e5c31af7Sopenharmony_ciclass ApiDependencies:
89e5c31af7Sopenharmony_ci    def __init__(self,
90e5c31af7Sopenharmony_ci                 registry_path = None,
91e5c31af7Sopenharmony_ci                 api_name = None):
92e5c31af7Sopenharmony_ci        """Load an API registry and generate extension dependencies
93e5c31af7Sopenharmony_ci
94e5c31af7Sopenharmony_ci        registry_path - relative filename of XML registry. If not specified,
95e5c31af7Sopenharmony_ci        uses the API default.
96e5c31af7Sopenharmony_ci
97e5c31af7Sopenharmony_ci        api_name - API name for which to generate dependencies. Only
98e5c31af7Sopenharmony_ci        extensions supported for that API are considered.
99e5c31af7Sopenharmony_ci        """
100e5c31af7Sopenharmony_ci
101e5c31af7Sopenharmony_ci        conventions = APIConventions()
102e5c31af7Sopenharmony_ci        if registry_path is None:
103e5c31af7Sopenharmony_ci            registry_path = conventions.registry_path
104e5c31af7Sopenharmony_ci        if api_name is None:
105e5c31af7Sopenharmony_ci            api_name = conventions.xml_api_name
106e5c31af7Sopenharmony_ci
107e5c31af7Sopenharmony_ci        self.allExts = set()
108e5c31af7Sopenharmony_ci        self.khrExts = set()
109e5c31af7Sopenharmony_ci        self.ratifiedExts = set()
110e5c31af7Sopenharmony_ci        self.graph = DiGraph()
111e5c31af7Sopenharmony_ci        self.extensions = {}
112e5c31af7Sopenharmony_ci        self.tree = etree.parse(registry_path)
113e5c31af7Sopenharmony_ci
114e5c31af7Sopenharmony_ci        # Loop over all supported extensions, creating a digraph of the
115e5c31af7Sopenharmony_ci        # extension dependencies in the 'depends' attribute, which is a
116e5c31af7Sopenharmony_ci        # boolean expression of core version and extension names.
117e5c31af7Sopenharmony_ci        # A static dependency tree can be constructed only by treating all
118e5c31af7Sopenharmony_ci        # extension names in the expression as dependencies, even though
119e5c31af7Sopenharmony_ci        # that may not be true if it is of form (ext OR ext).
120e5c31af7Sopenharmony_ci        # For the purpose these dependencies are used for - generating
121e5c31af7Sopenharmony_ci        # specifications with required dependencies included automatically -
122e5c31af7Sopenharmony_ci        # this will suffice.
123e5c31af7Sopenharmony_ci        # Separately tracks lists of all extensions and all KHR extensions,
124e5c31af7Sopenharmony_ci        # which are common specification targets.
125e5c31af7Sopenharmony_ci        for elem in self.tree.findall('extensions/extension'):
126e5c31af7Sopenharmony_ci            name = elem.get('name')
127e5c31af7Sopenharmony_ci            supported = elem.get('supported')
128e5c31af7Sopenharmony_ci            ratified = elem.get('ratified', '')
129e5c31af7Sopenharmony_ci
130e5c31af7Sopenharmony_ci            if api_name in supported.split(','):
131e5c31af7Sopenharmony_ci                self.allExts.add(name)
132e5c31af7Sopenharmony_ci
133e5c31af7Sopenharmony_ci                if 'KHR' in name:
134e5c31af7Sopenharmony_ci                    self.khrExts.add(name)
135e5c31af7Sopenharmony_ci
136e5c31af7Sopenharmony_ci                if api_name in ratified.split(','):
137e5c31af7Sopenharmony_ci                    self.ratifiedExts.add(name)
138e5c31af7Sopenharmony_ci
139e5c31af7Sopenharmony_ci                self.graph.add_node(name)
140e5c31af7Sopenharmony_ci
141e5c31af7Sopenharmony_ci                depends = elem.get('depends')
142e5c31af7Sopenharmony_ci                if depends:
143e5c31af7Sopenharmony_ci                    # Walk a list of the leaf nodes (version and extension
144e5c31af7Sopenharmony_ci                    # names) in the boolean expression.
145e5c31af7Sopenharmony_ci                    for dep in dependencyNames(depends):
146e5c31af7Sopenharmony_ci                        # Filter out version names, which are explicitly
147e5c31af7Sopenharmony_ci                        # specified when building a specification.
148e5c31af7Sopenharmony_ci                        if not conventions.is_api_version_name(dep):
149e5c31af7Sopenharmony_ci                            self.graph.add_edge(name, dep)
150e5c31af7Sopenharmony_ci            else:
151e5c31af7Sopenharmony_ci                # Skip unsupported extensions
152e5c31af7Sopenharmony_ci                pass
153e5c31af7Sopenharmony_ci
154e5c31af7Sopenharmony_ci    def allExtensions(self):
155e5c31af7Sopenharmony_ci        """Returns a set of all extensions in the graph"""
156e5c31af7Sopenharmony_ci        return self.allExts
157e5c31af7Sopenharmony_ci
158e5c31af7Sopenharmony_ci    def khrExtensions(self):
159e5c31af7Sopenharmony_ci        """Returns a set of all KHR extensions in the graph"""
160e5c31af7Sopenharmony_ci        return self.khrExts
161e5c31af7Sopenharmony_ci
162e5c31af7Sopenharmony_ci    def ratifiedExtensions(self):
163e5c31af7Sopenharmony_ci        """Returns a set of all ratified extensions in the graph"""
164e5c31af7Sopenharmony_ci        return self.ratifiedExts
165e5c31af7Sopenharmony_ci
166e5c31af7Sopenharmony_ci    def children(self, extension):
167e5c31af7Sopenharmony_ci        """Returns a set of the dependencies of an extension.
168e5c31af7Sopenharmony_ci           Throws an exception if the extension is not in the graph."""
169e5c31af7Sopenharmony_ci
170e5c31af7Sopenharmony_ci        if extension not in self.allExts:
171e5c31af7Sopenharmony_ci            raise Exception(f'Extension {extension} not found in XML!')
172e5c31af7Sopenharmony_ci
173e5c31af7Sopenharmony_ci        return set(self.graph.descendants(extension))
174e5c31af7Sopenharmony_ci
175e5c31af7Sopenharmony_ci
176e5c31af7Sopenharmony_ci# Test script
177e5c31af7Sopenharmony_ciif __name__ == '__main__':
178e5c31af7Sopenharmony_ci    parser = argparse.ArgumentParser()
179e5c31af7Sopenharmony_ci
180e5c31af7Sopenharmony_ci    parser.add_argument('-registry', action='store',
181e5c31af7Sopenharmony_ci                        default=APIConventions().registry_path,
182e5c31af7Sopenharmony_ci                        help='Use specified registry file instead of ' + APIConventions().registry_path)
183e5c31af7Sopenharmony_ci    parser.add_argument('-loops', action='store',
184e5c31af7Sopenharmony_ci                        default=10, type=int,
185e5c31af7Sopenharmony_ci                        help='Number of timing loops to run')
186e5c31af7Sopenharmony_ci    parser.add_argument('-test', action='store',
187e5c31af7Sopenharmony_ci                        default=None,
188e5c31af7Sopenharmony_ci                        help='Specify extension to find dependencies of')
189e5c31af7Sopenharmony_ci
190e5c31af7Sopenharmony_ci    args = parser.parse_args()
191e5c31af7Sopenharmony_ci
192e5c31af7Sopenharmony_ci    deps = ApiDependencies(args.registry)
193e5c31af7Sopenharmony_ci    print('KHR exts =', sorted(deps.khrExtensions()))
194e5c31af7Sopenharmony_ci    print('Ratified exts =', sorted(deps.ratifiedExtensions()))
195e5c31af7Sopenharmony_ci
196e5c31af7Sopenharmony_ci    import time
197e5c31af7Sopenharmony_ci    startTime = time.process_time()
198e5c31af7Sopenharmony_ci
199e5c31af7Sopenharmony_ci    for loop in range(args.loops):
200e5c31af7Sopenharmony_ci        deps = ApiDependencies(args.registry)
201e5c31af7Sopenharmony_ci
202e5c31af7Sopenharmony_ci    endTime = time.process_time()
203e5c31af7Sopenharmony_ci
204e5c31af7Sopenharmony_ci    deltaT = endTime - startTime
205e5c31af7Sopenharmony_ci    print('Total time = {} time/loop = {}'.format(deltaT, deltaT / args.loops))
206