1eb8dc403SDave Cobbley#!/usr/bin/env python3 2eb8dc403SDave Cobbley# 3eb8dc403SDave Cobbley# Copyright (C) 2018 Wind River Systems, Inc. 4eb8dc403SDave Cobbley# 5c342db35SBrad Bishop# SPDX-License-Identifier: GPL-2.0-only 6eb8dc403SDave Cobbley# 7eb8dc403SDave Cobbley 8eb8dc403SDave Cobbleyimport os 9eb8dc403SDave Cobbleyimport sys 10eb8dc403SDave Cobbleyimport argparse 11eb8dc403SDave Cobbleyimport logging 12eb8dc403SDave Cobbleyimport re 13eb8dc403SDave Cobbley 14eb8dc403SDave Cobbleyclass Dot(object): 15eb8dc403SDave Cobbley def __init__(self): 16eb8dc403SDave Cobbley parser = argparse.ArgumentParser( 17*5082cc7fSAndrew Geissler description="Analyse task-depends.dot generated by bitbake -g", 1878b72798SAndrew Geissler formatter_class=argparse.RawDescriptionHelpFormatter) 19eb8dc403SDave Cobbley parser.add_argument("dotfile", 20eb8dc403SDave Cobbley help = "Specify the dotfile", nargs = 1, action='store', default='') 21eb8dc403SDave Cobbley parser.add_argument("-k", "--key", 22eb8dc403SDave Cobbley help = "Specify the key, e.g., recipe name", 23eb8dc403SDave Cobbley action="store", default='') 24eb8dc403SDave Cobbley parser.add_argument("-d", "--depends", 25eb8dc403SDave Cobbley help = "Print the key's dependencies", 26eb8dc403SDave Cobbley action="store_true", default=False) 27eb8dc403SDave Cobbley parser.add_argument("-w", "--why", 28eb8dc403SDave Cobbley help = "Print why the key is built", 29eb8dc403SDave Cobbley action="store_true", default=False) 30eb8dc403SDave Cobbley parser.add_argument("-r", "--remove", 31eb8dc403SDave Cobbley help = "Remove duplicated dependencies to reduce the size of the dot files." 32eb8dc403SDave Cobbley " For example, A->B, B->C, A->C, then A->C can be removed.", 33eb8dc403SDave Cobbley action="store_true", default=False) 34eb8dc403SDave Cobbley 3578b72798SAndrew Geissler parser.epilog = """ 3678b72798SAndrew GeisslerExamples: 3778b72798SAndrew GeisslerFirst generate the .dot file: 3878b72798SAndrew Geissler bitbake -g core-image-minimal 3978b72798SAndrew Geissler 4078b72798SAndrew GeisslerTo find out why a package is being built: 4178b72798SAndrew Geissler %(prog)s -k <package> -w ./task-depends.dot 4278b72798SAndrew Geissler 4378b72798SAndrew GeisslerTo find out what a package depends on: 4478b72798SAndrew Geissler %(prog)s -k <package> -d ./task-depends.dot 4578b72798SAndrew Geissler 4678b72798SAndrew GeisslerReduce the .dot file packages only, no tasks: 4778b72798SAndrew Geissler %(prog)s -r ./task-depends.dot 4878b72798SAndrew Geissler""" 4978b72798SAndrew Geissler 50eb8dc403SDave Cobbley self.args = parser.parse_args() 51eb8dc403SDave Cobbley 52eb8dc403SDave Cobbley if len(sys.argv) != 3 and len(sys.argv) < 5: 53eb8dc403SDave Cobbley print('ERROR: Not enough args, see --help for usage') 54eb8dc403SDave Cobbley 551a4b7ee2SBrad Bishop @staticmethod 561a4b7ee2SBrad Bishop def insert_dep_chain(chain, rdeps, alldeps): 571a4b7ee2SBrad Bishop """ 581a4b7ee2SBrad Bishop insert elements to chain from rdeps, according to alldeps 591a4b7ee2SBrad Bishop """ 601a4b7ee2SBrad Bishop # chain should at least contain one element 611a4b7ee2SBrad Bishop if len(chain) == 0: 621a4b7ee2SBrad Bishop raise 631a4b7ee2SBrad Bishop 641a4b7ee2SBrad Bishop inserted_elements = [] 651a4b7ee2SBrad Bishop for rdep in rdeps: 661a4b7ee2SBrad Bishop if rdep in chain: 671a4b7ee2SBrad Bishop continue 681a4b7ee2SBrad Bishop else: 691a4b7ee2SBrad Bishop for i in range(0, len(chain)-1): 701a4b7ee2SBrad Bishop if chain[i] in alldeps[rdep] and rdep in alldeps[chain[i+1]]: 711a4b7ee2SBrad Bishop chain.insert(i+1, rdep) 721a4b7ee2SBrad Bishop inserted_elements.append(rdep) 731a4b7ee2SBrad Bishop break 741a4b7ee2SBrad Bishop if chain[-1] in alldeps[rdep] and rdep not in chain: 751a4b7ee2SBrad Bishop chain.append(rdep) 761a4b7ee2SBrad Bishop inserted_elements.append(rdep) 771a4b7ee2SBrad Bishop return inserted_elements 781a4b7ee2SBrad Bishop 791a4b7ee2SBrad Bishop @staticmethod 801a4b7ee2SBrad Bishop def print_dep_chains(key, rdeps, alldeps): 811a4b7ee2SBrad Bishop rlist = rdeps.copy() 821a4b7ee2SBrad Bishop chain = [] 831a4b7ee2SBrad Bishop removed_rdeps = [] # hold rdeps removed from rlist 841a4b7ee2SBrad Bishop 851a4b7ee2SBrad Bishop chain.append(key) 861a4b7ee2SBrad Bishop while (len(rlist) != 0): 871a4b7ee2SBrad Bishop # insert chain from rlist 881a4b7ee2SBrad Bishop inserted_elements = Dot.insert_dep_chain(chain, rlist, alldeps) 891a4b7ee2SBrad Bishop if not inserted_elements: 901a4b7ee2SBrad Bishop if chain[-1] in rlist: 911a4b7ee2SBrad Bishop rlist.remove(chain[-1]) 921a4b7ee2SBrad Bishop removed_rdeps.append(chain[-1]) 931a4b7ee2SBrad Bishop chain.pop() 941a4b7ee2SBrad Bishop continue 951a4b7ee2SBrad Bishop else: 961a4b7ee2SBrad Bishop # insert chain from removed_rdeps 971a4b7ee2SBrad Bishop Dot.insert_dep_chain(chain, removed_rdeps, alldeps) 981a4b7ee2SBrad Bishop print(' -> '.join(list(reversed(chain)))) 991a4b7ee2SBrad Bishop 100eb8dc403SDave Cobbley def main(self): 101eb8dc403SDave Cobbley #print(self.args.dotfile[0]) 102eb8dc403SDave Cobbley # The format is {key: depends} 103eb8dc403SDave Cobbley depends = {} 104eb8dc403SDave Cobbley with open(self.args.dotfile[0], 'r') as f: 105eb8dc403SDave Cobbley for line in f.readlines(): 106eb8dc403SDave Cobbley if ' -> ' not in line: 107eb8dc403SDave Cobbley continue 108eb8dc403SDave Cobbley line_no_quotes = line.replace('"', '') 109eb8dc403SDave Cobbley m = re.match("(.*) -> (.*)", line_no_quotes) 110eb8dc403SDave Cobbley if not m: 111eb8dc403SDave Cobbley print('WARNING: Found unexpected line: %s' % line) 112eb8dc403SDave Cobbley continue 113eb8dc403SDave Cobbley key = m.group(1) 114eb8dc403SDave Cobbley if key == "meta-world-pkgdata": 115eb8dc403SDave Cobbley continue 116eb8dc403SDave Cobbley dep = m.group(2) 11778b72798SAndrew Geissler key = key.split('.')[0] 11878b72798SAndrew Geissler dep = dep.split('.')[0] 11978b72798SAndrew Geissler if key == dep: 12078b72798SAndrew Geissler continue 121eb8dc403SDave Cobbley if key in depends: 122eb8dc403SDave Cobbley if not key in depends[key]: 123eb8dc403SDave Cobbley depends[key].add(dep) 124eb8dc403SDave Cobbley else: 125eb8dc403SDave Cobbley print('WARNING: Fonud duplicated line: %s' % line) 126eb8dc403SDave Cobbley else: 127eb8dc403SDave Cobbley depends[key] = set() 128eb8dc403SDave Cobbley depends[key].add(dep) 129eb8dc403SDave Cobbley 130eb8dc403SDave Cobbley if self.args.remove: 131eb8dc403SDave Cobbley reduced_depends = {} 132eb8dc403SDave Cobbley for k, deps in depends.items(): 133eb8dc403SDave Cobbley child_deps = set() 134eb8dc403SDave Cobbley added = set() 135eb8dc403SDave Cobbley # Both direct and indirect depends are already in the dict, so 136eb8dc403SDave Cobbley # we don't have to do this recursively. 137eb8dc403SDave Cobbley for dep in deps: 138eb8dc403SDave Cobbley if dep in depends: 139eb8dc403SDave Cobbley child_deps |= depends[dep] 140eb8dc403SDave Cobbley 141eb8dc403SDave Cobbley reduced_depends[k] = deps - child_deps 142eb8dc403SDave Cobbley outfile= '%s-reduced%s' % (self.args.dotfile[0][:-4], self.args.dotfile[0][-4:]) 143eb8dc403SDave Cobbley with open(outfile, 'w') as f: 144eb8dc403SDave Cobbley print('Saving reduced dot file to %s' % outfile) 145eb8dc403SDave Cobbley f.write('digraph depends {\n') 146eb8dc403SDave Cobbley for k, v in reduced_depends.items(): 147eb8dc403SDave Cobbley for dep in v: 148eb8dc403SDave Cobbley f.write('"%s" -> "%s"\n' % (k, dep)) 149eb8dc403SDave Cobbley f.write('}\n') 150eb8dc403SDave Cobbley sys.exit(0) 151eb8dc403SDave Cobbley 152eb8dc403SDave Cobbley if self.args.key not in depends: 153eb8dc403SDave Cobbley print("ERROR: Can't find key %s in %s" % (self.args.key, self.args.dotfile[0])) 154eb8dc403SDave Cobbley sys.exit(1) 155eb8dc403SDave Cobbley 156eb8dc403SDave Cobbley if self.args.depends: 157eb8dc403SDave Cobbley if self.args.key in depends: 158eb8dc403SDave Cobbley print('Depends: %s' % ' '.join(depends[self.args.key])) 159eb8dc403SDave Cobbley 160eb8dc403SDave Cobbley reverse_deps = [] 161eb8dc403SDave Cobbley if self.args.why: 162*5082cc7fSAndrew Geissler key_list = [self.args.key] 163*5082cc7fSAndrew Geissler current_key = self.args.key 164*5082cc7fSAndrew Geissler while (len(key_list) != 0): 165*5082cc7fSAndrew Geissler current_key = key_list.pop() 166eb8dc403SDave Cobbley for k, v in depends.items(): 167*5082cc7fSAndrew Geissler if current_key in v and not k in reverse_deps: 168eb8dc403SDave Cobbley reverse_deps.append(k) 169*5082cc7fSAndrew Geissler key_list.append(k) 170eb8dc403SDave Cobbley print('Because: %s' % ' '.join(reverse_deps)) 1711a4b7ee2SBrad Bishop Dot.print_dep_chains(self.args.key, reverse_deps, depends) 172eb8dc403SDave Cobbley 173eb8dc403SDave Cobbleyif __name__ == "__main__": 174eb8dc403SDave Cobbley try: 175eb8dc403SDave Cobbley dot = Dot() 176eb8dc403SDave Cobbley ret = dot.main() 177eb8dc403SDave Cobbley except Exception as esc: 178eb8dc403SDave Cobbley ret = 1 179eb8dc403SDave Cobbley import traceback 180eb8dc403SDave Cobbley traceback.print_exc() 181eb8dc403SDave Cobbley sys.exit(ret) 182