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