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