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