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 14218 2011-07-11 20:39:24Z 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  import re 
 46   
 47  import roslib.exceptions 
 48  import roslib.packages 
 49  import roslib.stack_manifest 
 50  from roslib.rosenv import ROS_ROOT, ROS_PACKAGE_PATH 
 51   
 52  STACK_FILE = 'stack.xml' 
 53  ROS_STACK = 'ros' 
 54   
55 -class ROSStackException(roslib.exceptions.ROSLibException): pass
56 -class InvalidROSStackException(ROSStackException): pass
57
58 -def stack_of(pkg, env=None):
59 """ 60 @param env: override environment variables 61 @type env: {str: str} 62 @return: name of stack that pkg is in, or None if pkg is not part of a stack 63 @rtype: str 64 @raise roslib.packages.InvalidROSPkgException: if pkg cannot be located 65 """ 66 if env is None: 67 env = os.environ 68 pkg_dir = roslib.packages.get_pkg_dir(pkg, ros_root=env[ROS_ROOT], ros_package_path=env.get(ROS_PACKAGE_PATH, None)) 69 d = pkg_dir 70 while d and os.path.dirname(d) != d: 71 stack_file = os.path.join(d, STACK_FILE) 72 if os.path.exists(stack_file): 73 #TODO: need to resolve issues regarding whether the 74 #stack.xml or the directory defines the stack name 75 return os.path.basename(d) 76 d = os.path.dirname(d)
77
78 -def packages_of(stack, env=None):
79 """ 80 @param env: override environment variables 81 @type env: {str: str} 82 @return: name of packages that are part of stack 83 @rtype: [str] 84 @raise InvalidROSStackException: if stack cannot be located 85 @raise ValueError: if stack name is invalid 86 """ 87 # record settings for error messages 88 if env is None: 89 env = os.environ 90 ros_root = env[ROS_ROOT] 91 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 92 93 if not stack: 94 raise ValueError("stack name not specified") 95 stack_dir = get_stack_dir(stack, env=env) 96 if stack_dir is None: 97 raise InvalidROSStackException("Cannot locate installation of stack %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(stack, ros_root,ros_package_path)) 98 # unary stack 99 if roslib.packages.is_pkg_dir(stack_dir): 100 return [stack] 101 packages = [] 102 l = [os.path.join(stack_dir, d) for d in os.listdir(stack_dir)] 103 # kwc: this really is just a 1-directory reimplementation of 104 # list_pkgs(). Should merge implementations, though have to deal 105 # with issues of cache, etc... 106 while l: 107 d = l.pop() 108 if os.path.isdir(d): 109 if roslib.packages.is_pkg_dir(d): 110 p = os.path.basename(d) 111 # this is sometimes true if we've descended into a build directory 112 if not p in packages: 113 packages.append(p) 114 elif os.path.exists(os.path.join(d, 'rospack_nosubdirs')): 115 # don't descend 116 pass 117 elif os.path.basename(d) not in ['build', '.svn', '.git']: #recurse 118 l.extend([os.path.join(d, e) for e in os.listdir(d)]) 119 return packages
120
121 -def get_stack_dir(stack, env=None):
122 """ 123 Get the directory of a ROS stack. This will initialize an internal 124 cache and return cached results if possible. 125 126 This routine is not thread-safe to os.environ changes. 127 128 @param env: override environment variables 129 @type env: {str: str} 130 @param stack: name of ROS stack to locate on disk 131 @type stack: str 132 @return: directory of stack. 133 @rtype: str 134 @raise InvalidROSStackException: if stack cannot be located. 135 """ 136 137 # it's possible to get incorrect results from this cache 138 # implementation by manipulating the environment and calling this 139 # from multiple threads. as that is an unusual use case and would 140 # require a slower implmentation, it's not supported. the 141 # interpretation of this routine is get_stack_dir for the 142 # environment this process was launched in. 143 global _dir_cache_marker 144 145 if env is None: 146 env = os.environ 147 if stack in _dir_cache: 148 ros_root = env[ROS_ROOT] 149 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 150 151 # we don't attempt to be thread-safe to environment changes, 152 # however we do need to be threadsafe to cache invalidation. 153 try: 154 if _dir_cache_marker == (ros_root, ros_package_path): 155 d = _dir_cache[stack] 156 if os.path.isfile(os.path.join(d, STACK_FILE)): 157 return d 158 else: 159 # invalidate the cache 160 _dir_cache_marker = None 161 _dir_cache.clear() 162 except KeyError: 163 pass 164 _update_stack_cache(env=env) #update cache 165 try: 166 val = _dir_cache[stack] 167 except KeyError: 168 raise InvalidROSStackException("Cannot location installation of stack %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(stack, env[ROS_ROOT], env.get(ROS_PACKAGE_PATH, ''))) 169 return val
170 171 # rosstack directory cache 172 _dir_cache = {} 173 # stores ROS_ROOT, ROS_PACKAGE_PATH of _dir_cache 174 _dir_cache_marker = None 175
176 -def _update_stack_cache(force=False, env=None):
177 """ 178 Update _dir_cache if environment has changed since last cache build. 179 180 @param env: override environment variables 181 @type env: {str: str} 182 @param force: force cache rebuild regardless of environment variables 183 @type force: bool 184 """ 185 global _dir_cache_marker 186 if env is None: 187 env = os.environ 188 ros_root = env[ROS_ROOT] 189 ros_package_path = env.get(ROS_PACKAGE_PATH, '') 190 191 if _dir_cache_marker == (ros_root, ros_package_path): 192 return 193 _dir_cache.clear() 194 _dir_cache_marker = ros_root, ros_package_path 195 196 pkg_dirs = roslib.packages.get_package_paths(env=env) 197 # ros is assumed to be at ROS_ROOT 198 if os.path.exists(os.path.join(ros_root, 'stack.xml')): 199 _dir_cache['ros'] = ros_root 200 pkg_dirs.remove(ros_root) 201 202 # pass in accumulated stacks list to each call. This ensures 203 # precedence (i.e. that stacks first on pkg_dirs path win). 204 stacks = [] 205 for pkg_root in pkg_dirs: 206 # list_stacks_by_path will append list into stacks, so that 207 # each call accumulates in it. 208 list_stacks_by_path(pkg_root, stacks, cache=_dir_cache)
209
210 -def list_stacks(env=None):
211 """ 212 Get list of all ROS stacks. This uses an internal cache. 213 214 This routine is not thread-safe to os.environ changes. 215 216 @param env: override environment variables 217 @type env: {str: str} 218 @return: complete list of stacks names in ROS environment 219 @rtype: [str] 220 """ 221 _update_stack_cache(env=env) 222 return list(_dir_cache.keys()) #py3k
223
224 -def list_stacks_by_path(path, stacks=None, cache=None):
225 """ 226 List ROS stacks within the specified path. 227 228 Optionally, a cache dictionary can be provided, which will be 229 updated with the stack->path mappings. list_stacks_by_path() does 230 NOT returned cached results -- it only updates the cache. 231 232 @param path: path to list stacks in 233 @type path: str 234 @param stacks: list of stacks to append to. If stack is 235 already present in stacks, it will be ignored. 236 @type stacks: [str] 237 @param cache: (optional) stack path cache to update. Maps stack name to directory path. 238 @type cache: {str: str} 239 @return: complete list of stack names in ROS environment. Same as stacks parameter. 240 @rtype: [str] 241 """ 242 if stacks is None: 243 stacks = [] 244 MANIFEST_FILE = roslib.packages.MANIFEST_FILE 245 basename = os.path.basename 246 for d, dirs, files in os.walk(path, topdown=True): 247 if STACK_FILE in files: 248 stack = basename(d) 249 if stack not in stacks: 250 stacks.append(stack) 251 if cache is not None: 252 cache[stack] = d 253 del dirs[:] 254 continue #leaf 255 elif MANIFEST_FILE in files: 256 del dirs[:] 257 continue #leaf 258 elif 'rospack_nosubdirs' in files: 259 del dirs[:] 260 continue #leaf 261 # remove hidden dirs (esp. .svn/.git) 262 [dirs.remove(di) for di in dirs if di[0] == '.'] 263 for sub_d in dirs: 264 # followlinks=True only available in Python 2.6, so we 265 # have to implement manually 266 sub_p = os.path.join(d, sub_d) 267 if os.path.islink(sub_p): 268 stacks.extend(list_stacks_by_path(sub_p, cache=cache)) 269 return stacks
270 271 # #2022
272 -def expand_to_packages(names, env=None):
273 """ 274 Expand names into a list of packages. Names can either be of packages or stacks. 275 276 @param names: names of stacks or packages 277 @type names: [str] 278 @return: ([packages], [not_found]). expand_packages() returns two 279 lists. The first is of packages names. The second is a list of 280 names for which no matching stack or package was found. Lists may have duplicates. 281 @rtype: ([str], [str]) 282 """ 283 if type(names) not in [tuple, list]: 284 raise ValueError("names must be a list of strings") 285 286 # do full package list first. This forces an entire tree 287 # crawl. This is less efficient for a small list of names, but 288 # much more efficient for many names. 289 package_list = roslib.packages.list_pkgs(env=env) 290 valid = [] 291 invalid = [] 292 for n in names: 293 if not n in package_list: 294 try: 295 valid.extend(roslib.stacks.packages_of(n, env=env)) 296 except roslib.stacks.InvalidROSStackException as e: 297 invalid.append(n) 298 else: 299 valid.append(n) 300 return valid, invalid
301
302 -def get_stack_version(stack, env=None):
303 """ 304 @param env: override environment variables 305 @type env: {str: str} 306 307 @return: version number of stack, or None if stack is unversioned. 308 @rtype: str 309 """ 310 return get_stack_version_by_dir(get_stack_dir(stack, env=env))
311
312 -def get_stack_version_by_dir(stack_dir):
313 """ 314 Get stack version where stack_dir points to root directory of stack. 315 316 @param env: override environment variables 317 @type env: {str: str} 318 319 @return: version number of stack, or None if stack is unversioned. 320 @rtype: str 321 """ 322 # REP 109: check for <version> tag first, then CMakeLists.txt 323 manifest_filename = os.path.join(stack_dir, STACK_FILE) 324 if os.path.isfile(manifest_filename): 325 m = roslib.stack_manifest.parse_file(manifest_filename) 326 if m.version: 327 return m.version 328 329 cmake_filename = os.path.join(stack_dir, 'CMakeLists.txt') 330 if os.path.isfile(cmake_filename): 331 with open(cmake_filename) as f: 332 return _get_cmake_version(f.read()) 333 else: 334 return None
335
336 -def _get_cmake_version(text):
337 for l in text.split('\n'): 338 if l.strip().startswith('rosbuild_make_distribution'): 339 x_re = re.compile(r'[()]') 340 lsplit = x_re.split(l.strip()) 341 if len(lsplit) < 2: 342 raise ReleaseException("couldn't find version number in CMakeLists.txt:\n\n%s"%l) 343 return lsplit[1]
344