build_with_dev0.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #!/usr/bin/python
  2. # Automated build of the BlackBox Component Builder for Windows under Linux Debian 7.
  3. # Looks at branches 'master' and 'development'.
  4. #
  5. # Ivan Denisov, Josef Templ
  6. #
  7. # Creates 3 artefacts:
  8. # 1. a build log file named blackbox-<AppVersion>.<buildnr>-buildlog.html
  9. # 2. a Windows installer file named blackbox-<AppVersion>.<buildnr>-setup.exe
  10. # 3. a zipped package named blackbox-<AppVersion>-<buildnr>.zip
  11. # In case of building a final release, buildnr is not included.
  12. #
  13. # By always rebuilding dev0.exe it avoids problems with changes in the symbol or object file formats
  14. # and acts as a rigorous test for some parts of BlackBox, in particular for the compiler itself.
  15. #
  16. # A note on error handling:
  17. # Stops building when a shell command writes to stderr, unless stopOnError is False.
  18. # Stops building when there is a Python exception.
  19. # Leaves temporary directory 'bb' in place in order to signal that the build has not been finished.
  20. # If an error is encountered in the BlackBox compiler or linker, 'bb' will be removed and the
  21. # branch's hash file will be updated but not the build number file.
  22. #
  23. # TODO git checkout reports a message on stderr but it works, so it is ignored
  24. # TODO detect errors in BlackBox compiler or linker
  25. # TODO at least add a test for starting BlackBox using Xvfb
  26. from subprocess import Popen, PIPE
  27. import sys, datetime, fileinput, os.path
  28. buildDate = datetime.datetime.now().isoformat()
  29. buildDir = "/var/www/tribiq/makeapp"
  30. bbName = "bb"
  31. bbDir = buildDir + "/" + bbName
  32. appbuildDir = bbDir + "/appbuild"
  33. localRepository = "/var/www/git/blackbox.git"
  34. unstableDir = "/var/www/tribiq/unstable"
  35. stableDir = "/var/www/tribiq/stable"
  36. wine = "/usr/local/bin/wine"
  37. iscc = "/usr/local/bin/iscc"
  38. windres="/usr/bin/i586-mingw32msvc-windres"
  39. branch = None
  40. commitHash = None
  41. logFile = None
  42. def repositoryLocked():
  43. return os.path.exists(localRepository + ".lock")
  44. def hashFilePath():
  45. return buildDir + "/lastBuildHash/" + branch
  46. def getLastHash():
  47. if os.path.exists(hashFilePath()):
  48. hashFile = open(hashFilePath(), "r")
  49. commit = hashFile.readline().strip()
  50. hashFile.close()
  51. return commit
  52. else:
  53. return ""
  54. def getCommitHash():
  55. gitLog = shellExec(localRepository, "git log " + branch + " -1")
  56. global commitHash
  57. commitHash = gitLog.split("\n")[0].split(" ")[1]
  58. return commitHash
  59. def needsRebuild():
  60. return getLastHash() != getCommitHash()
  61. def selectBranch():
  62. global branch
  63. branch = "master"
  64. if needsRebuild():
  65. return branch
  66. branch = "development"
  67. if needsRebuild():
  68. return branch
  69. return None
  70. def openLog():
  71. global logFile
  72. logFile = open(unstableDir + "/logFile.html", "w")
  73. def log(text, startMarkup="", endMarkup=""):
  74. if text != "" and logFile != None:
  75. for line in text.split("\n"):
  76. logFile.write(startMarkup + line + endMarkup + "<br/>\n")
  77. logFile.flush()
  78. def logErr(text): # use color red
  79. log(text, '<font color="#FF0000">', '</font>')
  80. def logStep(text): # use bold font
  81. log(text, '<b>', '</b>')
  82. def shellExec(wd, cmd, stopOnError=True):
  83. log("shellExec: " + "cd " + wd + " && " + cmd)
  84. (stdout, stderr) = Popen("cd " + wd + " && " + cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()
  85. log(stdout)
  86. if stderr == "":
  87. return stdout
  88. elif not stopOnError:
  89. logErr(stderr)
  90. logErr("--- error ignored ---")
  91. return stdout
  92. else:
  93. logErr(stderr)
  94. logErr("--- build aborted ---")
  95. print "--- build aborted ---"
  96. sys.exit()
  97. def prepareCompileAndLink():
  98. logStep("Preparing BlackBox.rc")
  99. vrsn = versionInfoVersion.replace(".", ",")
  100. shellExec(bbDir + "/Win/Rsrc", "mv BlackBox.rc BlackBox.rc_template")
  101. shellExec(bbDir + "/Win/Rsrc", "sed s/{#VERSION}/" + vrsn + "/ < BlackBox.rc_template > BlackBox.rc")
  102. shellExec(bbDir + "/Win/Rsrc", "rm BlackBox.rc_template")
  103. logStep("Creating the BlackBox.res resource file")
  104. shellExec(bbDir + "/Win/Rsrc", windres + " -i BlackBox.rc -o BlackBox.res")
  105. logStep("Preparing dev0.exe")
  106. shellExec(buildDir, "cp dev0.exe " + bbDir + "/")
  107. def errorInBlackBox():
  108. return False #TODO
  109. def buildDev0():
  110. logStep("Building command line compiler dev0.exe")
  111. shellExec(bbDir, wine + " dev0.exe < appbuild/newdev0.txt 2>&1")
  112. shellExec(bbDir, "mv newdev0.exe dev0.exe && chmod a+x dev0.exe")
  113. shellExec(bbDir, "rm -R Code Sym */*/*.osf */*/*.ocf BlackBox.exe")
  114. def compileAndLink():
  115. logStep("Setting system properties in build.txt")
  116. shellExec(appbuildDir, "sed s/{#AppVersion}/" + appVersion + "/ < build.txt > build_.txt")
  117. shellExec(appbuildDir, "sed s/{#BuildNum}/" + str(buildNum) + "/ < build_.txt > build.txt")
  118. shellExec(appbuildDir, "sed s/{#BuildDate}/" + buildDate[:10] + "/ < build.txt > build_.txt")
  119. shellExec(appbuildDir, "sed s/{#CommitHash}/" + commitHash + "/ < build_.txt > build.txt")
  120. logStep("Compiling and linking BlackBox")
  121. shellExec(bbDir, wine + " dev0.exe < appbuild/build.txt 2>&1")
  122. if errorInBlackBox():
  123. updateCommitHash()
  124. cleanup()
  125. logErr("build terminated due to BlackBox compile/link error")
  126. sys.exit()
  127. logStep("Moving Code and Sym to System")
  128. shellExec(bbDir, "mv Code System/ && mv Sym System/")
  129. def buildSetupFile():
  130. logStep("Building " + outputNamePrefix + "-setup.exe file")
  131. shellExec(bbDir, "cp Win/Rsrc/BlackBox.iss appbuild/")
  132. shellExec(appbuildDir, iscc + " - < ./BlackBox.iss" \
  133. + ' "/dAppVersion=' + appVersion
  134. + '" "/dAppVerName=' + appVerName
  135. + '" "/dVersionInfoVersion=' + versionInfoVersion
  136. + '"', False)
  137. shellExec(appbuildDir + "/Output", "mv setup.exe " + outputPathPrefix + "-setup.exe")
  138. def updateDev0():
  139. # if setup file creation was successfull, we know that the new dev0.exe works well
  140. logStep("Updating dev0.exe")
  141. shellExec(bbDir, "mv dev0.exe " + buildDir + "/")
  142. def buildZipFile():
  143. logStep("Removing auxiliary files and directories")
  144. shellExec(bbDir, "rm -R *.txt appbuild Cons Interp Script")
  145. logStep("Zipping package to file " + outputNamePrefix + ".zip")
  146. shellExec(bbDir, "zip -r " + outputPathPrefix + ".zip *")
  147. def getAppVerName(appVersion):
  148. x = appVersion
  149. if appVersion.find("-a") >= 0:
  150. x = appVersion.replace("-a", " Alpha ")
  151. elif appVersion.find("-b") >= 0:
  152. x = appVersion.replace("-b", " Beta ")
  153. elif appVersion.find("-rc") >= 0:
  154. x = appVersion.replace("-rc", " Release Candidate ")
  155. return "BlackBox Component Builder " + x
  156. def getVersionInfoVersion(appVersion, buildNum):
  157. version = appVersion.split("-")[0]
  158. v = version.split(".")
  159. v0 = v[0] if len(v) > 0 else "0"
  160. v1 = v[1] if len(v) > 1 else "0"
  161. v2 = v[2] if len(v) > 2 else "0"
  162. return v0 + "." + v1 + "." + v2 + "." + str(buildNum)
  163. def isFinal(appVersion):
  164. return appVersion.find("-") < 0
  165. def updateCommitHash():
  166. logStep("Updating commit hash for branch '" + branch + "'")
  167. hashFile = open(hashFilePath(), "w")
  168. hashFile.write(commitHash)
  169. hashFile.close()
  170. def incrementBuildNumber():
  171. logStep("Updating build number to " + str(buildNum + 1))
  172. numberFile.seek(0)
  173. numberFile.write(str(buildNum+1))
  174. numberFile.truncate()
  175. numberFile.close()
  176. def cleanup():
  177. logStep("Cleaning up")
  178. shellExec(buildDir, "rm " + bbName + " -R")
  179. def closeLog():
  180. logStep("Renaming 'logFile.html' to '" + outputNamePrefix + "-buildlog.html'")
  181. global logFile
  182. logFile.close()
  183. logFile = None
  184. shellExec(unstableDir, "mv logFile.html " + outputPathPrefix + "-buildlog.html")
  185. if os.path.exists(bbDir): # previous build is still running or was terminated after an error
  186. sys.exit()
  187. if repositoryLocked():
  188. sys.exit()
  189. if selectBranch() == None:
  190. sys.exit()
  191. # this file contains the build number to be used for this build; incremented after successfull build
  192. numberFile = open(buildDir + "/" + "number", "r+")
  193. buildNum = int(numberFile.readline().strip())
  194. openLog()
  195. print "<br/>Build " + str(buildNum) + " from '" + branch + "' at " + buildDate + "<br/>"
  196. log("<h2>Build " + str(buildNum) + " from '" + branch + "' at " + buildDate + "</h2>")
  197. log("git commit hash is: " + commitHash)
  198. logStep("Cloning repository into temporary folder '" + bbName + "'")
  199. shellExec(buildDir, "git clone " + localRepository + " " + bbName)
  200. logStep("Checking out branch '" + branch + "' into temporary folder '" + bbName + "'")
  201. shellExec(bbDir, "git checkout " + branch, False)
  202. appVersion = open(appbuildDir + "/AppVersion.txt", "r").readline().strip()
  203. appVerName = getAppVerName(appVersion)
  204. versionInfoVersion = getVersionInfoVersion(appVersion, buildNum)
  205. finalRelease = isFinal(appVersion)
  206. outputNamePrefix = "blackbox-" + appVersion + ("" if finalRelease else ("." + str(buildNum).zfill(3)))
  207. outputDir = stableDir if finalRelease else unstableDir
  208. outputPathPrefix = outputDir + "/" + outputNamePrefix
  209. prepareCompileAndLink()
  210. compileAndLink() #1
  211. buildDev0()
  212. compileAndLink() #2
  213. buildDev0()
  214. compileAndLink() #3
  215. buildSetupFile()
  216. updateDev0()
  217. buildZipFile()
  218. updateCommitHash()
  219. incrementBuildNumber()
  220. cleanup()
  221. closeLog()