| 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 | # | 
|  | 5 | # This program is free software; you can redistribute it and/or modify | 
|  | 6 | # it under the terms of the GNU General Public License version 2 as | 
|  | 7 | # published by the Free Software Foundation. | 
|  | 8 | # | 
|  | 9 | # This program is distributed in the hope that it will be useful, | 
|  | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 
|  | 12 | # See the GNU General Public License for more details. | 
|  | 13 | # | 
|  | 14 | # You should have received a copy of the GNU General Public License | 
|  | 15 | # along with this program; if not, write to the Free Software | 
|  | 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 
|  | 17 |  | 
|  | 18 | import os | 
|  | 19 | import sys | 
|  | 20 | import argparse | 
|  | 21 | import logging | 
|  | 22 | import re | 
|  | 23 |  | 
|  | 24 | class Dot(object): | 
|  | 25 | def __init__(self): | 
|  | 26 | parser = argparse.ArgumentParser( | 
|  | 27 | description="Analyse recipe-depends.dot generated by bitbake -g", | 
|  | 28 | epilog="Use %(prog)s --help to get help") | 
|  | 29 | parser.add_argument("dotfile", | 
|  | 30 | help = "Specify the dotfile", nargs = 1, action='store', default='') | 
|  | 31 | parser.add_argument("-k", "--key", | 
|  | 32 | help = "Specify the key, e.g., recipe name", | 
|  | 33 | action="store", default='') | 
|  | 34 | parser.add_argument("-d", "--depends", | 
|  | 35 | help = "Print the key's dependencies", | 
|  | 36 | action="store_true", default=False) | 
|  | 37 | parser.add_argument("-w", "--why", | 
|  | 38 | help = "Print why the key is built", | 
|  | 39 | action="store_true", default=False) | 
|  | 40 | parser.add_argument("-r", "--remove", | 
|  | 41 | help = "Remove duplicated dependencies to reduce the size of the dot files." | 
|  | 42 | " For example, A->B, B->C, A->C, then A->C can be removed.", | 
|  | 43 | action="store_true", default=False) | 
|  | 44 |  | 
|  | 45 | self.args = parser.parse_args() | 
|  | 46 |  | 
|  | 47 | if len(sys.argv) != 3 and len(sys.argv) < 5: | 
|  | 48 | print('ERROR: Not enough args, see --help for usage') | 
|  | 49 |  | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 50 | @staticmethod | 
|  | 51 | def insert_dep_chain(chain, rdeps, alldeps): | 
|  | 52 | """ | 
|  | 53 | insert elements to chain from rdeps, according to alldeps | 
|  | 54 | """ | 
|  | 55 | # chain should at least contain one element | 
|  | 56 | if len(chain) == 0: | 
|  | 57 | raise | 
|  | 58 |  | 
|  | 59 | inserted_elements = [] | 
|  | 60 | for rdep in rdeps: | 
|  | 61 | if rdep in chain: | 
|  | 62 | continue | 
|  | 63 | else: | 
|  | 64 | for i in range(0, len(chain)-1): | 
|  | 65 | if chain[i] in alldeps[rdep] and rdep in alldeps[chain[i+1]]: | 
|  | 66 | chain.insert(i+1, rdep) | 
|  | 67 | inserted_elements.append(rdep) | 
|  | 68 | break | 
|  | 69 | if chain[-1] in alldeps[rdep] and rdep not in chain: | 
|  | 70 | chain.append(rdep) | 
|  | 71 | inserted_elements.append(rdep) | 
|  | 72 | return inserted_elements | 
|  | 73 |  | 
|  | 74 | @staticmethod | 
|  | 75 | def print_dep_chains(key, rdeps, alldeps): | 
|  | 76 | rlist = rdeps.copy() | 
|  | 77 | chain = [] | 
|  | 78 | removed_rdeps = [] # hold rdeps removed from rlist | 
|  | 79 |  | 
|  | 80 | chain.append(key) | 
|  | 81 | while (len(rlist) != 0): | 
|  | 82 | # insert chain from rlist | 
|  | 83 | inserted_elements = Dot.insert_dep_chain(chain, rlist, alldeps) | 
|  | 84 | if not inserted_elements: | 
|  | 85 | if chain[-1] in rlist: | 
|  | 86 | rlist.remove(chain[-1]) | 
|  | 87 | removed_rdeps.append(chain[-1]) | 
|  | 88 | chain.pop() | 
|  | 89 | continue | 
|  | 90 | else: | 
|  | 91 | # insert chain from removed_rdeps | 
|  | 92 | Dot.insert_dep_chain(chain, removed_rdeps, alldeps) | 
|  | 93 | print(' -> '.join(list(reversed(chain)))) | 
|  | 94 |  | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 95 | def main(self): | 
|  | 96 | #print(self.args.dotfile[0]) | 
|  | 97 | # The format is {key: depends} | 
|  | 98 | depends = {} | 
|  | 99 | with open(self.args.dotfile[0], 'r') as f: | 
|  | 100 | for line in f.readlines(): | 
|  | 101 | if ' -> ' not in line: | 
|  | 102 | continue | 
|  | 103 | line_no_quotes = line.replace('"', '') | 
|  | 104 | m = re.match("(.*) -> (.*)", line_no_quotes) | 
|  | 105 | if not m: | 
|  | 106 | print('WARNING: Found unexpected line: %s' % line) | 
|  | 107 | continue | 
|  | 108 | key = m.group(1) | 
|  | 109 | if key == "meta-world-pkgdata": | 
|  | 110 | continue | 
|  | 111 | dep = m.group(2) | 
|  | 112 | if key in depends: | 
|  | 113 | if not key in depends[key]: | 
|  | 114 | depends[key].add(dep) | 
|  | 115 | else: | 
|  | 116 | print('WARNING: Fonud duplicated line: %s' % line) | 
|  | 117 | else: | 
|  | 118 | depends[key] = set() | 
|  | 119 | depends[key].add(dep) | 
|  | 120 |  | 
|  | 121 | if self.args.remove: | 
|  | 122 | reduced_depends = {} | 
|  | 123 | for k, deps in depends.items(): | 
|  | 124 | child_deps = set() | 
|  | 125 | added = set() | 
|  | 126 | # Both direct and indirect depends are already in the dict, so | 
|  | 127 | # we don't have to do this recursively. | 
|  | 128 | for dep in deps: | 
|  | 129 | if dep in depends: | 
|  | 130 | child_deps |= depends[dep] | 
|  | 131 |  | 
|  | 132 | reduced_depends[k] = deps - child_deps | 
|  | 133 | outfile= '%s-reduced%s' % (self.args.dotfile[0][:-4], self.args.dotfile[0][-4:]) | 
|  | 134 | with open(outfile, 'w') as f: | 
|  | 135 | print('Saving reduced dot file to %s' % outfile) | 
|  | 136 | f.write('digraph depends {\n') | 
|  | 137 | for k, v in reduced_depends.items(): | 
|  | 138 | for dep in v: | 
|  | 139 | f.write('"%s" -> "%s"\n' % (k, dep)) | 
|  | 140 | f.write('}\n') | 
|  | 141 | sys.exit(0) | 
|  | 142 |  | 
|  | 143 | if self.args.key not in depends: | 
|  | 144 | print("ERROR: Can't find key %s in %s" % (self.args.key, self.args.dotfile[0])) | 
|  | 145 | sys.exit(1) | 
|  | 146 |  | 
|  | 147 | if self.args.depends: | 
|  | 148 | if self.args.key in depends: | 
|  | 149 | print('Depends: %s' % ' '.join(depends[self.args.key])) | 
|  | 150 |  | 
|  | 151 | reverse_deps = [] | 
|  | 152 | if self.args.why: | 
|  | 153 | for k, v in depends.items(): | 
|  | 154 | if self.args.key in v and not k in reverse_deps: | 
|  | 155 | reverse_deps.append(k) | 
|  | 156 | print('Because: %s' % ' '.join(reverse_deps)) | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 157 | Dot.print_dep_chains(self.args.key, reverse_deps, depends) | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 158 |  | 
|  | 159 | if __name__ == "__main__": | 
|  | 160 | try: | 
|  | 161 | dot = Dot() | 
|  | 162 | ret = dot.main() | 
|  | 163 | except Exception as esc: | 
|  | 164 | ret = 1 | 
|  | 165 | import traceback | 
|  | 166 | traceback.print_exc() | 
|  | 167 | sys.exit(ret) |