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, load_mappings
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 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
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
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
145
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
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
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
192
193 if pkg not in file_deps[launch_file].pkgs:
194 file_deps[launch_file].pkgs.append(pkg)
195
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
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
228 name = NAME
229 print("""Usage:
230 \t%(name)s [options] <file-or-package>
231 """%locals())
232
234 pkgs = []
235
236
237 if verbose:
238 for f, deps in file_deps.items():
239 for p, t in deps.nodes:
240 print("%s [%s/%s]"%(p, p, t))
241
242 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(f)))
243 if pkg is None:
244 print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr)
245 else:
246 print("%s [%s]"%(pkg, f))
247 print('-'*80)
248
249
250 pkgs = []
251 for deps in file_deps.values():
252 pkgs.extend(deps.pkgs)
253
254 print(' '.join([p for p in set(pkgs)]))
255
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:
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
283
284
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
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
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