xref: /OK3568_Linux_fs/yocto/scripts/oe-depends-dot (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env python3
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (C) 2018 Wind River Systems, Inc.
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunimport os
9*4882a593Smuzhiyunimport sys
10*4882a593Smuzhiyunimport argparse
11*4882a593Smuzhiyunimport logging
12*4882a593Smuzhiyunimport re
13*4882a593Smuzhiyun
14*4882a593Smuzhiyunclass Dot(object):
15*4882a593Smuzhiyun    def __init__(self):
16*4882a593Smuzhiyun        parser = argparse.ArgumentParser(
17*4882a593Smuzhiyun            description="Analyse recipe-depends.dot generated by bitbake -g",
18*4882a593Smuzhiyun            epilog="Use %(prog)s --help to get help")
19*4882a593Smuzhiyun        parser.add_argument("dotfile",
20*4882a593Smuzhiyun            help = "Specify the dotfile", nargs = 1, action='store', default='')
21*4882a593Smuzhiyun        parser.add_argument("-k", "--key",
22*4882a593Smuzhiyun            help = "Specify the key, e.g., recipe name",
23*4882a593Smuzhiyun            action="store", default='')
24*4882a593Smuzhiyun        parser.add_argument("-d", "--depends",
25*4882a593Smuzhiyun            help = "Print the key's dependencies",
26*4882a593Smuzhiyun            action="store_true", default=False)
27*4882a593Smuzhiyun        parser.add_argument("-w", "--why",
28*4882a593Smuzhiyun            help = "Print why the key is built",
29*4882a593Smuzhiyun            action="store_true", default=False)
30*4882a593Smuzhiyun        parser.add_argument("-r", "--remove",
31*4882a593Smuzhiyun            help = "Remove duplicated dependencies to reduce the size of the dot files."
32*4882a593Smuzhiyun                    " For example, A->B, B->C, A->C, then A->C can be removed.",
33*4882a593Smuzhiyun            action="store_true", default=False)
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun        self.args = parser.parse_args()
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun        if len(sys.argv) != 3 and len(sys.argv) < 5:
38*4882a593Smuzhiyun            print('ERROR: Not enough args, see --help for usage')
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun    @staticmethod
41*4882a593Smuzhiyun    def insert_dep_chain(chain, rdeps, alldeps):
42*4882a593Smuzhiyun        """
43*4882a593Smuzhiyun        insert elements to chain from rdeps, according to alldeps
44*4882a593Smuzhiyun        """
45*4882a593Smuzhiyun        # chain should at least contain one element
46*4882a593Smuzhiyun        if len(chain) == 0:
47*4882a593Smuzhiyun            raise
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun        inserted_elements = []
50*4882a593Smuzhiyun        for rdep in rdeps:
51*4882a593Smuzhiyun            if rdep in chain:
52*4882a593Smuzhiyun                continue
53*4882a593Smuzhiyun            else:
54*4882a593Smuzhiyun                for i in range(0, len(chain)-1):
55*4882a593Smuzhiyun                    if chain[i] in alldeps[rdep] and rdep in alldeps[chain[i+1]]:
56*4882a593Smuzhiyun                        chain.insert(i+1, rdep)
57*4882a593Smuzhiyun                        inserted_elements.append(rdep)
58*4882a593Smuzhiyun                        break
59*4882a593Smuzhiyun                if chain[-1] in alldeps[rdep] and rdep not in chain:
60*4882a593Smuzhiyun                    chain.append(rdep)
61*4882a593Smuzhiyun                    inserted_elements.append(rdep)
62*4882a593Smuzhiyun        return inserted_elements
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun    @staticmethod
65*4882a593Smuzhiyun    def print_dep_chains(key, rdeps, alldeps):
66*4882a593Smuzhiyun        rlist = rdeps.copy()
67*4882a593Smuzhiyun        chain = []
68*4882a593Smuzhiyun        removed_rdeps = [] # hold rdeps removed from rlist
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun        chain.append(key)
71*4882a593Smuzhiyun        while (len(rlist) != 0):
72*4882a593Smuzhiyun            # insert chain from rlist
73*4882a593Smuzhiyun            inserted_elements = Dot.insert_dep_chain(chain, rlist, alldeps)
74*4882a593Smuzhiyun            if not inserted_elements:
75*4882a593Smuzhiyun                if chain[-1] in rlist:
76*4882a593Smuzhiyun                    rlist.remove(chain[-1])
77*4882a593Smuzhiyun                    removed_rdeps.append(chain[-1])
78*4882a593Smuzhiyun                chain.pop()
79*4882a593Smuzhiyun                continue
80*4882a593Smuzhiyun            else:
81*4882a593Smuzhiyun                # insert chain from removed_rdeps
82*4882a593Smuzhiyun                Dot.insert_dep_chain(chain, removed_rdeps, alldeps)
83*4882a593Smuzhiyun                print(' -> '.join(list(reversed(chain))))
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun    def main(self):
86*4882a593Smuzhiyun        #print(self.args.dotfile[0])
87*4882a593Smuzhiyun        # The format is {key: depends}
88*4882a593Smuzhiyun        depends = {}
89*4882a593Smuzhiyun        with open(self.args.dotfile[0], 'r') as f:
90*4882a593Smuzhiyun            for line in f.readlines():
91*4882a593Smuzhiyun                if ' -> ' not in line:
92*4882a593Smuzhiyun                    continue
93*4882a593Smuzhiyun                line_no_quotes = line.replace('"', '')
94*4882a593Smuzhiyun                m = re.match("(.*) -> (.*)", line_no_quotes)
95*4882a593Smuzhiyun                if not m:
96*4882a593Smuzhiyun                    print('WARNING: Found unexpected line: %s' % line)
97*4882a593Smuzhiyun                    continue
98*4882a593Smuzhiyun                key = m.group(1)
99*4882a593Smuzhiyun                if key == "meta-world-pkgdata":
100*4882a593Smuzhiyun                    continue
101*4882a593Smuzhiyun                dep = m.group(2)
102*4882a593Smuzhiyun                if key in depends:
103*4882a593Smuzhiyun                    if not key in depends[key]:
104*4882a593Smuzhiyun                        depends[key].add(dep)
105*4882a593Smuzhiyun                    else:
106*4882a593Smuzhiyun                        print('WARNING: Fonud duplicated line: %s' % line)
107*4882a593Smuzhiyun                else:
108*4882a593Smuzhiyun                    depends[key] = set()
109*4882a593Smuzhiyun                    depends[key].add(dep)
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun        if self.args.remove:
112*4882a593Smuzhiyun            reduced_depends = {}
113*4882a593Smuzhiyun            for k, deps in depends.items():
114*4882a593Smuzhiyun                child_deps = set()
115*4882a593Smuzhiyun                added = set()
116*4882a593Smuzhiyun                # Both direct and indirect depends are already in the dict, so
117*4882a593Smuzhiyun                # we don't have to do this recursively.
118*4882a593Smuzhiyun                for dep in deps:
119*4882a593Smuzhiyun                    if dep in depends:
120*4882a593Smuzhiyun                        child_deps |= depends[dep]
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun                reduced_depends[k] = deps - child_deps
123*4882a593Smuzhiyun                outfile= '%s-reduced%s' % (self.args.dotfile[0][:-4], self.args.dotfile[0][-4:])
124*4882a593Smuzhiyun            with open(outfile, 'w') as f:
125*4882a593Smuzhiyun                print('Saving reduced dot file to %s' % outfile)
126*4882a593Smuzhiyun                f.write('digraph depends {\n')
127*4882a593Smuzhiyun                for k, v in reduced_depends.items():
128*4882a593Smuzhiyun                    for dep in v:
129*4882a593Smuzhiyun                        f.write('"%s" -> "%s"\n' % (k, dep))
130*4882a593Smuzhiyun                f.write('}\n')
131*4882a593Smuzhiyun            sys.exit(0)
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun        if self.args.key not in depends:
134*4882a593Smuzhiyun            print("ERROR: Can't find key %s in %s" % (self.args.key, self.args.dotfile[0]))
135*4882a593Smuzhiyun            sys.exit(1)
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun        if self.args.depends:
138*4882a593Smuzhiyun            if self.args.key in depends:
139*4882a593Smuzhiyun                print('Depends: %s' % ' '.join(depends[self.args.key]))
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun        reverse_deps = []
142*4882a593Smuzhiyun        if self.args.why:
143*4882a593Smuzhiyun            for k, v in depends.items():
144*4882a593Smuzhiyun                if self.args.key in v and not k in reverse_deps:
145*4882a593Smuzhiyun                    reverse_deps.append(k)
146*4882a593Smuzhiyun            print('Because: %s' % ' '.join(reverse_deps))
147*4882a593Smuzhiyun            Dot.print_dep_chains(self.args.key, reverse_deps, depends)
148*4882a593Smuzhiyun
149*4882a593Smuzhiyunif __name__ == "__main__":
150*4882a593Smuzhiyun    try:
151*4882a593Smuzhiyun        dot = Dot()
152*4882a593Smuzhiyun        ret = dot.main()
153*4882a593Smuzhiyun    except Exception as esc:
154*4882a593Smuzhiyun        ret = 1
155*4882a593Smuzhiyun        import traceback
156*4882a593Smuzhiyun        traceback.print_exc()
157*4882a593Smuzhiyun    sys.exit(ret)
158