build_with_bbscript.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #!/usr/bin/python
  2. #
  3. # Automated build of the BlackBox Component Builder for Windows under Linux Debian 7.
  4. # Looks at all branches and puts the output into the branch's output folder 'unstable/<branch>'
  5. # unless building a final release, which is always put into folder 'stable'.
  6. #
  7. # Ivan Denisov, Josef Templ
  8. #
  9. # use: "build.py -h" to get a short help text
  10. #
  11. # Creates 3 files in case of success:
  12. # 1. a build log file named blackbox-<AppVersion>.<buildnr>-buildlog.html
  13. # 2. a Windows installer file named blackbox-<AppVersion>.<buildnr>-setup.exe
  14. # 3. a zipped package named blackbox-<AppVersion>-<buildnr>.zip
  15. # In case of building a final release, buildnr is not included.
  16. # In case building was started for a branch, updates the branch's last-build-commit hash.
  17. # In case of successfully finishing the build, increments the global build number.
  18. #
  19. # By always rebuilding bbscript.exe it avoids problems with changes in the symbol or object file formats
  20. # and acts as a rigorous test for some parts of BlackBox, in particular for the compiler itself.
  21. # This script uses the general purpose 'bbscript' scripting engine for BlackBox, which
  22. # can be found in the subsystem named 'Script'.
  23. #
  24. # Error handling:
  25. # Stops building when shellExec writes to stderr, unless stopOnError is False.
  26. # Stops building when there is an error reported by bbscript.
  27. # Stops building when there is a Python exception.
  28. # The next build will take place upon the next commit.
  29. #
  30. # TODO git checkout reports a message on stderr but it works, so it is ignored
  31. from subprocess import Popen, PIPE, call
  32. import sys, datetime, fileinput, os.path, argparse
  33. buildDate = datetime.datetime.now().isoformat()
  34. buildDir = "/var/www/tribiq/makeapp"
  35. bbName = "bb"
  36. bbDir = buildDir + "/" + bbName
  37. appbuildDir = bbDir + "/appbuild"
  38. localRepository = "/var/www/git/blackbox.git"
  39. unstableDir = "/var/www/tribiq/unstable"
  40. stableDir = "/var/www/tribiq/stable"
  41. wine = "/usr/local/bin/wine"
  42. bbscript = "export DISPLAY=:1 && " + wine + " bbscript.exe"
  43. iscc = "/usr/local/bin/iscc"
  44. windres="/usr/bin/i586-mingw32msvc-windres"
  45. testName = "testbuild"
  46. branch = None
  47. commitHash = None
  48. logFile = None
  49. parser = argparse.ArgumentParser(description='Build BlackBox')
  50. parser.add_argument('--verbose', action="store_true", default=False, help='turn verbose output on')
  51. parser.add_argument('--test', action="store_true", default=False, help='put all results into local directory "' + testName + '"')
  52. parser.add_argument('--branch', help='select BRANCH for building')
  53. args = parser.parse_args()
  54. def repositoryLocked():
  55. return os.path.exists(localRepository + ".lock")
  56. def hashFilePath():
  57. return buildDir + "/lastBuildHash/" + branch
  58. def getLastHash():
  59. if os.path.exists(hashFilePath()):
  60. hashFile = open(hashFilePath(), "r")
  61. commit = hashFile.readline().strip()
  62. hashFile.close()
  63. return commit
  64. else:
  65. return ""
  66. def getCommitHash():
  67. gitLog = shellExec(localRepository, "git log " + branch + " -1")
  68. global commitHash
  69. commitHash = gitLog.split("\n")[0].split(" ")[1]
  70. return commitHash
  71. def needsRebuild():
  72. return getLastHash() != getCommitHash()
  73. def selectBranch():
  74. global branch
  75. if args.branch != None:
  76. branch = args.branch
  77. getCommitHash()
  78. return branch
  79. else:
  80. branches = shellExec(localRepository, "git branch -a")
  81. for line in branches.split("\n"):
  82. branch = line[2:].strip()
  83. if branch != "" and needsRebuild():
  84. return branch
  85. return None
  86. def openLog():
  87. global logFile
  88. logFile = open(unstableDir + "/logFile.html", "w")
  89. def logVerbose(text):
  90. if args.verbose:
  91. print text # for testing, goes to console
  92. def log(text, startMarkup="", endMarkup=""):
  93. if text != "":
  94. if logFile != None:
  95. for line in text.split("\n"):
  96. logFile.write(startMarkup + line + endMarkup + "<br/>\n")
  97. logFile.flush()
  98. elif args.verbose:
  99. for line in text.split("\n"):
  100. logVerbose(line)
  101. def logErr(text): # use color red
  102. log(text, '<font color="#FF0000">', '</font>')
  103. def logStep(text): # use bold font
  104. log(text, '<b>', '</b>')
  105. def logShell(text): # use color green
  106. log(text, '<font color="#009600">', '</font>')
  107. def shellExec(wd, cmd, stopOnError=True):
  108. logShell("cd " + wd + " && " + cmd)
  109. (stdout, stderr) = Popen("cd " + wd + " && " + cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()
  110. log(stdout)
  111. if stderr == "":
  112. return stdout
  113. elif not stopOnError:
  114. logErr(stderr)
  115. logErr("--- error ignored ---")
  116. return stdout
  117. else:
  118. logErr(stderr)
  119. logErr("--- build aborted ---")
  120. print "--- build aborted ---"
  121. incrementBuildNumber() # if not args.test
  122. cleanup() # if not args.test
  123. renameLog() # if not args.test
  124. sys.exit()
  125. def getAppVerName(appVersion):
  126. x = appVersion
  127. if appVersion.find("-a") >= 0:
  128. x = appVersion.replace("-a", " Alpha ")
  129. elif appVersion.find("-b") >= 0:
  130. x = appVersion.replace("-b", " Beta ")
  131. elif appVersion.find("-rc") >= 0:
  132. x = appVersion.replace("-rc", " Release Candidate ")
  133. return "BlackBox Component Builder " + x
  134. def getVersionInfoVersion(appVersion, buildNum):
  135. version = appVersion.split("-")[0]
  136. v = version.split(".")
  137. v0 = v[0] if len(v) > 0 else "0"
  138. v1 = v[1] if len(v) > 1 else "0"
  139. v2 = v[2] if len(v) > 2 else "0"
  140. return v0 + "." + v1 + "." + v2 + "." + str(buildNum)
  141. def isFinal(appVersion):
  142. return appVersion.find("-") < 0
  143. def prepareCompileAndLink():
  144. logStep("Preparing BlackBox.rc")
  145. vrsn = versionInfoVersion.replace(".", ",")
  146. shellExec(bbDir + "/Win/Rsrc", "mv BlackBox.rc BlackBox.rc_template")
  147. shellExec(bbDir + "/Win/Rsrc", "sed s/{#VERSION}/" + vrsn + "/ < BlackBox.rc_template > BlackBox.rc")
  148. shellExec(bbDir + "/Win/Rsrc", "rm BlackBox.rc_template")
  149. logStep("Creating the BlackBox.res resource file")
  150. shellExec(bbDir + "/Win/Rsrc", windres + " -i BlackBox.rc -o BlackBox.res")
  151. logStep("Preparing bbscript.exe")
  152. shellExec(buildDir, "cp bbscript.exe " + bbDir + "/")
  153. logStep("Starting Xvfb")
  154. if os.path.exists("/tmp/.X1-lock"):
  155. log("Xvfb is already running: /tmp/.X1-lock exists")
  156. else:
  157. shellExec(buildDir, "Xvfb :1 &")
  158. def deleteBbFile(name):
  159. if os.path.exists(bbDir + "/" + name):
  160. shellExec(bbDir, "rm " + name)
  161. def runbbscript(fileName):
  162. deleteBbFile("StdLog.txt");
  163. # fileName is relative to bbscript.exe startup directory, which is bbDir
  164. # if a /USE param is useed it must an absolute path, otherwise some texts cannot be opened, e.g Converters.
  165. cmd = "cd " + bbDir + " && " + bbscript + ' /PAR "' + fileName + '"'
  166. logShell(cmd)
  167. bbres = call(cmd + " >wine_out.txt 2>&1", shell=True) # wine produces irrelevant error messages
  168. if bbres != 0:
  169. shellExec(bbDir, "cat StdLog.txt", False)
  170. cleanup()
  171. logErr("--- build aborted ---")
  172. renameLog() # if not args.test
  173. sys.exit()
  174. def compileAndLink():
  175. logStep("Compiling and linking BlackBox")
  176. runbbscript("Dev/Docu/Build-Tool.odc")
  177. shellExec(bbDir, "mv BlackBox2.exe BlackBox.exe && mv Code System/ && mv Sym System/")
  178. def buildBbscript():
  179. logStep("Incrementally building BlackBox scripting engine bbscript.exe")
  180. runbbscript("appbuild/newbbscript.txt")
  181. shellExec(bbDir, "mv newbbscript.exe bbscript.exe && chmod a+x bbscript.exe")
  182. shellExec(bbDir, "rm -R Code Sym */Code */Sym BlackBox.exe")
  183. def appendSystemProperties():
  184. logStep("Setting system properties in appendProps.txt")
  185. shellExec(appbuildDir, 'sed s/{#AppVersion}/"' + appVersion + '"/ < appendProps.txt > appendProps_.txt')
  186. shellExec(appbuildDir, 'sed s/{#AppVerName}/"' + appVerName + '"/ < appendProps_.txt > appendProps.txt')
  187. shellExec(appbuildDir, "sed s/{#FileVersion}/" + versionInfoVersion + "/ < appendProps.txt > appendProps_.txt")
  188. shellExec(appbuildDir, "sed s/{#BuildNum}/" + str(buildNum) + "/ < appendProps_.txt > appendProps.txt")
  189. shellExec(appbuildDir, "sed s/{#BuildDate}/" + buildDate[:10] + "/ < appendProps.txt > appendProps_.txt")
  190. shellExec(appbuildDir, "sed s/{#CommitHash}/" + commitHash + "/ < appendProps_.txt > appendProps.txt")
  191. logStep("Appending version properties to System/Rsrc/Strings.odc")
  192. runbbscript("appbuild/appendProps.txt")
  193. def updateBbscript():
  194. if not args.test:
  195. logStep("Updating bbscript.exe")
  196. shellExec(bbDir, "mv bbscript.exe " + buildDir + "/")
  197. else:
  198. logStep("Removing bbscript.exe becaue this is a test build")
  199. shellExec(bbDir, "rm bbscript.exe ")
  200. def buildSetupFile():
  201. logStep("Building " + outputNamePrefix + "-setup.exe file using InnoSetup")
  202. deleteBbFile("StdLog.txt");
  203. deleteBbFile("wine_out.txt");
  204. deleteBbFile("README.txt");
  205. shellExec(bbDir, "rm -R Cons Interp Script appbuild", False)
  206. shellExec(bbDir, iscc + " - < Win/Rsrc/BlackBox.iss" \
  207. + ' "/dAppVersion=' + appVersion
  208. + '" "/dAppVerName=' + appVerName
  209. + '" "/dVersionInfoVersion=' + versionInfoVersion
  210. + '"', False) # a meaningless error is displayed
  211. shellExec(bbDir, "mv Output/setup.exe " + outputPathPrefix + "-setup.exe")
  212. shellExec(bbDir, "rm -R Output")
  213. def buildZipFile():
  214. deleteBbFile("LICENSE.txt")
  215. logStep("Zipping package to file " + outputNamePrefix + ".zip")
  216. shellExec(bbDir, "zip -r " + outputPathPrefix + ".zip *")
  217. def updateCommitHash():
  218. if not args.test:
  219. logStep("Updating commit hash for branch '" + branch + "'")
  220. hashFile = open(hashFilePath(), "w")
  221. hashFile.write(commitHash)
  222. hashFile.close()
  223. def incrementBuildNumber():
  224. if not args.test:
  225. logStep("Updating build number to " + str(buildNum + 1))
  226. numberFile.seek(0)
  227. numberFile.write(str(buildNum+1))
  228. numberFile.truncate()
  229. numberFile.close()
  230. def cleanup():
  231. if not args.test:
  232. logStep("Cleaning up")
  233. shellExec(buildDir, "rm -R " + bbDir)
  234. def renameLog():
  235. if not args.test:
  236. logStep("Renaming 'logFile.html' to '" + outputNamePrefix + "-buildlog.html'")
  237. global logFile
  238. logFile.close()
  239. logFile = None
  240. shellExec(unstableDir, "mv logFile.html " + outputPathPrefix + "-buildlog.html")
  241. if args.test:
  242. unstableDir = buildDir + "/" + testName
  243. stableDir = unstableDir
  244. if (os.path.exists(bbDir)):
  245. shellExec(buildDir, "rm -R -f " + bbDir)
  246. if (os.path.exists(unstableDir)):
  247. shellExec(buildDir, "rm -R -f " + testName)
  248. shellExec(buildDir, "mkdir " + testName)
  249. if os.path.exists(bbDir): # previous build is still running or was terminated after an error
  250. logVerbose("no build because directory '" + bbDir + "' exists")
  251. sys.exit()
  252. if repositoryLocked():
  253. logVerbose("no build because repository is locked; probably due to sync process")
  254. sys.exit()
  255. if selectBranch() == None:
  256. logVerbose("no build because no new commit in any branch")
  257. sys.exit()
  258. updateCommitHash() # if not args.test
  259. # this file contains the build number to be used for this build; incremented after successfull build
  260. numberFile = open(buildDir + "/" + "number", "r+")
  261. buildNum = int(numberFile.readline().strip())
  262. openLog()
  263. log("<h2>Build " + str(buildNum) + " from '" + branch + "' at " + buildDate + "</h2>")
  264. log("<h3>git commit hash: " + commitHash + "</h3>")
  265. logStep("Cloning repository into temporary folder '" + bbName + "'")
  266. shellExec(buildDir, "git clone " + localRepository + " " + bbDir)
  267. if branch != "master":
  268. logStep("Checking out branch '" + branch + "'")
  269. shellExec(bbDir, "git checkout " + branch, False)
  270. if not os.path.exists(appbuildDir + "/AppVersion.txt"):
  271. cleanup() # if not args.test
  272. logStep('No build because file "appbuild/AppVersion.txt" not in branch')
  273. sys.exit()
  274. print "<br/>Build " + str(buildNum) + " from '" + branch + "' at " + buildDate + "<br/>" # goes to buildlog.html
  275. appVersion = open(appbuildDir + "/AppVersion.txt", "r").readline().strip()
  276. appVerName = getAppVerName(appVersion)
  277. versionInfoVersion = getVersionInfoVersion(appVersion, buildNum)
  278. finalRelease = isFinal(appVersion)
  279. outputNamePrefix = "blackbox-" + appVersion + ("" if finalRelease else ("." + str(buildNum).zfill(3)))
  280. outputDir = stableDir if finalRelease else unstableDir + "/" + branch
  281. outputPathPrefix = outputDir + "/" + outputNamePrefix
  282. if not os.path.exists(outputDir):
  283. shellExec(buildDir, "mkdir " + outputDir)
  284. prepareCompileAndLink()
  285. compileAndLink() #1
  286. buildBbscript()
  287. compileAndLink() #2
  288. buildBbscript()
  289. compileAndLink() #3
  290. appendSystemProperties()
  291. updateBbscript()
  292. buildSetupFile()
  293. buildZipFile()
  294. incrementBuildNumber() # if not args.test
  295. cleanup() # if not args.test
  296. renameLog() # if not args.test