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:
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         # defaults
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         # update arguments
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         # if selection did not change, we need not build up the graph again
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             # update internal graph structure
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         # if selection and display args did not change, no need to generate dotcode
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         # generate new dotcode
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         # if type of package is unknown don't hide it
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                     # try falling back to rosstack to find wet metapackages
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             # get system dependencies without recursion
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)


rqt_dep
Author(s): Thibault Kruse
autogenerated on Thu Jun 6 2019 20:36:24