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