diff --git a/src/kapidox/depdiagram/frameworkdb.py b/src/kapidox/depdiagram/frameworkdb.py index 7e4dafd..76b05fd 100644 --- a/src/kapidox/depdiagram/frameworkdb.py +++ b/src/kapidox/depdiagram/frameworkdb.py @@ -1,226 +1,226 @@ # -*- coding: utf-8 -*- # # Copyright 2014 Aurélien Gâteau # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging import fnmatch import os import re import gv import yaml -import kapidox.depdiagram.gvutils +from kapidox.depdiagram import gvutils from kapidox.depdiagram.framework import Framework TARGET_SHAPES = [ "polygon", # lib "house", # executable "octagon", # module (aka plugin) "diamond", # static lib ] DEPS_SHAPE = "ellipse" DEPS_BLACKLIST = [ "-l*", "-W*", # link flags "/*", # absolute dirs "m", "pthread", "util", "nsl", "resolv", # generic libs "*example*", "*demo*", "*test*", "*Test*", "*debug*" # helper targets ] def preprocess(fname): graph_handle = gv.read(fname) txt = open(fname).read() targets = [] # Replace the generated node names with their label. CMake generates a graph # like this: # # "node0" [ label="KF5DNSSD" shape="polygon"]; # "node1" [ label="Qt5::Network" shape="ellipse"]; # "node0" -> "node1" # # And we turn it into this: # # "KF5DNSSD" [ label="KF5DNSSD" shape="polygon"]; # "Qt5::Network" [ label="Qt5::Network" shape="ellipse"]; # "KF5DNSSD" -> "Qt5::Network" # # Using real framework names as labels makes it possible to merge multiple # .dot files. for node_handle in gvutils.get_node_list(graph_handle): node = gvutils.Node(node_handle) label = node.label.replace("KF5::", "") if node.shape in TARGET_SHAPES: targets.append(label) txt = txt.replace('"' + node.name + '"', '"' + label + '"') # Sometimes cmake will generate an entry for the target alias, something # like this: # # "node9" [ label="KParts" shape="polygon"]; # ... # "node15" [ label="KF5::KParts" shape="ellipse"]; # ... # # After our node renaming, this ends up with a second "KParts" node # definition, which we need to get rid of. for target in targets: rx = r' *"' + target + '".*label="KF5::' + target + '".*shape="ellipse".*;' txt = re.sub(rx, '', txt) return txt def _add_extra_dependencies(fw, dct): lst = dct.get("framework-dependencies") if lst is None: return for dep in lst: fw.add_extra_framework(dep) class DotFileParser(object): def __init__(self, with_qt): self._with_qt = with_qt def parse(self, tier, dot_file): name = os.path.basename(dot_file).replace(".dot", "") fw = Framework(tier, name) # Preprocess dot files so that they can be merged together. dot_data = preprocess(dot_file) self._init_fw_from_dot_data(fw, dot_data, self._with_qt) return fw def _init_fw_from_dot_data(self, fw, dot_data, with_qt): def target_from_node(node): return node.name.replace("KF5", "") src_handle = gv.readstring(dot_data) targets = set() for node_handle in gvutils.get_node_list(src_handle): node = gvutils.Node(node_handle) if node.shape in TARGET_SHAPES and self._want(node): target = target_from_node(node) targets.add(target) fw.add_target(target) for edge_handle in gvutils.get_edge_list(src_handle): edge = gvutils.Edge(edge_handle) target = target_from_node(edge.tail) if target in targets and self._want(edge.head): dep_target = target_from_node(edge.head) fw.add_target_dependency(target, dep_target) def _want(self, node): if node.shape not in TARGET_SHAPES and node.shape != DEPS_SHAPE: return False name = node.name for pattern in DEPS_BLACKLIST: if fnmatch.fnmatchcase(node.name, pattern): return False if not self._with_qt and name.startswith("Qt"): return False return True class FrameworkDb(object): def __init__(self): self._fw_list = [] self._fw_for_target = {} def populate(self, dot_files, with_qt=False): """ Init db from dot files """ parser = DotFileParser(with_qt) for dot_file in dot_files: yaml_file = dot_file.replace(".dot", ".yaml") with open(yaml_file) as f: dct = yaml.load(f) if 'tier' not in dct: # This mean it's not a frameworks continue tier = dct["tier"] dot_file = dot_file.encode("utf-8") fw = parser.parse(tier, dot_file) _add_extra_dependencies(fw, dct) self._fw_list.append(fw) self._update_fw_for_target() def _update_fw_for_target(self): self._fw_for_target = {} for fw in self._fw_list: for target in fw.get_targets(): self._fw_for_target[target] = fw def find_by_name(self, name): for fw in self._fw_list: if fw.name == name: return fw return None def remove_unused_frameworks(self, wanted_fw): def add_to_set(fw_set, wanted_fw): fw_set.add(wanted_fw) for target in wanted_fw.get_all_target_dependencies(): fw = self._fw_for_target.get(target) if fw is not None and not fw in fw_set: add_to_set(fw_set, fw) for fw_name in wanted_fw.get_extra_frameworks(): fw = self.find_by_name(fw_name) if not fw: logging.warning("Framework {} has an extra dependency on {}, but there is no such framework".format(wanted_fw, fw_name)) continue if not fw in fw_set: add_to_set(fw_set, fw) fw_set = set() add_to_set(fw_set, wanted_fw) self._fw_list = list(fw_set) def find_external_targets(self): all_targets = set([]) fw_targets = set([]) for fw in self._fw_list: fw_targets.update(fw.get_targets()) all_targets.update(fw.get_all_target_dependencies()) return all_targets.difference(fw_targets) def get_framework_for_target(self, target): return self._fw_for_target[target] def __iter__(self): return iter(self._fw_list)