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
188 if 'ROS_BUILD' in os.environ:
189 rospack = os.path.join(os.environ['ROS_BUILD'], 'bin', 'rospack')
190
191 if ros_package_path is not None:
192 ros_package_path = roslib.rosenv.resolve_paths(ros_package_path)
193 penv[ROS_PACKAGE_PATH] = ros_package_path
194 elif ROS_PACKAGE_PATH in os.environ:
195
196 ros_package_path = os.environ[ROS_PACKAGE_PATH]
197
198
199 if not _pkg_dir_cache:
200 _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path)
201
202
203 if package in _pkg_dir_cache:
204 dir_, rr, rpp = _pkg_dir_cache[package]
205 if rr == ros_root and rpp == ros_package_path:
206 if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)):
207 return dir_
208 else:
209
210 _invalidate_cache(_pkg_dir_cache)
211
212 rpout, rperr = Popen([rospack, 'find', package], \
213 stdout=PIPE, stderr=PIPE, env=penv).communicate()
214
215 pkg_dir = (rpout or '').strip()
216
217 if (isinstance(pkg_dir, bytes)):
218 pkg_dir = pkg_dir.decode()
219 if not pkg_dir:
220 raise InvalidROSPkgException("Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, rperr.strip(), ros_root, ros_package_path))
221
222 if not os.path.exists(pkg_dir):
223 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))
224 elif not os.path.isdir(pkg_dir):
225 raise InvalidROSPkgException("Package %s is invalid: file [%s] is in the way"%(package, pkg_dir))
226
227
228
229 return pkg_dir
230 except OSError as e:
231 if required:
232 raise InvalidROSPkgException("Environment configuration is invalid: cannot locate rospack (%s)"%e)
233 return None
234 except Exception as e:
235 if required:
236 raise
237 return None
238
240 """
241 @param required: if True, will attempt to create the subdirectory
242 if it does not exist. An exception will be raised if this fails.
243 @type required: bool
244 @param package_dir: directory of package
245 @type package_dir: str
246 @param subdir: name of subdirectory to locate
247 @type subdir: str
248 @param env: override os.environ dictionary
249 @type env: dict
250 @param required: if True, directory must exist
251 @type required: bool
252 @return: Package subdirectory if package exist, otherwise None.
253 @rtype: str
254 @raise InvalidROSPkgException: if required is True and directory does not exist
255 """
256 if env is None:
257 env = os.environ
258 try:
259 if not package_dir:
260 raise Exception("Cannot create a '%(subdir)s' directory in %(package_dir)s: package %(package) cannot be located"%locals())
261 d = os.path.join(package_dir, subdir)
262 if required and os.path.isfile(d):
263 raise Exception("""Package '%(package)s' is improperly configured:
264 file %(d)s is preventing the creation of a directory"""%locals())
265 elif required and not os.path.isdir(d):
266 try:
267 os.makedirs(d)
268 except error:
269 raise Exception("""Package '%(package)s' is improperly configured:
270 Cannot create a '%(subdir)s' directory in %(package_dir)s.
271 Please check permissions and try again.
272 """%locals())
273 return d
274 except Exception as e:
275 if required:
276 raise
277 return None
278
280 """
281 @param required: if True, will attempt to create the subdirectory
282 if it does not exist. An exception will be raised if this fails.
283 @type required: bool
284 @param package: name of package
285 @type package: str
286 @param env: override os.environ dictionary
287 @type env: dict
288 @param required: if True, directory must exist
289 @type required: bool
290 @return: Package subdirectory if package exist, otherwise None.
291 @rtype: str
292 @raise InvalidROSPkgException: if required is True and directory does not exist
293 """
294 if env is None:
295 env = os.environ
296 pkg_dir = get_pkg_dir(package, required, ros_root=env[ROS_ROOT])
297 return _get_pkg_subdir_by_dir(pkg_dir, subdir, required, env)
298
299
300
301
302
304 """
305 @param subdir: name of subdir -- these should be one of the
306 string constants, e.g. MSG_DIR
307 @type subdir: str
308 @return: path to resource in the specified subdirectory of the
309 package, or None if the package does not exists
310 @rtype: str
311 @raise roslib.packages.InvalidROSPkgException: If package does not exist
312 """
313 d = get_pkg_subdir(package, subdir, False)
314 if d is None:
315 raise InvalidROSPkgException(package)
316 return os.path.join(d, resource_name)
317
319 """
320 Internal routine to update global package directory cache
321
322 @return: True if cache is valid
323 @rtype: bool
324 """
325 if env is None:
326 env = os.environ
327 cache = _pkg_dir_cache
328 if cache:
329 return True
330 ros_root = env[ROS_ROOT]
331 ros_package_path = env.get(ROS_PACKAGE_PATH, '')
332 return _read_rospack_cache(cache, ros_root, ros_package_path)
333
335
336
337 cache.clear()
338
340 """
341 Read in rospack_cache data into cache. On-disk cache specifies a
342 ROS_ROOT and ROS_PACKAGE_PATH, which must match the requested
343 environment.
344
345 @param cache: empty dictionary to store package list in.
346 If no cache argument provided, list_pkgs() will use internal _pkg_dir_cache
347 and will return cached answers if available.
348 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}.
349 @type cache: {str: str, str, str}
350 @param ros_package_path: ROS_ROOT value
351 @type ros_root: str
352 @param ros_package_path: ROS_PACKAGE_PATH value or '' if not specified
353 @type ros_package_path: str
354 @return: True if on-disk cache matches and was loaded, false otherwise
355 @rtype: bool
356 """
357 try:
358 with open(os.path.join(roslib.rosenv.get_ros_home(), 'rospack_cache')) as f:
359 for l in f.readlines():
360 l = l[:-1]
361 if not len(l):
362 continue
363 if l[0] == '#':
364
365 if l.startswith('#ROS_ROOT='):
366 if not l[len('#ROS_ROOT='):] == ros_root:
367 return False
368 elif l.startswith('#ROS_PACKAGE_PATH='):
369 if not l[len('#ROS_PACKAGE_PATH='):] == ros_package_path:
370 return False
371 else:
372 cache[os.path.basename(l)] = l, ros_root, ros_package_path
373 return True
374 except:
375 pass
376
378 """
379 List packages in ROS_ROOT and ROS_PACKAGE_PATH.
380
381 If no cache and pkg_dirs arguments are provided, list_pkgs() will
382 use internal _pkg_dir_cache and will return cached answers if
383 available.
384
385 @param cache: Empty dictionary to store package list in.
386 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}.
387 @type cache: {str: str, str, str}
388 @return: complete list of package names in ROS environment
389 @rtype: [str]
390 """
391 pkg_dirs = get_package_paths(True, env=env)
392 if cache is None:
393
394
395
396
397
398
399
400
401 cache = _pkg_dir_cache
402 if cache:
403 return list(cache.keys())
404 if _update_rospack_cache(env=env):
405 return list(cache.keys())
406 packages = []
407 for pkg_root in pkg_dirs:
408 list_pkgs_by_path(pkg_root, packages, cache=cache, env=env)
409 return packages
410
412 """
413 List ROS packages within the specified path.
414
415 Optionally, a cache dictionary can be provided, which will be
416 updated with the package->path mappings. list_pkgs_by_path() does
417 NOT returned cached results -- it only updates the cache.
418
419 @param path: path to list packages in
420 @type path: str
421 @param packages: list of packages to append to. If package is
422 already present in packages, it will be ignored.
423 @type packages: [str]
424 @param cache: (optional) package path cache to update. Maps package name to directory path.
425 @type cache: {str: str}
426 @return: complete list of package names in ROS environment. Same as packages parameter.
427 @rtype: [str]
428 """
429 if packages is None:
430 packages = []
431 if env is None:
432 env = os.environ
433
434 ros_root = env[ROS_ROOT]
435 ros_package_path = env.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
480 if 'ROS_BUILD' in os.environ:
481 tst = os.path.join(os.environ['ROS_BUILD'], 'bin', node_type)
482 if os.path.isfile(tst):
483 return tst
484
485 d = get_pkg_dir(pkg, required=True, \
486 ros_root=ros_root, ros_package_path=ros_package_path)
487
488
489 if sys.platform in ['win32', 'cygwin']:
490
491
492
493
494
495
496
497
498
499
500
501
502 node_type = node_type.lower()
503 matches = [node_type, node_type+'.exe', node_type+'.bat']
504 for p, dirs, files in os.walk(d):
505
506 files = [f.lower() for f in files]
507 for m in matches:
508 if m in files:
509 test_path = os.path.join(p, m)
510 s = os.stat(test_path)
511 if (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) ==
512 (stat.S_IRUSR | stat.S_IXUSR)):
513 return test_path
514 if '.svn' in dirs:
515 dirs.remove('.svn')
516 elif '.git' in dirs:
517 dirs.remove('.git')
518 else:
519
520 for p, dirs, files in os.walk(d):
521 if node_type in files:
522 test_path = os.path.join(p, node_type)
523 s = os.stat(test_path)
524 if (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) ==
525 (stat.S_IRUSR | stat.S_IXUSR)):
526 return test_path
527 if '.svn' in dirs:
528 dirs.remove('.svn')
529 elif '.git' in dirs:
530 dirs.remove('.git')
531
532 -def find_resource(pkg, resource_name, filter_fn=None, ros_root=None, ros_package_path=None):
533 """
534 Locate the file named resource_name in package, optionally
535 matching specified filter
536 @param filter: function that takes in a path argument and
537 returns True if the it matches the desired resource
538 @type filter: fn(str)
539 @param ros_root: if specified, override ROS_ROOT
540 @type ros_root: str
541 @param ros_package_path: if specified, override ROS_PACKAGE_PATH
542 @type ros_package_path: str
543 @return: lists of matching paths for resource
544 @rtype: [str]
545 @raise roslib.packages.InvalidROSPkgException: If package does not exist
546 """
547 d = get_pkg_dir(pkg, required=True, \
548 ros_root=ros_root, ros_package_path=ros_package_path)
549
550 matches = []
551 node_exe = None
552 for p, dirs, files in os.walk(d):
553 if resource_name in files:
554 test_path = os.path.join(p, resource_name)
555 if filter_fn is not None:
556 if filter_fn(test_path):
557 matches.append(test_path)
558 else:
559 matches.append(test_path)
560 if '.svn' in dirs:
561 dirs.remove('.svn')
562 elif '.git' in dirs:
563 dirs.remove('.git')
564 return matches
565
567 """
568 Collect all rosdeps of specified packages into a dictionary.
569 @param packages: package names
570 @type packages: [str]
571 @return: dictionary mapping package names to list of rosdep names.
572 @rtype: {str: [str]}
573 """
574 if not type(packages) in [list, tuple]:
575 raise TypeError("packages must be list or tuple")
576 _update_rospack_cache()
577 from roslib.manifest import load_manifest
578 manifests = [load_manifest(p) for p in packages]
579 map = {}
580 for pkg, m in zip(packages, manifests):
581 map[pkg] = [d.name for d in m.rosdeps]
582 return map
583
592
594 """
595 UNSTABLE/EXPERIMENTAL
596
597 Utility class for querying properties about ROS packages. This
598 should be used when querying properties about multiple
599 packages. ROSPackages caches information about packages, which
600 enables it to have higher performance than alternatives like
601 shelling out to rospack.
602
603 Example::
604 rp = ROSPackages()
605 d = rp.depends1(['roscpp', 'rospy'])
606 print d['roscpp']
607 d = rp.rosdeps(['roscpp', 'rospy'])
608 print d['rospy']
609 """
610
612 self.manifests = {}
613 self._depends_cache = {}
614 self._rosdeps_cache = {}
615
617 """
618 Load manifests for specified packages into 'manifests' attribute.
619
620
621 @param packages: package names
622 @type packages: [str]
623 """
624
625 if not type(packages) in [list, tuple]:
626 raise TypeError("packages must be list or tuple")
627
628
629 to_load = [p for p in packages if not p in self.manifests]
630 if to_load:
631 _update_rospack_cache()
632 self.manifests.update(dict([(p, _safe_load_manifest(p)) for p in to_load]))
633
635 """
636 Collect all direct dependencies of specified packages into a
637 dictionary.
638
639 @param packages: package names
640 @type packages: [str]
641 @return: dictionary mapping package names to list of dependent package names.
642 @rtype: {str: [str]}
643 """
644 self.load_manifests(packages)
645 map = {}
646 manifests = self.manifests
647 for pkg in packages:
648 map[pkg] = [d.package for d in manifests[pkg].depends]
649 return map
650
652 """
653 Collect all dependencies of specified packages into a
654 dictionary.
655
656 @param packages: package names
657 @type packages: [str]
658 @return: dictionary mapping package names to list of dependent package names.
659 @rtype: {str: [str]}
660 """
661
662 self.load_manifests(packages)
663 map = {}
664 for pkg in packages:
665 if pkg in self._depends_cache:
666 map[pkg] = self._depends_cache[pkg]
667 else:
668
669 map[pkg] = self._depends(pkg)
670 return map
671
673 """
674 Compute recursive dependencies of a single package and cache
675 the result in self._depends_cache.
676
677 This is an internal routine. It assumes that
678 load_manifests() has already been invoked for package.
679
680 @param package: package name
681 @type package: str
682 @return: list of rosdeps
683 @rtype: [str]
684 """
685
686 if package in self._depends_cache:
687 return self._depends_cache[package]
688
689
690 self._depends_cache[package] = s = set()
691
692 manifests = self.manifests
693
694 pkgs = [p.package for p in manifests[package].depends]
695 self.load_manifests(pkgs)
696 for p in pkgs:
697 s.update(self._depends(p))
698
699 s.update(pkgs)
700
701 s = list(s)
702 self._depends_cache[package] = s
703 return s
704
706 """
707 Collect rosdeps of specified packages into a dictionary.
708 @param packages: package names
709 @type packages: [str]
710 @return: dictionary mapping package names to list of rosdep names.
711 @rtype: {str: [str]}
712 """
713
714 self.load_manifests(packages)
715 map = {}
716 manifests = self.manifests
717 for pkg in packages:
718 map[pkg] = [d.name for d in manifests[pkg].rosdeps]
719 return map
720
722 """
723 Collect all (recursive) dependencies of specified packages
724 into a dictionary.
725
726 @param packages: package names
727 @type packages: [str]
728 @return: dictionary mapping package names to list of dependent package names.
729 @rtype: {str: [str]}
730 """
731
732 self.load_manifests(packages)
733 map = {}
734 for pkg in packages:
735 if pkg in self._rosdeps_cache:
736 map[pkg] = self._rosdeps_cache[pkg]
737 else:
738
739 map[pkg] = self._rosdeps(pkg)
740 return map
741
743 """
744 Compute recursive rosdeps of a single package and cache the
745 result in self._rosdeps_cache.
746
747 This is an internal routine. It assumes that
748 load_manifests() has already been invoked for package.
749
750 @param package: package name
751 @type package: str
752 @return: list of rosdeps
753 @rtype: [str]
754 """
755
756 if package in self._rosdeps_cache:
757 return self._rosdeps_cache[package]
758
759 self._rosdeps_cache[package] = s = set()
760
761 manifests = self.manifests
762
763 pkgs = [p.package for p in manifests[package].depends]
764 self.load_manifests(pkgs)
765 for p in pkgs:
766 s.update(self._rosdeps(p))
767
768 s.update([d.name for d in manifests[package].rosdeps])
769
770 s = list(s)
771 self._rosdeps_cache[package] = s
772 return s
773
780
789
797