| #!/usr/bin/env python3 | |
| # | |
| # Copyright (C) 2018 Wind River Systems, Inc. | |
| # | |
| # SPDX-License-Identifier: GPL-2.0-only | |
| # | |
| import os | |
| import sys | |
| import argparse | |
| import logging | |
| import re | |
| class Dot(object): | |
| def __init__(self): | |
| parser = argparse.ArgumentParser( | |
| description="Analyse task-depends.dot generated by bitbake -g", | |
| formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument("dotfile", | |
| help = "Dotfile to inspect (default: ./task-depends.dot)", | |
| nargs='?', action='store', default='./task-depends.dot') | |
| parser.add_argument("-k", "--key", | |
| help = "Specify the key, e.g., recipe name", | |
| action="store", default='') | |
| parser.add_argument("-d", "--depends", | |
| help = "Print the key's dependencies", | |
| action="store_true", default=False) | |
| parser.add_argument("-w", "--why", | |
| help = "Print why the key is built", | |
| action="store_true", default=False) | |
| parser.add_argument("-r", "--remove", | |
| help = "Remove duplicated dependencies to reduce the size of the dot files." | |
| " For example, A->B, B->C, A->C, then A->C can be removed.", | |
| action="store_true", default=False) | |
| parser.epilog = """ | |
| Examples: | |
| First generate the .dot file: | |
| bitbake -g core-image-minimal | |
| To find out why a package is being built: | |
| %(prog)s -k <package> -w ./task-depends.dot | |
| To find out what a package depends on: | |
| %(prog)s -k <package> -d ./task-depends.dot | |
| Reduce the .dot file packages only, no tasks: | |
| %(prog)s -r ./task-depends.dot | |
| """ | |
| self.args = parser.parse_args() | |
| if not os.path.exists(self.args.dotfile): | |
| print(f"Dotfile {self.args.dotfile} does not exists. Did you run 'bitbake -g <recipe>'?") | |
| sys.exit(1) | |
| @staticmethod | |
| def insert_dep_chain(chain, rdeps, alldeps): | |
| """ | |
| insert elements to chain from rdeps, according to alldeps | |
| """ | |
| # chain should at least contain one element | |
| if len(chain) == 0: | |
| raise | |
| inserted_elements = [] | |
| for rdep in rdeps: | |
| if rdep in chain: | |
| continue | |
| else: | |
| for i in range(0, len(chain)-1): | |
| if chain[i] in alldeps[rdep] and rdep in alldeps[chain[i+1]]: | |
| chain.insert(i+1, rdep) | |
| inserted_elements.append(rdep) | |
| break | |
| if chain[-1] in alldeps[rdep] and rdep not in chain: | |
| chain.append(rdep) | |
| inserted_elements.append(rdep) | |
| return inserted_elements | |
| @staticmethod | |
| def print_dep_chains(key, rdeps, alldeps): | |
| rlist = rdeps.copy() | |
| chain = [] | |
| removed_rdeps = [] # hold rdeps removed from rlist | |
| chain.append(key) | |
| while (len(rlist) != 0): | |
| # insert chain from rlist | |
| inserted_elements = Dot.insert_dep_chain(chain, rlist, alldeps) | |
| if not inserted_elements: | |
| if chain[-1] in rlist: | |
| rlist.remove(chain[-1]) | |
| removed_rdeps.append(chain[-1]) | |
| chain.pop() | |
| continue | |
| else: | |
| # insert chain from removed_rdeps | |
| Dot.insert_dep_chain(chain, removed_rdeps, alldeps) | |
| print(' -> '.join(list(reversed(chain)))) | |
| def main(self): | |
| #print(self.args.dotfile[0]) | |
| # The format is {key: depends} | |
| depends = {} | |
| with open(self.args.dotfile, 'r') as f: | |
| for line in f.readlines(): | |
| if ' -> ' not in line: | |
| continue | |
| line_no_quotes = line.replace('"', '') | |
| m = re.match("(.*) -> (.*)", line_no_quotes) | |
| if not m: | |
| print('WARNING: Found unexpected line: %s' % line) | |
| continue | |
| key = m.group(1) | |
| if key == "meta-world-pkgdata": | |
| continue | |
| dep = m.group(2) | |
| key = key.rsplit('.', 1)[0] | |
| dep = dep.rsplit('.', 1)[0] | |
| if key == dep: | |
| continue | |
| if key in depends: | |
| if not key in depends[key]: | |
| depends[key].add(dep) | |
| else: | |
| print('WARNING: Found duplicated line: %s' % line) | |
| else: | |
| depends[key] = set() | |
| depends[key].add(dep) | |
| if self.args.remove: | |
| reduced_depends = {} | |
| for k, deps in depends.items(): | |
| child_deps = set() | |
| added = set() | |
| # Both direct and indirect depends are already in the dict, so | |
| # we don't have to do this recursively. | |
| for dep in deps: | |
| if dep in depends: | |
| child_deps |= depends[dep] | |
| reduced_depends[k] = deps - child_deps | |
| outfile= '%s-reduced%s' % (self.args.dotfile[:-4], self.args.dotfile[-4:]) | |
| with open(outfile, 'w') as f: | |
| print('Saving reduced dot file to %s' % outfile) | |
| f.write('digraph depends {\n') | |
| for k, v in reduced_depends.items(): | |
| for dep in v: | |
| f.write('"%s" -> "%s"\n' % (k, dep)) | |
| f.write('}\n') | |
| sys.exit(0) | |
| if self.args.key not in depends: | |
| print("ERROR: Can't find key %s in %s" % (self.args.key, self.args.dotfile)) | |
| sys.exit(1) | |
| if self.args.depends: | |
| if self.args.key in depends: | |
| print('Depends: %s' % ' '.join(depends[self.args.key])) | |
| reverse_deps = [] | |
| if self.args.why: | |
| key_list = [self.args.key] | |
| current_key = self.args.key | |
| while (len(key_list) != 0): | |
| current_key = key_list.pop() | |
| for k, v in depends.items(): | |
| if current_key in v and not k in reverse_deps: | |
| reverse_deps.append(k) | |
| key_list.append(k) | |
| print('Because: %s' % ' '.join(reverse_deps)) | |
| Dot.print_dep_chains(self.args.key, reverse_deps, depends) | |
| if __name__ == "__main__": | |
| try: | |
| dot = Dot() | |
| ret = dot.main() | |
| except Exception as esc: | |
| ret = 1 | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(ret) |