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

Source Code for Module roslib.stacks

  1  #! /usr/bin/env python 
  2  # Software License Agreement (BSD License) 
  3  # 
  4  # Copyright (c) 2008, Willow Garage, Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions 
  9  # are met: 
 10  # 
 11  #  * Redistributions of source code must retain the above copyright 
 12  #    notice, this list of conditions and the following disclaimer. 
 13  #  * Redistributions in binary form must reproduce the above 
 14  #    copyright notice, this list of conditions and the following 
 15  #    disclaimer in the documentation and/or other materials provided 
 16  #    with the distribution. 
 17  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 18  #    contributors may be used to endorse or promote products derived 
 19  #    from this software without specific prior written permission. 
 20  # 
 21  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 22  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 23  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 24  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 25  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 26  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 27  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 28  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 29  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 30  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 31  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 32  # POSSIBILITY OF SUCH DAMAGE. 
 33  # 
 34  # Revision $Id: stacks.py 9275 2010-04-21 00:45:06Z kwc $ 
 35   
 36  """ 
 37  Python utilities for manipulating ROS Stacks. 
 38  See: U{http://ros.org/wiki/Stacks} 
 39   
 40  Warning: this API is still fairly experimental and incomplete. 
 41  """ 
 42   
 43  import os 
 44  import sys 
 45   
 46  import roslib.exceptions 
 47  import roslib.packages 
 48  from roslib.rosenv import ROS_ROOT, ROS_PACKAGE_PATH 
 49   
 50   
 51  STACK_FILE = 'stack.xml' 
 52  ROS_STACK = 'ros' 
 53   
54 -class ROSStackException(roslib.exceptions.ROSLibException): pass
55 -class InvalidROSStackException(ROSStackException): pass
56
57 -def stack_of(pkg):
58 """ 59 @return: name of stack that pkg is in, or None if pkg is not part of a stack 60 @rtype: str 61 @raise roslib.packages.InvalidROSPkgException: if pkg cannot be located 62 """ 63 pkg_dir = roslib.packages.get_pkg_dir(pkg) 64 dir = os.path.dirname(pkg_dir) 65 while dir and os.path.dirname(dir) != dir: 66 stack_file = os.path.join(dir, STACK_FILE) 67 if os.path.exists(stack_file): 68 #TODO: need to resolve issues regarding whether the 69 #stack.xml or the directory defines the stack name 70 return os.path.basename(dir) 71 dir = os.path.dirname(dir)
72
73 -def packages_of(stack, env=os.environ):
74 """ 75 @return: name of packages that are part of stack 76 @rtype: [str] 77 @raise InvalidROSStackException: if stack cannot be located 78 @raise ValueError: if stack name is invalid 79 """ 80 # record settings for error messages 81 ros_root = env[ROS_ROOT] 82 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 83 84 if not stack: 85 raise ValueError("stack name not specified") 86 stack_dir = get_stack_dir(stack) 87 if stack_dir is None: 88 raise InvalidROSStackException("Cannot locate installation of stack %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(stack, ros_root,ros_package_path)) 89 packages = [] 90 l = [os.path.join(stack_dir, d) for d in os.listdir(stack_dir)] 91 # kwc: this really is just a 1-directory reimplementation of 92 # list_pkgs(). Should merge implementations, though have to deal 93 # with issues of cache, etc... 94 while l: 95 d = l.pop() 96 if os.path.isdir(d): 97 if roslib.packages.is_pkg_dir(d): 98 p = os.path.basename(d) 99 # this is sometimes true if we've descended into a build directory 100 if not p in packages: 101 packages.append(p) 102 elif os.path.exists(os.path.join(d, 'rospack_nosubdirs')): 103 # don't descend 104 pass 105 elif os.path.basename(d) not in ['build', '.svn', '.git']: #recurse 106 l.extend([os.path.join(d, e) for e in os.listdir(d)]) 107 return packages
108
109 -def get_stack_dir(stack):
110 """ 111 Get the directory of a ROS stack. This will initialize an internal 112 cache and return cached results if possible. 113 114 This routine is not thread-safe to os.environ changes. 115 116 @param stack: name of ROS stack to locate on disk 117 @type stack: str 118 @return: directory of stack. 119 @rtype: str 120 @raise InvalidROSStackException: if stack cannot be located. 121 """ 122 123 # it's possible to get incorrect results from this cache 124 # implementation by manipulating the environment and calling this 125 # from multiple threads. as that is an unusual use case and would 126 # require a slower implmentation, it's not supported. the 127 # interpretation of this routine is get_stack_dir for the 128 # environment this process was launched in. 129 global _dir_cache_marker 130 131 env = os.environ 132 if stack in _dir_cache: 133 ros_root = env[ROS_ROOT] 134 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 135 136 # we don't attempt to be thread-safe to environment changes, 137 # however we do need to be threadsafe to cache invalidation. 138 try: 139 if _dir_cache_marker == (ros_root, ros_package_path): 140 d = _dir_cache[stack] 141 if os.path.isfile(os.path.join(d, STACK_FILE)): 142 return d 143 else: 144 # invalidate the cache 145 _dir_cache_marker = None 146 _dir_cache.clear() 147 except KeyError: 148 pass 149 _update_stack_cache() #update cache 150 val = _dir_cache.get(stack, None) 151 if val is None: 152 raise InvalidROSStackException("Cannot location installation of stack %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(stack, env[ROS_ROOT], env.get(ROS_PACKAGE_PATH, ''))) 153 return val
154 155 # rosstack directory cache 156 _dir_cache = {} 157 # stores ROS_ROOT, ROS_PACKAGE_PATH of _dir_cache 158 _dir_cache_marker = None 159
160 -def _update_stack_cache(force=False):
161 """ 162 Update _dir_cache if environment has changed since last cache build. 163 164 @param force: force cache rebuild regardless of environment variables 165 @type force: bool 166 """ 167 global _dir_cache_marker 168 env = os.environ 169 ros_root = env[ROS_ROOT] 170 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 171 172 if _dir_cache_marker == (ros_root, ros_package_path): 173 return 174 _dir_cache.clear() 175 _dir_cache_marker = ros_root, ros_package_path 176 177 pkg_dirs = roslib.packages.get_package_paths(env=env) 178 # ros is assumed to be at ROS_ROOT 179 if os.path.exists(os.path.join(ros_root, 'stack.xml')): 180 _dir_cache['ros'] = ros_root 181 pkg_dirs.remove(ros_root) 182 183 # pass in accumulated stacks list to each call. This ensures 184 # precedence (i.e. that stacks first on pkg_dirs path win). 185 stacks = [] 186 for pkg_root in pkg_dirs: 187 # list_stacks_by_path will append list into stacks, so that 188 # each call accumulates in it. 189 list_stacks_by_path(pkg_root, stacks, cache=_dir_cache)
190
191 -def list_stacks():
192 """ 193 Get list of all ROS stacks. This uses an internal cache. 194 195 This routine is not thread-safe to os.environ changes. 196 197 @return: complete list of stacks names in ROS environment 198 @rtype: [str] 199 """ 200 _update_stack_cache() 201 return _dir_cache.keys()
202
203 -def list_stacks_by_path(path, stacks=None, cache=None):
204 """ 205 List ROS stacks within the specified path. 206 207 Optionally, a cache dictionary can be provided, which will be 208 updated with the stack->path mappings. list_stacks_by_path() does 209 NOT returned cached results -- it only updates the cache. 210 211 @param path: path to list stacks in 212 @type path: str 213 @param stacks: list of stacks to append to. If stack is 214 already present in stacks, it will be ignored. 215 @type stacks: [str] 216 @param cache: (optional) stack path cache to update. Maps stack name to directory path. 217 @type cache: {str: str} 218 @return: complete list of stack names in ROS environment. Same as stacks parameter. 219 @rtype: [str] 220 """ 221 if stacks is None: 222 stacks = [] 223 MANIFEST_FILE = roslib.packages.MANIFEST_FILE 224 basename = os.path.basename 225 for d, dirs, files in os.walk(path, topdown=True): 226 if STACK_FILE in files: 227 stack = basename(d) 228 if stack not in stacks: 229 stacks.append(stack) 230 if cache is not None: 231 cache[stack] = d 232 del dirs[:] 233 continue #leaf 234 elif MANIFEST_FILE in files: 235 del dirs[:] 236 continue #leaf 237 elif 'rospack_nosubdirs' in files: 238 del dirs[:] 239 continue #leaf 240 # remove hidden dirs (esp. .svn/.git) 241 [dirs.remove(di) for di in dirs if di[0] == '.'] 242 for sub_d in dirs: 243 # followlinks=True only available in Python 2.6, so we 244 # have to implement manually 245 sub_p = os.path.join(d, sub_d) 246 if os.path.islink(sub_p): 247 stacks.extend(list_stacks_by_path(sub_p, cache=cache)) 248 return stacks
249 250 # #2022
251 -def expand_to_packages(names):
252 """ 253 Expand names into a list of packages. Names can either be of packages or stacks. 254 255 @param names: names of stacks or packages 256 @type names: [str] 257 @return: ([packages], [not_found]). expand_packages() returns two 258 lists. The first is of packages names. The second is a list of 259 names for which no matching stack or package was found. Lists may have duplicates. 260 @rtype: ([str], [str]) 261 """ 262 if type(names) not in [tuple, list]: 263 raise ValueError("names must be a list of strings") 264 265 # do full package list first. This forces an entire tree 266 # crawl. This is less efficient for a small list of names, but 267 # much more efficient for many names. 268 package_list = roslib.packages.list_pkgs() 269 valid = [] 270 invalid = [] 271 for n in names: 272 if not n in package_list: 273 try: 274 valid.extend(roslib.stacks.packages_of(n)) 275 except roslib.stacks.InvalidROSStackException, e: 276 invalid.append(n) 277 else: 278 valid.append(n) 279 return valid, invalid
280