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 .loader import convert_value 
 49  from .substitution_args import resolve_args 
 50   
 51  NAME="roslaunch-deps" 
 52   
53 -class RoslaunchDepsException(Exception):
54 """ 55 Base exception of roslaunch.depends errors. 56 """ 57 pass
58
59 -class RoslaunchDeps(object):
60 """ 61 Represents dependencies of a roslaunch file. 62 """
63 - def __init__(self, nodes=None, includes=None, pkgs=None):
64 if nodes == None: 65 nodes = [] 66 if includes == None: 67 includes = [] 68 if pkgs == None: 69 pkgs = [] 70 self.nodes = nodes 71 self.includes = includes 72 self.pkgs = pkgs
73
74 - def __eq__(self, other):
75 if not isinstance(other, RoslaunchDeps): 76 return False 77 return set(self.nodes) == set(other.nodes) and \ 78 set(self.includes) == set(other.includes) and \ 79 set(self.pkgs) == set(other.pkgs)
80
81 - def __repr__(self):
82 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
83
84 - def __str__(self):
85 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
86
87 -def _get_arg_value(tag, context):
88 name = tag.attributes['name'].value 89 if tag.attributes.has_key('value'): 90 return resolve_args(tag.attributes['value'].value, context) 91 elif name in context['arg']: 92 return context['arg'][name] 93 elif tag.attributes.has_key('default'): 94 return resolve_args(tag.attributes['default'].value, context) 95 else: 96 raise RoslaunchDepsException("No value for arg [%s]"%(name))
97
98 -def _check_ifunless(tag, context):
99 if tag.attributes.has_key('if'): 100 val = resolve_args(tag.attributes['if'].value, context) 101 if not convert_value(val, 'bool'): 102 return False 103 elif tag.attributes.has_key('unless'): 104 val = resolve_args(tag.attributes['unless'].value, context) 105 if convert_value(val, 'bool'): 106 return False 107 return True
108
109 -def _parse_subcontext(tags, context):
110 subcontext = {'arg': {}} 111 112 if tags == None: 113 return subcontext 114 115 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]: 116 if tag.tagName == 'arg' and _check_ifunless(tag, context): 117 subcontext['arg'][tag.attributes['name'].value] = _get_arg_value(tag, context) 118 return subcontext
119
120 -def _parse_launch(tags, launch_file, file_deps, verbose, context):
121 dir_path = os.path.dirname(os.path.abspath(launch_file)) 122 launch_file_pkg = rospkg.get_package_name(dir_path) 123 124 # process group, include, node, and test tags from launch file 125 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]: 126 if not _check_ifunless(tag, context): 127 continue 128 129 if tag.tagName == 'group': 130 131 #descend group tags as they can contain node tags 132 _parse_launch(tag.childNodes, launch_file, file_deps, verbose, context) 133 134 elif tag.tagName == 'arg': 135 context['arg'][tag.attributes['name'].value] = _get_arg_value(tag, context) 136 137 elif tag.tagName == 'include': 138 try: 139 sub_launch_file = resolve_args(tag.attributes['file'].value, context) 140 except KeyError as e: 141 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml())) 142 143 # Check if an empty file is included, and skip if so. 144 # This will allow a default-empty <include> inside a conditional to pass 145 if sub_launch_file == '': 146 if verbose: 147 print("Empty <include> in %s. Skipping <include> of %s" % 148 (launch_file, tag.attributes['file'].value)) 149 continue 150 151 if verbose: 152 print("processing included launch %s"%sub_launch_file) 153 154 # determine package dependency for included file 155 sub_pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(sub_launch_file))) 156 if sub_pkg is None: 157 print("ERROR: cannot determine package for [%s]"%sub_launch_file, file=sys.stderr) 158 159 if sub_launch_file not in file_deps[launch_file].includes: 160 file_deps[launch_file].includes.append(sub_launch_file) 161 if launch_file_pkg != sub_pkg: 162 file_deps[launch_file].pkgs.append(sub_pkg) 163 164 # recurse 165 file_deps[sub_launch_file] = RoslaunchDeps() 166 try: 167 dom = parse(sub_launch_file).getElementsByTagName('launch') 168 if not len(dom): 169 print("ERROR: %s is not a valid roslaunch file"%sub_launch_file, file=sys.stderr) 170 else: 171 launch_tag = dom[0] 172 sub_context = _parse_subcontext(tag.childNodes, context) 173 try: 174 if tag.attributes['pass_all_args']: 175 sub_context["arg"] = context["arg"] 176 sub_context["arg"].update(_parse_subcontext(tag.childNodes, context)["arg"]) 177 except KeyError as e: 178 pass 179 _parse_launch(launch_tag.childNodes, sub_launch_file, file_deps, verbose, sub_context) 180 except IOError as e: 181 raise RoslaunchDepsException("Cannot load roslaunch include '%s' in '%s'"%(sub_launch_file, launch_file)) 182 183 elif tag.tagName in ['node', 'test']: 184 try: 185 pkg, type = [resolve_args(tag.attributes[a].value, context) for a in ['pkg', 'type']] 186 except KeyError as e: 187 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml())) 188 if (pkg, type) not in file_deps[launch_file].nodes: 189 file_deps[launch_file].nodes.append((pkg, type)) 190 # we actually want to include the package itself if that's referenced 191 #if launch_file_pkg != pkg: 192 if pkg not in file_deps[launch_file].pkgs: 193 file_deps[launch_file].pkgs.append(pkg)
194
195 -def parse_launch(launch_file, file_deps, verbose):
196 if verbose: 197 print("processing launch %s"%launch_file) 198 199 try: 200 dom = parse(launch_file).getElementsByTagName('launch') 201 except: 202 raise RoslaunchDepsException("invalid XML in %s"%(launch_file)) 203 if not len(dom): 204 raise RoslaunchDepsException("invalid XML in %s"%(launch_file)) 205 206 file_deps[launch_file] = RoslaunchDeps() 207 launch_tag = dom[0] 208 context = { 'arg': {}} 209 _parse_launch(launch_tag.childNodes, launch_file, file_deps, verbose, context)
210
211 -def rl_file_deps(file_deps, launch_file, verbose=False):
212 """ 213 Generate file_deps file dependency info for the specified 214 roslaunch file and its dependencies. 215 @param file_deps: dictionary mapping roslaunch filenames to 216 roslaunch dependency information. This dictionary will be 217 updated with dependency information. 218 @type file_deps: { str : RoslaunchDeps } 219 @param verbose: if True, print verbose output 220 @type verbose: bool 221 @param launch_file: name of roslaunch file 222 @type launch_file: str 223 """ 224 parse_launch(launch_file, file_deps, verbose)
225
226 -def fullusage():
227 name = NAME 228 print("""Usage: 229 \t%(name)s [options] <file-or-package> 230 """%locals())
231 254
255 -def calculate_missing(base_pkg, missing, file_deps, use_test_depends=False):
256 """ 257 Calculate missing package dependencies in the manifest. This is 258 mainly used as a subroutine of roslaunch_deps(). 259 260 @param base_pkg: name of package where initial walk begins (unused). 261 @type base_pkg: str 262 @param missing: dictionary mapping package names to set of missing package dependencies. 263 @type missing: { str: set(str) } 264 @param file_deps: dictionary mapping launch file names to RoslaunchDeps of each file 265 @type file_deps: { str: RoslaunchDeps} 266 @param use_test_depends [bool]: use test_depends as installed package 267 @type use_test_depends: [bool] 268 @return: missing (see parameter) 269 @rtype: { str: set(str) } 270 """ 271 rospack = rospkg.RosPack() 272 for launch_file in file_deps.keys(): 273 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file))) 274 275 if pkg is None: #cannot determine package 276 print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr) 277 continue 278 m = rospack.get_manifest(pkg) 279 d_pkgs = set([d.name for d in m.depends]) 280 if m.is_catkin: 281 # for catkin packages consider the run dependencies instead 282 # else not released packages will not appear in the dependency list 283 # since rospkg does uses rosdep to decide which dependencies to return 284 from catkin_pkg.package import parse_package 285 p = parse_package(os.path.dirname(m.filename)) 286 d_pkgs = set([d.name for d in p.run_depends]) 287 if use_test_depends: 288 for d in p.test_depends: 289 d_pkgs.add(d.name) 290 # make sure we don't count ourselves as a dep 291 d_pkgs.add(pkg) 292 293 diff = list(set(file_deps[launch_file].pkgs) - d_pkgs) 294 if not pkg in missing: 295 missing[pkg] = set() 296 missing[pkg].update(diff) 297 return missing
298 299
300 -def roslaunch_deps(files, verbose=False, use_test_depends=False):
301 """ 302 @param packages: list of packages to check 303 @type packages: [str] 304 @param files [str]: list of roslaunch files to check. Must be in 305 same package. 306 @type files: [str] 307 @param use_test_depends [bool]: use test_depends as installed package 308 @type use_test_depends: [bool] 309 @return: base_pkg, file_deps, missing. 310 base_pkg is the package of all files 311 file_deps is a { filename : RoslaunchDeps } dictionary of 312 roslaunch dependency information to update, indexed by roslaunch 313 file name. 314 missing is a { package : [packages] } dictionary of missing 315 manifest dependencies, indexed by package. 316 @rtype: str, dict, dict 317 """ 318 file_deps = {} 319 missing = {} 320 base_pkg = None 321 322 for launch_file in files: 323 if not os.path.exists(launch_file): 324 raise RoslaunchDepsException("roslaunch file [%s] does not exist"%launch_file) 325 326 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file))) 327 if base_pkg and pkg != base_pkg: 328 raise RoslaunchDepsException("roslaunch files must be in the same package: %s vs. %s"%(base_pkg, pkg)) 329 base_pkg = pkg 330 rl_file_deps(file_deps, launch_file, verbose) 331 332 calculate_missing(base_pkg, missing, file_deps, use_test_depends=use_test_depends) 333 return base_pkg, file_deps, missing
334
335 -def roslaunch_deps_main(argv=sys.argv):
336 from optparse import OptionParser 337 parser = OptionParser(usage="usage: %prog [options] <file(s)...>", prog=NAME) 338 parser.add_option("--verbose", "-v", action="store_true", 339 dest="verbose", default=False, 340 help="Verbose output") 341 parser.add_option("--warn", "-w", action="store_true", 342 dest="warn", default=False, 343 help="Warn about missing manifest dependencies") 344 345 (options, args) = parser.parse_args(argv[1:]) 346 if not args: 347 parser.error('please specify a launch file') 348 349 files = [arg for arg in args if os.path.exists(arg)] 350 packages = [arg for arg in args if not os.path.exists(arg)] 351 if packages: 352 parser.error("cannot locate %s"%','.join(packages)) 353 try: 354 base_pkg, file_deps, missing = roslaunch_deps(files, verbose=options.verbose) 355 except RoslaunchDepsException as e: 356 print(sys.stderr, str(e)) 357 sys.exit(1) 358 359 if options.warn: 360 print("Dependencies:") 361 362 print_deps(base_pkg, file_deps, options.verbose) 363 364 if options.warn: 365 print('\nMissing declarations:') 366 for pkg, miss in missing.items(): 367 if miss: 368 print("%s/manifest.xml:"%pkg) 369 for m in miss: 370 print(' <depend package="%s" />'%m)
371