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

Source Code for Module roslib.gentools

  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$ 
 35  # $Author$ 
 36   
 37  """ 
 38  Library for supporting message and service generation for all ROS 
 39  client libraries. This is mainly responsible for calculating the 
 40  md5sums and message definitions of classes. 
 41  """ 
 42   
 43  # NOTE: this should not contain any rospy-specific code. The rospy 
 44  # generator library is rospy.genpy. 
 45   
 46  import sys 
 47   
 48  try: 
 49      from cStringIO import StringIO  # Python 2.x 
 50  except ImportError: 
 51      from io import StringIO  # Python 3.x 
 52   
 53  import roslib.msgs 
 54  import roslib.names 
 55  import roslib.srvs 
 56  from roslib.msgs import MsgSpecException 
 57   
 58  import rospkg 
 59   
 60  # name of the Header type as gentools knows it 
 61  _header_type_name = 'std_msgs/Header' 
 62   
 63   
64 -def _add_msgs_depends(rospack, spec, deps, package_context):
65 """ 66 Add the list of message types that spec depends on to depends. 67 @param spec: message to compute dependencies for 68 @type spec: roslib.msgs.MsgSpec/roslib.srvs.SrvSpec 69 @param deps [str]: list of dependencies. This list will be updated 70 with the dependencies of spec when the method completes 71 @type deps: [str] 72 @raise KeyError for invalid dependent types due to missing package dependencies. 73 """ 74 def _get_valid_packages(package_context, rospack): 75 valid_packages = ['', package_context] 76 try: 77 valid_packages = valid_packages + rospack.get_depends(package_context, implicit=True) 78 except rospkg.ResourceNotFound: 79 # this happens in dynamic generation situations where the 80 # package is not present. we soft fail here because we assume 81 # missing messages will be caught later during lookup. 82 pass 83 return valid_packages
84 85 valid_packages = None 86 87 for t in spec.types: 88 t = roslib.msgs.base_msg_type(t) 89 if not roslib.msgs.is_builtin(t): 90 t_package, t_base = roslib.names.package_resource_name(t) 91 92 # special mapping for header 93 if t == roslib.msgs.HEADER: 94 # have to re-names Header 95 deps.append(_header_type_name) 96 97 if roslib.msgs.is_registered(t): 98 depspec = roslib.msgs.get_registered(t) 99 if t != roslib.msgs.HEADER: 100 if '/' in t: 101 deps.append(t) 102 else: 103 deps.append(package_context+'/'+t) 104 else: 105 if valid_packages is None: 106 valid_packages = _get_valid_packages(package_context, rospack) 107 if t_package in valid_packages: 108 # if we are allowed to load the message, load it. 109 key, depspec = roslib.msgs.load_by_type(t, package_context) 110 if t != roslib.msgs.HEADER: 111 deps.append(key) 112 roslib.msgs.register(key, depspec) 113 else: 114 # not allowed to load the message, so error. 115 raise KeyError(t) 116 _add_msgs_depends(rospack, depspec, deps, package_context) 117 118
119 -def compute_md5_text(get_deps_dict, spec, rospack=None):
120 """ 121 Compute the text used for md5 calculation. MD5 spec states that we 122 removes comments and non-meaningful whitespace. We also strip 123 packages names from type names. For convenience sake, constants are 124 reordered ahead of other declarations, in the order that they were 125 originally defined. 126 127 @return: text for ROS MD5-processing 128 @rtype: str 129 """ 130 uniquedeps = get_deps_dict['uniquedeps'] 131 package = get_deps_dict['package'] 132 # #1554: need to suppress computation of files in dynamic generation case 133 compute_files = 'files' in get_deps_dict 134 135 buff = StringIO() 136 137 for c in spec.constants: 138 buff.write('%s %s=%s\n' % (c.type, c.name, c.val_text)) 139 for type_, name in zip(spec.types, spec.names): 140 base_msg_type = roslib.msgs.base_msg_type(type_) 141 # md5 spec strips package names 142 if roslib.msgs.is_builtin(base_msg_type): 143 buff.write('%s %s\n' % (type_, name)) 144 else: 145 # recursively generate md5 for subtype. have to build up 146 # dependency representation for subtype in order to 147 # generate md5 148 149 # - ugly special-case handling of Header 150 if base_msg_type == roslib.msgs.HEADER: 151 base_msg_type = _header_type_name 152 153 sub_pkg, _ = roslib.names.package_resource_name(base_msg_type) 154 sub_pkg = sub_pkg or package 155 sub_spec = roslib.msgs.get_registered(base_msg_type, package) 156 sub_deps = get_dependencies(sub_spec, sub_pkg, compute_files=compute_files, rospack=rospack) 157 sub_md5 = compute_md5(sub_deps, rospack) 158 buff.write('%s %s\n' % (sub_md5, name)) 159 160 return buff.getvalue().strip() # remove trailing new line
161 162
163 -def _compute_hash(get_deps_dict, hash, rospack=None):
164 """ 165 subroutine of compute_md5() 166 @param get_deps_dict: dictionary returned by get_dependencies call 167 @type get_deps_dict: dict 168 @param hash: hash instance 169 @type hash: hash instance 170 """ 171 # accumulate the hash 172 # - root file 173 from roslib.msgs import MsgSpec 174 from roslib.srvs import SrvSpec 175 spec = get_deps_dict['spec'] 176 if isinstance(spec, MsgSpec): 177 hash.update(compute_md5_text(get_deps_dict, spec, rospack=rospack).encode()) 178 elif isinstance(spec, SrvSpec): 179 hash.update(compute_md5_text(get_deps_dict, spec.request, rospack=rospack).encode()) 180 hash.update(compute_md5_text(get_deps_dict, spec.response, rospack=rospack).encode()) 181 else: 182 raise Exception('[%s] is not a message or service' % spec) 183 return hash.hexdigest()
184 185
186 -def _compute_hash_v1(get_deps_dict, hash):
187 """ 188 subroutine of compute_md5_v1() 189 @param get_deps_dict: dictionary returned by get_dependencies call 190 @type get_deps_dict: dict 191 @param hash: hash instance 192 @type hash: hash instance 193 """ 194 uniquedeps = get_deps_dict['uniquedeps'] 195 spec = get_deps_dict['spec'] 196 # accumulate the hash 197 # - root file 198 hash.update(spec.text) 199 # - dependencies 200 for d in uniquedeps: 201 hash.update(roslib.msgs.get_registered(d).text) 202 return hash.hexdigest()
203 204
205 -def compute_md5_v1(get_deps_dict):
206 """ 207 Compute original V1 md5 hash for message/service. This was replaced with V2 in ROS 0.6. 208 @param get_deps_dict: dictionary returned by get_dependencies call 209 @type get_deps_dict: dict 210 @return: md5 hash 211 @rtype: str 212 """ 213 import hashlib 214 return _compute_hash_v1(get_deps_dict, hashlib.md5())
215 216
217 -def compute_md5(get_deps_dict, rospack=None):
218 """ 219 Compute md5 hash for message/service 220 @param get_deps_dict dict: dictionary returned by get_dependencies call 221 @type get_deps_dict: dict 222 @return: md5 hash 223 @rtype: str 224 """ 225 try: 226 # md5 is deprecated in Python 2.6 in favor of hashlib, but hashlib is 227 # unavailable in Python 2.4 228 import hashlib 229 return _compute_hash(get_deps_dict, hashlib.md5(), rospack=rospack) 230 except ImportError: 231 import md5 232 return _compute_hash(get_deps_dict, md5.new(), rospack=rospack)
233 234 235 # alias 236 compute_md5_v2 = compute_md5 237 238
239 -def compute_full_text(get_deps_dict):
240 """ 241 Compute full text of message/service, including text of embedded 242 types. The text of the main msg/srv is listed first. Embedded 243 msg/srv files are denoted first by an 80-character '=' separator, 244 followed by a type declaration line,'MSG: pkg/type', followed by 245 the text of the embedded type. 246 247 @param get_deps_dict dict: dictionary returned by get_dependencies call 248 @type get_deps_dict: dict 249 @return: concatenated text for msg/srv file and embedded msg/srv types. 250 @rtype: str 251 """ 252 buff = StringIO() 253 sep = '='*80+'\n' 254 255 # write the text of the top-level type 256 buff.write(get_deps_dict['spec'].text) 257 buff.write('\n') 258 # append the text of the dependencies (embedded types) 259 for d in get_deps_dict['uniquedeps']: 260 buff.write(sep) 261 buff.write('MSG: %s\n' % d) 262 buff.write(roslib.msgs.get_registered(d).text) 263 buff.write('\n') 264 # #1168: remove the trailing \n separator that is added by the concatenation logic 265 return buff.getvalue()[:-1]
266 267
268 -def get_file_dependencies(f, stdout=sys.stdout, stderr=sys.stderr, rospack=None):
269 """ 270 Compute dependencies of the specified message/service file 271 @param f: message or service file to get dependencies for 272 @type f: str 273 @param stdout pipe: stdout pipe 274 @type stdout: file 275 @param stderr pipe: stderr pipe 276 @type stderr: file 277 @return: 'files': list of files that \a file depends on, 278 'deps': list of dependencies by type, 'spec': Msgs/Srvs 279 instance. 280 @rtype: dict 281 """ 282 package = rospkg.get_package_name(f) 283 spec = None 284 if f.endswith(roslib.msgs.EXT): 285 _, spec = roslib.msgs.load_from_file(f) 286 elif f.endswith(roslib.srvs.EXT): 287 _, spec = roslib.srvs.load_from_file(f) 288 else: 289 raise Exception('[%s] does not appear to be a message or service' % spec) 290 return get_dependencies(spec, package, stdout, stderr, rospack=rospack)
291 292
293 -def get_dependencies(spec, package, compute_files=True, stdout=sys.stdout, stderr=sys.stderr, rospack=None):
294 """ 295 Compute dependencies of the specified Msgs/Srvs 296 @param spec: message or service instance 297 @type spec: L{roslib.msgs.MsgSpec}/L{roslib.srvs.SrvSpec} 298 @param package: package name 299 @type package: str 300 @param stdout: (optional) stdout pipe 301 @type stdout: file 302 @param stderr: (optional) stderr pipe 303 @type stderr: file 304 @param compute_files: (optional, default=True) compute file 305 dependencies of message ('files' key in return value) 306 @type compute_files: bool 307 @return: dict: 308 * 'files': list of files that \a file depends on 309 * 'deps': list of dependencies by type 310 * 'spec': Msgs/Srvs instance. 311 * 'uniquedeps': list of dependencies with duplicates removed, 312 * 'package': package that dependencies were generated relative to. 313 @rtype: dict 314 """ 315 316 # #518: as a performance optimization, we're going to manually control the loading 317 # of msgs instead of doing package-wide loads. 318 319 # we're going to manipulate internal apis of msgs, so have to manually init 320 roslib.msgs._init() 321 322 deps = [] 323 try: 324 if not rospack: 325 rospack = rospkg.RosPack() 326 if isinstance(spec, roslib.msgs.MsgSpec): 327 _add_msgs_depends(rospack, spec, deps, package) 328 elif isinstance(spec, roslib.srvs.SrvSpec): 329 _add_msgs_depends(rospack, spec.request, deps, package) 330 _add_msgs_depends(rospack, spec.response, deps, package) 331 else: 332 raise MsgSpecException('spec does not appear to be a message or service') 333 except KeyError as e: 334 raise MsgSpecException('Cannot load type %s. Perhaps the package is missing a dependency.' % (str(e))) 335 336 # convert from type names to file names 337 338 if compute_files: 339 files = {} 340 for d in set(deps): 341 d_pkg, t = roslib.names.package_resource_name(d) 342 d_pkg = d_pkg or package # convert '' -> local package 343 files[d] = roslib.msgs.msg_file(d_pkg, t) 344 else: 345 files = None 346 347 # create unique dependency list 348 uniquedeps = [] 349 for d in deps: 350 if d not in uniquedeps: 351 uniquedeps.append(d) 352 353 if compute_files: 354 return {'files': files, 'deps': deps, 'spec': spec, 'package': package, 'uniquedeps': uniquedeps} 355 else: 356 return {'deps': deps, 'spec': spec, 'package': package, 'uniquedeps': uniquedeps}
357