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