#!/usr/bin/env python3 # # Copyright 2017-2021 The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 """Generate a mapping of extension name -> all required extension names for that extension. This script generates a list of all extensions, and of just KHR extensions, that are placed into a Bash script and/or Python script. This script can then be sources or executed to set a variable (e.g. khrExts), Frontend scripts such as 'makeAllExts' and 'makeKHR' use this information to set the EXTENSIONS Makefile variable when building the spec. Sample Usage: python3 scripts/make_ext_dependency.py -outscript=temp.sh source temp.sh make EXTENSIONS="$khrExts" html rm temp.sh """ import argparse import errno import xml.etree.ElementTree as etree from pathlib import Path from vkconventions import VulkanConventions as APIConventions def enQuote(key): return "'" + str(key) + "'" def shList(names): """Return a sortable (list or set) of names as a string encoding of a Bash or Python list, sorted on the names.""" s = ('"' + ' '.join(str(key) for key in sorted(names)) + '"') return s def pyList(names): if names is not None: return ('[ ' + ', '.join(enQuote(key) for key in sorted(names)) + ' ]') else: return '[ ]' class DiGraph: """A directed graph. The implementation and API mimic that of networkx.DiGraph in networkx-1.11. networkx implements graphs as nested dicts; it's dicts all the way down, no lists. Some major differences between this implementation and that of networkx-1.11 are: * This omits edge and node attribute data, because we never use them yet they add additional code complexity. * This returns iterator objects when possible instead of collection objects, because it simplifies the implementation and should provide better performance. """ def __init__(self): self.__nodes = {} def add_node(self, node): if node not in self.__nodes: self.__nodes[node] = DiGraphNode() def add_edge(self, src, dest): self.add_node(src) self.add_node(dest) self.__nodes[src].adj.add(dest) def nodes(self): """Iterate over the nodes in the graph.""" return self.__nodes.keys() def descendants(self, node): """ Iterate over the nodes reachable from the given start node, excluding the start node itself. Each node in the graph is yielded at most once. """ # Implementation detail: Do a breadth-first traversal because it's # easier than depth-first. # All nodes seen during traversal. seen = set() # The stack of nodes that need visiting. visit_me = [] # Bootstrap the traversal. seen.add(node) for x in self.__nodes[node].adj: if x not in seen: seen.add(x) visit_me.append(x) while visit_me: x = visit_me.pop() assert x in seen yield x for y in self.__nodes[x].adj: if y not in seen: seen.add(y) visit_me.append(y) class DiGraphNode: def __init__(self): # Set of adjacent of nodes. self.adj = set() def make_dir(fn): outdir = Path(fn).parent try: outdir.mkdir(parents=True) except OSError as e: if e.errno != errno.EEXIST: raise # API conventions object conventions = APIConventions() # -extension name - may be a single extension name, a space-separated list # of names, or a regular expression. if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-registry', action='store', default=conventions.registry_path, help='Use specified registry file instead of ' + conventions.registry_path) parser.add_argument('-outscript', action='store', default=None, help='Shell script to create') parser.add_argument('-outpy', action='store', default=None, help='Python script to create') parser.add_argument('-test', action='store', default=None, help='Specify extension to find dependencies of') parser.add_argument('-quiet', action='store_true', default=False, help='Suppress script output during normal execution.') args = parser.parse_args() tree = etree.parse(args.registry) # Loop over all supported extensions, creating a digraph of the # extension dependencies in the 'requires' attribute, which is a # comma-separated list of extension names. Also track lists of # all extensions and all KHR extensions. allExts = set() khrExts = set() g = DiGraph() for elem in tree.findall('extensions/extension'): name = elem.get('name') supported = elem.get('supported') # This works for the present form of the 'supported' attribute, # which is a comma-separate list of XML API names if conventions.xml_api_name in supported.split(','): allExts.add(name) if 'KHR' in name: khrExts.add(name) deps = elem.get('requires') if deps: deps = deps.split(',') for dep in deps: g.add_edge(name, dep) else: g.add_node(name) else: # Skip unsupported extensions pass if args.outscript: make_dir(args.outscript) fp = open(args.outscript, 'w', encoding='utf-8') print('#!/bin/bash', file=fp) print('# Generated from make_ext_dependency.py', file=fp) print('# Specify maps of all extensions required by an enabled extension', file=fp) print('', file=fp) print('declare -A extensions', file=fp) # When printing lists of extensions, sort them so that the output script # remains as stable as possible as extensions are added to the API XML. for ext in sorted(g.nodes()): children = list(g.descendants(ext)) # Only emit an ifdef block if an extension has dependencies if children: print('extensions[' + ext + ']=' + shList(children), file=fp) print('', file=fp) print('# Define lists of all extensions and KHR extensions', file=fp) print('allExts=' + shList(allExts), file=fp) print('khrExts=' + shList(khrExts), file=fp) fp.close() if args.outpy: make_dir(args.outpy) fp = open(args.outpy, 'w', encoding='utf-8') print('#!/usr/bin/env python', file=fp) print('# Generated from make_ext_dependency.py', file=fp) print('# Specify maps of all extensions required by an enabled extension', file=fp) print('', file=fp) print('extensions = {}', file=fp) # When printing lists of extensions, sort them so that the output script # remains as stable as possible as extensions are added to the API XML. for ext in sorted(g.nodes()): children = list(g.descendants(ext)) print("extensions['" + ext + "'] = " + pyList(children), file=fp) print('', file=fp) print('# Define lists of all extensions and KHR extensions', file=fp) print('allExts = ' + pyList(allExts), file=fp) print('khrExts = ' + pyList(khrExts), file=fp) fp.close()