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