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