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                          show_system=False,
00081                          mark_selected=True,
00082                          colortheme=None,
00083                          rank='same',  # None, same, min, max, source, sink
00084                          ranksep=0.2,  # vertical distance between layers
00085                          rankdir='TB',  # direction of layout (TB top > bottom, LR left > right)
00086                          simplify=True,  # remove double edges
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         # defaults
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         # update arguments
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         # if selection did not change, we need not build up the graph again
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             # update internal graph structure
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         # if selection and display args did not change, no need to generate dotcode
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         #generate new dotcode
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         # print("In generate", self.with_stacks, len(self.stacks), len(self.packages), len(self.edges))
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         # if type of package is unknown don't hide it
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                     # try falling back to rosstack to find wet metapackages
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             # get system dependencies without recursion
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)


rqt_dep
Author(s): Thibault Kruse
autogenerated on Mon May 1 2017 03:02:57