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 """
37 Python utilities for manipulating ROS packages.
38 See: U{http://ros.org/wiki/Packages}
39
40 Warning: while most of this API is stable, some parts are still fairly
41 experimental and incomplete. In particular, the L{ROSPackages} class
42 in very experimental.
43 """
44
45 from __future__ import with_statement
46
47 import os
48 import sys
49 import stat
50 import string
51
52 from subprocess import Popen, PIPE
53
54 import roslib.exceptions
55 import roslib.manifest
56 import roslib.names
57 import roslib.rosenv
58 import roslib.os_detect
59
60 MSG_DIR = 'msg'
61 SRV_DIR = 'srv'
62 SRC_DIR = 'src'
63
64
65 ROS_PACKAGE_PATH = roslib.rosenv.ROS_PACKAGE_PATH
66 ROS_ROOT = roslib.rosenv.ROS_ROOT
67
69 """
70 Base class of package-related errors.
71 """
72 pass
74 """
75 Exception that indicates that a ROS package does not exist
76 """
77 pass
79 """
80 Exception that indicates that multiple ROS nodes by the same name are in the same package.
81 """
82 pass
83
84
85
86 MANIFEST_FILE = 'manifest.xml'
87
88
89
90
91
93 """
94 @param d: directory location
95 @type d: str
96 @return: True if d is the root directory of a ROS Package
97 @rtype: bool
98 """
99 return os.path.isfile(os.path.join(d, MANIFEST_FILE))
100
102 """
103 Get the paths to search for packages
104
105 @param ros_root_required: if True, raise exception if
106 environment is invalid (i.e. ROS_ROOT is not set properly)
107 @type ros_root_required: bool
108 @param env: override os.environ dictionary
109 @type env: dict
110 @raise roslib.rosenv.ROSEnvException: if ros_root_required is True
111 and ROS_ROOT is not set
112 """
113 if env is None:
114 env = os.environ
115 rpp = roslib.rosenv.get_ros_package_path(required=False, env=env)
116 if rpp:
117 paths = [x for x in rpp.split(os.pathsep) if x]
118 else:
119 paths = []
120 rr_path = roslib.rosenv.get_ros_root(required=ros_root_required, env=env)
121 if rr_path:
122 return paths + [rr_path]
123 else:
124 return paths
125
127 """
128 Get the package that the directory is contained within. This is
129 determined by finding the nearest parent manifest.xml file. This
130 isn't 100% reliable, but symlinks can full any heuristic that
131 relies on ROS_ROOT.
132 @param d: directory path
133 @type d: str
134 @return: (package_directory, package) of the specified directory, or None,None if not in a package
135 @rtype: (str, str)
136 """
137
138
139 parent = os.path.dirname(os.path.realpath(d))
140
141 while not os.path.exists(os.path.join(d, MANIFEST_FILE)) and parent != d:
142 d = parent
143 parent = os.path.dirname(d)
144 if os.path.exists(os.path.join(d, MANIFEST_FILE)):
145 pkg = os.path.basename(os.path.abspath(d))
146 return d, pkg
147 return None, None
148
149 _pkg_dir_cache = {}
150
151 -def get_pkg_dir(package, required=True, ros_root=None, ros_package_path=None):
152 """
153 Locate directory package is stored in. This routine uses an
154 internal cache.
155
156 NOTE: cache does *not* rebuild if packages are relocated after
157 this process is initiated.
158
159 @param package: package name
160 @type package: str
161 @param required: if True, an exception will be raised if the
162 package directory cannot be located.
163 @type required: bool
164 @param ros_root: if specified, override ROS_ROOT
165 @type ros_root: str
166 @param ros_package_path: if specified, override ROS_PACKAGE_PATH
167 @type ros_package_path: str
168 @return: directory containing package or None if package cannot be found and required is False.
169 @rtype: str
170 @raise InvalidROSPkgException: if required is True and package cannot be located
171 """
172
173
174
175 try:
176 penv = os.environ.copy()
177 if ros_root:
178 ros_root = roslib.rosenv.resolve_path(ros_root)
179 penv[ROS_ROOT] = ros_root
180 elif ROS_ROOT in os.environ:
181
182 ros_root = os.environ[ROS_ROOT]
183 if ros_root:
184 rospack = os.path.join(ros_root, 'bin', 'rospack')
185 else:
186 rospack = 'rospack'
187 if ros_package_path is not None:
188 ros_package_path = roslib.rosenv.resolve_paths(ros_package_path)
189 penv[ROS_PACKAGE_PATH] = ros_package_path
190 elif ROS_PACKAGE_PATH in os.environ:
191
192 ros_package_path = os.environ[ROS_PACKAGE_PATH]
193
194
195 if not _pkg_dir_cache:
196 _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path)
197
198
199 if package in _pkg_dir_cache:
200 dir_, rr, rpp = _pkg_dir_cache[package]
201 if rr == ros_root and rpp == ros_package_path:
202 if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)):
203 return dir_
204 else:
205
206 _invalidate_cache(_pkg_dir_cache)
207
208 rpout, rperr = Popen([rospack, 'find', package], \
209 stdout=PIPE, stderr=PIPE, env=penv).communicate()
210
211 pkg_dir = (rpout or '').strip()
212 if not pkg_dir:
213 raise InvalidROSPkgException("Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, rperr.strip(), ros_root, ros_package_path))
214
215 if not os.path.exists(pkg_dir):
216 raise InvalidROSPkgException("Cannot locate installation of package %s: [%s] is not a valid path. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, pkg_dir, ros_root, ros_package_path))
217 elif not os.path.isdir(pkg_dir):
218 raise InvalidROSPkgException("Package %s is invalid: file [%s] is in the way"%(package, pkg_dir))
219
220
221
222 return pkg_dir
223 except OSError, e:
224 if required:
225 raise InvalidROSPkgException("Environment configuration is invalid: cannot locate rospack (%s)"%e)
226 return None
227 except Exception, e:
228 if required:
229 raise
230 return None
231
233 """
234 @param required: if True, will attempt to create the subdirectory
235 if it does not exist. An exception will be raised if this fails.
236 @type required: bool
237 @param package_dir: directory of package
238 @type package_dir: str
239 @param subdir: name of subdirectory to locate
240 @type subdir: str
241 @param env: override os.environ dictionary
242 @type env: dict
243 @param required: if True, directory must exist
244 @type required: bool
245 @return: Package subdirectory if package exist, otherwise None.
246 @rtype: str
247 @raise InvalidROSPkgException: if required is True and directory does not exist
248 """
249 if env is None:
250 env = os.environ
251 try:
252 if not package_dir:
253 raise Exception("Cannot create a '%(subdir)s' directory in %(package_dir)s: package %(package) cannot be located"%locals())
254 dir = os.path.join(package_dir, subdir)
255 if required and os.path.isfile(dir):
256 raise Exception("""Package '%(package)s' is improperly configured:
257 file %(dir)s is preventing the creation of a directory"""%locals())
258 elif required and not os.path.isdir(dir):
259 try:
260 os.makedirs(dir)
261 except error:
262 raise Exception("""Package '%(package)s' is improperly configured:
263 Cannot create a '%(subdir)s' directory in %(package_dir)s.
264 Please check permissions and try again.
265 """%locals())
266 return dir
267 except Exception, e:
268 if required:
269 raise
270 return None
271
273 """
274 @param required: if True, will attempt to create the subdirectory
275 if it does not exist. An exception will be raised if this fails.
276 @type required: bool
277 @param package: name of package
278 @type package: str
279 @param env: override os.environ dictionary
280 @type env: dict
281 @param required: if True, directory must exist
282 @type required: bool
283 @return: Package subdirectory if package exist, otherwise None.
284 @rtype: str
285 @raise InvalidROSPkgException: if required is True and directory does not exist
286 """
287 if env is None:
288 env = os.environ
289 pkg_dir = get_pkg_dir(package, required, ros_root=env[ROS_ROOT])
290 return _get_pkg_subdir_by_dir(pkg_dir, subdir, required, env)
291
292
293
294
295
297 """
298 @param subdir: name of subdir -- these should be one of the
299 string constants, e.g. MSG_DIR
300 @type subdir: str
301 @return: path to resource in the specified subdirectory of the
302 package, or None if the package does not exists
303 @rtype: str
304 @raise roslib.packages.InvalidROSPkgException: If package does not exist
305 """
306 d = get_pkg_subdir(package, subdir, False)
307 if d is None:
308 raise InvalidROSPkgException(package)
309 return os.path.join(d, resource_name)
310
312 """
313 Internal routine to update global package directory cache
314
315 @return: True if cache is valid
316 @rtype: bool
317 """
318 cache = _pkg_dir_cache
319 if cache:
320 return True
321 ros_root = os.environ[ROS_ROOT]
322 ros_package_path = os.environ.get(ROS_PACKAGE_PATH, '')
323 return _read_rospack_cache(cache, ros_root, ros_package_path)
324
326
327
328 cache.clear()
329
331 """
332 Read in rospack_cache data into cache. On-disk cache specifies a
333 ROS_ROOT and ROS_PACKAGE_PATH, which must match the requested
334 environment.
335
336 @param cache: empty dictionary to store package list in.
337 If no cache argument provided, list_pkgs() will use internal _pkg_dir_cache
338 and will return cached answers if available.
339 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}.
340 @type cache: {str: str, str, str}
341 @param ros_package_path: ROS_ROOT value
342 @type ros_root: str
343 @param ros_package_path: ROS_PACKAGE_PATH value or '' if not specified
344 @type ros_package_path: str
345 @return: True if on-disk cache matches and was loaded, false otherwise
346 @rtype: bool
347 """
348 try:
349 with open(os.path.join(roslib.rosenv.get_ros_home(), 'rospack_cache')) as f:
350 for l in f.readlines():
351 l = l[:-1]
352 if not len(l):
353 continue
354 if l[0] == '#':
355
356 if l.startswith('#ROS_ROOT='):
357 if not l[len('#ROS_ROOT='):] == ros_root:
358 return False
359 elif l.startswith('#ROS_PACKAGE_PATH='):
360 if not l[len('#ROS_PACKAGE_PATH='):] == ros_package_path:
361 return False
362 else:
363 cache[os.path.basename(l)] = l, ros_root, ros_package_path
364 return True
365 except:
366 pass
367
369 """
370 List packages in ROS_ROOT and ROS_PACKAGE_PATH.
371
372 If no cache and pkg_dirs arguments are provided, list_pkgs() will
373 use internal _pkg_dir_cache and will return cached answers if
374 available.
375
376 NOTE: use of pkg_dirs argument is DEPRECATED. Use
377 list_pkgs_by_path() instead, which has clearer meaning with the
378 cache.
379
380 @param pkg_dirs: (optional) list of paths to search for packages
381 @type pkg_dirs: [str]
382
383 @param cache: Empty dictionary to store package list in.
384 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}.
385 @type cache: {str: str, str, str}
386 @return: complete list of package names in ROS environment
387 @rtype: [str]
388 """
389 if pkg_dirs is None:
390 pkg_dirs = get_package_paths(True)
391 if cache is None:
392
393
394
395
396
397
398
399
400 cache = _pkg_dir_cache
401 if cache:
402 return cache.keys()
403 if _update_rospack_cache():
404 return cache.keys()
405 else:
406 import warnings
407 warnings.warn("pkg_dirs argument is deprecated. Please use list_pkgs_by_path() instead", DeprecationWarning, stacklevel=2)
408 packages = []
409 for pkg_root in pkg_dirs:
410 list_pkgs_by_path(pkg_root, packages, cache=cache)
411 return packages
412
414 """
415 List ROS packages within the specified path.
416
417 Optionally, a cache dictionary can be provided, which will be
418 updated with the package->path mappings. list_pkgs_by_path() does
419 NOT returned cached results -- it only updates the cache.
420
421 @param path: path to list packages in
422 @type path: str
423 @param packages: list of packages to append to. If package is
424 already present in packages, it will be ignored.
425 @type packages: [str]
426 @param cache: (optional) package path cache to update. Maps package name to directory path.
427 @type cache: {str: str}
428 @return: complete list of package names in ROS environment. Same as packages parameter.
429 @rtype: [str]
430 """
431 if packages is None:
432 packages = []
433
434 ros_root = os.environ[ROS_ROOT]
435 ros_package_path = os.environ.get(ROS_PACKAGE_PATH, '')
436
437 path = os.path.abspath(path)
438 for d, dirs, files in os.walk(path, topdown=True):
439 if MANIFEST_FILE in files:
440 package = os.path.basename(d)
441 if package not in packages:
442 packages.append(package)
443 if cache is not None:
444 cache[package] = d, ros_root, ros_package_path
445 del dirs[:]
446 continue
447 elif 'rospack_nosubdirs' in files:
448 del dirs[:]
449 continue
450
451 elif '.svn' in dirs:
452 dirs.remove('.svn')
453 elif '.git' in dirs:
454 dirs.remove('.git')
455
456 for sub_d in dirs:
457
458
459 sub_p = os.path.join(d, sub_d)
460 if os.path.islink(sub_p):
461 packages.extend(list_pkgs_by_path(sub_p, cache=cache))
462
463 return packages
464
465 -def find_node(pkg, node_type, ros_root=None, ros_package_path=None):
466 """
467 Locate the executable that implements the node
468
469 @param node_type: type of node
470 @type node_type: str
471 @param ros_root: if specified, override ROS_ROOT
472 @type ros_root: str
473 @param ros_package_path: if specified, override ROS_PACKAGE_PATH
474 @type ros_package_path: str
475 @return: path to node or None if node is not in the package
476 @rtype: str
477 @raise roslib.packages.InvalidROSPkgException: If package does not exist
478 """
479 dir = get_pkg_dir(pkg, required=True, \
480 ros_root=ros_root, ros_package_path=ros_package_path)
481
482
483 if sys.platform in ['win32', 'cygwin']:
484
485
486
487
488
489
490
491
492
493
494
495
496 node_type = node_type.lower()
497 matches = [node_type, node_type+'.exe', node_type+'.bat']
498 for p, dirs, files in os.walk(dir):
499
500 files = [f.lower() for f in files]
501 for m in matches:
502 if m in files:
503 test_path = os.path.join(p, m)
504 s = os.stat(test_path)
505 if (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) ==
506 (stat.S_IRUSR | stat.S_IXUSR)):
507 return test_path
508 if '.svn' in dirs:
509 dirs.remove('.svn')
510 elif '.git' in dirs:
511 dirs.remove('.git')
512 else:
513
514 for p, dirs, files in os.walk(dir):
515 if node_type in files:
516 test_path = os.path.join(p, node_type)
517 s = os.stat(test_path)
518 if (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) ==
519 (stat.S_IRUSR | stat.S_IXUSR)):
520 return test_path
521 if '.svn' in dirs:
522 dirs.remove('.svn')
523 elif '.git' in dirs:
524 dirs.remove('.git')
525
526 -def find_resource(pkg, resource_name, filter_fn=None, ros_root=None, ros_package_path=None):
527 """
528 Locate the file named resource_name in package, optionally
529 matching specified filter
530 @param filter: function that takes in a path argument and
531 returns True if the it matches the desired resource
532 @type filter: fn(str)
533 @param ros_root: if specified, override ROS_ROOT
534 @type ros_root: str
535 @param ros_package_path: if specified, override ROS_PACKAGE_PATH
536 @type ros_package_path: str
537 @return: lists of matching paths for resource
538 @rtype: [str]
539 @raise roslib.packages.InvalidROSPkgException: If package does not exist
540 """
541 dir = get_pkg_dir(pkg, required=True, \
542 ros_root=ros_root, ros_package_path=ros_package_path)
543
544 matches = []
545 node_exe = None
546 for p, dirs, files in os.walk(dir):
547 if resource_name in files:
548 test_path = os.path.join(p, resource_name)
549 if filter_fn is not None:
550 if filter_fn(test_path):
551 matches.append(test_path)
552 else:
553 matches.append(test_path)
554 if '.svn' in dirs:
555 dirs.remove('.svn')
556 elif '.git' in dirs:
557 dirs.remove('.git')
558 return matches
559
561 """
562 Collect all rosdeps of specified packages into a dictionary.
563 @param packages: package names
564 @type packages: [str]
565 @return: dictionary mapping package names to list of rosdep names.
566 @rtype: {str: [str]}
567 """
568 if not type(packages) in [list, tuple]:
569 raise TypeError("packages must be list or tuple")
570 _update_rospack_cache()
571 from roslib.manifest import load_manifest
572 manifests = [load_manifest(p) for p in packages]
573 import itertools
574 map = {}
575 for pkg, m in itertools.izip(packages, manifests):
576 map[pkg] = [d.name for d in m.rosdeps]
577 return map
578
587
589 """
590 UNSTABLE/EXPERIMENTAL
591
592 Utility class for querying properties about ROS packages. This
593 should be used when querying properties about multiple
594 packages. ROSPackages caches information about packages, which
595 enables it to have higher performance than alternatives like
596 shelling out to rospack.
597
598 Example::
599 rp = ROSPackages()
600 d = rp.depends1(['roscpp', 'rospy'])
601 print d['roscpp']
602 d = rp.rosdeps(['roscpp', 'rospy'])
603 print d['rospy']
604 """
605
607 self.manifests = {}
608 self._depends_cache = {}
609 self._rosdeps_cache = {}
610
612 """
613 Load manifests for specified packages into 'manifests' attribute.
614
615
616 @param packages: package names
617 @type packages: [str]
618 """
619
620 if not type(packages) in [list, tuple]:
621 raise TypeError("packages must be list or tuple")
622
623
624 to_load = [p for p in packages if not p in self.manifests]
625 if to_load:
626 _update_rospack_cache()
627 self.manifests.update(dict([(p, _safe_load_manifest(p)) for p in to_load]))
628
630 """
631 Collect all direct dependencies of specified packages into a
632 dictionary.
633
634 @param packages: package names
635 @type packages: [str]
636 @return: dictionary mapping package names to list of dependent package names.
637 @rtype: {str: [str]}
638 """
639 self.load_manifests(packages)
640 map = {}
641 manifests = self.manifests
642 for pkg in packages:
643 map[pkg] = [d.package for d in manifests[pkg].depends]
644 return map
645
647 """
648 Collect all dependencies of specified packages into a
649 dictionary.
650
651 @param packages: package names
652 @type packages: [str]
653 @return: dictionary mapping package names to list of dependent package names.
654 @rtype: {str: [str]}
655 """
656
657 self.load_manifests(packages)
658 map = {}
659 for pkg in packages:
660 if pkg in self._depends_cache:
661 map[pkg] = self._depends_cache[pkg]
662 else:
663
664 map[pkg] = self._depends(pkg)
665 return map
666
668 """
669 Compute recursive dependencies of a single package and cache
670 the result in self._depends_cache.
671
672 This is an internal routine. It assumes that
673 load_manifests() has already been invoked for package.
674
675 @param package: package name
676 @type package: str
677 @return: list of rosdeps
678 @rtype: [str]
679 """
680
681 if package in self._depends_cache:
682 return self._depends_cache[package]
683
684
685 self._depends_cache[package] = s = set()
686
687 manifests = self.manifests
688
689 pkgs = [p.package for p in manifests[package].depends]
690 self.load_manifests(pkgs)
691 for p in pkgs:
692 s.update(self._depends(p))
693
694 s.update(pkgs)
695
696 s = list(s)
697 self._depends_cache[package] = s
698 return s
699
701 """
702 Collect rosdeps of specified packages into a dictionary.
703 @param packages: package names
704 @type packages: [str]
705 @return: dictionary mapping package names to list of rosdep names.
706 @rtype: {str: [str]}
707 """
708
709 self.load_manifests(packages)
710 map = {}
711 manifests = self.manifests
712 for pkg in packages:
713 map[pkg] = [d.name for d in manifests[pkg].rosdeps]
714 return map
715
717 """
718 Collect all (recursive) dependencies of specified packages
719 into a dictionary.
720
721 @param packages: package names
722 @type packages: [str]
723 @return: dictionary mapping package names to list of dependent package names.
724 @rtype: {str: [str]}
725 """
726
727 self.load_manifests(packages)
728 map = {}
729 for pkg in packages:
730 if pkg in self._rosdeps_cache:
731 map[pkg] = self._rosdeps_cache[pkg]
732 else:
733
734 map[pkg] = self._rosdeps(pkg)
735 return map
736
738 """
739 Compute recursive rosdeps of a single package and cache the
740 result in self._rosdeps_cache.
741
742 This is an internal routine. It assumes that
743 load_manifests() has already been invoked for package.
744
745 @param package: package name
746 @type package: str
747 @return: list of rosdeps
748 @rtype: [str]
749 """
750
751 if package in self._rosdeps_cache:
752 return self._rosdeps_cache[package]
753
754 self._rosdeps_cache[package] = s = set()
755
756 manifests = self.manifests
757
758 pkgs = [p.package for p in manifests[package].depends]
759 self.load_manifests(pkgs)
760 for p in pkgs:
761 s.update(self._rosdeps(p))
762
763 s.update([d.name for d in manifests[package].rosdeps])
764
765 s = list(s)
766 self._rosdeps_cache[package] = s
767 return s
768
775
784
792