blob: 6a8b2580da22d1250189a46897456a1ded470367 [file] [log] [blame]
Dominik Riebelingefb93432009-06-18 22:17:17 +00001#!/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.
Dominik Riebeling45c411e2009-10-04 16:45:59 +000020# 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#
Dominik Riebelingefb93432009-06-18 22:17:17 +000029# If the required Qt installation isn't in PATH use --qmake option.
30# Tested on Linux and MinGW / W32
31#
Dominik Riebeling45c411e2009-10-04 16:45:59 +000032# requires pysvn package.
Dominik Riebelingefb93432009-06-18 22:17:17 +000033# requires upx.exe in PATH on Windows.
34#
35
36import re
37import os
38import sys
39import tarfile
40import zipfile
41import shutil
42import subprocess
43import getopt
Dominik Riebelingd7b9f992009-09-20 14:59:29 +000044import time
Dominik Riebeling45c411e2009-10-04 16:45:59 +000045import hashlib
Dominik Riebeling45c411e2009-10-04 16:45:59 +000046import tempfile
Dominik Riebeling43a40ca2011-03-06 00:04:26 +000047import string
Dominik Riebelingefb93432009-06-18 22:17:17 +000048
Dominik Riebeling11212062009-10-30 21:40:07 +000049# 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)
Dominik Riebelingee3fc612010-12-19 20:10:22 +000056cpus = 1
57try:
58 import multiprocessing
59 cpus = multiprocessing.cpu_count()
60 print "Info: %s cores found." % cpus
61except ImportError:
62 print "Warning: multiprocessing module not found. Assuming 1 core."
Dominik Riebeling11212062009-10-30 21:40:07 +000063
Dominik Riebelingefb93432009-06-18 22:17:17 +000064# == Global stuff ==
Dominik Riebeling4756a772010-08-30 17:51:53 +000065# DLL files to ignore when searching for required DLL files.
66systemdlls = ['advapi32.dll',
67 'comdlg32.dll',
68 'gdi32.dll',
69 'imm32.dll',
70 'kernel32.dll',
71 'msvcrt.dll',
72 'msvcrt.dll',
73 'netapi32.dll',
74 'ole32.dll',
75 'oleaut32.dll',
76 'setupapi.dll',
77 'shell32.dll',
78 'user32.dll',
79 'winmm.dll',
80 'winspool.drv',
81 'ws2_32.dll']
82
83
Dominik Riebelingefb93432009-06-18 22:17:17 +000084# == Functions ==
85def usage(myself):
86 print "Usage: %s [options]" % myself
87 print " -q, --qmake=<qmake> path to qmake"
Dominik Riebeling45c411e2009-10-04 16:45:59 +000088 print " -p, --project=<pro> path to .pro file for building with local tree"
89 print " -t, --tag=<tag> use specified tag from svn"
Dominik Riebelingd5529ef2009-10-04 21:17:59 +000090 print " -a, --add=<file> add file to build folder before building"
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +000091 print " -s, --source-only only create source archive"
92 print " -b, --binary-only only create binary archive"
Dominik Riebeling93c81dc2010-08-08 20:56:58 +000093 if nsisscript != "":
94 print " -n, --makensis=<file> path to makensis for building Windows setup program."
Dominik Riebeling5f7a8462009-11-29 21:09:21 +000095 if sys.platform != "darwin":
96 print " -d, --dynamic link dynamically instead of static"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +000097 if sys.platform != "win32":
98 print " -x, --cross= prefix to cross compile for win32"
Dominik Riebelingaae22132010-03-31 20:52:07 +000099 print " -k, --keep-temp keep temporary folder on build failure"
Dominik Riebelingefb93432009-06-18 22:17:17 +0000100 print " -h, --help this help"
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000101 print " If neither a project file nor tag is specified trunk will get downloaded"
102 print " from svn."
103
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000104
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000105def which(executable):
106 path = os.environ.get("PATH", "").split(os.pathsep)
107 for p in path:
108 fullpath = p + "/" + executable
109 if os.path.exists(fullpath):
110 return fullpath
111 print "which: could not find " + executable
112 return ""
113
114
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000115def getsources(svnsrv, filelist, dest):
116 '''Get the files listed in filelist from svnsrv and put it at dest.'''
117 client = pysvn.Client()
118 print "Checking out sources from %s, please wait." % svnsrv
119
120 for elem in filelist:
121 url = re.subn('/$', '', svnsrv + elem)[0]
122 destpath = re.subn('/$', '', dest + elem)[0]
Dominik Riebeling17172172009-11-20 21:04:23 +0000123 # make sure the destination path does exist
124 d = os.path.dirname(destpath)
Dominik Riebeling17172172009-11-20 21:04:23 +0000125 if not os.path.exists(d):
126 os.makedirs(d)
127 # get from svn
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000128 try:
129 client.export(url, destpath)
130 except:
131 print "SVN client error: %s" % sys.exc_value
Dominik Riebeling17172172009-11-20 21:04:23 +0000132 print "URL: %s, destination: %s" % (url, destpath)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000133 return -1
134 print "Checkout finished."
135 return 0
136
137
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000138def getfolderrev(svnsrv):
139 '''Get the most recent revision for svnsrv'''
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000140 client = pysvn.Client()
141 entries = client.info2(svnsrv, recurse=False)
142 return entries[0][1].rev.number
Dominik Riebelingefb93432009-06-18 22:17:17 +0000143
144
145def findversion(versionfile):
146 '''figure most recent program version from version.h,
147 returns version string.'''
148 h = open(versionfile, "r")
149 c = h.read()
150 h.close()
151 r = re.compile("#define +VERSION +\"(.[0-9\.a-z]+)\"")
152 m = re.search(r, c)
153 s = re.compile("\$Revision: +([0-9]+)")
154 n = re.search(s, c)
155 if n == None:
156 print "WARNING: Revision not found!"
157 return m.group(1)
158
159
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000160def findqt(cross=""):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000161 '''Search for Qt4 installation. Return path to qmake.'''
162 print "Searching for Qt"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000163 bins = [cross + "qmake", cross + "qmake-qt4"]
Dominik Riebelingefb93432009-06-18 22:17:17 +0000164 for binary in bins:
Dominik Riebeling9e25f042009-10-03 18:16:41 +0000165 try:
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000166 q = which(binary)
Dominik Riebeling9e25f042009-10-03 18:16:41 +0000167 if len(q) > 0:
168 result = checkqt(q)
169 if not result == "":
170 return result
171 except:
172 print sys.exc_value
173
174 return ""
Dominik Riebelingefb93432009-06-18 22:17:17 +0000175
176
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000177def checkqt(qmakebin):
178 '''Check if given path to qmake exists and is a suitable version.'''
179 result = ""
180 # check if binary exists
181 if not os.path.exists(qmakebin):
182 print "Specified qmake path does not exist!"
183 return result
184 # check version
185 output = subprocess.Popen([qmakebin, "-version"], stdout=subprocess.PIPE,
186 stderr=subprocess.PIPE)
187 cmdout = output.communicate()
188 # don't check the qmake return code here, Qt3 doesn't return 0 on -version.
189 for ou in cmdout:
190 r = re.compile("Qt[^0-9]+([0-9\.]+[a-z]*)")
191 m = re.search(r, ou)
192 if not m == None:
193 print "Qt found: %s" % m.group(1)
194 s = re.compile("4\..*")
195 n = re.search(s, m.group(1))
196 if not n == None:
197 result = qmakebin
198 return result
199
200
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000201def qmake(qmake, projfile, platform=sys.platform, wd=".", static=True, cross=""):
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000202 print "Running qmake in %s..." % wd
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000203 command = [qmake, "-config", "release", "-config", "noccache"]
204 if static == True:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000205 command.extend(["-config", "-static"])
206 # special spec required?
207 if len(qmakespec[platform]) > 0:
208 command.extend(["-spec", qmakespec[platform]])
209 # cross compiling prefix set?
210 if len(cross) > 0:
211 command.extend(["-config", "cross"])
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000212 command.append(projfile)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000213 output = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=wd)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000214 output.communicate()
215 if not output.returncode == 0:
216 print "qmake returned an error!"
217 return -1
218 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000219
220
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000221def build(wd=".", platform=sys.platform, cross=""):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000222 # make
223 print "Building ..."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000224 # use the current platforms make here, cross compiling uses the native make.
225 command = [make[sys.platform]]
Dominik Riebelingee3fc612010-12-19 20:10:22 +0000226 if cpus > 1:
227 command.append("-j")
228 command.append(str(cpus))
229 output = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=wd)
Dominik Riebeling07e86472009-10-22 22:02:56 +0000230 while True:
231 c = output.stdout.readline()
232 sys.stdout.write(".")
233 sys.stdout.flush()
234 if not output.poll() == None:
235 sys.stdout.write("\n")
236 sys.stdout.flush()
237 if not output.returncode == 0:
238 print "Build failed!"
239 return -1
240 break
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000241 if platform != "darwin":
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000242 # strip. OS X handles this via macdeployqt.
243 print "Stripping binary."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000244 output = subprocess.Popen([cross + "strip", progexe[platform]], \
245 stdout=subprocess.PIPE, cwd=wd)
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000246 output.communicate()
247 if not output.returncode == 0:
248 print "Stripping failed!"
249 return -1
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000250 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000251
252
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000253def upxfile(wd=".", platform=sys.platform):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000254 # run upx on binary
255 print "UPX'ing binary ..."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000256 output = subprocess.Popen(["upx", progexe[platform]], \
257 stdout=subprocess.PIPE, cwd=wd)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000258 output.communicate()
259 if not output.returncode == 0:
260 print "UPX'ing failed!"
261 return -1
262 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000263
264
Dominik Riebeling4756a772010-08-30 17:51:53 +0000265def runnsis(versionstring, nsis, script, srcfolder):
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000266 # run script through nsis to create installer.
267 print "Running NSIS ..."
Dominik Riebeling4756a772010-08-30 17:51:53 +0000268
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000269 # Assume the generated installer gets placed in the same folder the nsi
270 # script lives in. This seems to be a valid assumption unless the nsi
271 # script specifies a path. NSIS expects files relative to source folder so
Dominik Riebeling4756a772010-08-30 17:51:53 +0000272 # copy progexe. Additional files are injected into the nsis script.
273
274 # FIXME: instead of copying binaries around copy the NSI file and inject
275 # the correct paths.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000276 # Only win32 supported as target platform so hard coded.
277 b = srcfolder + "/" + os.path.dirname(script) + "/" \
278 + os.path.dirname(progexe["win32"])
Dominik Riebeling4756a772010-08-30 17:51:53 +0000279 if not os.path.exists(b):
280 os.mkdir(b)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000281 shutil.copy(srcfolder + "/" + progexe["win32"], b)
282 output = subprocess.Popen([nsis, srcfolder + "/" + script], \
283 stdout=subprocess.PIPE)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000284 output.communicate()
285 if not output.returncode == 0:
286 print "NSIS failed!"
287 return -1
288 setupfile = program + "-" + versionstring + "-setup.exe"
Dominik Riebeling77b68252010-08-09 17:44:03 +0000289 # find output filename in nsis script file
290 nsissetup = ""
Dominik Riebeling4756a772010-08-30 17:51:53 +0000291 for line in open(srcfolder + "/" + script):
Dominik Riebeling77b68252010-08-09 17:44:03 +0000292 if re.match(r'^[^;]*OutFile\s+', line) != None:
293 nsissetup = re.sub(r'^[^;]*OutFile\s+"(.+)"', r'\1', line).rstrip()
294 if nsissetup == "":
295 print "Could not retrieve output file name!"
296 return -1
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000297 shutil.copy(srcfolder + "/" + os.path.dirname(script) + "/" + nsissetup, \
298 setupfile)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000299 return 0
300
301
Dominik Riebeling4756a772010-08-30 17:51:53 +0000302def nsisfileinject(nsis, outscript, filelist):
303 '''Inject files in filelist into NSIS script file after the File line
304 containing the main binary. This assumes that the main binary is present
305 in the NSIS script and that all additiona files (dlls etc) to get placed
306 into $INSTDIR.'''
307 output = open(outscript, "w")
308 for line in open(nsis, "r"):
309 output.write(line)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000310 # inject files after the progexe binary.
311 # Match the basename only to avoid path mismatches.
312 if re.match(r'^\s*File\s*.*' + os.path.basename(progexe["win32"]), \
313 line, re.IGNORECASE):
Dominik Riebeling4756a772010-08-30 17:51:53 +0000314 for f in filelist:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000315 injection = " File /oname=$INSTDIR\\" + os.path.basename(f) \
316 + " " + os.path.normcase(f) + "\n"
Dominik Riebeling4756a772010-08-30 17:51:53 +0000317 output.write(injection)
318 output.write(" ; end of injected files\n")
319 output.close()
320
321
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000322def finddlls(program, extrapaths=[], cross=""):
Dominik Riebeling4756a772010-08-30 17:51:53 +0000323 '''Check program for required DLLs. Find all required DLLs except ignored
324 ones and return a list of DLL filenames (including path).'''
325 # ask objdump about dependencies.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000326 output = subprocess.Popen([cross + "objdump", "-x", program], \
327 stdout=subprocess.PIPE)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000328 cmdout = output.communicate()
329
330 # create list of used DLLs. Store as lower case as W32 is case-insensitive.
331 dlls = []
332 for line in cmdout[0].split('\n'):
333 if re.match(r'\s*DLL Name', line) != None:
Dominik Riebeling9dfae972010-09-23 16:47:23 +0000334 dll = re.sub(r'^\s*DLL Name:\s+([a-zA-Z_\-0-9\.\+]+).*$', r'\1', line)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000335 dlls.append(dll.lower())
336
337 # find DLLs in extrapaths and PATH environment variable.
338 dllpaths = []
339 for file in dlls:
340 if file in systemdlls:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000341 print "System DLL: " + file
Dominik Riebeling4756a772010-08-30 17:51:53 +0000342 continue
343 dllpath = ""
344 for path in extrapaths:
345 if os.path.exists(path + "/" + file):
346 dllpath = re.sub(r"\\", r"/", path + "/" + file)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000347 print file + ": found at " + dllpath
Dominik Riebeling4756a772010-08-30 17:51:53 +0000348 dllpaths.append(dllpath)
349 break
350 if dllpath == "":
351 try:
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000352 dllpath = re.sub(r"\\", r"/", which(file))
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000353 print file + ": found at " + dllpath
354 dllpaths.append(dllpath)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000355 except:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000356 print "MISSING DLL: " + file
Dominik Riebeling4756a772010-08-30 17:51:53 +0000357 return dllpaths
358
359
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000360def zipball(programfiles, versionstring, buildfolder, platform=sys.platform):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000361 '''package created binary'''
362 print "Creating binary zipball."
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000363 archivebase = program + "-" + versionstring
364 outfolder = buildfolder + "/" + archivebase
365 archivename = archivebase + ".zip"
Dominik Riebelingefb93432009-06-18 22:17:17 +0000366 # create output folder
367 os.mkdir(outfolder)
368 # move program files to output folder
369 for f in programfiles:
Dominik Riebeling4756a772010-08-30 17:51:53 +0000370 if re.match(r'^(/|[a-zA-Z]:)', f) != None:
371 shutil.copy(f, outfolder)
372 else:
373 shutil.copy(buildfolder + "/" + f, outfolder)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000374 # create zipball from output folder
375 zf = zipfile.ZipFile(archivename, mode='w', compression=zipfile.ZIP_DEFLATED)
376 for root, dirs, files in os.walk(outfolder):
377 for name in files:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000378 physname = os.path.normpath(os.path.join(root, name))
379 filename = string.replace(physname, os.path.normpath(buildfolder), "")
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000380 zf.write(physname, filename)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000381 for name in dirs:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000382 physname = os.path.normpath(os.path.join(root, name))
383 filename = string.replace(physname, os.path.normpath(buildfolder), "")
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000384 zf.write(physname, filename)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000385 zf.close()
386 # remove output folder
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000387 shutil.rmtree(outfolder)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000388 return archivename
Dominik Riebelingefb93432009-06-18 22:17:17 +0000389
390
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000391def tarball(programfiles, versionstring, buildfolder):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000392 '''package created binary'''
393 print "Creating binary tarball."
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000394 archivebase = program + "-" + versionstring
395 outfolder = buildfolder + "/" + archivebase
396 archivename = archivebase + ".tar.bz2"
Dominik Riebelingefb93432009-06-18 22:17:17 +0000397 # create output folder
398 os.mkdir(outfolder)
399 # move program files to output folder
400 for f in programfiles:
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000401 shutil.copy(buildfolder + "/" + f, outfolder)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000402 # create tarball from output folder
403 tf = tarfile.open(archivename, mode='w:bz2')
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000404 tf.add(outfolder, archivebase)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000405 tf.close()
406 # remove output folder
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000407 shutil.rmtree(outfolder)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000408 return archivename
Dominik Riebelingefb93432009-06-18 22:17:17 +0000409
410
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000411def macdeploy(versionstring, buildfolder, platform=sys.platform):
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000412 '''package created binary to dmg'''
413 dmgfile = program + "-" + versionstring + ".dmg"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000414 appbundle = buildfolder + "/" + progexe[platform]
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000415
Dominik Riebelinga6df9322010-07-10 21:22:22 +0000416 # workaround to Qt issues when building out-of-tree. Copy files into bundle.
Dominik Riebeling85f84062010-07-28 21:05:16 +0000417 sourcebase = buildfolder + re.sub('[^/]+.pro$', '', project) + "/"
418 print sourcebase
Dominik Riebelinga6df9322010-07-10 21:22:22 +0000419 for src in bundlecopy:
Dominik Riebeling85f84062010-07-28 21:05:16 +0000420 shutil.copy(sourcebase + src, appbundle + "/" + bundlecopy[src])
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000421 # end of Qt workaround
422
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000423 output = subprocess.Popen(["macdeployqt", progexe[platform], "-dmg"], \
424 stdout=subprocess.PIPE, cwd=buildfolder)
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000425 output.communicate()
426 if not output.returncode == 0:
427 print "macdeployqt failed!"
428 return -1
429 # copy dmg to output folder
430 shutil.copy(buildfolder + "/" + program + ".dmg", dmgfile)
431 return dmgfile
432
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000433
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000434def filehashes(filename):
435 '''Calculate md5 and sha1 hashes for a given file.'''
436 if not os.path.exists(filename):
437 return ["", ""]
438 m = hashlib.md5()
439 s = hashlib.sha1()
440 f = open(filename, 'rb')
441 while True:
442 d = f.read(65536)
443 if d == "":
444 break
445 m.update(d)
446 s.update(d)
447 return [m.hexdigest(), s.hexdigest()]
448
449
450def filestats(filename):
451 if not os.path.exists(filename):
452 return
453 st = os.stat(filename)
454 print filename, "\n", "-" * len(filename)
455 print "Size: %i bytes" % st.st_size
456 h = filehashes(filename)
457 print "md5sum: %s" % h[0]
458 print "sha1sum: %s" % h[1]
459 print "-" * len(filename), "\n"
460
461
Dominik Riebelingaae22132010-03-31 20:52:07 +0000462def tempclean(workfolder, nopro):
463 if nopro == True:
464 print "Cleaning up working folder %s" % workfolder
465 shutil.rmtree(workfolder)
466 else:
467 print "Project file specified or cleanup disabled!"
468 print "Temporary files kept at %s" % workfolder
469
470
Dominik Riebelinga9acd1f2010-07-28 18:37:12 +0000471def deploy():
Dominik Riebelingd7b9f992009-09-20 14:59:29 +0000472 startup = time.time()
Dominik Riebelinga9acd1f2010-07-28 18:37:12 +0000473
Dominik Riebelingefb93432009-06-18 22:17:17 +0000474 try:
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000475 opts, args = getopt.getopt(sys.argv[1:], "q:p:t:a:n:sbdkx:i:h",
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000476 ["qmake=", "project=", "tag=", "add=", "makensis=", "source-only",
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000477 "binary-only", "dynamic", "keep-temp", "cross=", "buildid=", "help"])
Dominik Riebelingefb93432009-06-18 22:17:17 +0000478 except getopt.GetoptError, err:
479 print str(err)
480 usage(sys.argv[0])
481 sys.exit(1)
482 qt = ""
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000483 proj = ""
484 svnbase = svnserver + "trunk/"
485 tag = ""
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000486 addfiles = []
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000487 cleanup = True
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000488 binary = True
489 source = True
Dominik Riebelingaae22132010-03-31 20:52:07 +0000490 keeptemp = False
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000491 makensis = ""
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000492 cross = ""
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000493 buildid = None
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000494 platform = sys.platform
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000495 if sys.platform != "darwin":
496 static = True
497 else:
498 static = False
Dominik Riebelingefb93432009-06-18 22:17:17 +0000499 for o, a in opts:
500 if o in ("-q", "--qmake"):
501 qt = a
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000502 if o in ("-p", "--project"):
503 proj = a
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000504 cleanup = False
505 if o in ("-t", "--tag"):
506 tag = a
507 svnbase = svnserver + "tags/" + tag + "/"
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000508 if o in ("-a", "--add"):
509 addfiles.append(a)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000510 if o in ("-n", "--makensis"):
511 makensis = a
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000512 if o in ("-s", "--source-only"):
513 binary = False
514 if o in ("-b", "--binary-only"):
515 source = False
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000516 if o in ("-d", "--dynamic") and sys.platform != "darwin":
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000517 static = False
Dominik Riebelingaae22132010-03-31 20:52:07 +0000518 if o in ("-k", "--keep-temp"):
519 keeptemp = True
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000520 if o in ("-x", "--cross") and sys.platform != "win32":
521 cross = a
522 platform = "win32"
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000523 if o in ("-i", "--buildid"):
524 buildid = a
Dominik Riebelingefb93432009-06-18 22:17:17 +0000525 if o in ("-h", "--help"):
526 usage(sys.argv[0])
527 sys.exit(0)
528
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000529 if source == False and binary == False:
530 print "Building build neither source nor binary means nothing to do. Exiting."
531 sys.exit(1)
532
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000533 print "Building " + progexe[platform] + " for " + platform
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000534 # search for qmake
Dominik Riebelingefb93432009-06-18 22:17:17 +0000535 if qt == "":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000536 qm = findqt(cross)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000537 else:
538 qm = checkqt(qt)
539 if qm == "":
Dominik Riebelingefb93432009-06-18 22:17:17 +0000540 print "ERROR: No suitable Qt installation found."
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000541 sys.exit(1)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000542
543 # create working folder. Use current directory if -p option used.
544 if proj == "":
545 w = tempfile.mkdtemp()
546 # make sure the path doesn't contain backslashes to prevent issues
547 # later when running on windows.
548 workfolder = re.sub(r'\\', '/', w)
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000549 revision = getfolderrev(svnbase)
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000550 if buildid == None:
551 versionextra = ""
552 else:
553 versionextra = "-" + buildid
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000554 if tag != "":
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000555 sourcefolder = workfolder + "/" + tag + "/"
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000556 archivename = tag + versionextra + "-src.tar.bz2"
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000557 # get numeric version part from tag
558 ver = "v" + re.sub('^[^\d]+', '', tag)
559 else:
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000560 sourcefolder = workfolder + "/" + program + "-r" + str(revision) + versionextra + "/"
561 archivename = program + "-r" + str(revision) + versionextra + "-src.tar.bz2"
562 ver = "r" + str(revision)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000563 os.mkdir(sourcefolder)
564 else:
565 workfolder = "."
566 sourcefolder = "."
567 archivename = ""
568 # check if project file explicitly given. If yes, don't get sources from svn
569 if proj == "":
570 proj = sourcefolder + project
571 # get sources and pack source tarball
572 if not getsources(svnbase, svnpaths, sourcefolder) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000573 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000574 sys.exit(1)
575
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000576 # replace version strings.
577 print "Updating version information in sources"
578 for f in regreplace:
579 infile = open(sourcefolder + "/" + f, "r")
580 incontents = infile.readlines()
581 infile.close()
Dominik Riebeling2c297762011-04-06 20:17:29 +0000582
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000583 outfile = open(sourcefolder + "/" + f, "w")
584 for line in incontents:
585 newline = line
586 for r in regreplace[f]:
Dominik Riebeling2c297762011-04-06 20:17:29 +0000587 # replacements made on the replacement string:
588 # %REVISION% is replaced with the revision number
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000589 replacement = re.sub("%REVISION%", str(revision), r[1])
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000590 # %BUILD% is replace with buildid as passed on the command line
591 if buildid != None:
592 replacement = re.sub("%BUILDID%", str(buildid), replacement)
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000593 newline = re.sub(r[0], replacement, newline)
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000594 outfile.write(newline)
595 outfile.close()
Dominik Riebeling2c297762011-04-06 20:17:29 +0000596
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000597 if source == True:
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000598 print "Creating source tarball %s\n" % archivename
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000599 tf = tarfile.open(archivename, mode='w:bz2')
600 tf.add(sourcefolder, os.path.basename(re.subn('/$', '', sourcefolder)[0]))
601 tf.close()
602 if binary == False:
603 shutil.rmtree(workfolder)
604 sys.exit(0)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000605 else:
606 # figure version from sources. Need to take path to project file into account.
607 versionfile = re.subn('[\w\.]+$', "version.h", proj)[0]
608 ver = findversion(versionfile)
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000609 # append buildid if any.
610 if buildid != None:
611 ver += "-" + buildid
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000612
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000613 # check project file
614 if not os.path.exists(proj):
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000615 print "ERROR: path to project file wrong."
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000616 sys.exit(1)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000617
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000618 # copy specified (--add) files to working folder
619 for f in addfiles:
620 shutil.copy(f, sourcefolder)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000621 buildstart = time.time()
Dominik Riebelingefb93432009-06-18 22:17:17 +0000622 header = "Building %s %s" % (program, ver)
623 print header
624 print len(header) * "="
625
626 # build it.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000627 if not qmake(qm, proj, platform, sourcefolder, static, cross) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000628 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000629 sys.exit(1)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000630 if not build(sourcefolder, platform, cross) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000631 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000632 sys.exit(1)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000633 buildtime = time.time() - buildstart
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000634 progfiles = programfiles
635 progfiles.append(progexe[platform])
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000636 if platform == "win32":
Dominik Riebeling1727c992010-06-11 20:02:59 +0000637 if useupx == True:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000638 if not upxfile(sourcefolder, platform) == 0:
Dominik Riebeling1727c992010-06-11 20:02:59 +0000639 tempclean(workfolder, cleanup and not keeptemp)
640 sys.exit(1)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000641 dllfiles = finddlls(sourcefolder + "/" + progexe[platform], \
642 [os.path.dirname(qm)], cross)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000643 if dllfiles.count > 0:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000644 progfiles.extend(dllfiles)
645 archive = zipball(progfiles, ver, sourcefolder, platform)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000646 # only when running native right now.
647 if nsisscript != "" and makensis != "":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000648 nsisfileinject(sourcefolder + "/" + nsisscript, sourcefolder \
649 + "/" + nsisscript + ".tmp", dllfiles)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000650 runnsis(ver, makensis, nsisscript + ".tmp", sourcefolder)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000651 elif platform == "darwin":
652 archive = macdeploy(ver, sourcefolder, platform)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000653 else:
Dominik Riebeling168eba12010-06-23 20:15:14 +0000654 if os.uname()[4].endswith("64"):
655 ver += "-64bit"
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000656 archive = tarball(progfiles, ver, sourcefolder)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000657
658 # remove temporary files
Dominik Riebelingaae22132010-03-31 20:52:07 +0000659 tempclean(workfolder, cleanup)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000660
661 # display summary
662 headline = "Build Summary for %s" % program
663 print "\n", headline, "\n", "=" * len(headline)
664 if not archivename == "":
665 filestats(archivename)
666 filestats(archive)
Dominik Riebelingd7b9f992009-09-20 14:59:29 +0000667 duration = time.time() - startup
668 durmins = (int)(duration / 60)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000669 dursecs = (int)(duration % 60)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000670 buildmins = (int)(buildtime / 60)
671 buildsecs = (int)(buildtime % 60)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000672 print "Overall time %smin %ssec, building took %smin %ssec." % \
673 (durmins, dursecs, buildmins, buildsecs)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000674
675
676if __name__ == "__main__":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000677 print "You cannot run this module directly!"
678 print "Set required environment and call deploy()."