blob: 6ce8c43a144d1b7dc196e7bbb6f55423bd914eea [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 Riebelingefb93432009-06-18 22:17:17 +000032# requires upx.exe in PATH on Windows.
33#
34
35import re
36import os
37import sys
38import tarfile
39import zipfile
40import shutil
41import subprocess
42import getopt
Dominik Riebelingd7b9f992009-09-20 14:59:29 +000043import time
Dominik Riebeling45c411e2009-10-04 16:45:59 +000044import hashlib
Dominik Riebeling45c411e2009-10-04 16:45:59 +000045import tempfile
Dominik Riebeling001eb3b2012-02-05 22:00:57 +010046import gitscraper
Dominik Riebeling83609372015-06-07 22:19:54 +020047from datetime import datetime
Dominik Riebelingefb93432009-06-18 22:17:17 +000048
Dominik Riebeling11212062009-10-30 21:40:07 +000049# modules that are not part of python itself.
Dominik Riebelingee3fc612010-12-19 20:10:22 +000050cpus = 1
51try:
52 import multiprocessing
53 cpus = multiprocessing.cpu_count()
54 print "Info: %s cores found." % cpus
55except ImportError:
56 print "Warning: multiprocessing module not found. Assuming 1 core."
Dominik Riebeling11212062009-10-30 21:40:07 +000057
Dominik Riebelingefb93432009-06-18 22:17:17 +000058# == Global stuff ==
Dominik Riebeling4756a772010-08-30 17:51:53 +000059# DLL files to ignore when searching for required DLL files.
60systemdlls = ['advapi32.dll',
61 'comdlg32.dll',
62 'gdi32.dll',
63 'imm32.dll',
64 'kernel32.dll',
65 'msvcrt.dll',
66 'msvcrt.dll',
67 'netapi32.dll',
68 'ole32.dll',
69 'oleaut32.dll',
70 'setupapi.dll',
71 'shell32.dll',
72 'user32.dll',
73 'winmm.dll',
74 'winspool.drv',
75 'ws2_32.dll']
76
Dominik Riebeling001eb3b2012-02-05 22:00:57 +010077gitrepo = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
Dominik Riebeling4756a772010-08-30 17:51:53 +000078
Dominik Riebelingee1f54c2012-02-26 17:09:08 +010079
Dominik Riebelingefb93432009-06-18 22:17:17 +000080# == Functions ==
81def usage(myself):
82 print "Usage: %s [options]" % myself
83 print " -q, --qmake=<qmake> path to qmake"
Dominik Riebeling45c411e2009-10-04 16:45:59 +000084 print " -p, --project=<pro> path to .pro file for building with local tree"
85 print " -t, --tag=<tag> use specified tag from svn"
Dominik Riebelingd5529ef2009-10-04 21:17:59 +000086 print " -a, --add=<file> add file to build folder before building"
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +000087 print " -s, --source-only only create source archive"
88 print " -b, --binary-only only create binary archive"
Dominik Riebeling93c81dc2010-08-08 20:56:58 +000089 if nsisscript != "":
90 print " -n, --makensis=<file> path to makensis for building Windows setup program."
Dominik Riebeling5f7a8462009-11-29 21:09:21 +000091 if sys.platform != "darwin":
92 print " -d, --dynamic link dynamically instead of static"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +000093 if sys.platform != "win32":
94 print " -x, --cross= prefix to cross compile for win32"
Dominik Riebelingaae22132010-03-31 20:52:07 +000095 print " -k, --keep-temp keep temporary folder on build failure"
Dominik Riebelingefb93432009-06-18 22:17:17 +000096 print " -h, --help this help"
Dominik Riebeling45c411e2009-10-04 16:45:59 +000097 print " If neither a project file nor tag is specified trunk will get downloaded"
98 print " from svn."
99
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000100
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000101def which(executable):
102 path = os.environ.get("PATH", "").split(os.pathsep)
103 for p in path:
104 fullpath = p + "/" + executable
105 if os.path.exists(fullpath):
106 return fullpath
107 print "which: could not find " + executable
108 return ""
109
110
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100111def getsources(treehash, filelist, dest):
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000112 '''Get the files listed in filelist from svnsrv and put it at dest.'''
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100113 gitscraper.scrape_files(gitrepo, treehash, filelist, dest)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000114 return 0
115
116
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000117def getfolderrev(svnsrv):
118 '''Get the most recent revision for svnsrv'''
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000119 client = pysvn.Client()
120 entries = client.info2(svnsrv, recurse=False)
121 return entries[0][1].rev.number
Dominik Riebelingefb93432009-06-18 22:17:17 +0000122
123
124def findversion(versionfile):
125 '''figure most recent program version from version.h,
126 returns version string.'''
127 h = open(versionfile, "r")
128 c = h.read()
129 h.close()
Dominik Riebeling2af2a122015-05-30 17:59:50 +0200130 version = dict()
131 for v in ['MAJOR', 'MINOR', 'MICRO']:
132 r = re.compile("#define +VERSION_" + v + " +([0-9a-z]+)")
133 m = re.search(r, c)
134 version[v] = m.group(1)
135 return "%s.%s.%s" % (version['MAJOR'], version['MINOR'], version['MICRO'])
Dominik Riebelingefb93432009-06-18 22:17:17 +0000136
137
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000138def findqt(cross=""):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000139 '''Search for Qt4 installation. Return path to qmake.'''
140 print "Searching for Qt"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000141 bins = [cross + "qmake", cross + "qmake-qt4"]
Dominik Riebelingefb93432009-06-18 22:17:17 +0000142 for binary in bins:
Dominik Riebeling9e25f042009-10-03 18:16:41 +0000143 try:
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000144 q = which(binary)
Dominik Riebeling9e25f042009-10-03 18:16:41 +0000145 if len(q) > 0:
146 result = checkqt(q)
147 if not result == "":
148 return result
149 except:
150 print sys.exc_value
151
152 return ""
Dominik Riebelingefb93432009-06-18 22:17:17 +0000153
154
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000155def checkqt(qmakebin):
156 '''Check if given path to qmake exists and is a suitable version.'''
157 result = ""
158 # check if binary exists
159 if not os.path.exists(qmakebin):
160 print "Specified qmake path does not exist!"
161 return result
162 # check version
163 output = subprocess.Popen([qmakebin, "-version"], stdout=subprocess.PIPE,
164 stderr=subprocess.PIPE)
165 cmdout = output.communicate()
166 # don't check the qmake return code here, Qt3 doesn't return 0 on -version.
167 for ou in cmdout:
168 r = re.compile("Qt[^0-9]+([0-9\.]+[a-z]*)")
169 m = re.search(r, ou)
170 if not m == None:
171 print "Qt found: %s" % m.group(1)
Cástor Muñoz2ae94312016-01-11 03:56:41 +0100172 s = re.compile("[45]\..*")
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000173 n = re.search(s, m.group(1))
174 if not n == None:
175 result = qmakebin
176 return result
177
178
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000179def qmake(qmake, projfile, platform=sys.platform, wd=".", static=True, cross=""):
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000180 print "Running qmake in %s..." % wd
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000181 command = [qmake, "-config", "release", "-config", "noccache"]
182 if static == True:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000183 command.extend(["-config", "-static"])
184 # special spec required?
185 if len(qmakespec[platform]) > 0:
186 command.extend(["-spec", qmakespec[platform]])
187 # cross compiling prefix set?
188 if len(cross) > 0:
189 command.extend(["-config", "cross"])
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000190 command.append(projfile)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000191 output = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=wd)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000192 output.communicate()
193 if not output.returncode == 0:
194 print "qmake returned an error!"
195 return -1
196 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000197
198
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000199def build(wd=".", platform=sys.platform, cross=""):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000200 # make
201 print "Building ..."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000202 # use the current platforms make here, cross compiling uses the native make.
203 command = [make[sys.platform]]
Dominik Riebelingee3fc612010-12-19 20:10:22 +0000204 if cpus > 1:
205 command.append("-j")
206 command.append(str(cpus))
207 output = subprocess.Popen(command, stdout=subprocess.PIPE, cwd=wd)
Dominik Riebeling07e86472009-10-22 22:02:56 +0000208 while True:
209 c = output.stdout.readline()
210 sys.stdout.write(".")
211 sys.stdout.flush()
212 if not output.poll() == None:
213 sys.stdout.write("\n")
214 sys.stdout.flush()
215 if not output.returncode == 0:
216 print "Build failed!"
217 return -1
218 break
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000219 if platform != "darwin":
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000220 # strip. OS X handles this via macdeployqt.
221 print "Stripping binary."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000222 output = subprocess.Popen([cross + "strip", progexe[platform]], \
223 stdout=subprocess.PIPE, cwd=wd)
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000224 output.communicate()
225 if not output.returncode == 0:
226 print "Stripping failed!"
227 return -1
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000228 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000229
230
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000231def upxfile(wd=".", platform=sys.platform):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000232 # run upx on binary
233 print "UPX'ing binary ..."
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000234 output = subprocess.Popen(["upx", progexe[platform]], \
235 stdout=subprocess.PIPE, cwd=wd)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000236 output.communicate()
237 if not output.returncode == 0:
238 print "UPX'ing failed!"
239 return -1
240 return 0
Dominik Riebelingefb93432009-06-18 22:17:17 +0000241
242
Dominik Riebeling4756a772010-08-30 17:51:53 +0000243def runnsis(versionstring, nsis, script, srcfolder):
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000244 # run script through nsis to create installer.
245 print "Running NSIS ..."
Dominik Riebeling4756a772010-08-30 17:51:53 +0000246
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000247 # Assume the generated installer gets placed in the same folder the nsi
248 # script lives in. This seems to be a valid assumption unless the nsi
249 # script specifies a path. NSIS expects files relative to source folder so
Dominik Riebeling4756a772010-08-30 17:51:53 +0000250 # copy progexe. Additional files are injected into the nsis script.
251
252 # FIXME: instead of copying binaries around copy the NSI file and inject
253 # the correct paths.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000254 # Only win32 supported as target platform so hard coded.
255 b = srcfolder + "/" + os.path.dirname(script) + "/" \
256 + os.path.dirname(progexe["win32"])
Dominik Riebeling4756a772010-08-30 17:51:53 +0000257 if not os.path.exists(b):
258 os.mkdir(b)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000259 shutil.copy(srcfolder + "/" + progexe["win32"], b)
260 output = subprocess.Popen([nsis, srcfolder + "/" + script], \
261 stdout=subprocess.PIPE)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000262 output.communicate()
263 if not output.returncode == 0:
264 print "NSIS failed!"
265 return -1
266 setupfile = program + "-" + versionstring + "-setup.exe"
Dominik Riebeling77b68252010-08-09 17:44:03 +0000267 # find output filename in nsis script file
268 nsissetup = ""
Dominik Riebeling4756a772010-08-30 17:51:53 +0000269 for line in open(srcfolder + "/" + script):
Dominik Riebeling77b68252010-08-09 17:44:03 +0000270 if re.match(r'^[^;]*OutFile\s+', line) != None:
271 nsissetup = re.sub(r'^[^;]*OutFile\s+"(.+)"', r'\1', line).rstrip()
272 if nsissetup == "":
273 print "Could not retrieve output file name!"
274 return -1
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000275 shutil.copy(srcfolder + "/" + os.path.dirname(script) + "/" + nsissetup, \
276 setupfile)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000277 return 0
278
279
Dominik Riebeling4756a772010-08-30 17:51:53 +0000280def nsisfileinject(nsis, outscript, filelist):
281 '''Inject files in filelist into NSIS script file after the File line
282 containing the main binary. This assumes that the main binary is present
283 in the NSIS script and that all additiona files (dlls etc) to get placed
284 into $INSTDIR.'''
285 output = open(outscript, "w")
286 for line in open(nsis, "r"):
287 output.write(line)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000288 # inject files after the progexe binary.
289 # Match the basename only to avoid path mismatches.
290 if re.match(r'^\s*File\s*.*' + os.path.basename(progexe["win32"]), \
291 line, re.IGNORECASE):
Dominik Riebeling4756a772010-08-30 17:51:53 +0000292 for f in filelist:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000293 injection = " File /oname=$INSTDIR\\" + os.path.basename(f) \
294 + " " + os.path.normcase(f) + "\n"
Dominik Riebeling4756a772010-08-30 17:51:53 +0000295 output.write(injection)
296 output.write(" ; end of injected files\n")
297 output.close()
298
299
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000300def finddlls(program, extrapaths=[], cross=""):
Dominik Riebeling4756a772010-08-30 17:51:53 +0000301 '''Check program for required DLLs. Find all required DLLs except ignored
302 ones and return a list of DLL filenames (including path).'''
303 # ask objdump about dependencies.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000304 output = subprocess.Popen([cross + "objdump", "-x", program], \
305 stdout=subprocess.PIPE)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000306 cmdout = output.communicate()
307
308 # create list of used DLLs. Store as lower case as W32 is case-insensitive.
309 dlls = []
310 for line in cmdout[0].split('\n'):
311 if re.match(r'\s*DLL Name', line) != None:
Dominik Riebeling9dfae972010-09-23 16:47:23 +0000312 dll = re.sub(r'^\s*DLL Name:\s+([a-zA-Z_\-0-9\.\+]+).*$', r'\1', line)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000313 dlls.append(dll.lower())
314
315 # find DLLs in extrapaths and PATH environment variable.
316 dllpaths = []
317 for file in dlls:
318 if file in systemdlls:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000319 print "System DLL: " + file
Dominik Riebeling4756a772010-08-30 17:51:53 +0000320 continue
321 dllpath = ""
322 for path in extrapaths:
323 if os.path.exists(path + "/" + file):
324 dllpath = re.sub(r"\\", r"/", path + "/" + file)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000325 print file + ": found at " + dllpath
Dominik Riebeling4756a772010-08-30 17:51:53 +0000326 dllpaths.append(dllpath)
327 break
328 if dllpath == "":
329 try:
Dominik Riebelinge79fca92011-08-14 18:57:49 +0000330 dllpath = re.sub(r"\\", r"/", which(file))
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000331 print file + ": found at " + dllpath
332 dllpaths.append(dllpath)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000333 except:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000334 print "MISSING DLL: " + file
Dominik Riebeling4756a772010-08-30 17:51:53 +0000335 return dllpaths
336
337
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000338def zipball(programfiles, versionstring, buildfolder, platform=sys.platform):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000339 '''package created binary'''
340 print "Creating binary zipball."
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000341 archivebase = program + "-" + versionstring
342 outfolder = buildfolder + "/" + archivebase
343 archivename = archivebase + ".zip"
Dominik Riebelingefb93432009-06-18 22:17:17 +0000344 # create output folder
345 os.mkdir(outfolder)
346 # move program files to output folder
347 for f in programfiles:
Dominik Riebeling4756a772010-08-30 17:51:53 +0000348 if re.match(r'^(/|[a-zA-Z]:)', f) != None:
349 shutil.copy(f, outfolder)
350 else:
351 shutil.copy(buildfolder + "/" + f, outfolder)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000352 # create zipball from output folder
353 zf = zipfile.ZipFile(archivename, mode='w', compression=zipfile.ZIP_DEFLATED)
354 for root, dirs, files in os.walk(outfolder):
355 for name in files:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000356 physname = os.path.normpath(os.path.join(root, name))
Dominik Riebeling5f5d7f62015-05-30 18:21:32 +0200357 filename = os.path.relpath(physname, buildfolder)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000358 zf.write(physname, filename)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000359 zf.close()
360 # remove output folder
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000361 shutil.rmtree(outfolder)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000362 return archivename
Dominik Riebelingefb93432009-06-18 22:17:17 +0000363
364
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000365def tarball(programfiles, versionstring, buildfolder):
Dominik Riebelingefb93432009-06-18 22:17:17 +0000366 '''package created binary'''
367 print "Creating binary tarball."
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000368 archivebase = program + "-" + versionstring
369 outfolder = buildfolder + "/" + archivebase
370 archivename = archivebase + ".tar.bz2"
Dominik Riebelingefb93432009-06-18 22:17:17 +0000371 # create output folder
372 os.mkdir(outfolder)
373 # move program files to output folder
374 for f in programfiles:
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000375 shutil.copy(buildfolder + "/" + f, outfolder)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000376 # create tarball from output folder
377 tf = tarfile.open(archivename, mode='w:bz2')
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000378 tf.add(outfolder, archivebase)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000379 tf.close()
380 # remove output folder
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000381 shutil.rmtree(outfolder)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000382 return archivename
Dominik Riebelingefb93432009-06-18 22:17:17 +0000383
384
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000385def macdeploy(versionstring, buildfolder, platform=sys.platform):
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000386 '''package created binary to dmg'''
387 dmgfile = program + "-" + versionstring + ".dmg"
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000388 appbundle = buildfolder + "/" + progexe[platform]
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000389
Dominik Riebelinga6df9322010-07-10 21:22:22 +0000390 # workaround to Qt issues when building out-of-tree. Copy files into bundle.
Dominik Riebeling85f84062010-07-28 21:05:16 +0000391 sourcebase = buildfolder + re.sub('[^/]+.pro$', '', project) + "/"
392 print sourcebase
Dominik Riebelinga6df9322010-07-10 21:22:22 +0000393 for src in bundlecopy:
Dominik Riebeling85f84062010-07-28 21:05:16 +0000394 shutil.copy(sourcebase + src, appbundle + "/" + bundlecopy[src])
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000395 # end of Qt workaround
396
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000397 output = subprocess.Popen(["macdeployqt", progexe[platform], "-dmg"], \
398 stdout=subprocess.PIPE, cwd=buildfolder)
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000399 output.communicate()
400 if not output.returncode == 0:
401 print "macdeployqt failed!"
402 return -1
403 # copy dmg to output folder
404 shutil.copy(buildfolder + "/" + program + ".dmg", dmgfile)
405 return dmgfile
406
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000407
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000408def filehashes(filename):
409 '''Calculate md5 and sha1 hashes for a given file.'''
410 if not os.path.exists(filename):
411 return ["", ""]
412 m = hashlib.md5()
413 s = hashlib.sha1()
414 f = open(filename, 'rb')
415 while True:
416 d = f.read(65536)
417 if d == "":
418 break
419 m.update(d)
420 s.update(d)
421 return [m.hexdigest(), s.hexdigest()]
422
423
424def filestats(filename):
425 if not os.path.exists(filename):
426 return
427 st = os.stat(filename)
428 print filename, "\n", "-" * len(filename)
429 print "Size: %i bytes" % st.st_size
430 h = filehashes(filename)
431 print "md5sum: %s" % h[0]
432 print "sha1sum: %s" % h[1]
433 print "-" * len(filename), "\n"
434
435
Dominik Riebelingaae22132010-03-31 20:52:07 +0000436def tempclean(workfolder, nopro):
437 if nopro == True:
438 print "Cleaning up working folder %s" % workfolder
439 shutil.rmtree(workfolder)
440 else:
441 print "Project file specified or cleanup disabled!"
442 print "Temporary files kept at %s" % workfolder
443
444
Dominik Riebelinga9acd1f2010-07-28 18:37:12 +0000445def deploy():
Dominik Riebelingd7b9f992009-09-20 14:59:29 +0000446 startup = time.time()
Dominik Riebelinga9acd1f2010-07-28 18:37:12 +0000447
Dominik Riebelingefb93432009-06-18 22:17:17 +0000448 try:
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000449 opts, args = getopt.getopt(sys.argv[1:], "q:p:t:a:n:sbdkx:i:h",
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000450 ["qmake=", "project=", "tag=", "add=", "makensis=", "source-only",
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000451 "binary-only", "dynamic", "keep-temp", "cross=", "buildid=", "help"])
Dominik Riebelingefb93432009-06-18 22:17:17 +0000452 except getopt.GetoptError, err:
453 print str(err)
454 usage(sys.argv[0])
455 sys.exit(1)
456 qt = ""
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000457 proj = ""
458 svnbase = svnserver + "trunk/"
459 tag = ""
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000460 addfiles = []
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000461 cleanup = True
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000462 binary = True
463 source = True
Dominik Riebelingaae22132010-03-31 20:52:07 +0000464 keeptemp = False
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000465 makensis = ""
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000466 cross = ""
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000467 buildid = None
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000468 platform = sys.platform
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100469 treehash = gitscraper.get_refs(gitrepo)['refs/remotes/origin/HEAD']
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000470 if sys.platform != "darwin":
471 static = True
472 else:
473 static = False
Dominik Riebelingefb93432009-06-18 22:17:17 +0000474 for o, a in opts:
475 if o in ("-q", "--qmake"):
476 qt = a
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000477 if o in ("-p", "--project"):
478 proj = a
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000479 cleanup = False
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000480 if o in ("-a", "--add"):
481 addfiles.append(a)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000482 if o in ("-n", "--makensis"):
483 makensis = a
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000484 if o in ("-s", "--source-only"):
485 binary = False
486 if o in ("-b", "--binary-only"):
487 source = False
Dominik Riebeling5f7a8462009-11-29 21:09:21 +0000488 if o in ("-d", "--dynamic") and sys.platform != "darwin":
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000489 static = False
Dominik Riebelingaae22132010-03-31 20:52:07 +0000490 if o in ("-k", "--keep-temp"):
491 keeptemp = True
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100492 if o in ("-t", "--tree"):
493 treehash = a
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000494 if o in ("-x", "--cross") and sys.platform != "win32":
495 cross = a
496 platform = "win32"
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000497 if o in ("-i", "--buildid"):
498 buildid = a
Dominik Riebelingefb93432009-06-18 22:17:17 +0000499 if o in ("-h", "--help"):
500 usage(sys.argv[0])
501 sys.exit(0)
502
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000503 if source == False and binary == False:
504 print "Building build neither source nor binary means nothing to do. Exiting."
505 sys.exit(1)
506
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000507 print "Building " + progexe[platform] + " for " + platform
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000508 # search for qmake
Dominik Riebelingefb93432009-06-18 22:17:17 +0000509 if qt == "":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000510 qm = findqt(cross)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000511 else:
512 qm = checkqt(qt)
513 if qm == "":
Dominik Riebelingefb93432009-06-18 22:17:17 +0000514 print "ERROR: No suitable Qt installation found."
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000515 sys.exit(1)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000516
517 # create working folder. Use current directory if -p option used.
518 if proj == "":
519 w = tempfile.mkdtemp()
520 # make sure the path doesn't contain backslashes to prevent issues
521 # later when running on windows.
522 workfolder = re.sub(r'\\', '/', w)
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100523 revision = gitscraper.describe_treehash(gitrepo, treehash)
524 # try to find a version number from describe output.
525 # WARNING: this is broken and just a temporary workaround!
526 v = re.findall('([\d\.a-f]+)', revision)
527 if v:
528 if v[-1].find('.') >= 0:
529 revision = "v" + v[-1]
530 else:
531 revision = v[-1]
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000532 if buildid == None:
533 versionextra = ""
534 else:
535 versionextra = "-" + buildid
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100536 sourcefolder = workfolder + "/" + program + "-" + str(revision) + versionextra + "/"
537 archivename = program + "-" + str(revision) + versionextra + "-src.tar.bz2"
538 ver = str(revision)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000539 os.mkdir(sourcefolder)
Dominik Riebelinge5320302015-05-30 18:40:40 +0200540 print "Version: %s" % revision
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000541 else:
542 workfolder = "."
543 sourcefolder = "."
544 archivename = ""
545 # check if project file explicitly given. If yes, don't get sources from svn
546 if proj == "":
547 proj = sourcefolder + project
548 # get sources and pack source tarball
Dominik Riebeling001eb3b2012-02-05 22:00:57 +0100549 if not getsources(treehash, svnpaths, sourcefolder) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000550 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000551 sys.exit(1)
552
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000553 # replace version strings.
554 print "Updating version information in sources"
555 for f in regreplace:
556 infile = open(sourcefolder + "/" + f, "r")
557 incontents = infile.readlines()
558 infile.close()
Dominik Riebeling2c297762011-04-06 20:17:29 +0000559
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000560 outfile = open(sourcefolder + "/" + f, "w")
561 for line in incontents:
562 newline = line
563 for r in regreplace[f]:
Dominik Riebeling2c297762011-04-06 20:17:29 +0000564 # replacements made on the replacement string:
565 # %REVISION% is replaced with the revision number
Dominik Riebeling7d4616e2011-05-08 19:34:03 +0000566 replacement = re.sub("%REVISION%", str(revision), r[1])
Dominik Riebeling06662982012-03-06 22:06:40 +0100567 newline = re.sub(r[0], replacement, newline)
568 # %BUILD% is replaced with buildid as passed on the command line
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000569 if buildid != None:
Dominik Riebeling06662982012-03-06 22:06:40 +0100570 replacement = re.sub("%BUILDID%", "-" + str(buildid), replacement)
571 else:
572 replacement = re.sub("%BUILDID%", "", replacement)
573 newline = re.sub(r[0], replacement, newline)
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000574 outfile.write(newline)
575 outfile.close()
Dominik Riebeling2c297762011-04-06 20:17:29 +0000576
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000577 if source == True:
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000578 print "Creating source tarball %s\n" % archivename
Dominik Riebeling6b51e5b2009-10-05 18:35:28 +0000579 tf = tarfile.open(archivename, mode='w:bz2')
580 tf.add(sourcefolder, os.path.basename(re.subn('/$', '', sourcefolder)[0]))
581 tf.close()
582 if binary == False:
583 shutil.rmtree(workfolder)
584 sys.exit(0)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000585 else:
586 # figure version from sources. Need to take path to project file into account.
587 versionfile = re.subn('[\w\.]+$', "version.h", proj)[0]
Dominik Riebeling83609372015-06-07 22:19:54 +0200588 ver = findversion(versionfile) + "-dev" + datetime.now().strftime('%Y%m%d%H%M%S')
Dominik Riebeling6ba552c2011-05-05 17:19:00 +0000589 # append buildid if any.
590 if buildid != None:
591 ver += "-" + buildid
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000592
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000593 # check project file
594 if not os.path.exists(proj):
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000595 print "ERROR: path to project file wrong."
Dominik Riebeling2b05ef72009-10-03 20:42:02 +0000596 sys.exit(1)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000597
Dominik Riebelingd5529ef2009-10-04 21:17:59 +0000598 # copy specified (--add) files to working folder
599 for f in addfiles:
600 shutil.copy(f, sourcefolder)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000601 buildstart = time.time()
Dominik Riebelingefb93432009-06-18 22:17:17 +0000602 header = "Building %s %s" % (program, ver)
603 print header
604 print len(header) * "="
605
606 # build it.
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000607 if not qmake(qm, proj, platform, sourcefolder, static, cross) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000608 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000609 sys.exit(1)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000610 if not build(sourcefolder, platform, cross) == 0:
Dominik Riebelingaae22132010-03-31 20:52:07 +0000611 tempclean(workfolder, cleanup and not keeptemp)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000612 sys.exit(1)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000613 buildtime = time.time() - buildstart
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000614 progfiles = programfiles
615 progfiles.append(progexe[platform])
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000616 if platform == "win32":
Dominik Riebeling1727c992010-06-11 20:02:59 +0000617 if useupx == True:
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000618 if not upxfile(sourcefolder, platform) == 0:
Dominik Riebeling1727c992010-06-11 20:02:59 +0000619 tempclean(workfolder, cleanup and not keeptemp)
620 sys.exit(1)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000621 dllfiles = finddlls(sourcefolder + "/" + progexe[platform], \
622 [os.path.dirname(qm)], cross)
Dominik Riebelingee1f54c2012-02-26 17:09:08 +0100623 if len(dllfiles) > 0:
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000624 progfiles.extend(dllfiles)
625 archive = zipball(progfiles, ver, sourcefolder, platform)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000626 # only when running native right now.
627 if nsisscript != "" and makensis != "":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000628 nsisfileinject(sourcefolder + "/" + nsisscript, sourcefolder \
629 + "/" + nsisscript + ".tmp", dllfiles)
Dominik Riebeling4756a772010-08-30 17:51:53 +0000630 runnsis(ver, makensis, nsisscript + ".tmp", sourcefolder)
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000631 elif platform == "darwin":
632 archive = macdeploy(ver, sourcefolder, platform)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000633 else:
Dominik Riebelingee1f54c2012-02-26 17:09:08 +0100634 if platform == "linux2":
635 for p in progfiles:
636 prog = sourcefolder + "/" + p
637 output = subprocess.Popen(["file", prog],
638 stdout=subprocess.PIPE)
639 res = output.communicate()
640 if re.findall("ELF 64-bit", res[0]):
641 ver += "-64bit"
642 break
643
Dominik Riebeling4cc2cc42011-03-06 17:37:27 +0000644 archive = tarball(progfiles, ver, sourcefolder)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000645
646 # remove temporary files
Dominik Riebelingaae22132010-03-31 20:52:07 +0000647 tempclean(workfolder, cleanup)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000648
649 # display summary
650 headline = "Build Summary for %s" % program
651 print "\n", headline, "\n", "=" * len(headline)
652 if not archivename == "":
653 filestats(archivename)
654 filestats(archive)
Dominik Riebelingd7b9f992009-09-20 14:59:29 +0000655 duration = time.time() - startup
656 durmins = (int)(duration / 60)
Dominik Riebelingbbe9d322009-10-03 17:19:07 +0000657 dursecs = (int)(duration % 60)
Dominik Riebeling93c81dc2010-08-08 20:56:58 +0000658 buildmins = (int)(buildtime / 60)
659 buildsecs = (int)(buildtime % 60)
Dominik Riebeling45c411e2009-10-04 16:45:59 +0000660 print "Overall time %smin %ssec, building took %smin %ssec." % \
661 (durmins, dursecs, buildmins, buildsecs)
Dominik Riebelingefb93432009-06-18 22:17:17 +0000662
663
664if __name__ == "__main__":
Dominik Riebeling43a40ca2011-03-06 00:04:26 +0000665 print "You cannot run this module directly!"
666 print "Set required environment and call deploy()."