1#!/usr/bin/env python3 2 3# Simple graph query utility 4# useful for getting answers from .dot files produced by bitbake -g 5# 6# Written by: Paul Eggleton <paul.eggleton@linux.intel.com> 7# 8# Copyright 2013 Intel Corporation 9# 10# SPDX-License-Identifier: GPL-2.0-only 11# 12 13import sys 14import os 15import argparse 16 17scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib')) 18sys.path.insert(0, scripts_lib_path) 19import argparse_oe 20 21 22def get_path_networkx(dotfile, fromnode, tonode): 23 try: 24 import networkx 25 except ImportError: 26 print('ERROR: Please install the networkx python module') 27 sys.exit(1) 28 29 graph = networkx.DiGraph(networkx.nx_pydot.read_dot(dotfile)) 30 def node_missing(node): 31 import difflib 32 close_matches = difflib.get_close_matches(node, graph.nodes(), cutoff=0.7) 33 if close_matches: 34 print('ERROR: no node "%s" in graph. Close matches:\n %s' % (node, '\n '.join(close_matches))) 35 sys.exit(1) 36 37 if not fromnode in graph: 38 node_missing(fromnode) 39 if not tonode in graph: 40 node_missing(tonode) 41 return networkx.all_simple_paths(graph, source=fromnode, target=tonode) 42 43 44def find_paths(args): 45 path = None 46 for path in get_path_networkx(args.dotfile, args.fromnode, args.tonode): 47 print(" -> ".join(map(str, path))) 48 if not path: 49 print("ERROR: no path from %s to %s in graph" % (args.fromnode, args.tonode)) 50 return 1 51 52 53def filter_graph(args): 54 import fnmatch 55 56 exclude_tasks = [] 57 if args.exclude_tasks: 58 for task in args.exclude_tasks.split(','): 59 if not task.startswith('do_'): 60 task = 'do_%s' % task 61 exclude_tasks.append(task) 62 63 def checkref(strval): 64 strval = strval.strip().strip('"') 65 target, taskname = strval.rsplit('.', 1) 66 if exclude_tasks: 67 for extask in exclude_tasks: 68 if fnmatch.fnmatch(taskname, extask): 69 return False 70 if strval in args.ref or target in args.ref: 71 return True 72 return False 73 74 with open(args.infile, 'r') as f: 75 for line in f: 76 line = line.rstrip() 77 if line.startswith(('digraph', '}')): 78 print(line) 79 elif '->' in line: 80 linesplit = line.split('->') 81 if checkref(linesplit[0]) and checkref(linesplit[1]): 82 print(line) 83 elif (not args.no_nodes) and checkref(line.split()[0]): 84 print(line) 85 86 87def main(): 88 parser = argparse_oe.ArgumentParser(description='Small utility for working with .dot graph files') 89 90 subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') 91 subparsers.required = True 92 93 parser_find_paths = subparsers.add_parser('find-paths', 94 help='Find all of the paths between two nodes in a dot graph', 95 description='Finds all of the paths between two nodes in a dot graph') 96 parser_find_paths.add_argument('dotfile', help='.dot graph to search in') 97 parser_find_paths.add_argument('fromnode', help='starting node name') 98 parser_find_paths.add_argument('tonode', help='ending node name') 99 parser_find_paths.set_defaults(func=find_paths) 100 101 parser_filter = subparsers.add_parser('filter', 102 help='Pare down a task graph to contain only the specified references', 103 description='Pares down a task-depends.dot graph produced by bitbake -g to contain only the specified references') 104 parser_filter.add_argument('infile', help='Input file') 105 parser_filter.add_argument('ref', nargs='+', help='Reference to include (either recipe/target name or full target.taskname specification)') 106 parser_filter.add_argument('-n', '--no-nodes', action='store_true', help='Skip node formatting lines') 107 parser_filter.add_argument('-x', '--exclude-tasks', help='Comma-separated list of tasks to exclude (do_ prefix optional, wildcards allowed)') 108 parser_filter.set_defaults(func=filter_graph) 109 110 args = parser.parse_args() 111 112 ret = args.func(args) 113 return ret 114 115 116if __name__ == "__main__": 117 ret = main() 118 sys.exit(ret) 119