| #!/usr/bin/env python3 |
| |
| # Simple graph query utility |
| # useful for getting answers from .dot files produced by bitbake -g |
| # |
| # Written by: Paul Eggleton <paul.eggleton@linux.intel.com> |
| # |
| # Copyright 2013 Intel Corporation |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| import sys |
| import os |
| import argparse |
| |
| scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib')) |
| sys.path.insert(0, scripts_lib_path) |
| import argparse_oe |
| |
| |
| def get_path_networkx(dotfile, fromnode, tonode): |
| try: |
| import networkx |
| except ImportError: |
| print('ERROR: Please install the networkx python module') |
| sys.exit(1) |
| |
| graph = networkx.DiGraph(networkx.nx_pydot.read_dot(dotfile)) |
| def node_missing(node): |
| import difflib |
| close_matches = difflib.get_close_matches(node, graph.nodes(), cutoff=0.7) |
| if close_matches: |
| print('ERROR: no node "%s" in graph. Close matches:\n %s' % (node, '\n '.join(close_matches))) |
| sys.exit(1) |
| |
| if not fromnode in graph: |
| node_missing(fromnode) |
| if not tonode in graph: |
| node_missing(tonode) |
| return networkx.all_simple_paths(graph, source=fromnode, target=tonode) |
| |
| |
| def find_paths(args): |
| path = None |
| for path in get_path_networkx(args.dotfile, args.fromnode, args.tonode): |
| print(" -> ".join(map(str, path))) |
| if not path: |
| print("ERROR: no path from %s to %s in graph" % (args.fromnode, args.tonode)) |
| return 1 |
| |
| |
| def filter_graph(args): |
| import fnmatch |
| |
| exclude_tasks = [] |
| if args.exclude_tasks: |
| for task in args.exclude_tasks.split(','): |
| if not task.startswith('do_'): |
| task = 'do_%s' % task |
| exclude_tasks.append(task) |
| |
| def checkref(strval): |
| strval = strval.strip().strip('"') |
| target, taskname = strval.rsplit('.', 1) |
| if exclude_tasks: |
| for extask in exclude_tasks: |
| if fnmatch.fnmatch(taskname, extask): |
| return False |
| if strval in args.ref or target in args.ref: |
| return True |
| return False |
| |
| with open(args.infile, 'r') as f: |
| for line in f: |
| line = line.rstrip() |
| if line.startswith(('digraph', '}')): |
| print(line) |
| elif '->' in line: |
| linesplit = line.split('->') |
| if checkref(linesplit[0]) and checkref(linesplit[1]): |
| print(line) |
| elif (not args.no_nodes) and checkref(line.split()[0]): |
| print(line) |
| |
| |
| def main(): |
| parser = argparse_oe.ArgumentParser(description='Small utility for working with .dot graph files') |
| |
| subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') |
| subparsers.required = True |
| |
| parser_find_paths = subparsers.add_parser('find-paths', |
| help='Find all of the paths between two nodes in a dot graph', |
| description='Finds all of the paths between two nodes in a dot graph') |
| parser_find_paths.add_argument('dotfile', help='.dot graph to search in') |
| parser_find_paths.add_argument('fromnode', help='starting node name') |
| parser_find_paths.add_argument('tonode', help='ending node name') |
| parser_find_paths.set_defaults(func=find_paths) |
| |
| parser_filter = subparsers.add_parser('filter', |
| help='Pare down a task graph to contain only the specified references', |
| description='Pares down a task-depends.dot graph produced by bitbake -g to contain only the specified references') |
| parser_filter.add_argument('infile', help='Input file') |
| parser_filter.add_argument('ref', nargs='+', help='Reference to include (either recipe/target name or full target.taskname specification)') |
| parser_filter.add_argument('-n', '--no-nodes', action='store_true', help='Skip node formatting lines') |
| parser_filter.add_argument('-x', '--exclude-tasks', help='Comma-separated list of tasks to exclude (do_ prefix optional, wildcards allowed)') |
| parser_filter.set_defaults(func=filter_graph) |
| |
| args = parser.parse_args() |
| |
| ret = args.func(args) |
| return ret |
| |
| |
| if __name__ == "__main__": |
| ret = main() |
| sys.exit(ret) |