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 sys 
 44  import stat 
 45  import string 
 46   
 47  from subprocess import Popen, PIPE 
 48   
 49  import rospkg 
 50   
 51  import roslib.manifest 
 52   
 53  SRC_DIR = 'src' 
 54  # TODO: these really don't belong here 
 55  CATKIN_SOURCE_DIR = 'CATKIN_SOURCE_DIR' 
 56  CATKIN_BINARY_DIR = 'CATKIN_BINARY_DIR' 
 57   
 58  # aliases 
 59  ROS_PACKAGE_PATH = rospkg.environment.ROS_PACKAGE_PATH 
 60  ROS_ROOT = rospkg.environment.ROS_ROOT 
 61   
62 -class ROSPkgException(Exception):
63 """ 64 Base class of package-related errors. 65 """ 66 pass
67 -class InvalidROSPkgException(ROSPkgException):
68 """ 69 Exception that indicates that a ROS package does not exist 70 """ 71 pass
72 -class MultipleNodesException(ROSPkgException):
73 """ 74 Exception that indicates that multiple ROS nodes by the same name are in the same package. 75 """ 76 pass
77 78 # TODO: go through the code and eliminate unused methods -- there's far too many combos here 79 80 MANIFEST_FILE = 'manifest.xml' 81 82 # 83 # Map package/directory structure 84 # 85
86 -def get_dir_pkg(d):
87 """ 88 Get the package that the directory is contained within. This is 89 determined by finding the nearest parent manifest.xml file. This 90 isn't 100% reliable, but symlinks can fool any heuristic that 91 relies on ROS_ROOT. 92 @param d: directory path 93 @type d: str 94 @return: (package_directory, package) of the specified directory, or None,None if not in a package 95 @rtype: (str, str) 96 """ 97 #TODO: the realpath is going to create issues with symlinks, most likely 98 99 parent = os.path.dirname(os.path.realpath(d)) 100 #walk up until we hit ros root or ros/pkg 101 while not os.path.exists(os.path.join(d, MANIFEST_FILE)) and parent != d: 102 d = parent 103 parent = os.path.dirname(d) 104 if os.path.exists(os.path.join(d, MANIFEST_FILE)): 105 pkg = os.path.basename(os.path.abspath(d)) 106 return d, pkg 107 return None, None
108 109 _pkg_dir_cache = {} 110
111 -def get_pkg_dir(package, required=True, ros_root=None, ros_package_path=None):
112 """ 113 Locate directory package is stored in. This routine uses an 114 internal cache. 115 116 NOTE: cache does *not* rebuild if packages are relocated after 117 this process is initiated. 118 119 @param package: package name 120 @type package: str 121 @param required: if True, an exception will be raised if the 122 package directory cannot be located. 123 @type required: bool 124 @param ros_root: if specified, override ROS_ROOT 125 @type ros_root: str 126 @param ros_package_path: if specified, override ROS_PACKAGE_PATH 127 @type ros_package_path: str 128 @return: directory containing package or None if package cannot be found and required is False. 129 @rtype: str 130 @raise InvalidROSPkgException: if required is True and package cannot be located 131 """ 132 133 #UNIXONLY 134 #TODO: replace with non-rospack-based solution (e.g. os.walk()) 135 try: 136 penv = os.environ.copy() 137 if ros_root: 138 ros_root = rospkg.environment._resolve_path(ros_root) 139 penv[ROS_ROOT] = ros_root 140 elif ROS_ROOT in os.environ: 141 # record setting for _pkg_dir_cache 142 ros_root = os.environ[ROS_ROOT] 143 144 # determine rospack exe name 145 rospack = 'rospack' 146 147 if ros_package_path is not None: 148 ros_package_path = rospkg.environment._resolve_paths(ros_package_path) 149 penv[ROS_PACKAGE_PATH] = ros_package_path 150 elif ROS_PACKAGE_PATH in os.environ: 151 # record setting for _pkg_dir_cache 152 ros_package_path = os.environ[ROS_PACKAGE_PATH] 153 154 # update cache if we haven't. NOTE: we only get one cache 155 if not _pkg_dir_cache: 156 _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path) 157 158 # now that we've resolved the args, check the cache 159 if package in _pkg_dir_cache: 160 dir_, rr, rpp = _pkg_dir_cache[package] 161 if rr == ros_root and rpp == ros_package_path: 162 if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)): 163 return dir_ 164 else: 165 # invalidate cache 166 _invalidate_cache(_pkg_dir_cache) 167 168 rpout, rperr = Popen([rospack, 'find', package], \ 169 stdout=PIPE, stderr=PIPE, env=penv).communicate() 170 171 pkg_dir = (rpout or '').strip() 172 #python3.1 popen returns as bytes 173 if (isinstance(pkg_dir, bytes)): 174 pkg_dir = pkg_dir.decode() 175 if not pkg_dir: 176 raise InvalidROSPkgException("Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, rperr.strip(), ros_root, ros_package_path)) 177 178 if not os.path.exists(pkg_dir): 179 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)) 180 elif not os.path.isdir(pkg_dir): 181 raise InvalidROSPkgException("Package %s is invalid: file [%s] is in the way"%(package, pkg_dir)) 182 # don't update cache: this should only be updated from 183 # rospack_cache as it will corrupt package list otherwise. 184 #_pkg_dir_cache[package] = (pkg_dir, ros_root, ros_package_path) 185 return pkg_dir 186 except OSError as e: 187 if required: 188 raise InvalidROSPkgException("Environment configuration is invalid: cannot locate rospack (%s)"%e) 189 return None 190 except Exception as e: 191 if required: 192 raise 193 return None
194
195 -def _get_pkg_subdir_by_dir(package_dir, subdir, required=True, env=None):
196 """ 197 @param required: if True, will attempt to create the subdirectory 198 if it does not exist. An exception will be raised if this fails. 199 @type required: bool 200 @param package_dir: directory of package 201 @type package_dir: str 202 @param subdir: name of subdirectory to locate 203 @type subdir: str 204 @param env: override os.environ dictionary 205 @type env: dict 206 @param required: if True, directory must exist 207 @type required: bool 208 @return: Package subdirectory if package exist, otherwise None. 209 @rtype: str 210 @raise InvalidROSPkgException: if required is True and directory does not exist 211 """ 212 if env is None: 213 env = os.environ 214 try: 215 if not package_dir: 216 raise Exception("Cannot create a '%(subdir)s' directory in %(package_dir)s: package %(package) cannot be located"%locals()) 217 d = os.path.join(package_dir, subdir) 218 if required and os.path.isfile(d): 219 raise Exception("""Package '%(package)s' is improperly configured: 220 file %(d)s is preventing the creation of a directory"""%locals()) 221 elif required and not os.path.isdir(d): 222 try: 223 os.makedirs(d) #lazy create 224 except error: 225 raise Exception("""Package '%(package)s' is improperly configured: 226 Cannot create a '%(subdir)s' directory in %(package_dir)s. 227 Please check permissions and try again. 228 """%locals()) 229 return d 230 except Exception as e: 231 if required: 232 raise 233 return None
234
235 -def get_pkg_subdir(package, subdir, required=True, env=None):
236 """ 237 @param required: if True, will attempt to create the subdirectory 238 if it does not exist. An exception will be raised if this fails. 239 @type required: bool 240 @param package: name of package 241 @type package: str 242 @param env: override os.environ dictionary 243 @type env: dict 244 @param required: if True, directory must exist 245 @type required: bool 246 @return: Package subdirectory if package exist, otherwise None. 247 @rtype: str 248 @raise InvalidROSPkgException: if required is True and directory does not exist 249 """ 250 if env is None: 251 env = os.environ 252 pkg_dir = get_pkg_dir(package, required, ros_root=env[ROS_ROOT]) 253 return _get_pkg_subdir_by_dir(pkg_dir, subdir, required, env)
254 255 # 256 # Map ROS resources to files 257 # 258
259 -def resource_file(package, subdir, resource_name):
260 """ 261 @param subdir: name of subdir -- these should be one of the 262 string constants, e.g. MSG_DIR 263 @type subdir: str 264 @return: path to resource in the specified subdirectory of the 265 package, or None if the package does not exists 266 @rtype: str 267 @raise roslib.packages.InvalidROSPkgException: If package does not exist 268 """ 269 d = get_pkg_subdir(package, subdir, False) 270 if d is None: 271 raise InvalidROSPkgException(package) 272 return os.path.join(d, resource_name)
273
274 -def _update_rospack_cache(env=None):
275 """ 276 Internal routine to update global package directory cache 277 278 @return: True if cache is valid 279 @rtype: bool 280 """ 281 if env is None: 282 env = os.environ 283 cache = _pkg_dir_cache 284 if cache: 285 return True 286 ros_root = env[ROS_ROOT] 287 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 288 return _read_rospack_cache(cache, ros_root, ros_package_path)
289
290 -def _invalidate_cache(cache):
291 # I've only made this a separate routine because roslib.packages should really be using 292 # the roslib.stacks cache implementation instead with the separate cache marker 293 cache.clear()
294
295 -def _read_rospack_cache(cache, ros_root, ros_package_path):
296 """ 297 Read in rospack_cache data into cache. On-disk cache specifies a 298 ROS_ROOT and ROS_PACKAGE_PATH, which must match the requested 299 environment. 300 301 @param cache: empty dictionary to store package list in. 302 If no cache argument provided, will use internal _pkg_dir_cache 303 and will return cached answers if available. 304 The format of the cache is {package_name: dir_path, ros_root, ros_package_path}. 305 @type cache: {str: str, str, str} 306 @param ros_package_path: ROS_ROOT value 307 @type ros_root: str 308 @param ros_package_path: ROS_PACKAGE_PATH value or '' if not specified 309 @type ros_package_path: str 310 @return: True if on-disk cache matches and was loaded, false otherwise 311 @rtype: bool 312 """ 313 try: 314 with open(os.path.join(rospkg.get_ros_home(), 'rospack_cache')) as f: 315 for l in f.readlines(): 316 l = l[:-1] 317 if not len(l): 318 continue 319 if l[0] == '#': 320 # check that the cache matches our env 321 if l.startswith('#ROS_ROOT='): 322 if not l[len('#ROS_ROOT='):] == ros_root: 323 return False 324 elif l.startswith('#ROS_PACKAGE_PATH='): 325 if not l[len('#ROS_PACKAGE_PATH='):] == ros_package_path: 326 return False 327 else: 328 cache[os.path.basename(l)] = l, ros_root, ros_package_path 329 return True 330 except: 331 pass
332
333 -def list_pkgs_by_path(path, packages=None, cache=None, env=None):
334 """ 335 List ROS packages within the specified path. 336 337 Optionally, a cache dictionary can be provided, which will be 338 updated with the package->path mappings. list_pkgs_by_path() does 339 NOT returned cached results -- it only updates the cache. 340 341 @param path: path to list packages in 342 @type path: str 343 @param packages: list of packages to append to. If package is 344 already present in packages, it will be ignored. 345 @type packages: [str] 346 @param cache: (optional) package path cache to update. Maps package name to directory path. 347 @type cache: {str: str} 348 @return: complete list of package names in ROS environment. Same as packages parameter. 349 @rtype: [str] 350 """ 351 if packages is None: 352 packages = [] 353 if env is None: 354 env = os.environ 355 # record settings for cache 356 ros_root = env[ROS_ROOT] 357 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 358 359 path = os.path.abspath(path) 360 for d, dirs, files in os.walk(path, topdown=True): 361 if MANIFEST_FILE in files: 362 package = os.path.basename(d) 363 if package not in packages: 364 packages.append(package) 365 if cache is not None: 366 cache[package] = d, ros_root, ros_package_path 367 del dirs[:] 368 continue #leaf 369 elif 'rospack_nosubdirs' in files: 370 del dirs[:] 371 continue #leaf 372 #small optimization 373 elif '.svn' in dirs: 374 dirs.remove('.svn') 375 elif '.git' in dirs: 376 dirs.remove('.git') 377 378 for sub_d in dirs: 379 # followlinks=True only available in Python 2.6, so we 380 # have to implement manually 381 sub_p = os.path.join(d, sub_d) 382 if os.path.islink(sub_p): 383 packages.extend(list_pkgs_by_path(sub_p, cache=cache)) 384 385 return packages
386
387 -def find_node(pkg, node_type, rospack=None, catkin_packages_cache=None):
388 """ 389 Warning: unstable API due to catkin. 390 391 Locate the executable that implements the node 392 393 :param node_type: type of node, ``str`` 394 :returns: path to node or None if node is not in the package ``str`` 395 :raises: :exc:rospkg.ResourceNotFound` If package does not exist 396 """ 397 398 if rospack is None: 399 rospack = rospkg.RosPack() 400 return find_resource(pkg, node_type, filter_fn=_executable_filter, 401 rospack=rospack, catkin_packages_cache=catkin_packages_cache)
402
403 -def _executable_filter(test_path):
404 s = os.stat(test_path) 405 return (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) == (stat.S_IRUSR | stat.S_IXUSR))
406 407 # TODO: this routine really belongs in catkin
408 -def _load_catkin_packages_cache(catkin_packages_cache, env=None):
409 """ 410 env[CATKIN_BINARY_DIR] *must* be set 411 412 :param env: OS environment (defaults to os.environ if None/not set) 413 :param catkin_packages_cache: dictionary to read cache into. 414 Contents of dictionary will be replaced if cache is read. ``dict`` 415 416 :raises: :exc:`KeyError` if env[CATKIN_BINARY_DIR] is not set 417 """ 418 if env is None: 419 env=os.environ 420 prefix = env[CATKIN_BINARY_DIR] 421 cache_file = os.path.join(prefix, 'etc', 'packages.list') 422 if os.path.isfile(cache_file): 423 catkin_packages_cache.clear() 424 with open(cache_file, 'r') as f: 425 for l in f.readlines(): 426 l = l.strip() 427 # Example: 428 # rosconsole ros_comm/tools/rosconsole\n 429 if not l: 430 continue 431 idx = l.find(' ') 432 catkin_packages_cache[l[:idx]] = os.path.join(prefix, l[idx+1:])
433
434 -def _find_resource(d, resource_name, filter_fn=None):
435 """ 436 subroutine of find_resource 437 """ 438 matches = [] 439 # TODO: figure out how to generalize find_resource to take multiple resource name options 440 if sys.platform in ['win32', 'cygwin']: 441 # Windows logic requires more file patterns to resolve and is 442 # not case-sensitive, so leave it separate 443 444 # in the near-term, just hack in support for .exe/.bat. In the long 445 # term this needs to: 446 # 447 # * parse PATHEXT to generate matches 448 # * perform case-insensitive compares against potential 449 # matches, in path-ext order 450 451 # - We still have to look for bare node_type as user may have 452 # specified extension manually 453 resource_name = resource_name.lower() 454 patterns = [resource_name, resource_name+'.exe', resource_name+'.bat'] 455 for p, dirs, files in os.walk(d): 456 # case insensitive 457 files = [f.lower() for f in files] 458 for name in patterns: 459 if name in files: 460 test_path = os.path.join(p, name) 461 if filter_fn is not None: 462 if filter_fn(test_path): 463 matches.append(test_path) 464 else: 465 matches.append(test_path) 466 # remove .svn/.git/etc 467 to_prune = [x for x in dirs if x.startswith('.')] 468 for x in to_prune: 469 dirs.remove(x) 470 else: #UNIX 471 for p, dirs, files in os.walk(d): 472 if resource_name in files: 473 test_path = os.path.join(p, resource_name) 474 if filter_fn is not None: 475 if filter_fn(test_path): 476 matches.append(test_path) 477 else: 478 matches.append(test_path) 479 # remove .svn/.git/etc 480 to_prune = [x for x in dirs if x.startswith('.')] 481 for x in to_prune: 482 dirs.remove(x) 483 return [os.path.abspath(m) for m in matches]
484 485 # TODO: this routine really belongs in rospkg, but the catkin-isms really, really don't 486 # belong in rospkg. With more thought, they can probably be abstracted out so as 487 # to no longer be catkin-specific.
488 -def find_resource(pkg, resource_name, filter_fn=None, rospack=None, catkin_packages_cache=None):
489 """ 490 Warning: unstable API due to catkin. 491 492 Locate the file named resource_name in package, optionally 493 matching specified filter. find_resource() will return a list of 494 matches, but only for a given scope. If the resource is found in 495 the binary build directory, it will only return matches in that 496 directory; it will not return matches from the ROS_PACKAGE_PATH as 497 well in this case. 498 499 :param filter: function that takes in a path argument and 500 returns True if the it matches the desired resource, ``fn(str)`` 501 :param rospack: `rospkg.RosPack` instance to use 502 :param catkin_packages_cache: dictionary for caching catkin packages.list 503 :returns: lists of matching paths for resource within a given scope, ``[str]`` 504 :raises: :exc:`rospkg.ResourceNotFound` If package does not exist 505 """ 506 507 # New resource-location policy in Fuerte, induced by the new catkin 508 # build system: 509 # (1) If CATKIN_BINARY_DIR is set, look recursively there. If the 510 # resource is found, done. Else continue: 511 # (2) If ROS_PACKAGE_PATH is set, look recursively there. If the 512 # resource is found, done. Else raise 513 # 514 # NOTE: package *must* exist on ROS_PACKAGE_PATH no matter what 515 516 if rospack is None: 517 rospack = rospkg.RosPack() 518 if catkin_packages_cache is None: 519 catkin_packages_cache = {} 520 521 # lookup package as it *must* exist 522 pkg_path = rospack.get_path(pkg) 523 524 # load catkin packages list, if necessary 525 if CATKIN_BINARY_DIR in os.environ and not catkin_packages_cache: 526 _load_catkin_packages_cache(catkin_packages_cache) 527 528 # if found in binary dir, start with that. in any case, use matches 529 # from ros_package_path 530 matches = [] 531 if pkg in catkin_packages_cache: 532 matches.extend(_find_resource(catkin_packages_cache[pkg], resource_name, filter_fn=filter_fn)) 533 matches.extend(_find_resource(pkg_path, resource_name, filter_fn=filter_fn)) 534 # Uniquify the results, in case we found the same file twice 535 return list(set(matches))
536