Package roslib :: Module packages
[frames] | no frames]

Source Code for Module roslib.packages

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id$ 
 34  # $Author$ 
 35   
 36  """ 
 37  Warning: do not use this library.  It is unstable and most of the routines 
 38  here have been superceded by other libraries (e.g. rospkg).  These 
 39  routines will likely be *deleted* in future releases. 
 40  """ 
 41   
 42  import os 
 43  import stat 
 44  import sys 
 45  from subprocess import PIPE 
 46  from subprocess import Popen 
 47   
 48  from catkin.find_in_workspaces import find_in_workspaces as catkin_find 
 49   
 50  import roslib.manifest  # noqa: F401 
 51   
 52  import rospkg 
 53   
 54  SRC_DIR = 'src' 
 55   
 56  # aliases 
 57  ROS_PACKAGE_PATH = rospkg.environment.ROS_PACKAGE_PATH 
 58  ROS_ROOT = rospkg.environment.ROS_ROOT 
 59   
 60   
61 -class ROSPkgException(Exception):
62 """ 63 Base class of package-related errors. 64 """ 65 pass
66 67
68 -class InvalidROSPkgException(ROSPkgException):
69 """ 70 Exception that indicates that a ROS package does not exist 71 """ 72 pass
73 74
75 -class MultipleNodesException(ROSPkgException):
76 """ 77 Exception that indicates that multiple ROS nodes by the same name are in the same package. 78 """ 79 pass
80 81 # TODO: go through the code and eliminate unused methods -- there's far too many combos here 82 83 84 MANIFEST_FILE = 'manifest.xml' 85 PACKAGE_FILE = 'package.xml' 86 87 88 # 89 # Map package/directory structure 90 # 91
92 -def get_dir_pkg(d):
93 """ 94 Get the package that the directory is contained within. This is 95 determined by finding the nearest parent manifest.xml file. This 96 isn't 100% reliable, but symlinks can fool any heuristic that 97 relies on ROS_ROOT. 98 @param d: directory path 99 @type d: str 100 @return: (package_directory, package) of the specified directory, or None,None if not in a package 101 @rtype: (str, str) 102 """ 103 # TODO: the realpath is going to create issues with symlinks, most likely 104 105 parent = os.path.dirname(os.path.realpath(d)) 106 # walk up until we hit ros root or ros/pkg 107 while not os.path.exists(os.path.join(d, MANIFEST_FILE)) and not os.path.exists(os.path.join(d, PACKAGE_FILE)) and parent != d: 108 d = parent 109 parent = os.path.dirname(d) 110 if os.path.exists(os.path.join(d, MANIFEST_FILE)) or os.path.exists(os.path.join(d, PACKAGE_FILE)): 111 pkg = os.path.basename(os.path.abspath(d)) 112 return d, pkg 113 return None, None
114 115 116 _pkg_dir_cache = {} 117 118
119 -def get_pkg_dir(package, required=True, ros_root=None, ros_package_path=None):
120 """ 121 Locate directory package is stored in. This routine uses an 122 internal cache. 123 124 NOTE: cache does *not* rebuild if packages are relocated after 125 this process is initiated. 126 127 @param package: package name 128 @type package: str 129 @param required: if True, an exception will be raised if the 130 package directory cannot be located. 131 @type required: bool 132 @param ros_root: if specified, override ROS_ROOT 133 @type ros_root: str 134 @param ros_package_path: if specified, override ROS_PACKAGE_PATH 135 @type ros_package_path: str 136 @return: directory containing package or None if package cannot be found and required is False. 137 @rtype: str 138 @raise InvalidROSPkgException: if required is True and package cannot be located 139 """ 140 141 # UNIXONLY 142 # TODO: replace with non-rospack-based solution (e.g. os.walk()) 143 try: 144 penv = os.environ.copy() 145 if ros_root: 146 ros_root = rospkg.environment._resolve_path(ros_root) 147 penv[ROS_ROOT] = ros_root 148 elif ROS_ROOT in os.environ: 149 # record setting for _pkg_dir_cache 150 ros_root = os.environ[ROS_ROOT] 151 152 # determine rospack exe name 153 rospack = 'rospack' 154 155 if ros_package_path is not None: 156 ros_package_path = rospkg.environment._resolve_paths(ros_package_path) 157 penv[ROS_PACKAGE_PATH] = ros_package_path 158 elif ROS_PACKAGE_PATH in os.environ: 159 # record setting for _pkg_dir_cache 160 ros_package_path = os.environ[ROS_PACKAGE_PATH] 161 162 # update cache if we haven't. NOTE: we only get one cache 163 if not _pkg_dir_cache: 164 _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path) 165 166 # now that we've resolved the args, check the cache 167 if package in _pkg_dir_cache: 168 dir_, rr, rpp = _pkg_dir_cache[package] 169 if rr == ros_root and rpp == ros_package_path: 170 if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)): 171 return dir_ 172 else: 173 # invalidate cache 174 _invalidate_cache(_pkg_dir_cache) 175 176 rpout, rperr = Popen([rospack, 'find', package], 177 stdout=PIPE, stderr=PIPE, env=penv).communicate() 178 179 pkg_dir = (rpout or '').strip() 180 # python3.1 popen returns as bytes 181 if (isinstance(pkg_dir, bytes)): 182 pkg_dir = pkg_dir.decode() 183 if not pkg_dir: 184 raise InvalidROSPkgException('Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]' % (package, rperr.strip(), ros_root, ros_package_path)) 185 186 pkg_dir = os.path.normpath(pkg_dir) 187 if not os.path.exists(pkg_dir): 188 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)) 189 elif not os.path.isdir(pkg_dir): 190 raise InvalidROSPkgException('Package %s is invalid: file [%s] is in the way' % (package, pkg_dir)) 191 # don't update cache: this should only be updated from 192 # rospack_cache as it will corrupt package list otherwise. 193 # _pkg_dir_cache[package] = (pkg_dir, ros_root, ros_package_path) 194 return pkg_dir 195 except OSError as e: 196 if required: 197 raise InvalidROSPkgException('Environment configuration is invalid: cannot locate rospack (%s)' % e) 198 return None 199 except Exception: 200 if required: 201 raise 202 return None
203 204
205 -def _get_pkg_subdir_by_dir(package_dir, subdir, required=True, env=None):
206 """ 207 @param required: if True, will attempt to create the subdirectory 208 if it does not exist. An exception will be raised if this fails. 209 @type required: bool 210 @param package_dir: directory of package 211 @type package_dir: str 212 @param subdir: name of subdirectory to locate 213 @type subdir: str 214 @param env: override os.environ dictionary 215 @type env: dict 216 @param required: if True, directory must exist 217 @type required: bool 218 @return: Package subdirectory if package exist, otherwise None. 219 @rtype: str 220 @raise InvalidROSPkgException: if required is True and directory does not exist 221 """ 222 if env is None: 223 env = os.environ 224 try: 225 if not package_dir: 226 raise Exception("Cannot create a '%(subdir)s' directory in %(package_dir)s: package %(package) cannot be located" % locals()) 227 d = os.path.join(package_dir, subdir) 228 if required and os.path.isfile(d): 229 raise Exception("""Package '%(package)s' is improperly configured: 230 file %(d)s is preventing the creation of a directory""" % locals()) 231 elif required and not os.path.isdir(d): 232 try: 233 os.makedirs(d) # lazy create 234 except os.error: 235 raise Exception("""Package '%(package)s' is improperly configured: 236 Cannot create a '%(subdir)s' directory in %(package_dir)s. 237 Please check permissions and try again. 238 """ % locals()) 239 return d 240 except Exception: 241 if required: 242 raise 243 return None
244 245
246 -def get_pkg_subdir(package, subdir, required=True, env=None):
247 """ 248 @param required: if True, will attempt to create the subdirectory 249 if it does not exist. An exception will be raised if this fails. 250 @type required: bool 251 @param package: name of package 252 @type package: str 253 @param env: override os.environ dictionary 254 @type env: dict 255 @param required: if True, directory must exist 256 @type required: bool 257 @return: Package subdirectory if package exist, otherwise None. 258 @rtype: str 259 @raise InvalidROSPkgException: if required is True and directory does not exist 260 """ 261 if env is None: 262 env = os.environ 263 pkg_dir = get_pkg_dir(package, required, ros_root=env[ROS_ROOT]) 264 return _get_pkg_subdir_by_dir(pkg_dir, subdir, required, env)
265 266 267 # 268 # Map ROS resources to files 269 # 270
271 -def resource_file(package, subdir, resource_name):
272 """ 273 @param subdir: name of subdir -- these should be one of the 274 string constants, e.g. MSG_DIR 275 @type subdir: str 276 @return: path to resource in the specified subdirectory of the 277 package, or None if the package does not exists 278 @rtype: str 279 @raise roslib.packages.InvalidROSPkgException: If package does not exist 280 """ 281 d = get_pkg_subdir(package, subdir, False) 282 if d is None: 283 raise InvalidROSPkgException(package) 284 return os.path.join(d, resource_name)
285 286
287 -def _update_rospack_cache(env=None):
288 """ 289 Internal routine to update global package directory cache 290 291 @return: True if cache is valid 292 @rtype: bool 293 """ 294 if env is None: 295 env = os.environ 296 cache = _pkg_dir_cache 297 if cache: 298 return True 299 ros_root = env[ROS_ROOT] 300 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 301 return _read_rospack_cache(cache, ros_root, ros_package_path)
302 303
304 -def _invalidate_cache(cache):
305 # I've only made this a separate routine because roslib.packages should really be using 306 # the roslib.stacks cache implementation instead with the separate cache marker 307 cache.clear()
308 309
310 -def _read_rospack_cache(cache, ros_root, ros_package_path):
311 """ 312 Read in rospack_cache data into cache. On-disk cache specifies a 313 ROS_ROOT and ROS_PACKAGE_PATH, which must match the requested 314 environment. 315 316 @param cache: empty dictionary to store package list in. 317 If no cache argument provided, will use internal _pkg_dir_cache 318 and will return cached answers if available. 319 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}. 320 @type cache: {str: str, str, str} 321 @param ros_package_path: ROS_ROOT value 322 @type ros_root: str 323 @param ros_package_path: ROS_PACKAGE_PATH value or '' if not specified 324 @type ros_package_path: str 325 @return: True if on-disk cache matches and was loaded, false otherwise 326 @rtype: bool 327 """ 328 try: 329 with open(os.path.join(rospkg.get_ros_home(), 'rospack_cache')) as f: 330 for l in f.readlines(): 331 l = l[:-1] 332 if not len(l): 333 continue 334 if l[0] == '#': 335 # check that the cache matches our env 336 if l.startswith('#ROS_ROOT='): 337 if not l[len('#ROS_ROOT='):] == ros_root: 338 return False 339 elif l.startswith('#ROS_PACKAGE_PATH='): 340 if not l[len('#ROS_PACKAGE_PATH='):] == ros_package_path: 341 return False 342 else: 343 cache[os.path.basename(l)] = l, ros_root, ros_package_path 344 return True 345 except Exception: 346 pass
347 348
349 -def list_pkgs_by_path(path, packages=None, cache=None, env=None):
350 """ 351 List ROS packages within the specified path. 352 353 Optionally, a cache dictionary can be provided, which will be 354 updated with the package->path mappings. list_pkgs_by_path() does 355 NOT returned cached results -- it only updates the cache. 356 357 @param path: path to list packages in 358 @type path: str 359 @param packages: list of packages to append to. If package is 360 already present in packages, it will be ignored. 361 @type packages: [str] 362 @param cache: (optional) package path cache to update. Maps package name to directory path. 363 @type cache: {str: str} 364 @return: complete list of package names in ROS environment. Same as packages parameter. 365 @rtype: [str] 366 """ 367 if packages is None: 368 packages = [] 369 if env is None: 370 env = os.environ 371 # record settings for cache 372 ros_root = env[ROS_ROOT] 373 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 374 375 path = os.path.abspath(path) 376 for d, dirs, files in os.walk(path, topdown=True): 377 if MANIFEST_FILE in files: 378 package = os.path.basename(d) 379 if package not in packages: 380 packages.append(package) 381 if cache is not None: 382 cache[package] = d, ros_root, ros_package_path 383 del dirs[:] 384 continue # leaf 385 elif 'rospack_nosubdirs' in files: 386 del dirs[:] 387 continue # leaf 388 # small optimization 389 elif '.svn' in dirs: 390 dirs.remove('.svn') 391 elif '.git' in dirs: 392 dirs.remove('.git') 393 394 for sub_d in dirs: 395 # followlinks=True only available in Python 2.6, so we 396 # have to implement manually 397 sub_p = os.path.join(d, sub_d) 398 if os.path.islink(sub_p): 399 packages.extend(list_pkgs_by_path(sub_p, cache=cache)) 400 401 return packages
402 403
404 -def find_node(pkg, node_type, rospack=None):
405 """ 406 Warning: unstable API due to catkin. 407 408 Locate the executable that implements the node 409 410 :param node_type: type of node, ``str`` 411 :returns: path to node or None if node is not in the package ``str`` 412 :raises: :exc:rospkg.ResourceNotFound` If package does not exist 413 """ 414 415 if rospack is None: 416 rospack = rospkg.RosPack() 417 return find_resource(pkg, node_type, filter_fn=_executable_filter, rospack=rospack)
418 419
420 -def _executable_filter(test_path):
421 s = os.stat(test_path) 422 flags = stat.S_IRUSR | stat.S_IXUSR 423 424 # Python scripts in ROS tend to omit .py extension since they could become executable 425 # by adding a shebang line (#!/usr/bin/env python) in Linux environments 426 # special handle this case in Windows environment 427 if os.name == 'nt' and os.path.splitext(test_path)[1].lower() in ['.py', '']: 428 flags = stat.S_IRUSR 429 return (s.st_mode & flags) == flags
430 431
432 -def _find_resource(d, resource_name, filter_fn=None):
433 """ 434 subroutine of find_resource 435 """ 436 matches = [] 437 # TODO: figure out how to generalize find_resource to take multiple resource name options 438 if sys.platform in ['win32', 'cygwin']: 439 # Windows logic requires more file patterns to resolve and is 440 # not case-sensitive, so leave it separate 441 442 # in the near-term, just hack in support for .exe/.bat/.py. In the long 443 # term this needs to: 444 # 445 # * parse PATHEXT to generate matches 446 # * perform case-insensitive compares against potential 447 # matches, in path-ext order 448 449 # - We still have to look for bare node_type as user may have 450 # specified extension manually 451 resource_name = resource_name.lower() 452 patterns = [resource_name, resource_name+'.exe', resource_name+'.bat', resource_name+'.py'] 453 for p, dirs, files in os.walk(d): 454 # case insensitive 455 files = [f.lower() for f in files] 456 for name in patterns: 457 if name in files: 458 test_path = os.path.join(p, name) 459 if filter_fn is not None: 460 if filter_fn(test_path): 461 matches.append(test_path) 462 else: 463 matches.append(test_path) 464 # remove .svn/.git/etc 465 to_prune = [x for x in dirs if x.startswith('.')] 466 for x in to_prune: 467 dirs.remove(x) 468 else: # UNIX 469 for p, dirs, files in os.walk(d, followlinks=True): 470 if resource_name in files: 471 test_path = os.path.join(p, resource_name) 472 if filter_fn is not None: 473 if filter_fn(test_path): 474 matches.append(test_path) 475 else: 476 matches.append(test_path) 477 # remove .svn/.git/etc 478 to_prune = [x for x in dirs if x.startswith('.')] 479 for x in to_prune: 480 dirs.remove(x) 481 return [os.path.abspath(m) for m in matches]
482 483 484 # TODO: this routine really belongs in rospkg, but the catkin-isms really, really don't 485 # belong in rospkg. With more thought, they can probably be abstracted out so as 486 # to no longer be catkin-specific.
487 -def find_resource(pkg, resource_name, filter_fn=None, rospack=None):
488 """ 489 Warning: unstable API due to catkin. 490 491 Locate the file named resource_name in package, optionally 492 matching specified filter. find_resource() will return a list of 493 matches, but only for a given scope. If the resource is found in 494 the binary build directory, it will only return matches in that 495 directory; it will not return matches from the ROS_PACKAGE_PATH as 496 well in this case. 497 498 :param filter: function that takes in a path argument and 499 returns True if the it matches the desired resource, ``fn(str)`` 500 :param rospack: `rospkg.RosPack` instance to use 501 :returns: lists of matching paths for resource within a given scope, ``[str]`` 502 :raises: :exc:`rospkg.ResourceNotFound` If package does not exist 503 """ 504 505 # New resource-location policy in Fuerte, induced by the new catkin 506 # build system: 507 # (1) Use catkin_find to find libexec and share locations, look 508 # recursively there. If the resource is found, done. 509 # Else continue: 510 # (2) If ROS_PACKAGE_PATH is set, look recursively there. If the 511 # resource is found, done. Else raise 512 # 513 # NOTE: package *must* exist on ROS_PACKAGE_PATH no matter what 514 515 if rospack is None: 516 rospack = rospkg.RosPack() 517 518 # lookup package as it *must* exist 519 pkg_path = rospack.get_path(pkg) 520 521 source_path_to_packages = rospack.get_custom_cache('source_path_to_packages', {}) 522 523 # if found in binary dir, start with that. in any case, use matches 524 # from ros_package_path 525 matches = [] 526 search_paths = catkin_find( 527 search_dirs=['libexec', 'share'], project=pkg, first_matching_workspace_only=True, 528 source_path_to_packages=source_path_to_packages) 529 530 # persist mapping of packages in rospack instance 531 if source_path_to_packages: 532 rospack.set_custom_cache('source_path_to_packages', source_path_to_packages) 533 534 for search_path in search_paths: 535 matches.extend(_find_resource(search_path, resource_name, filter_fn=filter_fn)) 536 537 matches.extend(_find_resource(pkg_path, resource_name, filter_fn=filter_fn)) 538 539 # Uniquify the results, in case we found the same file twice, while keeping order 540 unique_matches = [] 541 for match in matches: 542 if match not in unique_matches: 543 unique_matches.append(match) 544 return unique_matches
545