| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 | 
|  | 2 | # | 
|  | 3 | # Copyright (C) 2018 Wind River Systems, Inc. | 
|  | 4 | # | 
| Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 5 | # SPDX-License-Identifier: GPL-2.0-only | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 6 | # | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 7 |  | 
|  | 8 | import os | 
|  | 9 | import sys | 
|  | 10 | import argparse | 
|  | 11 | import logging | 
|  | 12 | import re | 
|  | 13 |  | 
|  | 14 | class Dot(object): | 
|  | 15 | def __init__(self): | 
|  | 16 | parser = argparse.ArgumentParser( | 
|  | 17 | description="Analyse recipe-depends.dot generated by bitbake -g", | 
| Andrew Geissler | 78b7279 | 2022-06-14 06:47:25 -0500 | [diff] [blame^] | 18 | formatter_class=argparse.RawDescriptionHelpFormatter) | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 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 |  | 
| Andrew Geissler | 78b7279 | 2022-06-14 06:47:25 -0500 | [diff] [blame^] | 35 | parser.epilog = """ | 
|  | 36 | Examples: | 
|  | 37 | First generate the .dot file: | 
|  | 38 | bitbake -g core-image-minimal | 
|  | 39 |  | 
|  | 40 | To find out why a package is being built: | 
|  | 41 | %(prog)s -k <package> -w ./task-depends.dot | 
|  | 42 |  | 
|  | 43 | To find out what a package depends on: | 
|  | 44 | %(prog)s -k <package> -d ./task-depends.dot | 
|  | 45 |  | 
|  | 46 | Reduce the .dot file packages only, no tasks: | 
|  | 47 | %(prog)s -r ./task-depends.dot | 
|  | 48 | """ | 
|  | 49 |  | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 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 |  | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 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 |  | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 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) | 
| Andrew Geissler | 78b7279 | 2022-06-14 06:47:25 -0500 | [diff] [blame^] | 117 | key = key.split('.')[0] | 
|  | 118 | dep = dep.split('.')[0] | 
|  | 119 | if key == dep: | 
|  | 120 | continue | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 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 | for k, v in depends.items(): | 
|  | 163 | if self.args.key in v and not k in reverse_deps: | 
|  | 164 | reverse_deps.append(k) | 
|  | 165 | print('Because: %s' % ' '.join(reverse_deps)) | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 166 | Dot.print_dep_chains(self.args.key, reverse_deps, depends) | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 167 |  | 
|  | 168 | if __name__ == "__main__": | 
|  | 169 | try: | 
|  | 170 | dot = Dot() | 
|  | 171 | ret = dot.main() | 
|  | 172 | except Exception as esc: | 
|  | 173 | ret = 1 | 
|  | 174 | import traceback | 
|  | 175 | traceback.print_exc() | 
|  | 176 | sys.exit(ret) |