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
35 """
36 Utility module of roslaunch that extracts dependency information from
37 roslaunch files, including calculating missing package dependencies.
38 """
39
40 import roslib; roslib.load_manifest('roslaunch')
41
42 import os
43 import sys
44
45 import roslib.manifest
46 import roslib.packages
47
48
49 from xml.dom.minidom import parse, parseString
50 from xml.dom import Node as DomNode
51
52 NAME="roslaunch-deps"
53
55 """
56 Base exception of roslaunch.depends errors.
57 """
58 pass
59
61 """
62 Represents dependencies of a roslaunch file.
63 """
65 self.nodes = []
66 self.includes = []
67 self.pkgs = []
68
70 _, launch_file_pkg = roslib.packages.get_dir_pkg(os.path.dirname(os.path.abspath(launch_file)))
71
72
73 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
74
75 if tag.tagName == 'group':
76
77
78 _parse_launch(tag.childNodes, launch_file, file_deps, verbose)
79
80 elif tag.tagName == 'include':
81 try:
82 sub_launch_file = resolve_args(tag.attributes['file'].value)
83 except KeyError, e:
84 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml()))
85
86 if verbose:
87 print "processing included launch %s"%sub_launch_file
88
89
90 _, sub_pkg = roslib.packages.get_dir_pkg(os.path.dirname(os.path.abspath(sub_launch_file)))
91 if sub_pkg is None:
92 print >> sys.stderr, "ERROR: cannot determine package for [%s]"%sub_launch_file
93
94 file_deps[launch_file].includes.append(sub_launch_file)
95 if launch_file_pkg != sub_pkg:
96 file_deps[launch_file].pkgs.append(sub_pkg)
97
98
99 file_deps[sub_launch_file] = RoslaunchDeps()
100 try:
101 dom = parse(sub_launch_file).getElementsByTagName('launch')
102 if not len(dom):
103 print >> sys.stderr, "ERROR: %s is not a valid roslaunch file"%sub_launch_file
104 else:
105 launch_tag = dom[0]
106 _parse_launch(launch_tag.childNodes, sub_launch_file, file_deps, verbose)
107 except IOError, e:
108 raise RoslaunchDepsException("Cannot load roslaunch include '%s' in '%s'"%(sub_launch_file, launch_file))
109
110 elif tag.tagName in ['node', 'test']:
111 try:
112 pkg, type = [resolve_args(tag.attributes[a].value) for a in ['pkg', 'type']]
113 except KeyError, e:
114 raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml()))
115 file_deps[launch_file].nodes.append((pkg, type))
116
117
118 file_deps[launch_file].pkgs.append(pkg)
119
121 if verbose:
122 print "processing launch %s"%launch_file
123
124 dom = parse(launch_file).getElementsByTagName('launch')
125 if not len(dom):
126 print >> sys.stderr, "ignoring %s as it is not a roslaunch file"%launch_file
127 return
128
129 file_deps[launch_file] = RoslaunchDeps()
130 launch_tag = dom[0]
131 _parse_launch(launch_tag.childNodes, launch_file, file_deps, verbose)
132
134 """
135 Generate file_deps file dependency info for the specified
136 roslaunch file and its dependencies.
137 @param file_deps: dictionary mapping roslaunch filenames to
138 roslaunch dependency information. This dictionary will be
139 updated with dependency information.
140 @type file_deps: { str : RoslaunchDeps }
141 @param verbose: if True, print verbose output
142 @type verbose: bool
143 @param launch_file: name of roslaunch file
144 @type launch_file: str
145 """
146 parse_launch(launch_file, file_deps, verbose)
147
149 name = NAME
150 print """Usage:
151 \t%(name)s [options] <file-or-package>
152 """%locals()
153
155 pkgs = []
156
157
158 if verbose:
159 for f, deps in file_deps.iteritems():
160 for p, t in deps.nodes:
161 print "%s [%s/%s]"%(p, p, t)
162
163 pkg_dir, pkg = roslib.packages.get_dir_pkg(os.path.dirname(os.path.abspath(f)))
164 if pkg is None:
165 print >> sys.stderr, "ERROR: cannot determine package for [%s]"%pkg
166 else:
167 print "%s [%s]"%(pkg, f)
168 print '-'*80
169
170
171 pkgs = []
172 for deps in file_deps.itervalues():
173 pkgs.extend(deps.pkgs)
174
175 print ' '.join([p for p in set(pkgs)])
176
178 """
179 Calculate missing package dependencies in the manifest. This is
180 mainly used as a subroutine of roslaunch_deps().
181
182 @param base_pkg: name of package where initial walk begins (unused).
183 @type base_pkg: str
184 @param missing: dictionary mapping package names to set of missing package dependencies.
185 @type missing: { str: set(str) }
186 @param file_deps: dictionary mapping launch file names to RoslaunchDeps of each file
187 @type file_deps: { str: RoslaunchDeps}
188 @return: missing (see parameter)
189 @rtype: { str: set(str) }
190 """
191 for launch_file in file_deps.iterkeys():
192 pkg_dir, pkg = roslib.packages.get_dir_pkg(os.path.dirname(os.path.abspath(launch_file)))
193
194 if pkg is None:
195 print >> sys.stderr, "ERROR: cannot determine package for [%s]"%pkg
196 continue
197 m_file = roslib.manifest.manifest_file(pkg)
198 m = roslib.manifest.parse_file(m_file)
199 d_pkgs = set([d.package for d in m.depends])
200
201 d_pkgs.add(pkg)
202
203
204 diff = list(set(file_deps[launch_file].pkgs) - d_pkgs)
205 if not pkg in missing:
206 missing[pkg] = set()
207 missing[pkg].update(diff)
208 return missing
209
210
212 """
213 @param packages: list of packages to check
214 @type packages: [str]
215 @param files [str]: list of roslaunch files to check. Must be in
216 same package.
217 @type files: [str]
218 @return: base_pkg, file_deps, missing.
219 base_pkg is the package of all files
220 file_deps is a { filename : RoslaunchDeps } dictionary of
221 roslaunch dependency information to update, indexed by roslaunch
222 file name.
223 missing is a { package : [packages] } dictionary of missing
224 manifest dependencies, indexed by package.
225 @rtype: str, dict, dict
226 """
227 file_deps = {}
228 missing = {}
229 base_pkg = None
230
231 for launch_file in files:
232 if not os.path.exists(launch_file):
233 raise RoslaunchDepsException("roslaunch file [%s] does not exist"%launch_file)
234
235 pkg_dir, pkg = roslib.packages.get_dir_pkg(os.path.dirname(os.path.abspath(launch_file)))
236 if base_pkg and pkg != base_pkg:
237 raise RoslaunchDepsException("roslaunch files must be in the same package: %s vs. %s"%(base_pkg, pkg))
238 base_pkg = pkg
239 rl_file_deps(file_deps, launch_file, verbose)
240
241 calculate_missing(base_pkg, missing, file_deps)
242 return base_pkg, file_deps, missing
243
244 -def roslaunch_deps_main(argv=sys.argv):
245 from optparse import OptionParser
246 parser = OptionParser(usage="usage: %prog [options] <file(s)...>", prog=NAME)
247 parser.add_option("--verbose", "-v", action="store_true",
248 dest="verbose", default=False,
249 help="Verbose output")
250 parser.add_option("--warn", "-w", action="store_true",
251 dest="warn", default=False,
252 help="Warn about missing manifest dependencies")
253
254 (options, args) = parser.parse_args(argv[1:])
255 if not args:
256 parser.error('please specify a launch file')
257
258 files = [arg for arg in args if os.path.exists(arg)]
259 packages = [arg for arg in args if not os.path.exists(arg)]
260 if packages:
261 parser.error("cannot located %s"%','.join(packages))
262 try:
263 base_pkg, file_deps, missing = roslaunch_deps(files, verbose=options.verbose)
264 except RoslaunchDepsException, e:
265 print >> sys.stderr, str(e)
266 sys.exit(1)
267
268 if options.warn:
269 print "Dependencies:"
270
271 print_deps(base_pkg, file_deps, options.verbose)
272
273 if options.warn:
274 print '\nMissing declarations:'
275 for pkg, miss in missing.iteritems():
276 if miss:
277 print "%s/manifest.xml:"%pkg
278 for m in miss:
279 print ' <depend package="%s" />'%m
280
281 if __name__ == '__main__':
282 main()
283