00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036 from __future__ import with_statement
00037
00038 import os
00039 import subprocess
00040 import tempfile
00041 import re
00042 import tarfile
00043 import shutil
00044 import urllib2
00045 import re
00046 import hashlib
00047
00048 import yaml
00049
00050 import roslib.packages
00051 import roslib.stacks
00052 import roslib.stack_manifest
00053
00054 from rosdeb import control_data
00055
00056 from rosdeb.rosutil import checkout_svn_to_tmp, checkout_dev_to_tmp
00057
00058 class ReleaseException(Exception): pass
00059
00060 def get_source_version(distro, stack_name):
00061 dev_svn = distro.stacks[stack_name].dev_svn
00062 print "Reading version number from %s"%(dev_svn)
00063
00064 source_url = dev_svn + '/stack.xml'
00065 try:
00066 f = urllib2.urlopen(source_url)
00067 text = f.read()
00068 f.close()
00069 except urllib2.HTTPError:
00070 raise ReleaseException("failed to fetch stack.xml for stack.\nA common cause is the '_rules' are not set correctly for this stack.\nThe URL was %s"%(source_url))
00071
00072 try:
00073 m = roslib.stack_manifest.parse(text)
00074 if m.version:
00075 return m.version
00076 except:
00077 raise ReleaseException("stack.xml failed validation. Read from URL [%s]"%(source_url))
00078
00079 version = get_stack_manifest_version(text)
00080 if version:
00081 return version
00082
00083
00084 source_url = dev_svn + '/CMakeLists.txt'
00085 try:
00086 f = urllib2.urlopen(source_url)
00087 text = f.read()
00088 f.close()
00089 except urllib2.HTTPError:
00090 raise ReleaseException("failed to fetch CMakeLists.txt for stack.\nA common cause is the '_rules' are not set correctly for this stack.\nThe URL was %s"%(source_url))
00091
00092 return get_cmake_version(text)
00093
00094
00095
00096 def compute_stack_depends(stack_dir):
00097 """
00098 @return: depends, licenses
00099 @rtype: {str: [str]}, [str]
00100 @raise ReleaseException: if error occurs detecting dependencies
00101 """
00102 stack = os.path.basename(os.path.abspath(stack_dir))
00103 if os.path.exists(stack_dir):
00104 packages = roslib.packages.list_pkgs_by_path(os.path.abspath(stack_dir))
00105 depends = _compute_stack_depends_and_licenses(stack, packages)
00106 else:
00107 depends = dict()
00108
00109 if not 'ros' in depends and stack != 'ros':
00110 depends['ros'] = []
00111 return depends
00112
00113 def _compute_stack_depends_and_licenses(stack, packages):
00114 pkg_depends = []
00115 for pkg in packages:
00116 m = roslib.manifest.parse_file(roslib.manifest.manifest_file(pkg))
00117 pkg_depends.extend([d.package for d in m.depends])
00118
00119 stack_depends = {}
00120 for pkg in pkg_depends:
00121 if pkg in packages:
00122 continue
00123 try:
00124 st = roslib.stacks.stack_of(pkg)
00125 except roslib.packages.InvalidROSPkgException:
00126 raise ReleaseException("cannot locate package [%s], which is a dependency in the [%s] stack"%(pkg, stack))
00127 if not st:
00128 raise ReleaseException("WARNING: stack depends on [%s], which is not in a stack"%pkg)
00129 if st == stack:
00130 continue
00131 if not st in stack_depends:
00132 stack_depends[st] = []
00133 stack_depends[st].append(pkg)
00134 return stack_depends
00135
00136
00137 def get_stack_version(stack_dir, stack_name):
00138 return get_stack_version_by_dir(stack_dir)
00139
00140
00141
00142 def get_stack_version_by_dir(stack_dir):
00143 """
00144 Get stack version where stack_dir points to root directory of stack.
00145
00146 @param env: override environment variables
00147 @type env: {str: str}
00148
00149 @return: version number of stack, or None if stack is unversioned.
00150 @rtype: str
00151 """
00152
00153 manifest_filename = os.path.join(stack_dir, roslib.stacks.STACK_FILE)
00154 if os.path.isfile(manifest_filename):
00155 m = roslib.stack_manifest.parse_file(manifest_filename)
00156 try:
00157
00158 if m.version:
00159 return m.version
00160 except AttributeError:
00161 pass
00162
00163 cmake_filename = os.path.join(stack_dir, 'CMakeLists.txt')
00164 if os.path.isfile(cmake_filename):
00165 with open(cmake_filename) as f:
00166 return get_cmake_version(f.read())
00167 else:
00168 return None
00169
00170 def get_cmake_version(text):
00171 for l in text.split('\n'):
00172 if l.strip().startswith('rosbuild_make_distribution'):
00173 x_re = re.compile(r'[()]')
00174 lsplit = x_re.split(l.strip())
00175 if len(lsplit) < 2:
00176 raise ReleaseException("couldn't find version number in CMakeLists.txt:\n\n%s"%l)
00177 return lsplit[1]
00178
00179 def update_rosdistro_yaml(stack_name, version, distro_file):
00180 """
00181 Update distro file for new stack version
00182 """
00183 if not os.path.exists(distro_file):
00184 raise ReleaseException("[%s] does not exist"%distro_file)
00185
00186 with open(distro_file) as f:
00187 d = [d for d in yaml.load_all(f.read())]
00188 if len(d) != 1:
00189 raise ReleaseException("found more than one release document in [%s]"%distro_file)
00190 d = d[0]
00191
00192 distro_d = d
00193 if not 'stacks' in d:
00194 d['stacks'] = {}
00195 d = d['stacks']
00196 if not stack_name in d:
00197 d[stack_name] = {}
00198 d = d[stack_name]
00199
00200 d['version'] = str(version)
00201
00202 print "Writing new release properties to [%s]"%distro_file
00203 with open(distro_file, 'w') as f:
00204 f.write(yaml.safe_dump(distro_d))
00205
00206 def make_dist(name, version, distro_stack, repair=False):
00207 """
00208 Create tarball in a temporary directory.
00209
00210 @param repair: if True, repair tarball from tagged release.
00211 @type repair: bool
00212 @return: tarball file path, control data.
00213 """
00214 tmp_dir = checkout_stack(name, distro_stack, repair)
00215
00216 return make_dist_of_dir(tmp_dir, name, version, distro_stack)
00217
00218 def checkout_stack(name, distro_stack):
00219 """
00220 Checkout the stack into a tempdir
00221
00222 @return The temporary directory to find the stack inside
00223 """
00224 tmp_dir = checkout_dev_to_tmp(name, distro_stack)
00225 return tmp_dir
00226
00227 def md5sum_file(filename):
00228 m = hashlib.md5()
00229 with open(filename, 'rb') as f:
00230 data = f.read(4096)
00231 while data:
00232 m.update(data)
00233 data = f.read(4096)
00234 return m.hexdigest()
00235
00236 def make_dist_of_dir(tmp_dir, name, version, distro_stack):
00237 """
00238 Create tarball in a temporary directory.
00239 It is expected the tempdir has a fresh checkout of the stack.
00240
00241 @param name: stack name
00242 @param version: stack version
00243 @return: tarball file path, control data.
00244 """
00245 tmp_source_dir = os.path.join(tmp_dir, name)
00246 print 'Building a distribution for %s in %s'%(name, tmp_source_dir)
00247 tarball = create_stack_tarball(tmp_source_dir, name, version)
00248 md5sum = md5sum_file(tarball)
00249 control = control_data(name, version, md5sum, stack_file=os.path.join(tmp_source_dir, 'stack.xml'))
00250
00251
00252 dst = os.path.join(tempfile.gettempdir(), os.path.basename(tarball))
00253 shutil.copyfile(tarball, dst)
00254 return dst, control
00255
00256 TAR_IGNORE_TOP=['build']
00257 TAR_IGNORE_ALL=['.svn', '.git', '.hg']
00258
00259 def tar_exclude(name):
00260 if name.split('/')[-1] in TAR_IGNORE_ALL:
00261 return True
00262 else:
00263 return False
00264
00265 def create_stack_tarball(path, stack_name, stack_version):
00266 """
00267 Create a source tarball from a stack at a particular path.
00268
00269 @param name: name of stack
00270 @param stack_version: version number of stack
00271 @param path: the path of the stack to package up
00272 @return: the path of the resulting tarball, or else None
00273 """
00274
00275
00276 stack_xml_path = os.path.join(path, roslib.stack_manifest.STACK_FILE)
00277 cmake_lists_path = os.path.join(path, 'CMakeLists.txt')
00278
00279 if not os.path.exists(stack_xml_path):
00280 raise ReleaseException("Could not find stack manifest, expected [%s]."%(stack_xml_path))
00281 if not os.path.exists(cmake_lists_path):
00282 raise ReleaseException("Could not find CMakeLists.txt file, expected [%s]."%(cmake_lists_path))
00283
00284
00285 build_dir = os.path.join(path, 'build')
00286 if not os.path.exists(build_dir):
00287 os.makedirs(build_dir)
00288
00289 tarfile_name = os.path.join(build_dir,'%s-%s.tar.bz2'%(stack_name, stack_version))
00290 archive_dir = '%s-%s'%(stack_name, stack_version)
00291
00292 tar = tarfile.open(tarfile_name, 'w:bz2')
00293
00294 for x in os.listdir(path):
00295 if x not in TAR_IGNORE_TOP + TAR_IGNORE_ALL:
00296
00297 p = os.path.join(path,x)
00298
00299 tp = os.path.join(archive_dir, x)
00300
00301 tar.add(p, tp, exclude=tar_exclude)
00302
00303 tar.close()
00304 return tarfile_name
00305