1#!/usr/bin/python3 2"""A script for parsing and filtering component dot file. 3Adapted from vendor/google_clockwork/packages/SystemUI/daggervis/parser.py 4 5Input: input_dot_file output_dot_file [beginning_nodes_filter] 6Output: create a new dot file with styles applied. The output dot file will only contain nodes 7reachable from the beginning_nodes_filter if it's specified. 8""" 9import sys 10import os 11try: 12 import pydot 13except ImportError as e: 14 print("Error: python3-pydot is not installed. Please run \"sudo apt install python3-pydot\" first.", file=sys.stderr) 15 sys.exit(1) 16 17def main(): 18 # Parse args 19 if len(sys.argv) < 2: 20 print("Error: please specify an input dot file", file=sys.stderr) 21 sys.exit(1) 22 if len(sys.argv) < 3: 23 print("Error: please specify an output dot file", file=sys.stderr) 24 sys.exit(1) 25 input_path = sys.argv[1] 26 output_path = sys.argv[2] 27 if len(sys.argv) > 3: 28 beginning_nodes_filter= sys.argv[3] 29 else: 30 beginning_nodes_filter= None 31 32 # Load graph 33 try: 34 graph = pydot.graph_from_dot_file(input_path)[0] 35 except Exception as e: 36 print("Error: unable to load dot file \"" + input_path + "\"", file=sys.stderr) 37 sys.exit(1) 38 print("Loaded dot file from " + input_path) 39 40 # Trim graph 41 if beginning_nodes_filter!= None: 42 trim_graph(graph, beginning_nodes_filter) 43 44 # Add styles 45 style_graph(graph) 46 47 with open(output_path, "w") as f: 48 f.write(str(graph)) 49 print("Saved output dot file " + output_path) 50 51""" 52Trim a graph by only keeping nodes/edges reachable from beginning nodes. 53""" 54def trim_graph(graph, beginning_nodes_filter): 55 beginning_node_names = set() 56 all_nodes = graph.get_nodes() 57 for n in all_nodes: 58 if beginning_nodes_filter in get_label(n): 59 beginning_node_names.add(n.get_name()) 60 if len(beginning_node_names) == 0: 61 print("Error: unable to find nodes matching \"" + beginning_nodes_filter + "\"", file=sys.stderr) 62 sys.exit(1) 63 filtered_node_names = set() 64 all_edges = graph.get_edges() 65 for node_name in beginning_node_names: 66 dfs(all_edges, node_name, filtered_node_names) 67 cnt_trimmed_nodes = 0 68 for node in all_nodes: 69 if not node.get_name() in filtered_node_names: 70 graph.del_node(node.get_name()) 71 cnt_trimmed_nodes += 1 72 cnt_trimmed_edges = 0 73 for edge in all_edges: 74 if not edge.get_source() in filtered_node_names: 75 graph.del_edge(edge.get_source(), edge.get_destination()) 76 cnt_trimmed_edges += 1 77 print("Trimed " + str(cnt_trimmed_nodes) + " nodes and " + str(cnt_trimmed_edges) + " edges") 78 79def dfs(all_edges, node_name, filtered_node_names): 80 if node_name in filtered_node_names: 81 return 82 filtered_node_names.add(node_name) 83 for edge in all_edges: 84 if edge.get_source() == node_name: 85 dfs(all_edges, edge.get_destination(), filtered_node_names) 86 87""" 88Apply styles to the dot graph. 89""" 90def style_graph(graph): 91 for n in graph.get_nodes(): 92 label = get_label(n) 93 # Style SystemUI nodes 94 if "com.android.systemui" in label: 95 n.obj_dict["attributes"]["color"] = "burlywood" 96 n.obj_dict["attributes"]["shape"] = "box" 97 n.add_style("filled") 98 # Style CarSystemUI nodes 99 elif ("car" in label): 100 n.obj_dict["attributes"]["color"] = "darkolivegreen1" 101 n.add_style("filled") 102 103 # Trim common labels 104 trim_replacements = [("java.util.", ""), ("javax.inject.", "") , ("com.", "c."), 105 ("google.", "g."), ("android.", "a."), ("car.", "c."), 106 ("java.lang.", ""), ("dagger.Lazy", "Lazy"), ("java.util.function.", "")] 107 for (before, after) in trim_replacements: 108 if before in label: 109 n.obj_dict["attributes"]["label"] = label = label.replace(before, after) 110 111def get_label(node): 112 try: 113 return node.obj_dict["attributes"]["label"] 114 except Exception: 115 return "" 116 117if __name__ == "__main__": 118 main()