• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (c) 2017-2020 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6"""Generate a mapping of extension name -> all required extension names for that extension.
7
8This script generates a list of all extensions, and of just KHR
9extensions, that are placed into a Bash script and/or Python script. This
10script can then be sources or executed to set a variable (e.g. khrExts),
11Frontend scripts such as 'makeAllExts' and 'makeKHR' use this information
12to set the EXTENSIONS Makefile variable when building the spec.
13
14Sample Usage:
15
16python3 scripts/make_ext_dependency.py -outscript=temp.sh
17source temp.sh
18make EXTENSIONS="$khrExts" html
19rm temp.sh
20"""
21
22import argparse
23import errno
24import xml.etree.ElementTree as etree
25from pathlib import Path
26
27from vkconventions import VulkanConventions as APIConventions
28
29
30def enQuote(key):
31    return "'" + str(key) + "'"
32
33
34def shList(names):
35    """Return a sortable (list or set) of names as a string encoding
36    of a Bash or Python list, sorted on the names."""
37    s = ('"' +
38         ' '.join(str(key) for key in sorted(names)) +
39         '"')
40    return s
41
42
43def pyList(names):
44    if names is not None:
45        return ('[ ' +
46                ', '.join(enQuote(key) for key in sorted(names)) +
47                ' ]')
48    else:
49        return '[ ]'
50
51class DiGraph:
52    """A directed graph.
53
54    The implementation and API mimic that of networkx.DiGraph in networkx-1.11.
55    networkx implements graphs as nested dicts; it's dicts all the way down, no
56    lists.
57
58    Some major differences between this implementation and that of
59    networkx-1.11 are:
60
61        * This omits edge and node attribute data, because we never use them
62          yet they add additional code complexity.
63
64        * This returns iterator objects when possible instead of collection
65          objects, because it simplifies the implementation and should provide
66          better performance.
67    """
68
69    def __init__(self):
70        self.__nodes = {}
71
72    def add_node(self, node):
73        if node not in self.__nodes:
74            self.__nodes[node] = DiGraphNode()
75
76    def add_edge(self, src, dest):
77        self.add_node(src)
78        self.add_node(dest)
79        self.__nodes[src].adj.add(dest)
80
81    def nodes(self):
82        """Iterate over the nodes in the graph."""
83        return self.__nodes.keys()
84
85    def descendants(self, node):
86        """
87        Iterate over the nodes reachable from the given start node, excluding
88        the start node itself. Each node in the graph is yielded at most once.
89        """
90
91        # Implementation detail: Do a breadth-first traversal because it's
92        # easier than depth-first.
93
94        # All nodes seen during traversal.
95        seen = set()
96
97        # The stack of nodes that need visiting.
98        visit_me = []
99
100        # Bootstrap the traversal.
101        seen.add(node)
102        for x in self.__nodes[node].adj:
103            if x not in seen:
104                seen.add(x)
105                visit_me.append(x)
106
107        while visit_me:
108            x = visit_me.pop()
109            assert x in seen
110            yield x
111
112            for y in self.__nodes[x].adj:
113                if y not in seen:
114                    seen.add(y)
115                    visit_me.append(y)
116
117
118class DiGraphNode:
119
120    def __init__(self):
121        # Set of adjacent of nodes.
122        self.adj = set()
123
124
125def make_dir(fn):
126    outdir = Path(fn).parent
127    try:
128        outdir.mkdir(parents=True)
129    except OSError as e:
130        if e.errno != errno.EEXIST:
131            raise
132
133
134# API conventions object
135conventions = APIConventions()
136
137# -extension name - may be a single extension name, a space-separated list
138# of names, or a regular expression.
139if __name__ == '__main__':
140    parser = argparse.ArgumentParser()
141
142    parser.add_argument('-registry', action='store',
143                        default=conventions.registry_path,
144                        help='Use specified registry file instead of ' + conventions.registry_path)
145    parser.add_argument('-outscript', action='store',
146                        default=None,
147                        help='Shell script to create')
148    parser.add_argument('-outpy', action='store',
149                        default=None,
150                        help='Python script to create')
151    parser.add_argument('-test', action='store',
152                        default=None,
153                        help='Specify extension to find dependencies of')
154    parser.add_argument('-quiet', action='store_true', default=False,
155                        help='Suppress script output during normal execution.')
156
157    args = parser.parse_args()
158
159    tree = etree.parse(args.registry)
160
161    # Loop over all supported extensions, creating a digraph of the
162    # extension dependencies in the 'requires' attribute, which is a
163    # comma-separated list of extension names. Also track lists of
164    # all extensions and all KHR extensions.
165
166    allExts = set()
167    khrExts = set()
168    g = DiGraph()
169
170    for elem in tree.findall('extensions/extension'):
171        name = elem.get('name')
172        supported = elem.get('supported')
173
174        # This works for the present form of the 'supported' attribute,
175        # which is a comma-separate list of XML API names
176        if conventions.xml_api_name in supported.split(','):
177            allExts.add(name)
178
179            if 'KHR' in name:
180                khrExts.add(name)
181
182            deps = elem.get('requires')
183            if deps:
184                deps = deps.split(',')
185
186                for dep in deps:
187                    g.add_edge(name, dep)
188            else:
189                g.add_node(name)
190        else:
191            # Skip unsupported extensions
192            pass
193
194    if args.outscript:
195        make_dir(args.outscript)
196        fp = open(args.outscript, 'w', encoding='utf-8')
197
198        print('#!/bin/bash', file=fp)
199        print('# Generated from make_ext_dependency.py', file=fp)
200        print('# Specify maps of all extensions required by an enabled extension', file=fp)
201        print('', file=fp)
202        print('declare -A extensions', file=fp)
203
204        # When printing lists of extensions, sort them so that the output script
205        # remains as stable as possible as extensions are added to the API XML.
206
207        for ext in sorted(g.nodes()):
208            children = list(g.descendants(ext))
209
210            # Only emit an ifdef block if an extension has dependencies
211            if children:
212                print('extensions[' + ext + ']=' + shList(children), file=fp)
213
214        print('', file=fp)
215        print('# Define lists of all extensions and KHR extensions', file=fp)
216        print('allExts=' + shList(allExts), file=fp)
217        print('khrExts=' + shList(khrExts), file=fp)
218
219        fp.close()
220
221    if args.outpy:
222        make_dir(args.outpy)
223        fp = open(args.outpy, 'w', encoding='utf-8')
224
225        print('#!/usr/bin/env python', file=fp)
226        print('# Generated from make_ext_dependency.py', file=fp)
227        print('# Specify maps of all extensions required by an enabled extension', file=fp)
228        print('', file=fp)
229        print('extensions = {}', file=fp)
230
231        # When printing lists of extensions, sort them so that the output script
232        # remains as stable as possible as extensions are added to the API XML.
233
234        for ext in sorted(g.nodes()):
235            children = list(g.descendants(ext))
236            print("extensions['" + ext + "'] = " + pyList(children), file=fp)
237
238        print('', file=fp)
239        print('# Define lists of all extensions and KHR extensions', file=fp)
240        print('allExts = ' + pyList(allExts), file=fp)
241        print('khrExts = ' + pyList(khrExts), file=fp)
242
243        fp.close()
244