dotcode_pack.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2008, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from __future__ import with_statement, print_function
34 
35 import os
36 import re
37 
38 from rospkg import MANIFEST_FILE
39 from rospkg.common import ResourceNotFound
40 from qt_dotgraph.colors import get_color_for_string
41 
42 
43 def matches_any(name, patternlist):
44  for pattern in patternlist:
45  if name == pattern:
46  return True
47  if re.match("^[a-zA-Z0-9_]+$", pattern) is None:
48  if re.match(pattern, name) is not None:
49  return True
50  return False
51 
52 
54 
55  def __init__(self, rospack, rosstack):
56  """
57  :param rospack: use rospkg.RosPack()
58  :param rosstack: use rospkg.RosStack()
59  """
60  self.rospack = rospack
61  self.rosstack = rosstack
62  self.stacks = {}
63  self.packages = {}
64  self.package_types = {}
65  self.edges = {}
68  self.last_drawargs = None
69  self.last_selection = None
70 
71  def generate_dotcode(self,
72  dotcode_factory,
73  selected_names=[],
74  excludes=[],
75  depth=3,
76  with_stacks=True,
77  descendants=True,
78  ancestors=True,
79  hide_transitives=True,
80  show_system=False,
81  mark_selected=True,
82  colortheme=None,
83  rank='same', # None, same, min, max, source, sink
84  ranksep=0.2, # vertical distance between layers
85  rankdir='TB', # direction of layout (TB top > bottom, LR left > right)
86  simplify=True, # remove double edges
87  force_refresh=False,
88  hide_wet=False,
89  hide_dry=False):
90  """
91 
92  :param hide_transitives:
93  if true, then dependency of children to grandchildren will be hidden if parent has
94  same dependency
95  :param show_system: if true, then system dependencies will be shown
96  """
97 
98  # defaults
99  selected_names = filter(lambda x: x is not None and x != '', selected_names)
100  excludes = filter(lambda x: x is not None and x != '', excludes)
101  if selected_names is None or selected_names == []:
102  selected_names = ['.*']
103  self.depth = 1
104  if depth is None:
105  depth = -1
106 
107  # update arguments
108 
109  selection_args = {
110  "dotcode_factory": dotcode_factory,
111  "with_stacks": with_stacks,
112  "depth": depth,
113  "hide_transitives": hide_transitives,
114  "show_system": show_system,
115  "selected_names": selected_names,
116  "excludes": excludes,
117  "ancestors": ancestors,
118  "descendants": descendants,
119  "hide_wet": hide_wet,
120  "hide_dry": hide_dry
121  }
122 
123  # if selection did not change, we need not build up the graph again
124  selection_changed = False
125  if self.last_selection != selection_args:
126  selection_changed = True
127  self.last_selection = selection_args
128 
129  self.dotcode_factory = dotcode_factory
130  self.with_stacks = with_stacks
131  self.depth = depth
132  self.hide_transitives = hide_transitives
133  self.show_system = show_system
134  self.selected_names = selected_names
135  self.excludes = excludes
136  self.ancestors = ancestors
137  self.descendants = descendants
138  self.hide_wet = hide_wet
139  self.hide_dry = hide_dry
140 
141  if force_refresh or selection_changed:
142  self.stacks = {}
143  self.packages = {}
144  self.package_types = {}
145  self.edges = {}
146  self.traversed_ancestors = {}
147  self.traversed_descendants = {}
148  # update internal graph structure
149  for name in self.rospack.list():
150  if matches_any(name, self.selected_names):
151  if descendants:
153  if ancestors:
155  for stackname in self.rosstack.list():
156  if matches_any(stackname, self.selected_names):
157  manifest = self.rosstack.get_manifest(stackname)
158  if manifest.is_catkin:
159  if descendants:
161  if ancestors:
162  self.add_package_ancestors_recursively(stackname)
163  else:
164  for package_name in self.rosstack.packages_of(stackname):
165  if descendants:
166  self.add_package_descendants_recursively(package_name)
167  if ancestors:
168  self.add_package_ancestors_recursively(package_name)
169 
170  drawing_args = {
171  'dotcode_factory': dotcode_factory,
172  "rank": rank,
173  "rankdir": rankdir,
174  "ranksep": ranksep,
175  "simplify": simplify,
176  "colortheme": colortheme,
177  "mark_selected": mark_selected
178  }
179 
180  # if selection and display args did not change, no need to generate dotcode
181  display_changed = False
182  if self.last_drawargs != drawing_args:
183  display_changed = True
184  self.last_drawargs = drawing_args
185 
186  self.dotcode_factory = dotcode_factory
187  self.rank = rank
188  self.rankdir = rankdir
189  self.ranksep = ranksep
190  self.simplify = simplify
191  self.colortheme = colortheme
192  self.dotcode_factory = dotcode_factory
193  self.mark_selected = mark_selected
194 
195  # generate new dotcode
196  if force_refresh or selection_changed or display_changed:
197  self.graph = self.generate(self.dotcode_factory)
198  self.dotcode = dotcode_factory.create_dot(self.graph)
199 
200  return self.dotcode
201 
202  def generate(self, dotcode_factory):
203  graph = dotcode_factory.get_graph(rank=self.rank,
204  rankdir=self.rankdir,
205  ranksep=self.ranksep,
206  simplify=self.simplify)
207  packages_in_stacks = []
208  if self.with_stacks and not self.hide_dry:
209  for stackname in self.stacks:
210  color = None
211  if self.mark_selected and \
212  '.*' not in self.selected_names and \
213  matches_any(stackname, self.selected_names):
214  color = 'tomato'
215  else:
216  color = 'gray'
217  if self.colortheme is not None:
218  color = get_color_for_string(stackname)
219  g = dotcode_factory.add_subgraph_to_graph(graph,
220  stackname,
221  color=color,
222  rank=self.rank,
223  rankdir=self.rankdir,
224  ranksep=self.ranksep,
225  simplify=self.simplify)
226 
227  for package_name in self.stacks[stackname]['packages']:
228  packages_in_stacks.append(package_name)
229  self._generate_package(dotcode_factory, g, package_name)
230 
231  for package_name, attributes in self.packages.items():
232  if package_name not in packages_in_stacks:
233  self._generate_package(dotcode_factory, graph, package_name, attributes)
234  for name1, name2 in self.edges.keys():
235  dotcode_factory.add_edge_to_graph(graph, name1, name2)
236  return graph
237 
238  def _generate_package(self, dotcode_factory, graph, package_name, attributes=None):
239  if self._hide_package(package_name):
240  return
241  color = None
242  if self.mark_selected and \
243  '.*' not in self.selected_names and \
244  matches_any(package_name, self.selected_names):
245  if attributes and attributes['is_catkin']:
246  color = 'red'
247  else:
248  color = 'tomato'
249  elif attributes and not attributes['is_catkin']:
250  color = 'gray'
251  if attributes and 'not_found' in attributes and attributes['not_found']:
252  color = 'orange'
253  package_name += ' ?'
254  dotcode_factory.add_node_to_graph(graph, package_name, color=color)
255 
256  def _add_stack(self, stackname):
257  if stackname is None or stackname in self.stacks:
258  return
259  self.stacks[stackname] = {'packages': []}
260 
261  def _add_package(self, package_name, parent=None):
262  """
263  adds object based on package_name to self.packages
264  :param parent: packagename which referenced package_name (for debugging only)
265  """
266  if self._hide_package(package_name):
267  return
268  if package_name in self.packages:
269  return False
270 
271  catkin_package = self._is_package_wet(package_name)
272  if catkin_package is None:
273  return False
274  self.packages[package_name] = {'is_catkin': catkin_package}
275 
276  if self.with_stacks:
277  try:
278  stackname = self.rospack.stack_of(package_name)
279  except ResourceNotFound as e:
280  print(
281  'RosPackageGraphDotcodeGenerator._add_package(%s), '
282  'parent %s: ResourceNotFound:' % (package_name, parent), e)
283  stackname = None
284  if not stackname is None and stackname != '':
285  if not stackname in self.stacks:
286  self._add_stack(stackname)
287  self.stacks[stackname]['packages'].append(package_name)
288  return True
289 
290  def _hide_package(self, package_name):
291  if not self.hide_wet and not self.hide_dry:
292  return False
293  catkin_package = self._is_package_wet(package_name)
294  if self.hide_wet and catkin_package:
295  return True
296  if self.hide_dry and catkin_package is False:
297  return True
298  # if type of package is unknown don't hide it
299  return False
300 
301  def _is_package_wet(self, package_name):
302  if package_name not in self.package_types:
303  try:
304  package_path = self.rospack.get_path(package_name)
305  manifest_file = os.path.join(package_path, MANIFEST_FILE)
306  self.package_types[package_name] = not os.path.exists(manifest_file)
307  except ResourceNotFound:
308  return None
309  return self.package_types[package_name]
310 
311  def _add_edge(self, name1, name2, attributes=None):
312  if self._hide_package(name1) or self._hide_package(name2):
313  return
314  self.edges[(name1, name2)] = attributes
315 
317  self, package_name, expanded_up=None,
318  depth=None, implicit=False, parent=None):
319  """
320  :param package_name: the name of package for which to add ancestors
321  :param expanded_up: names that have already been expanded (to avoid cycles)
322  :param depth: how many layers to follow
323  :param implicit: arg to rospack
324  :param parent: package that referenced package_name for error message only
325  """
326  if package_name in self.traversed_ancestors:
327  traversed_depth = self.traversed_ancestors[package_name]
328  if traversed_depth is None:
329  return
330  if depth is not None and traversed_depth >= depth:
331  return
332  self.traversed_ancestors[package_name] = depth
333 
334  if matches_any(package_name, self.excludes):
335  return False
336  if (depth == 0):
337  return False
338  if (depth == None):
339  depth = self.depth
340  self._add_package(package_name, parent=parent)
341  if expanded_up is None:
342  expanded_up = []
343  expanded_up.append(package_name)
344  if (depth != 1):
345  try:
346  depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
347  except ResourceNotFound as e:
348  print(
349  'RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s),'
350  ' parent %s: ResourceNotFound:' % (package_name, parent), e)
351  depends_on = []
352  new_nodes = []
353  for dep_on_name in [x for x in depends_on if not matches_any(x, self.excludes)]:
354  if not self.hide_transitives or not dep_on_name in expanded_up:
355  new_nodes.append(dep_on_name)
356  self._add_edge(dep_on_name, package_name)
357  self._add_package(dep_on_name, parent=package_name)
358  expanded_up.append(dep_on_name)
359  for dep_on_name in new_nodes:
360  self.add_package_ancestors_recursively(package_name=dep_on_name,
361  expanded_up=expanded_up,
362  depth=depth - 1,
363  implicit=implicit,
364  parent=package_name)
365 
367  self, package_name, expanded=None,
368  depth=None, implicit=False, parent=None):
369  if package_name in self.traversed_descendants:
370  traversed_depth = self.traversed_descendants[package_name]
371  if traversed_depth is None:
372  return
373  if depth is not None and traversed_depth >= depth:
374  return
375  self.traversed_descendants[package_name] = depth
376 
377  if matches_any(package_name, self.excludes):
378  return
379  if (depth == 0):
380  return
381  if (depth == None):
382  depth = self.depth
383  self._add_package(package_name, parent=parent)
384  if expanded is None:
385  expanded = []
386  expanded.append(package_name)
387  if (depth != 1):
388  try:
389  try:
390  depends = self.rospack.get_depends(package_name, implicit=implicit)
391  except ResourceNotFound:
392  # try falling back to rosstack to find wet metapackages
393  manifest = self.rosstack.get_manifest(package_name)
394  if manifest.is_catkin:
395  depends = [d.name for d in manifest.depends]
396  else:
397  raise
398  except ResourceNotFound as e:
399  print(
400  'RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), '
401  'parent: %s: ResourceNotFound:' % (package_name, parent), e)
402  depends = []
403  # get system dependencies without recursion
404  if self.show_system:
405  rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
406  for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
407  if not self.hide_transitives or not dep_name in expanded:
408  self._add_edge(package_name, dep_name)
409  self._add_package(dep_name, parent=package_name)
410  expanded.append(dep_name)
411  new_nodes = []
412  for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
413  if not self.hide_transitives or not dep_name in expanded:
414  new_nodes.append(dep_name)
415  self._add_edge(package_name, dep_name)
416  self._add_package(dep_name, parent=package_name)
417  expanded.append(dep_name)
418  for dep_name in new_nodes:
419  self.add_package_descendants_recursively(package_name=dep_name,
420  expanded=expanded,
421  depth=depth - 1,
422  implicit=implicit,
423  parent=package_name)
def _add_edge(self, name1, name2, attributes=None)
def _generate_package(self, dotcode_factory, graph, package_name, attributes=None)
def generate_dotcode(self, dotcode_factory, selected_names=[], excludes=[], depth=3, with_stacks=True, descendants=True, ancestors=True, hide_transitives=True, show_system=False, mark_selected=True, colortheme=None, rank='same', ranksep=0.2, rankdir='TB', simplify=True, force_refresh=False, hide_wet=False, hide_dry=False)
Definition: dotcode_pack.py:89
def add_package_ancestors_recursively(self, package_name, expanded_up=None, depth=None, implicit=False, parent=None)
def _add_package(self, package_name, parent=None)
def matches_any(name, patternlist)
Definition: dotcode_pack.py:43
def add_package_descendants_recursively(self, package_name, expanded=None, depth=None, implicit=False, parent=None)


rqt_dep
Author(s): Thibault Kruse
autogenerated on Wed Jun 5 2019 21:25:12