summaryrefslogtreecommitdiff
path: root/rbutil/rbutilqt/deploy.py
diff options
context:
space:
mode:
Diffstat (limited to 'rbutil/rbutilqt/deploy.py')
-rwxr-xr-xrbutil/rbutilqt/deploy.py497
1 files changed, 497 insertions, 0 deletions
diff --git a/rbutil/rbutilqt/deploy.py b/rbutil/rbutilqt/deploy.py
new file mode 100755
index 0000000000..f4f3fac786
--- /dev/null
+++ b/rbutil/rbutilqt/deploy.py
@@ -0,0 +1,497 @@
1#!/usr/bin/python
2# __________ __ ___.
3# Open \______ \ ____ ____ | | _\_ |__ _______ ___
4# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7# \/ \/ \/ \/ \/
8# $Id$
9#
10# Copyright (c) 2009 Dominik Riebeling
11#
12# All files in this archive are subject to the GNU General Public License.
13# See the file COPYING in the source tree root for full license agreement.
14#
15# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16# KIND, either express or implied.
17#
18#
19# Automate building releases for deployment.
20# Run from any folder to build
21# - trunk
22# - any tag (using the -t option)
23# - any local folder (using the -p option)
24# Will build a binary archive (tar.bz2 / zip) and source archive.
25# The source archive won't be built for local builds. Trunk and
26# tag builds will retrieve the sources directly from svn and build
27# below the systems temporary folder.
28#
29# If the required Qt installation isn't in PATH use --qmake option.
30# Tested on Linux and MinGW / W32
31#
32# requires python which package (http://code.google.com/p/which/)
33# requires pysvn package.
34# requires upx.exe in PATH on Windows.
35#
36
37import re
38import os
39import sys
40import tarfile
41import zipfile
42import shutil
43import subprocess
44import getopt
45import time
46import hashlib
47import tempfile
48
49# modules that are not part of python itself.
50try:
51 import pysvn
52except ImportError:
53 print "Fatal: This script requires the pysvn package to run."
54 print " See http://pysvn.tigris.org/."
55 sys.exit(-5)
56try:
57 import which
58except ImportError:
59 print "Fatal: This script requires the which package to run."
60 print " See http://code.google.com/p/which/."
61 sys.exit(-5)
62
63# == Global stuff ==
64# Windows nees some special treatment. Differentiate between program name
65# and executable filename.
66program = ""
67project = ""
68environment = os.environ
69progexe = ""
70make = "make"
71programfiles = []
72
73svnserver = ""
74# Paths and files to retrieve from svn when creating a tarball.
75# This is a mixed list, holding both paths and filenames.
76svnpaths = [ ]
77# set this to true to run upx on the resulting binary, false to skip this step.
78# only used on w32.
79useupx = False
80
81# OS X: files to copy into the bundle. Workaround for out-of-tree builds.
82bundlecopy = { }
83
84# == Functions ==
85def usage(myself):
86 print "Usage: %s [options]" % myself
87 print " -q, --qmake=<qmake> path to qmake"
88 print " -p, --project=<pro> path to .pro file for building with local tree"
89 print " -t, --tag=<tag> use specified tag from svn"
90 print " -a, --add=<file> add file to build folder before building"
91 print " -s, --source-only only create source archive"
92 print " -b, --binary-only only create binary archive"
93 if sys.platform != "darwin":
94 print " -d, --dynamic link dynamically instead of static"
95 print " -k, --keep-temp keep temporary folder on build failure"
96 print " -h, --help this help"
97 print " If neither a project file nor tag is specified trunk will get downloaded"
98 print " from svn."
99
100def getsources(svnsrv, filelist, dest):
101 '''Get the files listed in filelist from svnsrv and put it at dest.'''
102 client = pysvn.Client()
103 print "Checking out sources from %s, please wait." % svnsrv
104
105 for elem in filelist:
106 url = re.subn('/$', '', svnsrv + elem)[0]
107 destpath = re.subn('/$', '', dest + elem)[0]
108 # make sure the destination path does exist
109 d = os.path.dirname(destpath)
110 if not os.path.exists(d):
111 os.makedirs(d)
112 # get from svn
113 try:
114 client.export(url, destpath)
115 except:
116 print "SVN client error: %s" % sys.exc_value
117 print "URL: %s, destination: %s" % (url, destpath)
118 return -1
119 print "Checkout finished."
120 return 0
121
122
123def gettrunkrev(svnsrv):
124 '''Get the revision of trunk for svnsrv'''
125 client = pysvn.Client()
126 entries = client.info2(svnsrv, recurse=False)
127 return entries[0][1].rev.number
128
129
130def findversion(versionfile):
131 '''figure most recent program version from version.h,
132 returns version string.'''
133 h = open(versionfile, "r")
134 c = h.read()
135 h.close()
136 r = re.compile("#define +VERSION +\"(.[0-9\.a-z]+)\"")
137 m = re.search(r, c)
138 s = re.compile("\$Revision: +([0-9]+)")
139 n = re.search(s, c)
140 if n == None:
141 print "WARNING: Revision not found!"
142 return m.group(1)
143
144
145def findqt():
146 '''Search for Qt4 installation. Return path to qmake.'''
147 print "Searching for Qt"
148 bins = ["qmake", "qmake-qt4"]
149 for binary in bins:
150 try:
151 q = which.which(binary)
152 if len(q) > 0:
153 result = checkqt(q)
154 if not result == "":
155 return result
156 except:
157 print sys.exc_value
158
159 return ""
160
161
162def checkqt(qmakebin):
163 '''Check if given path to qmake exists and is a suitable version.'''
164 result = ""
165 # check if binary exists
166 if not os.path.exists(qmakebin):
167 print "Specified qmake path does not exist!"
168 return result
169 # check version
170 output = subprocess.Popen([qmakebin, "-version"], stdout=subprocess.PIPE,
171 stderr=subprocess.PIPE)
172 cmdout = output.communicate()
173 # don't check the qmake return code here, Qt3 doesn't return 0 on -version.
174 for ou in cmdout:
175 r = re.compile("Qt[^0-9]+([0-9\.]+[a-z]*)")
176 m = re.search(r, ou)
177 if not m == None:
178 print "Qt found: %s" % m.group(1)
179 s = re.compile("4\..*")
180 n = re.search(s, m.group(1))
181 if not n == None:
182 result = qmakebin
183 return result
184
185
186def qmake(qmake="qmake", projfile=project, wd=".", static=True):
187 print "Running qmake in %s..." % wd
188 command = [qmake, "-config", "release", "-config", "noccache"]
189 if static == True:
190 command.append("-config")
191 command.append("static")
192 command.append(projfile)
193 output = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=wd, env=environment)
194 output.communicate()
195 if not output.returncode == 0:
196 print "qmake returned an error!"
197 return -1
198 return 0
199
200
201def build(wd="."):
202 # make
203 print "Building ..."
204 output = subprocess.Popen([make], stdout=subprocess.PIPE, cwd=wd)
205 while True:
206 c = output.stdout.readline()
207 sys.stdout.write(".")
208 sys.stdout.flush()
209 if not output.poll() == None:
210 sys.stdout.write("\n")
211 sys.stdout.flush()
212 if not output.returncode == 0:
213 print "Build failed!"
214 return -1
215 break
216 if sys.platform != "darwin":
217 # strip. OS X handles this via macdeployqt.
218 print "Stripping binary."
219 output = subprocess.Popen(["strip", progexe], stdout=subprocess.PIPE, cwd=wd)
220 output.communicate()
221 if not output.returncode == 0:
222 print "Stripping failed!"
223 return -1
224 return 0
225
226
227def upxfile(wd="."):
228 # run upx on binary
229 print "UPX'ing binary ..."
230 output = subprocess.Popen(["upx", progexe], stdout=subprocess.PIPE, cwd=wd)
231 output.communicate()
232 if not output.returncode == 0:
233 print "UPX'ing failed!"
234 return -1
235 return 0
236
237
238def zipball(versionstring, buildfolder):
239 '''package created binary'''
240 print "Creating binary zipball."
241 archivebase = program + "-" + versionstring
242 outfolder = buildfolder + "/" + archivebase
243 archivename = archivebase + ".zip"
244 # create output folder
245 os.mkdir(outfolder)
246 # move program files to output folder
247 for f in programfiles:
248 shutil.copy(buildfolder + "/" + f, outfolder)
249 # create zipball from output folder
250 zf = zipfile.ZipFile(archivename, mode='w', compression=zipfile.ZIP_DEFLATED)
251 for root, dirs, files in os.walk(outfolder):
252 for name in files:
253 physname = os.path.join(root, name)
254 filename = re.sub("^" + buildfolder, "", physname)
255 zf.write(physname, filename)
256 for name in dirs:
257 physname = os.path.join(root, name)
258 filename = re.sub("^" + buildfolder, "", physname)
259 zf.write(physname, filename)
260 zf.close()
261 # remove output folder
262 shutil.rmtree(outfolder)
263 return archivename
264
265
266def tarball(versionstring, buildfolder):
267 '''package created binary'''
268 print "Creating binary tarball."
269 archivebase = program + "-" + versionstring
270 outfolder = buildfolder + "/" + archivebase
271 archivename = archivebase + ".tar.bz2"
272 # create output folder
273 os.mkdir(outfolder)
274 # move program files to output folder
275 for f in programfiles:
276 shutil.copy(buildfolder + "/" + f, outfolder)
277 # create tarball from output folder
278 tf = tarfile.open(archivename, mode='w:bz2')
279 tf.add(outfolder, archivebase)
280 tf.close()
281 # remove output folder
282 shutil.rmtree(outfolder)
283 return archivename
284
285
286def macdeploy(versionstring, buildfolder):
287 '''package created binary to dmg'''
288 dmgfile = program + "-" + versionstring + ".dmg"
289 appbundle = buildfolder + "/" + progexe
290
291 # workaround to Qt issues when building out-of-tree. Copy files into bundle.
292 sourcebase = buildfolder + re.sub('rbutilqt.pro$', '', project)
293 for src in bundlecopy:
294 shutil.copy(sourcebase + src, appbundle + bundlecopy[src])
295 # end of Qt workaround
296
297 output = subprocess.Popen(["macdeployqt", progexe, "-dmg"], stdout=subprocess.PIPE, cwd=buildfolder)
298 output.communicate()
299 if not output.returncode == 0:
300 print "macdeployqt failed!"
301 return -1
302 # copy dmg to output folder
303 shutil.copy(buildfolder + "/" + program + ".dmg", dmgfile)
304 return dmgfile
305
306def filehashes(filename):
307 '''Calculate md5 and sha1 hashes for a given file.'''
308 if not os.path.exists(filename):
309 return ["", ""]
310 m = hashlib.md5()
311 s = hashlib.sha1()
312 f = open(filename, 'rb')
313 while True:
314 d = f.read(65536)
315 if d == "":
316 break
317 m.update(d)
318 s.update(d)
319 return [m.hexdigest(), s.hexdigest()]
320
321
322def filestats(filename):
323 if not os.path.exists(filename):
324 return
325 st = os.stat(filename)
326 print filename, "\n", "-" * len(filename)
327 print "Size: %i bytes" % st.st_size
328 h = filehashes(filename)
329 print "md5sum: %s" % h[0]
330 print "sha1sum: %s" % h[1]
331 print "-" * len(filename), "\n"
332
333
334def tempclean(workfolder, nopro):
335 if nopro == True:
336 print "Cleaning up working folder %s" % workfolder
337 shutil.rmtree(workfolder)
338 else:
339 print "Project file specified or cleanup disabled!"
340 print "Temporary files kept at %s" % workfolder
341
342
343def deploy():
344 startup = time.time()
345
346 try:
347 opts, args = getopt.getopt(sys.argv[1:], "q:p:t:a:sbdkh",
348 ["qmake=", "project=", "tag=", "add=", "source-only", "binary-only", "dynamic", "keep-temp", "help"])
349 except getopt.GetoptError, err:
350 print str(err)
351 usage(sys.argv[0])
352 sys.exit(1)
353 qt = ""
354 proj = ""
355 svnbase = svnserver + "trunk/"
356 tag = ""
357 addfiles = []
358 cleanup = True
359 binary = True
360 source = True
361 keeptemp = False
362 if sys.platform != "darwin":
363 static = True
364 else:
365 static = False
366 for o, a in opts:
367 if o in ("-q", "--qmake"):
368 qt = a
369 if o in ("-p", "--project"):
370 proj = a
371 cleanup = False
372 if o in ("-t", "--tag"):
373 tag = a
374 svnbase = svnserver + "tags/" + tag + "/"
375 if o in ("-a", "--add"):
376 addfiles.append(a)
377 if o in ("-s", "--source-only"):
378 binary = False
379 if o in ("-b", "--binary-only"):
380 source = False
381 if o in ("-d", "--dynamic") and sys.platform != "darwin":
382 static = False
383 if o in ("-k", "--keep-temp"):
384 keeptemp = True
385 if o in ("-h", "--help"):
386 usage(sys.argv[0])
387 sys.exit(0)
388
389 if source == False and binary == False:
390 print "Building build neither source nor binary means nothing to do. Exiting."
391 sys.exit(1)
392
393 # search for qmake
394 if qt == "":
395 qm = findqt()
396 else:
397 qm = checkqt(qt)
398 if qm == "":
399 print "ERROR: No suitable Qt installation found."
400 sys.exit(1)
401
402 # create working folder. Use current directory if -p option used.
403 if proj == "":
404 w = tempfile.mkdtemp()
405 # make sure the path doesn't contain backslashes to prevent issues
406 # later when running on windows.
407 workfolder = re.sub(r'\\', '/', w)
408 if not tag == "":
409 sourcefolder = workfolder + "/" + tag + "/"
410 archivename = tag + "-src.tar.bz2"
411 # get numeric version part from tag
412 ver = "v" + re.sub('^[^\d]+', '', tag)
413 else:
414 trunk = gettrunkrev(svnbase)
415 sourcefolder = workfolder + "/" + program + "-r" + str(trunk) + "/"
416 archivename = program + "-r" + str(trunk) + "-src.tar.bz2"
417 ver = "r" + str(trunk)
418 os.mkdir(sourcefolder)
419 else:
420 workfolder = "."
421 sourcefolder = "."
422 archivename = ""
423 # check if project file explicitly given. If yes, don't get sources from svn
424 if proj == "":
425 proj = sourcefolder + project
426 # get sources and pack source tarball
427 if not getsources(svnbase, svnpaths, sourcefolder) == 0:
428 tempclean(workfolder, cleanup and not keeptemp)
429 sys.exit(1)
430
431 if source == True:
432 tf = tarfile.open(archivename, mode='w:bz2')
433 tf.add(sourcefolder, os.path.basename(re.subn('/$', '', sourcefolder)[0]))
434 tf.close()
435 if binary == False:
436 shutil.rmtree(workfolder)
437 sys.exit(0)
438 else:
439 # figure version from sources. Need to take path to project file into account.
440 versionfile = re.subn('[\w\.]+$', "version.h", proj)[0]
441 ver = findversion(versionfile)
442
443 # check project file
444 if not os.path.exists(proj):
445 print "ERROR: path to project file wrong."
446 sys.exit(1)
447
448 # copy specified (--add) files to working folder
449 for f in addfiles:
450 shutil.copy(f, sourcefolder)
451 buildstart = time.time()
452 header = "Building %s %s" % (program, ver)
453 print header
454 print len(header) * "="
455
456 # build it.
457 if not qmake(qm, proj, sourcefolder, static) == 0:
458 tempclean(workfolder, cleanup and not keeptemp)
459 sys.exit(1)
460 if not build(sourcefolder) == 0:
461 tempclean(workfolder, cleanup and not keeptemp)
462 sys.exit(1)
463 if sys.platform == "win32":
464 if useupx == True:
465 if not upxfile(sourcefolder) == 0:
466 tempclean(workfolder, cleanup and not keeptemp)
467 sys.exit(1)
468 archive = zipball(ver, sourcefolder)
469 elif sys.platform == "darwin":
470 archive = macdeploy(ver, sourcefolder)
471 else:
472 if os.uname()[4].endswith("64"):
473 ver += "-64bit"
474 archive = tarball(ver, sourcefolder)
475
476 # remove temporary files
477 tempclean(workfolder, cleanup)
478
479 # display summary
480 headline = "Build Summary for %s" % program
481 print "\n", headline, "\n", "=" * len(headline)
482 if not archivename == "":
483 filestats(archivename)
484 filestats(archive)
485 duration = time.time() - startup
486 building = time.time() - buildstart
487 durmins = (int)(duration / 60)
488 dursecs = (int)(duration % 60)
489 buildmins = (int)(building / 60)
490 buildsecs = (int)(building % 60)
491 print "Overall time %smin %ssec, building took %smin %ssec." % \
492 (durmins, dursecs, buildmins, buildsecs)
493
494
495if __name__ == "__main__":
496 deploy()
497