KApiDox

frameworkdb.py
1 # -*- coding: utf-8 -*-
2 #
3 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <[email protected]>
4 #
5 # SPDX-License-Identifier: BSD-2-Clause
6 
7 import logging
8 import fnmatch
9 import os
10 import re
11 
12 import gv
13 import yaml
14 
15 from kapidox.depdiagram import gvutils
16 from kapidox.depdiagram.framework import Framework
17 
18 
19 TARGET_SHAPES = [
20  "polygon", # lib
21  "house", # executable
22  "octagon", # module (aka plugin)
23  "diamond", # static lib
24  ]
25 
26 DEPS_SHAPE = "ellipse"
27 
28 DEPS_BLACKLIST = [
29  "-l*", "-W*", # link flags
30  "/*", # absolute dirs
31  "m", "pthread", "util", "nsl", "resolv", # generic libs
32  "*example*", "*demo*", "*test*", "*Test*", "*debug*" # helper targets
33  ]
34 
35 
36 def preprocess(fname):
37  graph_handle = gv.read(fname)
38  txt = open(fname).read()
39  targets = []
40 
41  # Replace the generated node names with their label. CMake generates a graph
42  # like this:
43  #
44  # "node0" [ label="KF5DNSSD" shape="polygon"];
45  # "node1" [ label="Qt5::Network" shape="ellipse"];
46  # "node0" -> "node1"
47  #
48  # And we turn it into this:
49  #
50  # "KF5DNSSD" [ label="KF5DNSSD" shape="polygon"];
51  # "Qt5::Network" [ label="Qt5::Network" shape="ellipse"];
52  # "KF5DNSSD" -> "Qt5::Network"
53  #
54  # Using real framework names as labels makes it possible to merge multiple
55  # .dot files.
56  for node_handle in gvutils.get_node_list(graph_handle):
57  node = gvutils.Node(node_handle)
58  label = node.label.replace("KF5::", "")
59  if node.shape in TARGET_SHAPES:
60  targets.append(label)
61  txt = txt.replace('"' + node.name + '"', '"' + label + '"')
62 
63  # Sometimes cmake will generate an entry for the target alias, something
64  # like this:
65  #
66  # "node9" [ label="KParts" shape="polygon"];
67  # ...
68  # "node15" [ label="KF5::KParts" shape="ellipse"];
69  # ...
70  #
71  # After our node renaming, this ends up with a second "KParts" node
72  # definition, which we need to get rid of.
73  for target in targets:
74  rx = r' *"' + target + '".*label="KF5::' + target + '".*shape="ellipse".*;'
75  txt = re.sub(rx, '', txt)
76  return txt
77 
78 
79 def _add_extra_dependencies(fw, dct):
80  lst = dct.get("framework-dependencies")
81  if lst is None:
82  return
83  for dep in lst:
84  fw.add_extra_framework(dep)
85 
86 
87 class DotFileParser(object):
88  def __init__(self, with_qt):
89  self._with_qt = with_qt
90 
91  def parse(self, tier, dot_file):
92  name = os.path.basename(dot_file).replace(".dot", "")
93  fw = Framework(tier, name)
94 
95  # Preprocess dot files so that they can be merged together.
96  dot_data = preprocess(dot_file)
97  self._init_fw_from_dot_data(fw, dot_data, self._with_qt)
98 
99  return fw
100 
101  def _init_fw_from_dot_data(self, fw, dot_data, with_qt):
102  def target_from_node(node):
103  return node.name.replace("KF5", "")
104 
105  src_handle = gv.readstring(dot_data)
106 
107  targets = set()
108  for node_handle in gvutils.get_node_list(src_handle):
109  node = gvutils.Node(node_handle)
110  if node.shape in TARGET_SHAPES and self._want(node):
111  target = target_from_node(node)
112  targets.add(target)
113  fw.add_target(target)
114 
115  for edge_handle in gvutils.get_edge_list(src_handle):
116  edge = gvutils.Edge(edge_handle)
117  target = target_from_node(edge.tail)
118  if target in targets and self._want(edge.head):
119  dep_target = target_from_node(edge.head)
120  fw.add_target_dependency(target, dep_target)
121 
122  def _want(self, node):
123  if node.shape not in TARGET_SHAPES and node.shape != DEPS_SHAPE:
124  return False
125  name = node.name
126 
127  for pattern in DEPS_BLACKLIST:
128  if fnmatch.fnmatchcase(node.name, pattern):
129  return False
130  if not self._with_qt and name.startswith("Qt"):
131  return False
132  return True
133 
134 
135 class FrameworkDb(object):
136  def __init__(self):
137  self._fw_list = []
138  self._fw_for_target = {}
139 
140  def populate(self, dot_files, with_qt=False):
141  """
142  Init db from dot files
143  """
144  parser = DotFileParser(with_qt)
145  for dot_file in dot_files:
146  yaml_file = dot_file.replace(".dot", ".yaml")
147  with open(yaml_file) as f:
148  dct = yaml.safe_load(f)
149 
150  if 'tier' not in dct:
151  # This mean it's not a frameworks
152  continue
153 
154  tier = dct["tier"]
155  fw = parser.parse(tier, dot_file)
156 
157  _add_extra_dependencies(fw, dct)
158  self._fw_list.append(fw)
159  self._update_fw_for_target()
160 
161  def _update_fw_for_target(self):
162  self._fw_for_target = {}
163  for fw in self._fw_list:
164  for target in fw.get_targets():
165  self._fw_for_target[target] = fw
166 
167  def find_by_name(self, name):
168  for fw in self._fw_list:
169  if fw.name == name:
170  return fw
171  return None
172 
173  def remove_unused_frameworks(self, wanted_fw):
174  def add_to_set(fw_set, wanted_fw):
175  fw_set.add(wanted_fw)
176 
177  for target in wanted_fw.get_all_target_dependencies():
178  fw = self._fw_for_target.get(target)
179  if fw is not None and not fw in fw_set:
180  add_to_set(fw_set, fw)
181 
182  for fw_name in wanted_fw.get_extra_frameworks():
183  fw = self.find_by_name(fw_name)
184  if not fw:
185  logging.warning("Framework {} has an extra dependency on {}, but there is no such framework".format(wanted_fw, fw_name))
186  continue
187  if not fw in fw_set:
188  add_to_set(fw_set, fw)
189 
190  fw_set = set()
191  add_to_set(fw_set, wanted_fw)
192  self._fw_list = list(fw_set)
193 
194  def find_external_targets(self):
195  all_targets = set([])
196  fw_targets = set([])
197  for fw in self._fw_list:
198  fw_targets.update(fw.get_targets())
199  all_targets.update(fw.get_all_target_dependencies())
200  return all_targets.difference(fw_targets)
201 
202  def get_framework_for_target(self, target):
203  return self._fw_for_target[target]
204 
205  def __iter__(self):
206  return iter(self._fw_list)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Nov 30 2020 22:54:36 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.