dotcode_pack.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
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',  # None, same, min, max, source, sink
00083                          ranksep=0.2,  # vertical distance between layers
00084                          rankdir='TB',  # direction of layout (TB top > bottom, LR left > right)
00085                          simplify=True,  # remove double edges
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         # defaults
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         # update arguments
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         # if selection did not change, we need not build up the graph again
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             # update internal graph structure
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         # if selection and display args did not change, no need to generate dotcode
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         #generate new dotcode
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         # print("In generate", self.with_stacks, len(self.stacks), len(self.packages), len(self.edges))
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         # if type of package is unknown don't hide it
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                     # try falling back to rosstack to find wet metapackages
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)


rqt_dep
Author(s): Thibault Kruse
autogenerated on Mon Oct 6 2014 07:15:28