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:
00093 if true, then dependency of children to grandchildren will be hidden if parent has
00094 same dependency
00095 :param show_system: if true, then system dependencies will be shown
00096 """
00097
00098
00099 selected_names = filter(lambda x: x is not None and x != '', selected_names)
00100 excludes = filter(lambda x: x is not None and x != '', excludes)
00101 if selected_names is None or selected_names == []:
00102 selected_names = ['.*']
00103 self.depth = 1
00104 if depth is None:
00105 depth = -1
00106
00107
00108
00109 selection_args = {
00110 "dotcode_factory": dotcode_factory,
00111 "with_stacks": with_stacks,
00112 "depth": depth,
00113 "hide_transitives": hide_transitives,
00114 "show_system": show_system,
00115 "selected_names": selected_names,
00116 "excludes": excludes,
00117 "ancestors": ancestors,
00118 "descendants": descendants,
00119 "hide_wet": hide_wet,
00120 "hide_dry": hide_dry
00121 }
00122
00123
00124 selection_changed = False
00125 if self.last_selection != selection_args:
00126 selection_changed = True
00127 self.last_selection = selection_args
00128
00129 self.dotcode_factory = dotcode_factory
00130 self.with_stacks = with_stacks
00131 self.depth = depth
00132 self.hide_transitives = hide_transitives
00133 self.show_system = show_system
00134 self.selected_names = selected_names
00135 self.excludes = excludes
00136 self.ancestors = ancestors
00137 self.descendants = descendants
00138 self.hide_wet = hide_wet
00139 self.hide_dry = hide_dry
00140
00141 if force_refresh or selection_changed:
00142 self.stacks = {}
00143 self.packages = {}
00144 self.package_types = {}
00145 self.edges = {}
00146 self.traversed_ancestors = {}
00147 self.traversed_descendants = {}
00148
00149 for name in self.rospack.list():
00150 if matches_any(name, self.selected_names):
00151 if descendants:
00152 self.add_package_descendants_recursively(name)
00153 if ancestors:
00154 self.add_package_ancestors_recursively(name)
00155 for stackname in self.rosstack.list():
00156 if matches_any(stackname, self.selected_names):
00157 manifest = self.rosstack.get_manifest(stackname)
00158 if manifest.is_catkin:
00159 if descendants:
00160 self.add_package_descendants_recursively(stackname)
00161 if ancestors:
00162 self.add_package_ancestors_recursively(stackname)
00163 else:
00164 for package_name in self.rosstack.packages_of(stackname):
00165 if descendants:
00166 self.add_package_descendants_recursively(package_name)
00167 if ancestors:
00168 self.add_package_ancestors_recursively(package_name)
00169
00170 drawing_args = {
00171 'dotcode_factory': dotcode_factory,
00172 "rank": rank,
00173 "rankdir": rankdir,
00174 "ranksep": ranksep,
00175 "simplify": simplify,
00176 "colortheme": colortheme,
00177 "mark_selected": mark_selected
00178 }
00179
00180
00181 display_changed = False
00182 if self.last_drawargs != drawing_args:
00183 display_changed = True
00184 self.last_drawargs = drawing_args
00185
00186 self.dotcode_factory = dotcode_factory
00187 self.rank = rank
00188 self.rankdir = rankdir
00189 self.ranksep = ranksep
00190 self.simplify = simplify
00191 self.colortheme = colortheme
00192 self.dotcode_factory = dotcode_factory
00193 self.mark_selected = mark_selected
00194
00195
00196 if force_refresh or selection_changed or display_changed:
00197 self.graph = self.generate(self.dotcode_factory)
00198 self.dotcode = dotcode_factory.create_dot(self.graph)
00199
00200 return self.dotcode
00201
00202 def generate(self, dotcode_factory):
00203 graph = dotcode_factory.get_graph(rank=self.rank,
00204 rankdir=self.rankdir,
00205 ranksep=self.ranksep,
00206 simplify=self.simplify)
00207 packages_in_stacks = []
00208 if self.with_stacks and not self.hide_dry:
00209 for stackname in self.stacks:
00210 color = None
00211 if self.mark_selected and \
00212 '.*' not in self.selected_names and \
00213 matches_any(stackname, self.selected_names):
00214 color = 'tomato'
00215 else:
00216 color = 'gray'
00217 if self.colortheme is not None:
00218 color = get_color_for_string(stackname)
00219 g = dotcode_factory.add_subgraph_to_graph(graph,
00220 stackname,
00221 color=color,
00222 rank=self.rank,
00223 rankdir=self.rankdir,
00224 ranksep=self.ranksep,
00225 simplify=self.simplify)
00226
00227 for package_name in self.stacks[stackname]['packages']:
00228 packages_in_stacks.append(package_name)
00229 self._generate_package(dotcode_factory, g, package_name)
00230
00231 for package_name, attributes in self.packages.items():
00232 if package_name not in packages_in_stacks:
00233 self._generate_package(dotcode_factory, graph, package_name, attributes)
00234 for name1, name2 in self.edges.keys():
00235 dotcode_factory.add_edge_to_graph(graph, name1, name2)
00236 return graph
00237
00238 def _generate_package(self, dotcode_factory, graph, package_name, attributes=None):
00239 if self._hide_package(package_name):
00240 return
00241 color = None
00242 if self.mark_selected and \
00243 '.*' not in self.selected_names and \
00244 matches_any(package_name, self.selected_names):
00245 if attributes and attributes['is_catkin']:
00246 color = 'red'
00247 else:
00248 color = 'tomato'
00249 elif attributes and not attributes['is_catkin']:
00250 color = 'gray'
00251 if attributes and 'not_found' in attributes and attributes['not_found']:
00252 color = 'orange'
00253 package_name += ' ?'
00254 dotcode_factory.add_node_to_graph(graph, package_name, color=color)
00255
00256 def _add_stack(self, stackname):
00257 if stackname is None or stackname in self.stacks:
00258 return
00259 self.stacks[stackname] = {'packages': []}
00260
00261 def _add_package(self, package_name, parent=None):
00262 """
00263 adds object based on package_name to self.packages
00264 :param parent: packagename which referenced package_name (for debugging only)
00265 """
00266 if self._hide_package(package_name):
00267 return
00268 if package_name in self.packages:
00269 return False
00270
00271 catkin_package = self._is_package_wet(package_name)
00272 if catkin_package is None:
00273 return False
00274 self.packages[package_name] = {'is_catkin': catkin_package}
00275
00276 if self.with_stacks:
00277 try:
00278 stackname = self.rospack.stack_of(package_name)
00279 except ResourceNotFound as e:
00280 print(
00281 'RosPackageGraphDotcodeGenerator._add_package(%s), '
00282 'parent %s: ResourceNotFound:' % (package_name, parent), e)
00283 stackname = None
00284 if not stackname is None and stackname != '':
00285 if not stackname in self.stacks:
00286 self._add_stack(stackname)
00287 self.stacks[stackname]['packages'].append(package_name)
00288 return True
00289
00290 def _hide_package(self, package_name):
00291 if not self.hide_wet and not self.hide_dry:
00292 return False
00293 catkin_package = self._is_package_wet(package_name)
00294 if self.hide_wet and catkin_package:
00295 return True
00296 if self.hide_dry and catkin_package is False:
00297 return True
00298
00299 return False
00300
00301 def _is_package_wet(self, package_name):
00302 if package_name not in self.package_types:
00303 try:
00304 package_path = self.rospack.get_path(package_name)
00305 manifest_file = os.path.join(package_path, MANIFEST_FILE)
00306 self.package_types[package_name] = not os.path.exists(manifest_file)
00307 except ResourceNotFound:
00308 return None
00309 return self.package_types[package_name]
00310
00311 def _add_edge(self, name1, name2, attributes=None):
00312 if self._hide_package(name1) or self._hide_package(name2):
00313 return
00314 self.edges[(name1, name2)] = attributes
00315
00316 def add_package_ancestors_recursively(
00317 self, package_name, expanded_up=None,
00318 depth=None, implicit=False, parent=None):
00319 """
00320 :param package_name: the name of package for which to add ancestors
00321 :param expanded_up: names that have already been expanded (to avoid cycles)
00322 :param depth: how many layers to follow
00323 :param implicit: arg to rospack
00324 :param parent: package that referenced package_name for error message only
00325 """
00326 if package_name in self.traversed_ancestors:
00327 traversed_depth = self.traversed_ancestors[package_name]
00328 if traversed_depth is None:
00329 return
00330 if depth is not None and traversed_depth >= depth:
00331 return
00332 self.traversed_ancestors[package_name] = depth
00333
00334 if matches_any(package_name, self.excludes):
00335 return False
00336 if (depth == 0):
00337 return False
00338 if (depth == None):
00339 depth = self.depth
00340 self._add_package(package_name, parent=parent)
00341 if expanded_up is None:
00342 expanded_up = []
00343 expanded_up.append(package_name)
00344 if (depth != 1):
00345 try:
00346 depends_on = self.rospack.get_depends_on(package_name, implicit=implicit)
00347 except ResourceNotFound as e:
00348 print(
00349 'RosPackageGraphDotcodeGenerator.add_package_ancestors_recursively(%s),'
00350 ' parent %s: ResourceNotFound:' % (package_name, parent), e)
00351 depends_on = []
00352 new_nodes = []
00353 for dep_on_name in [x for x in depends_on if not matches_any(x, self.excludes)]:
00354 if not self.hide_transitives or not dep_on_name in expanded_up:
00355 new_nodes.append(dep_on_name)
00356 self._add_edge(dep_on_name, package_name)
00357 self._add_package(dep_on_name, parent=package_name)
00358 expanded_up.append(dep_on_name)
00359 for dep_on_name in new_nodes:
00360 self.add_package_ancestors_recursively(package_name=dep_on_name,
00361 expanded_up=expanded_up,
00362 depth=depth - 1,
00363 implicit=implicit,
00364 parent=package_name)
00365
00366 def add_package_descendants_recursively(
00367 self, package_name, expanded=None,
00368 depth=None, implicit=False, parent=None):
00369 if package_name in self.traversed_descendants:
00370 traversed_depth = self.traversed_descendants[package_name]
00371 if traversed_depth is None:
00372 return
00373 if depth is not None and traversed_depth >= depth:
00374 return
00375 self.traversed_descendants[package_name] = depth
00376
00377 if matches_any(package_name, self.excludes):
00378 return
00379 if (depth == 0):
00380 return
00381 if (depth == None):
00382 depth = self.depth
00383 self._add_package(package_name, parent=parent)
00384 if expanded is None:
00385 expanded = []
00386 expanded.append(package_name)
00387 if (depth != 1):
00388 try:
00389 try:
00390 depends = self.rospack.get_depends(package_name, implicit=implicit)
00391 except ResourceNotFound:
00392
00393 manifest = self.rosstack.get_manifest(package_name)
00394 if manifest.is_catkin:
00395 depends = [d.name for d in manifest.depends]
00396 else:
00397 raise
00398 except ResourceNotFound as e:
00399 print(
00400 'RosPackageGraphDotcodeGenerator.add_package_descendants_recursively(%s), '
00401 'parent: %s: ResourceNotFound:' % (package_name, parent), e)
00402 depends = []
00403
00404 if self.show_system:
00405 rosdeps = self.rospack.get_rosdeps(package_name, implicit=implicit)
00406 for dep_name in [x for x in rosdeps if not matches_any(x, self.excludes)]:
00407 if not self.hide_transitives or not dep_name in expanded:
00408 self._add_edge(package_name, dep_name)
00409 self._add_package(dep_name, parent=package_name)
00410 expanded.append(dep_name)
00411 new_nodes = []
00412 for dep_name in [x for x in depends if not matches_any(x, self.excludes)]:
00413 if not self.hide_transitives or not dep_name in expanded:
00414 new_nodes.append(dep_name)
00415 self._add_edge(package_name, dep_name)
00416 self._add_package(dep_name, parent=package_name)
00417 expanded.append(dep_name)
00418 for dep_name in new_nodes:
00419 self.add_package_descendants_recursively(package_name=dep_name,
00420 expanded=expanded,
00421 depth=depth - 1,
00422 implicit=implicit,
00423 parent=package_name)