Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 | # |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 10 | # SPDX-License-Identifier: GPL-2.0-only |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 11 | # |
| 12 | |
| 13 | import sys |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 14 | import os |
| 15 | import argparse |
| 16 | |
| 17 | scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib')) |
| 18 | sys.path.insert(0, scripts_lib_path) |
| 19 | import argparse_oe |
| 20 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 21 | |
| 22 | def 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 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 29 | graph = networkx.DiGraph(networkx.nx_pydot.read_dot(dotfile)) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 44 | def find_paths(args): |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 45 | path = None |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 46 | for path in get_path_networkx(args.dotfile, args.fromnode, args.tonode): |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 47 | print(" -> ".join(map(str, path))) |
| 48 | if not path: |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 49 | print("ERROR: no path from %s to %s in graph" % (args.fromnode, args.tonode)) |
| 50 | return 1 |
| 51 | |
| 52 | |
| 53 | def 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 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 86 | |
| 87 | def main(): |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 88 | parser = argparse_oe.ArgumentParser(description='Small utility for working with .dot graph files') |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 89 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 90 | subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') |
| 91 | subparsers.required = True |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 92 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 100 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 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) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 109 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 110 | args = parser.parse_args() |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 111 | |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 112 | ret = args.func(args) |
| 113 | return ret |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 114 | |
| 115 | |
| 116 | if __name__ == "__main__": |
Andrew Geissler | d25ed32 | 2020-06-27 00:28:28 -0500 | [diff] [blame^] | 117 | ret = main() |
| 118 | sys.exit(ret) |