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  from catkin.find_in_workspaces import find_in_workspaces as catkin_find 
 50  import rospkg 
 51   
 52  import roslib.manifest 
 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 -class ROSPkgException(Exception):
61 """ 62 Base class of package-related errors. 63 """ 64 pass
65 -class InvalidROSPkgException(ROSPkgException):
66 """ 67 Exception that indicates that a ROS package does not exist 68 """ 69 pass
70 -class MultipleNodesException(ROSPkgException):
71 """ 72 Exception that indicates that multiple ROS nodes by the same name are in the same package. 73 """ 74 pass
75 76 # TODO: go through the code and eliminate unused methods -- there's far too many combos here 77 78 MANIFEST_FILE = 'manifest.xml' 79 PACKAGE_FILE = 'package.xml' 80 81 # 82 # Map package/directory structure 83 # 84
85 -def get_dir_pkg(d):
86 """ 87 Get the package that the directory is contained within. This is 88 determined by finding the nearest parent manifest.xml file. This 89 isn't 100% reliable, but symlinks can fool any heuristic that 90 relies on ROS_ROOT. 91 @param d: directory path 92 @type d: str 93 @return: (package_directory, package) of the specified directory, or None,None if not in a package 94 @rtype: (str, str) 95 """ 96 #TODO: the realpath is going to create issues with symlinks, most likely 97 98 parent = os.path.dirname(os.path.realpath(d)) 99 #walk up until we hit ros root or ros/pkg 100 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: 101 d = parent 102 parent = os.path.dirname(d) 103 if os.path.exists(os.path.join(d, MANIFEST_FILE)) or os.path.exists(os.path.join(d, PACKAGE_FILE)): 104 pkg = os.path.basename(os.path.abspath(d)) 105 return d, pkg 106 return None, None
107 108 _pkg_dir_cache = {} 109
110 -def get_pkg_dir(package, required=True, ros_root=None, ros_package_path=None):
111 """ 112 Locate directory package is stored in. This routine uses an 113 internal cache. 114 115 NOTE: cache does *not* rebuild if packages are relocated after 116 this process is initiated. 117 118 @param package: package name 119 @type package: str 120 @param required: if True, an exception will be raised if the 121 package directory cannot be located. 122 @type required: bool 123 @param ros_root: if specified, override ROS_ROOT 124 @type ros_root: str 125 @param ros_package_path: if specified, override ROS_PACKAGE_PATH 126 @type ros_package_path: str 127 @return: directory containing package or None if package cannot be found and required is False. 128 @rtype: str 129 @raise InvalidROSPkgException: if required is True and package cannot be located 130 """ 131 132 #UNIXONLY 133 #TODO: replace with non-rospack-based solution (e.g. os.walk()) 134 try: 135 penv = os.environ.copy() 136 if ros_root: 137 ros_root = rospkg.environment._resolve_path(ros_root) 138 penv[ROS_ROOT] = ros_root 139 elif ROS_ROOT in os.environ: 140 # record setting for _pkg_dir_cache 141 ros_root = os.environ[ROS_ROOT] 142 143 # determine rospack exe name 144 rospack = 'rospack' 145 146 if ros_package_path is not None: 147 ros_package_path = rospkg.environment._resolve_paths(ros_package_path) 148 penv[ROS_PACKAGE_PATH] = ros_package_path 149 elif ROS_PACKAGE_PATH in os.environ: 150 # record setting for _pkg_dir_cache 151 ros_package_path = os.environ[ROS_PACKAGE_PATH] 152 153 # update cache if we haven't. NOTE: we only get one cache 154 if not _pkg_dir_cache: 155 _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path) 156 157 # now that we've resolved the args, check the cache 158 if package in _pkg_dir_cache: 159 dir_, rr, rpp = _pkg_dir_cache[package] 160 if rr == ros_root and rpp == ros_package_path: 161 if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)): 162 return dir_ 163 else: 164 # invalidate cache 165 _invalidate_cache(_pkg_dir_cache) 166 167 rpout, rperr = Popen([rospack, 'find', package], \ 168 stdout=PIPE, stderr=PIPE, env=penv).communicate() 169 170 pkg_dir = (rpout or '').strip() 171 #python3.1 popen returns as bytes 172 if (isinstance(pkg_dir, bytes)): 173 pkg_dir = pkg_dir.decode() 174 if not pkg_dir: 175 raise InvalidROSPkgException("Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, rperr.strip(), ros_root, ros_package_path)) 176 177 pkg_dir = os.path.normpath(pkg_dir) 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):
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, rospack=rospack)
401
402 -def _executable_filter(test_path):
403 s = os.stat(test_path) 404 flags = stat.S_IRUSR | stat.S_IXUSR 405 if os.name == 'nt' and os.path.splitext(test_path)[1] == '.py': 406 flags = stat.S_IRUSR 407 return (s.st_mode & flags) == flags
408
409 -def _find_resource(d, resource_name, filter_fn=None):
410 """ 411 subroutine of find_resource 412 """ 413 matches = [] 414 # TODO: figure out how to generalize find_resource to take multiple resource name options 415 if sys.platform in ['win32', 'cygwin']: 416 # Windows logic requires more file patterns to resolve and is 417 # not case-sensitive, so leave it separate 418 419 # in the near-term, just hack in support for .exe/.bat/.py. In the long 420 # term this needs to: 421 # 422 # * parse PATHEXT to generate matches 423 # * perform case-insensitive compares against potential 424 # matches, in path-ext order 425 426 # - We still have to look for bare node_type as user may have 427 # specified extension manually 428 resource_name = resource_name.lower() 429 patterns = [resource_name, resource_name+'.exe', resource_name+'.bat', resource_name+'.py'] 430 for p, dirs, files in os.walk(d): 431 # case insensitive 432 files = [f.lower() for f in files] 433 for name in patterns: 434 if name in files: 435 test_path = os.path.join(p, name) 436 if filter_fn is not None: 437 if filter_fn(test_path): 438 matches.append(test_path) 439 else: 440 matches.append(test_path) 441 # remove .svn/.git/etc 442 to_prune = [x for x in dirs if x.startswith('.')] 443 for x in to_prune: 444 dirs.remove(x) 445 else: #UNIX 446 for p, dirs, files in os.walk(d): 447 if resource_name in files: 448 test_path = os.path.join(p, resource_name) 449 if filter_fn is not None: 450 if filter_fn(test_path): 451 matches.append(test_path) 452 else: 453 matches.append(test_path) 454 # remove .svn/.git/etc 455 to_prune = [x for x in dirs if x.startswith('.')] 456 for x in to_prune: 457 dirs.remove(x) 458 return [os.path.abspath(m) for m in matches]
459 460 # TODO: this routine really belongs in rospkg, but the catkin-isms really, really don't 461 # belong in rospkg. With more thought, they can probably be abstracted out so as 462 # to no longer be catkin-specific.
463 -def find_resource(pkg, resource_name, filter_fn=None, rospack=None):
464 """ 465 Warning: unstable API due to catkin. 466 467 Locate the file named resource_name in package, optionally 468 matching specified filter. find_resource() will return a list of 469 matches, but only for a given scope. If the resource is found in 470 the binary build directory, it will only return matches in that 471 directory; it will not return matches from the ROS_PACKAGE_PATH as 472 well in this case. 473 474 :param filter: function that takes in a path argument and 475 returns True if the it matches the desired resource, ``fn(str)`` 476 :param rospack: `rospkg.RosPack` instance to use 477 :returns: lists of matching paths for resource within a given scope, ``[str]`` 478 :raises: :exc:`rospkg.ResourceNotFound` If package does not exist 479 """ 480 481 # New resource-location policy in Fuerte, induced by the new catkin 482 # build system: 483 # (1) Use catkin_find to find libexec and share locations, look 484 # recursively there. If the resource is found, done. 485 # Else continue: 486 # (2) If ROS_PACKAGE_PATH is set, look recursively there. If the 487 # resource is found, done. Else raise 488 # 489 # NOTE: package *must* exist on ROS_PACKAGE_PATH no matter what 490 491 if rospack is None: 492 rospack = rospkg.RosPack() 493 494 # lookup package as it *must* exist 495 pkg_path = rospack.get_path(pkg) 496 497 source_path_to_packages = rospack.get_custom_cache('source_path_to_packages', {}) 498 499 # if found in binary dir, start with that. in any case, use matches 500 # from ros_package_path 501 matches = [] 502 search_paths = catkin_find( 503 search_dirs=['libexec', 'share'], project=pkg, first_matching_workspace_only=True, 504 source_path_to_packages=source_path_to_packages) 505 506 # persist mapping of packages in rospack instance 507 if source_path_to_packages: 508 rospack.set_custom_cache('source_path_to_packages', source_path_to_packages) 509 510 for search_path in search_paths: 511 matches.extend(_find_resource(search_path, resource_name, filter_fn=filter_fn)) 512 513 matches.extend(_find_resource(pkg_path, resource_name, filter_fn=filter_fn)) 514 515 # Uniquify the results, in case we found the same file twice, while keeping order 516 unique_matches = [] 517 for match in matches: 518 if match not in unique_matches: 519 unique_matches.append(match) 520 return unique_matches
521