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