| #!/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() |