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