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 .substitution_args import resolve_args
49
50 NAME="roslaunch-deps"
51
53 """
54 Base exception of roslaunch.depends errors.
55 """
56 pass
57
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
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
81 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
82
84 return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
85
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
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
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
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
132 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
133
134 if tag.tagName == 'group':
135
136
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
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
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
184
185 if pkg not in file_deps[launch_file].pkgs:
186 file_deps[launch_file].pkgs.append(pkg)
187
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
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
220 name = NAME
221 print("""Usage:
222 \t%(name)s [options] <file-or-package>
223 """%locals())
224
226 pkgs = []
227
228
229 if verbose:
230 for f, deps in file_deps.iteritems():
231 for p, t in deps.nodes:
232 print("%s [%s/%s]"%(p, p, t))
233
234 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(f)))
235 if pkg is None:
236 print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr)
237 else:
238 print("%s [%s]"%(pkg, f))
239 print('-'*80)
240
241
242 pkgs = []
243 for deps in file_deps.itervalues():
244 pkgs.extend(deps.pkgs)
245
246 print(' '.join([p for p in set(pkgs)]))
247
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.iterkeys():
264 pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file)))
265
266 if pkg is None:
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
273
274
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
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
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.iteritems():
353 if miss:
354 print("%s/manifest.xml:"%pkg)
355 for m in miss:
356 print(' <depend package="%s" />'%m)
357