1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
54 """
55 Base exception of roslaunch.depends errors.
56 """
57 pass
58
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
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
82 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
83
85 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
86
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
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
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
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
144
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
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
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
191
192 if pkg not in file_deps[launch_file].pkgs:
193 file_deps[launch_file].pkgs.append(pkg)
194
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
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
227 name = NAME
228 print("""Usage:
229 \t%(name)s [options] <file-or-package>
230 """%locals())
231
233 pkgs = []
234
235
236 if verbose:
237 for f, deps in file_deps.items():
238 for p, t in deps.nodes:
239 print("%s [%s/%s]"%(p, p, t))
240
241 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(f)))
242 if pkg is None:
243 print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr)
244 else:
245 print("%s [%s]"%(pkg, f))
246 print('-'*80)
247
248
249 pkgs = []
250 for deps in file_deps.values():
251 pkgs.extend(deps.pkgs)
252
253 print(' '.join([p for p in set(pkgs)]))
254
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:
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
282
283
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
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
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