| # Copyright 2009-2013, 2019 Peter A. Bigot |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| # This implementation is derived from the one in |
| # [PyXB](https://github.com/pabigot/pyxb), stripped down and modified |
| # specifically to manage edtlib Node instances. |
| |
| import collections |
| |
| from operator import attrgetter |
| |
| class Graph: |
| """ |
| Represent a directed graph with edtlib Node objects as nodes. |
| |
| This is used to determine order dependencies among nodes in a |
| devicetree. An edge from C{source} to C{target} indicates that |
| some aspect of C{source} requires that some aspect of C{target} |
| already be available. |
| """ |
| |
| def __init__(self, root=None): |
| self.__roots = None |
| if root is not None: |
| self.__roots = {root} |
| self.__edge_map = collections.defaultdict(set) |
| self.__reverse_map = collections.defaultdict(set) |
| self.__nodes = set() |
| |
| def add_edge(self, source, target): |
| """ |
| Add a directed edge from the C{source} to the C{target}. |
| |
| The nodes are added to the graph if necessary. |
| """ |
| self.__edge_map[source].add(target) |
| if source != target: |
| self.__reverse_map[target].add(source) |
| self.__nodes.add(source) |
| self.__nodes.add(target) |
| |
| def roots(self): |
| """ |
| Return the set of nodes calculated to be roots (i.e., those |
| that have no incoming edges). |
| |
| This caches the roots calculated in a previous invocation. |
| |
| @rtype: C{set} |
| """ |
| if not self.__roots: |
| self.__roots = set() |
| for n in self.__nodes: |
| if n not in self.__reverse_map: |
| self.__roots.add(n) |
| return self.__roots |
| |
| def _tarjan(self): |
| # Execute Tarjan's algorithm on the graph. |
| # |
| # Tarjan's algorithm |
| # (http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) |
| # computes the strongly-connected components |
| # (http://en.wikipedia.org/wiki/Strongly_connected_component) |
| # of the graph: i.e., the sets of nodes that form a minimal |
| # closed set under edge transition. In essence, the loops. |
| # We use this to detect groups of components that have a |
| # dependency cycle, and to impose a total order on components |
| # based on dependencies. |
| |
| self.__stack = [] |
| self.__scc_order = [] |
| self.__index = 0 |
| self.__tarjan_index = {} |
| self.__tarjan_low_link = {} |
| for v in self.__nodes: |
| self.__tarjan_index[v] = None |
| roots = self.roots() |
| if self.__nodes and not roots: |
| raise Exception('TARJAN: No roots found in graph with {} nodes'.format(len(self.__nodes))) |
| for r in sorted(roots, key=attrgetter('path')): |
| self._tarjan_root(r) |
| |
| # Assign ordinals for edtlib |
| ordinal = 0 |
| for scc in self.__scc_order: |
| # Zephyr customization: devicetree Node graphs should have |
| # no loops, so all SCCs should be singletons. That may |
| # change in the future, but for now we only give an |
| # ordinal to singletons. |
| if len(scc) == 1: |
| scc[0].dep_ordinal = ordinal |
| ordinal += 1 |
| |
| def _tarjan_root(self, v): |
| # Do the work of Tarjan's algorithm for a given root node. |
| |
| if self.__tarjan_index.get(v) is not None: |
| # "Root" was already reached. |
| return |
| self.__tarjan_index[v] = self.__tarjan_low_link[v] = self.__index |
| self.__index += 1 |
| self.__stack.append(v) |
| source = v |
| for target in sorted(self.__edge_map[source], key=attrgetter('path')): |
| if self.__tarjan_index[target] is None: |
| self._tarjan_root(target) |
| self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) |
| elif target in self.__stack: |
| self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target]) |
| |
| if self.__tarjan_low_link[v] == self.__tarjan_index[v]: |
| scc = [] |
| while True: |
| scc.append(self.__stack.pop()) |
| if v == scc[-1]: |
| break |
| self.__scc_order.append(scc) |
| |
| def scc_order(self): |
| """Return the strongly-connected components in order. |
| |
| The data structure is a list, in dependency order, of strongly |
| connected components (which can be single nodes). Appearance |
| of a node in a set earlier in the list indicates that it has |
| no dependencies on any node that appears in a subsequent set. |
| This order is preferred over a depth-first-search order for |
| code generation, since it detects loops. |
| """ |
| if not self.__scc_order: |
| self._tarjan() |
| return self.__scc_order |
| __scc_order = None |
| |
| def depends_on(self, node): |
| """Get the nodes that 'node' directly depends on.""" |
| return sorted(self.__edge_map[node], key=attrgetter('path')) |
| |
| def required_by(self, node): |
| """Get the nodes that directly depend on 'node'.""" |
| return sorted(self.__reverse_map[node], key=attrgetter('path')) |