Package roslaunch :: Module depends
[frames] | no frames]

Source Code for Module roslaunch.depends

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32   
 33  """ 
 34  Utility module of roslaunch that extracts dependency information from 
 35  roslaunch files, including calculating missing package dependencies. 
 36  """ 
 37   
 38  from __future__ import print_function 
 39   
 40  import os 
 41  import sys 
 42   
 43  from xml.dom.minidom import parse 
 44  from xml.dom import Node as DomNode 
 45   
 46  import rospkg 
 47   
 48  from .substitution_args import resolve_args 
 49   
 50  NAME="roslaunch-deps" 
 51   
52 -class RoslaunchDepsException(Exception):
53 """ 54 Base exception of roslaunch.depends errors. 55 """ 56 pass
57
58 -class RoslaunchDeps(object):
59 """ 60 Represents dependencies of a roslaunch file. 61 """
62 - def __init__(self, nodes=None, includes=None, pkgs=None):
63 if nodes == None: 64 nodes = [] 65 if includes == None: 66 includes = [] 67 if pkgs == None: 68 pkgs = [] 69 self.nodes = nodes 70 self.includes = includes 71 self.pkgs = pkgs
72
73 - def __eq__(self, other):
74 if not isinstance(other, RoslaunchDeps): 75 return False 76 return set(self.nodes) == set(other.nodes) and \ 77 set(self.includes) == set(other.includes) and \ 78 set(self.pkgs) == set(other.pkgs)
79
80 - def __repr__(self):
81 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
82
83 - def __str__(self):
84 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
85
86 -def _get_arg_value(tag, context):
87 name = tag.attributes['name'].value 88 if tag.attributes.has_key('value'): 89 return resolve_args(tag.attributes['value'].value, context) 90 elif name in context['arg']: 91 return context['arg'][name] 92 elif tag.attributes.has_key('default'): 93 return resolve_args(tag.attributes['default'].value, context) 94 else: 95 raise RoslaunchDepsException("No value for arg [%s]"%(name))
96
97 -def _parse_arg(tag, context):
98 name = tag.attributes['name'].value 99 if tag.attributes.has_key('if'): 100 val = resolve_args(tag.attributes['if'].value, context) 101 if val == '1' or val == 'true': 102 return (name, _get_arg_value(tag, context)) 103 elif tag.attributes.has_key('unless'): 104 val = resolve_args(tag.attributes['unless'].value, context) 105 if val == '0' or val == 'false': 106 return (name, _get_arg_value(tag, context)) 107 else: 108 return (name, _get_arg_value(tag, context)) 109 # nothing to return (no value, or conditional wasn't satisfied) 110 return None
111
112 -def _parse_subcontext(tags, context):
113 subcontext = {'arg': {}} 114 115 if tags == None: 116 return subcontext 117 118 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]: 119 if tag.tagName == 'arg': 120 # None is returned for args with if/unless that evaluate to false 121 ret = _parse_arg(tag, context) 122 if ret is not None: 123 (name, val) = ret 124 subcontext['arg'][name] = val 125 return subcontext
126
127 -def _parse_launch(tags, launch_file, file_deps, verbose, context):
128 dir_path = os.path.dirname(os.path.abspath(launch_file)) 129 launch_file_pkg = rospkg.get_package_name(dir_path) 130 131 # process group, include, node, and test tags from launch file 132 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]: 133 134 if tag.tagName == 'group': 135 136 #descend group tags as they can contain node tags 137 _parse_launch(tag.childNodes, launch_file, file_deps, verbose, context) 138 139 elif tag.tagName == 'arg': 140 v = _parse_arg(tag, context) 141 if v: 142 (name, val) = v 143 context['arg'][name] = val 144 elif tag.tagName == 'include': 145 try: 146 sub_launch_file = resolve_args(tag.attributes['file'].value, context) 147 except KeyError as e: 148 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml())) 149 150 if verbose: 151 print("processing included launch %s"%sub_launch_file) 152 153 # determine package dependency for included file 154 sub_pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(sub_launch_file))) 155 if sub_pkg is None: 156 print("ERROR: cannot determine package for [%s]"%sub_launch_file, file=sys.stderr) 157 158 if sub_launch_file not in file_deps[launch_file].includes: 159 file_deps[launch_file].includes.append(sub_launch_file) 160 if launch_file_pkg != sub_pkg: 161 file_deps[launch_file].pkgs.append(sub_pkg) 162 163 # recurse 164 file_deps[sub_launch_file] = RoslaunchDeps() 165 try: 166 dom = parse(sub_launch_file).getElementsByTagName('launch') 167 if not len(dom): 168 print("ERROR: %s is not a valid roslaunch file"%sub_launch_file, file=sys.stderr) 169 else: 170 launch_tag = dom[0] 171 sub_context = _parse_subcontext(tag.childNodes, context) 172 _parse_launch(launch_tag.childNodes, sub_launch_file, file_deps, verbose, sub_context) 173 except IOError as e: 174 raise RoslaunchDepsException("Cannot load roslaunch include '%s' in '%s'"%(sub_launch_file, launch_file)) 175 176 elif tag.tagName in ['node', 'test']: 177 try: 178 pkg, type = [resolve_args(tag.attributes[a].value, context) for a in ['pkg', 'type']] 179 except KeyError as e: 180 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml())) 181 if (pkg, type) not in file_deps[launch_file].nodes: 182 file_deps[launch_file].nodes.append((pkg, type)) 183 # we actually want to include the package itself if that's referenced 184 #if launch_file_pkg != pkg: 185 if pkg not in file_deps[launch_file].pkgs: 186 file_deps[launch_file].pkgs.append(pkg)
187
188 -def parse_launch(launch_file, file_deps, verbose):
189 if verbose: 190 print("processing launch %s"%launch_file) 191 192 try: 193 dom = parse(launch_file).getElementsByTagName('launch') 194 except: 195 raise RoslaunchDepsException("invalid XML in %s"%(launch_file)) 196 if not len(dom): 197 raise RoslaunchDepsException("invalid XML in %s"%(launch_file)) 198 199 file_deps[launch_file] = RoslaunchDeps() 200 launch_tag = dom[0] 201 context = { 'arg': {}} 202 _parse_launch(launch_tag.childNodes, launch_file, file_deps, verbose, context)
203
204 -def rl_file_deps(file_deps, launch_file, verbose=False):
205 """ 206 Generate file_deps file dependency info for the specified 207 roslaunch file and its dependencies. 208 @param file_deps: dictionary mapping roslaunch filenames to 209 roslaunch dependency information. This dictionary will be 210 updated with dependency information. 211 @type file_deps: { str : RoslaunchDeps } 212 @param verbose: if True, print verbose output 213 @type verbose: bool 214 @param launch_file: name of roslaunch file 215 @type launch_file: str 216 """ 217 parse_launch(launch_file, file_deps, verbose)
218
219 -def fullusage():
220 name = NAME 221 print("""Usage: 222 \t%(name)s [options] <file-or-package> 223 """%locals())
224 247
248 -def calculate_missing(base_pkg, missing, file_deps):
249 """ 250 Calculate missing package dependencies in the manifest. This is 251 mainly used as a subroutine of roslaunch_deps(). 252 253 @param base_pkg: name of package where initial walk begins (unused). 254 @type base_pkg: str 255 @param missing: dictionary mapping package names to set of missing package dependencies. 256 @type missing: { str: set(str) } 257 @param file_deps: dictionary mapping launch file names to RoslaunchDeps of each file 258 @type file_deps: { str: RoslaunchDeps} 259 @return: missing (see parameter) 260 @rtype: { str: set(str) } 261 """ 262 rospack = rospkg.RosPack() 263 for launch_file in file_deps.keys(): 264 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file))) 265 266 if pkg is None: #cannot determine package 267 print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr) 268 continue 269 m = rospack.get_manifest(pkg) 270 d_pkgs = set([d.name for d in m.depends]) 271 if m.is_catkin: 272 # for catkin packages consider the run dependencies instead 273 # else not released packages will not appear in the dependency list 274 # since rospkg does uses rosdep to decide which dependencies to return 275 from catkin_pkg.package import parse_package 276 p = parse_package(os.path.dirname(m.filename)) 277 d_pkgs = set([d.name for d in p.run_depends]) 278 # make sure we don't count ourselves as a dep 279 d_pkgs.add(pkg) 280 281 diff = list(set(file_deps[launch_file].pkgs) - d_pkgs) 282 if not pkg in missing: 283 missing[pkg] = set() 284 missing[pkg].update(diff) 285 return missing
286 287
288 -def roslaunch_deps(files, verbose=False):
289 """ 290 @param packages: list of packages to check 291 @type packages: [str] 292 @param files [str]: list of roslaunch files to check. Must be in 293 same package. 294 @type files: [str] 295 @return: base_pkg, file_deps, missing. 296 base_pkg is the package of all files 297 file_deps is a { filename : RoslaunchDeps } dictionary of 298 roslaunch dependency information to update, indexed by roslaunch 299 file name. 300 missing is a { package : [packages] } dictionary of missing 301 manifest dependencies, indexed by package. 302 @rtype: str, dict, dict 303 """ 304 file_deps = {} 305 missing = {} 306 base_pkg = None 307 308 for launch_file in files: 309 if not os.path.exists(launch_file): 310 raise RoslaunchDepsException("roslaunch file [%s] does not exist"%launch_file) 311 312 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file))) 313 if base_pkg and pkg != base_pkg: 314 raise RoslaunchDepsException("roslaunch files must be in the same package: %s vs. %s"%(base_pkg, pkg)) 315 base_pkg = pkg 316 rl_file_deps(file_deps, launch_file, verbose) 317 318 calculate_missing(base_pkg, missing, file_deps) 319 return base_pkg, file_deps, missing
320
321 -def roslaunch_deps_main(argv=sys.argv):
322 from optparse import OptionParser 323 parser = OptionParser(usage="usage: %prog [options] <file(s)...>", prog=NAME) 324 parser.add_option("--verbose", "-v", action="store_true", 325 dest="verbose", default=False, 326 help="Verbose output") 327 parser.add_option("--warn", "-w", action="store_true", 328 dest="warn", default=False, 329 help="Warn about missing manifest dependencies") 330 331 (options, args) = parser.parse_args(argv[1:]) 332 if not args: 333 parser.error('please specify a launch file') 334 335 files = [arg for arg in args if os.path.exists(arg)] 336 packages = [arg for arg in args if not os.path.exists(arg)] 337 if packages: 338 parser.error("cannot locate %s"%','.join(packages)) 339 try: 340 base_pkg, file_deps, missing = roslaunch_deps(files, verbose=options.verbose) 341 except RoslaunchDepsException as e: 342 print(sys.stderr, str(e)) 343 sys.exit(1) 344 345 if options.warn: 346 print("Dependencies:") 347 348 print_deps(base_pkg, file_deps, options.verbose) 349 350 if options.warn: 351 print('\nMissing declarations:') 352 for pkg, miss in missing.items(): 353 if miss: 354 print("%s/manifest.xml:"%pkg) 355 for m in miss: 356 print(' <depend package="%s" />'%m)
357