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=None,
74  excludes=None,
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  if selected_names is None:
99  selected_names = []
100  if excludes is None:
101  excludes = []
102  # defaults
103  selected_names = [x for x in selected_names if x is not None and x != '']
104  excludes = [x for x in excludes if x is not None and x != '']
105  if not selected_names:
106  selected_names = ['.*']
107  self.depth = 1
108  if depth is None:
109  depth = -1
110 
111  # update arguments
112 
113  selection_args = {
114  "dotcode_factory": dotcode_factory,
115  "with_stacks": with_stacks,
116  "depth": depth,
117  "hide_transitives": hide_transitives,
118  "show_system": show_system,
119  "selected_names": selected_names,
120  "excludes": excludes,
121  "ancestors": ancestors,
122  "descendants": descendants,
123  "hide_wet": hide_wet,
124  "hide_dry": hide_dry
125  }
126 
127  # if selection did not change, we need not build up the graph again
128  selection_changed = False
129  if self.last_selection != selection_args:
130  selection_changed = True
131  self.last_selection = selection_args
132 
133  self.dotcode_factory = dotcode_factory
134  self.with_stacks = with_stacks
135  self.depth = depth
136  self.hide_transitives = hide_transitives
137  self.show_system = show_system
138  self.selected_names = selected_names
139  self.excludes = excludes
140  self.ancestors = ancestors
141  self.descendants = descendants
142  self.hide_wet = hide_wet
143  self.hide_dry = hide_dry
144 
145  if force_refresh or selection_changed:
146  self.stacks = {}
147  self.packages = {}
148  self.package_types = {}
149  self.edges = {}
150  self.traversed_ancestors = {}
151  self.traversed_descendants = {}
152  # update internal graph structure
153  for name in self.rospack.list():
154  if matches_any(name, self.selected_names):
155  if descendants:
157  if ancestors:
159  for stackname in self.rosstack.list():
160  if matches_any(stackname, self.selected_names):
161  manifest = self.rosstack.get_manifest(stackname)
162  if manifest.is_catkin:
163  if descendants:
165  if ancestors:
166  self.add_package_ancestors_recursively(stackname)
167  else:
168  for package_name in self.rosstack.packages_of(stackname):
169  if descendants:
170  self.add_package_descendants_recursively(package_name)
171  if ancestors:
172  self.add_package_ancestors_recursively(package_name)
173 
174  drawing_args = {
175  'dotcode_factory': dotcode_factory,
176  "rank": rank,
177  "rankdir": rankdir,
178  "ranksep": ranksep,
179  "simplify": simplify,
180  "colortheme": colortheme,
181  "mark_selected": mark_selected
182  }
183 
184  # if selection and display args did not change, no need to generate dotcode
185  display_changed = False
186  if self.last_drawargs != drawing_args:
187  display_changed = True
188  self.last_drawargs = drawing_args
189 
190  self.dotcode_factory = dotcode_factory
191  self.rank = rank
192  self.rankdir = rankdir
193  self.ranksep = ranksep
194  self.simplify = simplify
195  self.colortheme = colortheme
196  self.dotcode_factory = dotcode_factory
197  self.mark_selected = mark_selected
198 
199  # generate new dotcode
200  if force_refresh or selection_changed or display_changed:
201  self.graph = self.generate(self.dotcode_factory)
202  self.dotcode = dotcode_factory.create_dot(self.graph)
203 
204  return self.dotcode
205 
206  def generate(self, dotcode_factory):
207  graph = dotcode_factory.get_graph(rank=self.rank,
208  rankdir=self.rankdir,
209  ranksep=self.ranksep,
210  simplify=self.simplify)
211  packages_in_stacks = []
212  if self.with_stacks and not self.hide_dry:
213  for stackname in self.stacks:
214  color = None
215  if self.mark_selected and \
216  '.*' not in self.selected_names and \
217  matches_any(stackname, self.selected_names):
218  color = 'tomato'
219  else:
220  color = 'gray'
221  if self.colortheme is not None:
222  color = get_color_for_string(stackname)
223  g = dotcode_factory.add_subgraph_to_graph(graph,
224  stackname,
225  color=color,
226  rank=self.rank,
227  rankdir=self.rankdir,
228  ranksep=self.ranksep,
229  simplify=self.simplify)
230 
231  for package_name in self.stacks[stackname]['packages']:
232  packages_in_stacks.append(package_name)
233  self._generate_package(dotcode_factory, g, package_name)
234 
235  for package_name, attributes in self.packages.items():
236  if package_name not in packages_in_stacks:
237  self._generate_package(dotcode_factory, graph, package_name, attributes)
238  for name1, name2 in self.edges.keys():
239  dotcode_factory.add_edge_to_graph(graph, name1, name2)
240  return graph
241 
242  def _generate_package(self, dotcode_factory, graph, package_name, attributes=None):
243  if self._hide_package(package_name):
244  return
245  color = None
246  if self.mark_selected and \
247  '.*' not in self.selected_names and \
248  matches_any(package_name, self.selected_names):
249  if attributes and attributes['is_catkin']:
250  color = 'red'
251  else:
252  color = 'tomato'
253  elif attributes and not attributes['is_catkin']:
254  color = 'gray'
255  if attributes and 'not_found' in attributes and attributes['not_found']:
256  color = 'orange'
257  package_name += ' ?'
258  dotcode_factory.add_node_to_graph(graph, package_name, color=color)
259 
260  def _add_stack(self, stackname):
261  if stackname is None or stackname in self.stacks:
262  return
263  self.stacks[stackname] = {'packages': []}
264 
265  def _add_package(self, package_name, parent=None):
266  """
267  adds object based on package_name to self.packages
268  :param parent: packagename which referenced package_name (for debugging only)
269  """
270  if self._hide_package(package_name):
271  return
272  if package_name in self.packages:
273  return False
274 
275  catkin_package = self._is_package_wet(package_name)
276  if catkin_package is None:
277  return False
278  self.packages[package_name] = {'is_catkin': catkin_package}
279 
280  if self.with_stacks:
281  try:
282  stackname = self.rospack.stack_of(package_name)
283  except ResourceNotFound as e:
284  print(
285  'RosPackageGraphDotcodeGenerator._add_package(%s), '
286  'parent %s: ResourceNotFound:' % (package_name, parent), e)
287  stackname = None
288  if not stackname is None and stackname != '':
289  if not stackname in self.stacks:
290  self._add_stack(stackname)
291  self.stacks[stackname]['packages'].append(package_name)
292  return True
293 
294  def _hide_package(self, package_name):
295  if not self.hide_wet and not self.hide_dry:
296  return False
297  catkin_package = self._is_package_wet(package_name)
298  if self.hide_wet and catkin_package:
299  return True
300  if self.hide_dry and catkin_package is False:
301  return True
302  # if type of package is unknown don't hide it
303  return False
304 
305  def _is_package_wet(self, package_name):
306  if package_name not in self.package_types:
307  try:
308  package_path = self.rospack.get_path(package_name)
309  manifest_file = os.path.join(package_path, MANIFEST_FILE)
310  self.package_types[package_name] = not os.path.exists(manifest_file)
311  except ResourceNotFound:
312  return None
313  return self.package_types[package_name]
314 
315  def _add_edge(self, name1, name2, attributes=None):
316  if self._hide_package(name1) or self._hide_package(name2):
317  return
318  self.edges[(name1, name2)] = attributes
319 
321  self, package_name, expanded_up=None,
322  depth=None, implicit=False, parent=None):
323  """
324  :param package_name: the name of package for which to add ancestors
325  :param expanded_up: names that have already been expanded (to avoid cycles)
326  :param depth: how many layers to follow
327  :param implicit: arg to rospack
328  :param parent: package that referenced package_name for error message only
329  """
330  if package_name in self.traversed_ancestors:
331  traversed_depth = self.traversed_ancestors[package_name]
332  if traversed_depth is None:
333  return
334  if depth is not None and traversed_depth >= depth:
335  return
336  self.traversed_ancestors[package_name] = depth
337 
338  if matches_any(package_name, self.excludes):
339  return False
340  if (depth == 0):
341  return False
342  if (depth == None):
343  depth = self.depth
344  self._add_package(package_name, parent=parent)
345  if expanded_up is None:
346  expanded_up = []
347  expanded_up.append(package_name)
348  if (depth != 1):
349  try:
350  depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
351  except ResourceNotFound as e:
352  print(
353  'RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s),'
354  ' parent %s: ResourceNotFound:' % (package_name, parent), e)
355  depends_on = []
356  new_nodes = []
357  for dep_on_name in [x for x in depends_on if not matches_any(x, self.excludes)]:
358  if not self.hide_transitives or not dep_on_name in expanded_up:
359  new_nodes.append(dep_on_name)
360  self._add_edge(dep_on_name, package_name)
361  self._add_package(dep_on_name, parent=package_name)
362  expanded_up.append(dep_on_name)
363  for dep_on_name in new_nodes:
364  self.add_package_ancestors_recursively(package_name=dep_on_name,
365  expanded_up=expanded_up,
366  depth=depth - 1,
367  implicit=implicit,
368  parent=package_name)
369 
371  self, package_name, expanded=None,
372  depth=None, implicit=False, parent=None):
373  if package_name in self.traversed_descendants:
374  traversed_depth = self.traversed_descendants[package_name]
375  if traversed_depth is None:
376  return
377  if depth is not None and traversed_depth >= depth:
378  return
379  self.traversed_descendants[package_name] = depth
380 
381  if matches_any(package_name, self.excludes):
382  return
383  if (depth == 0):
384  return
385  if (depth == None):
386  depth = self.depth
387  self._add_package(package_name, parent=parent)
388  if expanded is None:
389  expanded = []
390  expanded.append(package_name)
391  if (depth != 1):
392  try:
393  try:
394  depends = self.rospack.get_depends(package_name, implicit=implicit)
395  except ResourceNotFound:
396  # try falling back to rosstack to find wet metapackages
397  manifest = self.rosstack.get_manifest(package_name)
398  if manifest.is_catkin:
399  depends = [d.name for d in manifest.depends]
400  else:
401  raise
402  except ResourceNotFound as e:
403  print(
404  'RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), '
405  'parent: %s: ResourceNotFound:' % (package_name, parent), e)
406  depends = []
407  # get system dependencies without recursion
408  if self.show_system:
409  rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
410  for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
411  if not self.hide_transitives or not dep_name in expanded:
412  self._add_edge(package_name, dep_name)
413  self._add_package(dep_name, parent=package_name)
414  expanded.append(dep_name)
415  new_nodes = []
416  for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
417  if not self.hide_transitives or not dep_name in expanded:
418  new_nodes.append(dep_name)
419  self._add_edge(package_name, dep_name)
420  self._add_package(dep_name, parent=package_name)
421  expanded.append(dep_name)
422  for dep_name in new_nodes:
423  self.add_package_descendants_recursively(package_name=dep_name,
424  expanded=expanded,
425  depth=depth - 1,
426  implicit=implicit,
427  parent=package_name)
def generate_dotcode(self, dotcode_factory, selected_names=None, excludes=None, 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_edge(self, name1, name2, attributes=None)
def _generate_package(self, dotcode_factory, graph, package_name, attributes=None)
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): Aaron Blasdel, Dirk Thomas, Thibault Kruse
autogenerated on Wed May 12 2021 03:00:41