File indexing completed on 2024-05-12 03:53:40
0001 # -*- coding: utf-8 -*- 0002 # 0003 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org> 0004 # 0005 # SPDX-License-Identifier: BSD-2-Clause 0006 0007 import logging 0008 import fnmatch 0009 import os 0010 import re 0011 0012 import gv 0013 import yaml 0014 0015 from kapidox.depdiagram import gvutils 0016 from kapidox.depdiagram.framework import Framework 0017 0018 0019 TARGET_SHAPES = [ 0020 "polygon", # lib 0021 "house", # executable 0022 "octagon", # module (aka plugin) 0023 "diamond", # static lib 0024 ] 0025 0026 DEPS_SHAPE = "ellipse" 0027 0028 DEPS_BLACKLIST = [ 0029 "-l*", "-W*", # link flags 0030 "/*", # absolute dirs 0031 "m", "pthread", "util", "nsl", "resolv", # generic libs 0032 "*example*", "*demo*", "*test*", "*Test*", "*debug*" # helper targets 0033 ] 0034 0035 0036 def preprocess(fname): 0037 graph_handle = gv.read(fname) 0038 txt = open(fname).read() 0039 targets = [] 0040 0041 # Replace the generated node names with their label. CMake generates a graph 0042 # like this: 0043 # 0044 # "node0" [ label="KF5DNSSD" shape="polygon"]; 0045 # "node1" [ label="Qt5::Network" shape="ellipse"]; 0046 # "node0" -> "node1" 0047 # 0048 # And we turn it into this: 0049 # 0050 # "KF5DNSSD" [ label="KF5DNSSD" shape="polygon"]; 0051 # "Qt5::Network" [ label="Qt5::Network" shape="ellipse"]; 0052 # "KF5DNSSD" -> "Qt5::Network" 0053 # 0054 # Using real framework names as labels makes it possible to merge multiple 0055 # .dot files. 0056 for node_handle in gvutils.get_node_list(graph_handle): 0057 node = gvutils.Node(node_handle) 0058 label = node.label.replace("KF5::", "") 0059 if node.shape in TARGET_SHAPES: 0060 targets.append(label) 0061 txt = txt.replace('"' + node.name + '"', '"' + label + '"') 0062 0063 # Sometimes cmake will generate an entry for the target alias, something 0064 # like this: 0065 # 0066 # "node9" [ label="KParts" shape="polygon"]; 0067 # ... 0068 # "node15" [ label="KF5::KParts" shape="ellipse"]; 0069 # ... 0070 # 0071 # After our node renaming, this ends up with a second "KParts" node 0072 # definition, which we need to get rid of. 0073 for target in targets: 0074 rx = r' *"' + target + '".*label="KF5::' + target + '".*shape="ellipse".*;' 0075 txt = re.sub(rx, '', txt) 0076 return txt 0077 0078 0079 def _add_extra_dependencies(fw, dct): 0080 lst = dct.get("framework-dependencies") 0081 if lst is None: 0082 return 0083 for dep in lst: 0084 fw.add_extra_framework(dep) 0085 0086 0087 class DotFileParser(object): 0088 def __init__(self, with_qt): 0089 self._with_qt = with_qt 0090 0091 def parse(self, tier, dot_file): 0092 name = os.path.basename(dot_file).replace(".dot", "") 0093 fw = Framework(tier, name) 0094 0095 # Preprocess dot files so that they can be merged together. 0096 dot_data = preprocess(dot_file) 0097 self._init_fw_from_dot_data(fw, dot_data, self._with_qt) 0098 0099 return fw 0100 0101 def _init_fw_from_dot_data(self, fw, dot_data, with_qt): 0102 def target_from_node(node): 0103 return node.name.replace("KF5", "") 0104 0105 src_handle = gv.readstring(dot_data) 0106 0107 targets = set() 0108 for node_handle in gvutils.get_node_list(src_handle): 0109 node = gvutils.Node(node_handle) 0110 if node.shape in TARGET_SHAPES and self._want(node): 0111 target = target_from_node(node) 0112 targets.add(target) 0113 fw.add_target(target) 0114 0115 for edge_handle in gvutils.get_edge_list(src_handle): 0116 edge = gvutils.Edge(edge_handle) 0117 target = target_from_node(edge.tail) 0118 if target in targets and self._want(edge.head): 0119 dep_target = target_from_node(edge.head) 0120 fw.add_target_dependency(target, dep_target) 0121 0122 def _want(self, node): 0123 if node.shape not in TARGET_SHAPES and node.shape != DEPS_SHAPE: 0124 return False 0125 name = node.name 0126 0127 for pattern in DEPS_BLACKLIST: 0128 if fnmatch.fnmatchcase(node.name, pattern): 0129 return False 0130 if not self._with_qt and name.startswith("Qt"): 0131 return False 0132 return True 0133 0134 0135 class FrameworkDb(object): 0136 def __init__(self): 0137 self._fw_list = [] 0138 self._fw_for_target = {} 0139 0140 def populate(self, dot_files, with_qt=False): 0141 """ 0142 Init db from dot files 0143 """ 0144 parser = DotFileParser(with_qt) 0145 for dot_file in dot_files: 0146 yaml_file = dot_file.replace(".dot", ".yaml") 0147 with open(yaml_file) as f: 0148 dct = yaml.safe_load(f) 0149 0150 if 'tier' not in dct: 0151 # This mean it's not a frameworks 0152 continue 0153 0154 tier = dct["tier"] 0155 fw = parser.parse(tier, dot_file) 0156 0157 _add_extra_dependencies(fw, dct) 0158 self._fw_list.append(fw) 0159 self._update_fw_for_target() 0160 0161 def _update_fw_for_target(self): 0162 self._fw_for_target = {} 0163 for fw in self._fw_list: 0164 for target in fw.get_targets(): 0165 self._fw_for_target[target] = fw 0166 0167 def find_by_name(self, name): 0168 for fw in self._fw_list: 0169 if fw.name == name: 0170 return fw 0171 return None 0172 0173 def remove_unused_frameworks(self, wanted_fw): 0174 def add_to_set(fw_set, wanted_fw): 0175 fw_set.add(wanted_fw) 0176 0177 for target in wanted_fw.get_all_target_dependencies(): 0178 fw = self._fw_for_target.get(target) 0179 if fw is not None and not fw in fw_set: 0180 add_to_set(fw_set, fw) 0181 0182 for fw_name in wanted_fw.get_extra_frameworks(): 0183 fw = self.find_by_name(fw_name) 0184 if not fw: 0185 logging.warning("Framework {} has an extra dependency on {}, but there is no such framework".format(wanted_fw, fw_name)) 0186 continue 0187 if not fw in fw_set: 0188 add_to_set(fw_set, fw) 0189 0190 fw_set = set() 0191 add_to_set(fw_set, wanted_fw) 0192 self._fw_list = list(fw_set) 0193 0194 def find_external_targets(self): 0195 all_targets = set([]) 0196 fw_targets = set([]) 0197 for fw in self._fw_list: 0198 fw_targets.update(fw.get_targets()) 0199 all_targets.update(fw.get_all_target_dependencies()) 0200 return all_targets.difference(fw_targets) 0201 0202 def get_framework_for_target(self, target): 0203 return self._fw_for_target[target] 0204 0205 def __iter__(self): 0206 return iter(self._fw_list)