00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import with_statement, print_function
00034
00035 import os
00036 import re
00037
00038 from rospkg import MANIFEST_FILE
00039 from rospkg.common import ResourceNotFound
00040 from qt_dotgraph.colors import get_color_for_string
00041
00042
00043 def matches_any(name, patternlist):
00044 for pattern in patternlist:
00045 if name == pattern:
00046 return True
00047 if re.match("^[a-zA-Z0-9_]+$", pattern) is None:
00048 if re.match(pattern, name) is not None:
00049 return True
00050 return False
00051
00052
00053 class RosPackageGraphDotcodeGenerator:
00054
00055 def __init__(self, rospack, rosstack):
00056 """
00057 :param rospack: use rospkg.RosPack()
00058 :param rosstack: use rospkg.RosStack()
00059 """
00060 self.rospack = rospack
00061 self.rosstack = rosstack
00062 self.stacks = {}
00063 self.packages = {}
00064 self.package_types = {}
00065 self.edges = {}
00066 self.traversed_ancestors = {}
00067 self.traversed_descendants = {}
00068 self.last_drawargs = None
00069 self.last_selection = None
00070
00071 def generate_dotcode(self,
00072 dotcode_factory,
00073 selected_names=[],
00074 excludes=[],
00075 depth=3,
00076 with_stacks=True,
00077 descendants=True,
00078 ancestors=True,
00079 hide_transitives=True,
00080 show_system=False,
00081 mark_selected=True,
00082 colortheme=None,
00083 rank='same',
00084 ranksep=0.2,
00085 rankdir='TB',
00086 simplify=True,
00087 force_refresh=False,
00088 hide_wet=False,
00089 hide_dry=False):
00090 """
00091
00092 :param hide_transitives: if true, then dependency of children to grandchildren will be hidden if parent has same dependency
00093 :param show_system: if true, then system dependencies will be shown
00094 """
00095
00096
00097 selected_names = filter(lambda x: x is not None and x != '', selected_names)
00098 excludes = filter(lambda x: x is not None and x != '', excludes)
00099 if selected_names is None or selected_names == []:
00100 selected_names = ['.*']
00101 self.depth = 1
00102 if depth is None:
00103 depth = -1
00104
00105
00106
00107 selection_args = {
00108 "dotcode_factory": dotcode_factory,
00109 "with_stacks": with_stacks,
00110 "depth": depth,
00111 "hide_transitives": hide_transitives,
00112 "show_system": show_system,
00113 "selected_names": selected_names,
00114 "excludes": excludes,
00115 "ancestors": ancestors,
00116 "descendants": descendants,
00117 "hide_wet": hide_wet,
00118 "hide_dry": hide_dry
00119 }
00120
00121
00122 selection_changed = False
00123 if self.last_selection != selection_args:
00124 selection_changed = True
00125 self.last_selection = selection_args
00126
00127 self.dotcode_factory = dotcode_factory
00128 self.with_stacks = with_stacks
00129 self.depth = depth
00130 self.hide_transitives = hide_transitives
00131 self.show_system = show_system
00132 self.selected_names = selected_names
00133 self.excludes = excludes
00134 self.ancestors = ancestors
00135 self.descendants = descendants
00136 self.hide_wet = hide_wet
00137 self.hide_dry = hide_dry
00138
00139 if force_refresh or selection_changed:
00140 self.stacks = {}
00141 self.packages = {}
00142 self.package_types = {}
00143 self.edges = {}
00144 self.traversed_ancestors = {}
00145 self.traversed_descendants = {}
00146
00147 for name in self.rospack.list():
00148 if matches_any(name, self.selected_names):
00149 if descendants:
00150 self.add_package_descendants_recursively(name)
00151 if ancestors:
00152 self.add_package_ancestors_recursively(name)
00153 for stackname in self.rosstack.list():
00154 if matches_any(stackname, self.selected_names):
00155 manifest = self.rosstack.get_manifest(stackname)
00156 if manifest.is_catkin:
00157 if descendants:
00158 self.add_package_descendants_recursively(stackname)
00159 if ancestors:
00160 self.add_package_ancestors_recursively(stackname)
00161 else:
00162 for package_name in self.rosstack.packages_of(stackname):
00163 if descendants:
00164 self.add_package_descendants_recursively(package_name)
00165 if ancestors:
00166 self.add_package_ancestors_recursively(package_name)
00167
00168 drawing_args = {
00169 'dotcode_factory': dotcode_factory,
00170 "rank": rank,
00171 "rankdir": rankdir,
00172 "ranksep": ranksep,
00173 "simplify": simplify,
00174 "colortheme": colortheme,
00175 "mark_selected": mark_selected
00176 }
00177
00178
00179 display_changed = False
00180 if self.last_drawargs != drawing_args:
00181 display_changed = True
00182 self.last_drawargs = drawing_args
00183
00184 self.dotcode_factory = dotcode_factory
00185 self.rank = rank
00186 self.rankdir = rankdir
00187 self.ranksep = ranksep
00188 self.simplify = simplify
00189 self.colortheme = colortheme
00190 self.dotcode_factory = dotcode_factory
00191 self.mark_selected = mark_selected
00192
00193
00194 if force_refresh or selection_changed or display_changed:
00195 self.graph = self.generate(self.dotcode_factory)
00196 self.dotcode = dotcode_factory.create_dot(self.graph)
00197
00198 return self.dotcode
00199
00200 def generate(self, dotcode_factory):
00201 graph = dotcode_factory.get_graph(rank=self.rank,
00202 rankdir=self.rankdir,
00203 ranksep=self.ranksep,
00204 simplify=self.simplify)
00205
00206 packages_in_stacks = []
00207 if self.with_stacks and not self.hide_dry:
00208 for stackname in self.stacks:
00209 color = None
00210 if self.mark_selected and not '.*' in self.selected_names and matches_any(stackname, self.selected_names):
00211 color = 'tomato'
00212 else:
00213 color = 'gray'
00214 if self.colortheme is not None:
00215 color = get_color_for_string(stackname)
00216 g = dotcode_factory.add_subgraph_to_graph(graph,
00217 stackname,
00218 color=color,
00219 rank=self.rank,
00220 rankdir=self.rankdir,
00221 ranksep=self.ranksep,
00222 simplify=self.simplify)
00223
00224 for package_name in self.stacks[stackname]['packages']:
00225 packages_in_stacks.append(package_name)
00226 self._generate_package(dotcode_factory, g, package_name)
00227
00228 for package_name, attributes in self.packages.items():
00229 if package_name not in packages_in_stacks:
00230 self._generate_package(dotcode_factory, graph, package_name, attributes)
00231 for name1, name2 in self.edges.keys():
00232 dotcode_factory.add_edge_to_graph(graph, name1, name2)
00233 return graph
00234
00235 def _generate_package(self, dotcode_factory, graph, package_name, attributes=None):
00236 if self._hide_package(package_name):
00237 return
00238 color = None
00239 if self.mark_selected and not '.*' in self.selected_names and matches_any(package_name, self.selected_names):
00240 if attributes and attributes['is_catkin']:
00241 color = 'red'
00242 else:
00243 color = 'tomato'
00244 elif attributes and not attributes['is_catkin']:
00245 color = 'gray'
00246 if attributes and 'not_found' in attributes and attributes['not_found']:
00247 color = 'orange'
00248 package_name += ' ?'
00249 dotcode_factory.add_node_to_graph(graph, package_name, color=color)
00250
00251 def _add_stack(self, stackname):
00252 if stackname is None or stackname in self.stacks:
00253 return
00254 self.stacks[stackname] = {'packages': []}
00255
00256 def _add_package(self, package_name, parent=None):
00257 """
00258 adds object based on package_name to self.packages
00259 :param parent: packagename which referenced package_name (for debugging only)
00260 """
00261 if self._hide_package(package_name):
00262 return
00263 if package_name in self.packages:
00264 return False
00265
00266 catkin_package = self._is_package_wet(package_name)
00267 if catkin_package is None:
00268 return False
00269 self.packages[package_name] = {'is_catkin': catkin_package}
00270
00271 if self.with_stacks:
00272 try:
00273 stackname = self.rospack.stack_of(package_name)
00274 except ResourceNotFound as e:
00275 print('RosPackageGraphDotcodeGenerator._add_package(%s), parent %s: ResourceNotFound:' % (package_name, parent), e)
00276 stackname = None
00277 if not stackname is None and stackname != '':
00278 if not stackname in self.stacks:
00279 self._add_stack(stackname)
00280 self.stacks[stackname]['packages'].append(package_name)
00281 return True
00282
00283 def _hide_package(self, package_name):
00284 if not self.hide_wet and not self.hide_dry:
00285 return False
00286 catkin_package = self._is_package_wet(package_name)
00287 if self.hide_wet and catkin_package:
00288 return True
00289 if self.hide_dry and catkin_package is False:
00290 return True
00291
00292 return False
00293
00294 def _is_package_wet(self, package_name):
00295 if package_name not in self.package_types:
00296 try:
00297 package_path = self.rospack.get_path(package_name)
00298 manifest_file = os.path.join(package_path, MANIFEST_FILE)
00299 self.package_types[package_name] = not os.path.exists(manifest_file)
00300 except ResourceNotFound:
00301 return None
00302 return self.package_types[package_name]
00303
00304 def _add_edge(self, name1, name2, attributes=None):
00305 if self._hide_package(name1) or self._hide_package(name2):
00306 return
00307 self.edges[(name1, name2)] = attributes
00308
00309 def add_package_ancestors_recursively(self, package_name, expanded_up=None, depth=None, implicit=False, parent=None):
00310 """
00311 :param package_name: the name of package for which to add ancestors
00312 :param expanded_up: names that have already been expanded (to avoid cycles)
00313 :param depth: how many layers to follow
00314 :param implicit: arg to rospack
00315 :param parent: package that referenced package_name for error message only
00316 """
00317 if package_name in self.traversed_ancestors:
00318 traversed_depth = self.traversed_ancestors[package_name]
00319 if traversed_depth is None:
00320 return
00321 if depth is not None and traversed_depth >= depth:
00322 return
00323 self.traversed_ancestors[package_name] = depth
00324
00325 if matches_any(package_name, self.excludes):
00326 return False
00327 if (depth == 0):
00328 return False
00329 if (depth == None):
00330 depth = self.depth
00331 self._add_package(package_name, parent=parent)
00332 if expanded_up is None:
00333 expanded_up = []
00334 expanded_up.append(package_name)
00335 if (depth != 1):
00336 try:
00337 depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
00338 except ResourceNotFound as e:
00339 print('RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s), parent %s: ResourceNotFound:' % (package_name, parent), e)
00340 depends_on = []
00341 new_nodes = []
00342 for dep_on_name in [x for x in depends_on if not matches_any(x, self.excludes)]:
00343 if not self.hide_transitives or not dep_on_name in expanded_up:
00344 new_nodes.append(dep_on_name)
00345 self._add_edge(dep_on_name, package_name)
00346 self._add_package(dep_on_name, parent=package_name)
00347 expanded_up.append(dep_on_name)
00348 for dep_on_name in new_nodes:
00349 self.add_package_ancestors_recursively(package_name=dep_on_name,
00350 expanded_up=expanded_up,
00351 depth=depth - 1,
00352 implicit=implicit,
00353 parent=package_name)
00354
00355 def add_package_descendants_recursively(self, package_name, expanded=None, depth=None, implicit=False, parent=None):
00356 if package_name in self.traversed_descendants:
00357 traversed_depth = self.traversed_descendants[package_name]
00358 if traversed_depth is None:
00359 return
00360 if depth is not None and traversed_depth >= depth:
00361 return
00362 self.traversed_descendants[package_name] = depth
00363
00364 if matches_any(package_name, self.excludes):
00365 return
00366 if (depth == 0):
00367 return
00368 if (depth == None):
00369 depth = self.depth
00370 self._add_package(package_name, parent=parent)
00371 if expanded is None:
00372 expanded = []
00373 expanded.append(package_name)
00374 if (depth != 1):
00375 try:
00376 try:
00377 depends = self.rospack.get_depends(package_name, implicit=implicit)
00378 except ResourceNotFound:
00379
00380 manifest = self.rosstack.get_manifest(package_name)
00381 if manifest.is_catkin:
00382 depends = [d.name for d in manifest.depends]
00383 else:
00384 raise
00385 except ResourceNotFound as e:
00386 print('RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), parent: %s: ResourceNotFound:' % (package_name, parent), e)
00387 depends = []
00388
00389 if self.show_system:
00390 rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
00391 for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
00392 if not self.hide_transitives or not dep_name in expanded:
00393 self._add_edge(package_name, dep_name)
00394 self._add_package(dep_name, parent=package_name)
00395 expanded.append(dep_name)
00396 new_nodes = []
00397 for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
00398 if not self.hide_transitives or not dep_name in expanded:
00399 new_nodes.append(dep_name)
00400 self._add_edge(package_name, dep_name)
00401 self._add_package(dep_name, parent=package_name)
00402 expanded.append(dep_name)
00403 for dep_name in new_nodes:
00404 self.add_package_descendants_recursively(package_name=dep_name,
00405 expanded=expanded,
00406 depth=depth - 1,
00407 implicit=implicit,
00408 parent=package_name)