Deployment.hx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. package runci;
  2. import runci.Config.*;
  3. import runci.System.*;
  4. import sys.io.File;
  5. import sys.FileSystem;
  6. import haxe.*;
  7. using StringTools;
  8. class Deployment {
  9. static var S3_HXBUILDS_ADDR(default, null) = 's3://hxbuilds/builds/haxe';
  10. static var gitInfo(get, null):{repo:String, branch:String, commit:String, timestamp:Float, date:String};
  11. static function get_gitInfo() return if (gitInfo != null) gitInfo else gitInfo = {
  12. repo: switch (ci) {
  13. case TravisCI:
  14. Sys.getEnv("TRAVIS_REPO_SLUG");
  15. case AppVeyor:
  16. Sys.getEnv("APPVEYOR_PROJECT_SLUG");
  17. case _:
  18. commandResult("git", ["config", "--get", "remote.origin.url"]).stdout.trim();
  19. },
  20. branch: switch (ci) {
  21. case TravisCI:
  22. Sys.getEnv("TRAVIS_BRANCH");
  23. case AppVeyor:
  24. Sys.getEnv("APPVEYOR_REPO_BRANCH");
  25. case _:
  26. commandResult("git", ["rev-parse", "--abbrev-ref", "HEAD"]).stdout.trim();
  27. },
  28. commit: commandResult("git", ["rev-parse", "HEAD"]).stdout.trim(),
  29. timestamp: Std.parseFloat(commandResult("git", ["show", "-s", "--format=%ct", "HEAD"]).stdout),
  30. date: {
  31. var gitTime = commandResult("git", ["show", "-s", "--format=%ct", "HEAD"]).stdout;
  32. var tzd = {
  33. var z = Date.fromTime(0);
  34. z.getHours() * 60 * 60 * 1000 + z.getMinutes() * 60 * 1000;
  35. }
  36. // make time in the UTC time zone
  37. var time = Date.fromTime(Std.parseFloat(gitTime) * 1000 - tzd);
  38. DateTools.format(time, "%Y-%m-%dT%H:%M:%SZ");
  39. }
  40. }
  41. static function isDeployNightlies() {
  42. return
  43. Sys.getEnv("DEPLOY_NIGHTLIES") != null &&
  44. (gitInfo.branch == "development" || gitInfo.branch == "master" || gitInfo.branch == "deploy-test");
  45. }
  46. static function deployBintray():Void {
  47. if (
  48. Sys.getEnv("BINTRAY") != null &&
  49. Sys.getEnv("BINTRAY_USERNAME") != null &&
  50. Sys.getEnv("BINTRAY_API_KEY") != null
  51. ) {
  52. // generate bintray config
  53. var tpl = new Template(File.getContent("extra/bintray.tpl.json"));
  54. var compatDate = ~/[^0-9]/g.replace(gitInfo.date, "");
  55. var json = tpl.execute({
  56. packageSubject: {
  57. var sub = Sys.getEnv("BINTRAY_SUBJECT");
  58. sub != null ? sub : Sys.getEnv("BINTRAY_USERNAME");
  59. },
  60. os: systemName.toLowerCase(),
  61. versionName: '${haxeVer}+${compatDate}.${gitInfo.commit.substr(0,7)}',
  62. versionDesc: "Automated CI build.",
  63. gitRepo: gitInfo.repo,
  64. gitBranch: gitInfo.branch,
  65. gitCommit: gitInfo.commit,
  66. gitDate: gitInfo.date,
  67. });
  68. var path = "extra/bintray.json";
  69. File.saveContent("extra/bintray.json", json);
  70. infoMsg("saved " + FileSystem.absolutePath(path) + " with content:");
  71. Sys.println(json);
  72. }
  73. }
  74. static function isDeployApiDocsRequired() {
  75. return
  76. Sys.getEnv("DEPLOY_API_DOCS") != null &&
  77. (
  78. gitInfo.branch == "development" ||
  79. switch(Sys.getEnv("TRAVIS_TAG")) {
  80. case null, _.trim() => "":
  81. false;
  82. case tag:
  83. true;
  84. }
  85. );
  86. }
  87. /**
  88. Deploy doc to api.haxe.org.
  89. */
  90. static function deployApiDoc():Void {
  91. changeDirectory(repoDir);
  92. runCommand("make", ["xmldoc"]);
  93. File.saveContent("extra/doc/info.json", Json.stringify({
  94. "commit": gitInfo.commit,
  95. "branch": gitInfo.branch,
  96. }));
  97. switch (Sys.getEnv("GHP_REMOTE")) { // should be in the form of https://[email protected]/account/repo.git
  98. case null:
  99. infoMsg('Missing GHP_REMOTE, skip api doc deploy.');
  100. case remoteRepo:
  101. var localRepo = "extra/api.haxe.org";
  102. runCommand("git", ["clone", remoteRepo, localRepo]);
  103. runCommand("haxe", ["--cwd", localRepo, "--run", "ImportXml", FileSystem.absolutePath("extra/doc")]);
  104. }
  105. }
  106. static function cleanup32BitDlls()
  107. {
  108. infoMsg('Cleaning up the 32-bit DLLS');
  109. cleanup32BitDll('zlib1.dll');
  110. }
  111. static function cleanup32BitDll(name:String)
  112. {
  113. var cygRoot = Sys.getEnv("CYG_ROOT");
  114. if (cygRoot != null) {
  115. while (true)
  116. {
  117. var proc = new sys.io.Process('$cygRoot/bin/bash', ['-lc', '/usr/bin/cygpath -w "`which $name`"']);
  118. var out = proc.stdout.readAll().toString().trim();
  119. var err = proc.stderr.readAll().toString().trim();
  120. if (proc.exitCode() == 0)
  121. {
  122. if (!is64BitDll(out))
  123. {
  124. infoMsg('Deleting the file $out because it is a 32-bit DLL');
  125. sys.FileSystem.deleteFile(out);
  126. } else {
  127. break;
  128. }
  129. } else {
  130. infoMsg('Error while getting the cygpath for $name: $out\n$err');
  131. break; // no more dlls
  132. }
  133. }
  134. } else {
  135. var path = Sys.getEnv('PATH').split(';');
  136. for (base in path)
  137. {
  138. var fullPath = '$base/$name';
  139. if (sys.FileSystem.exists(fullPath) && !is64BitDll(fullPath))
  140. {
  141. infoMsg('Deleting the file $fullPath because it is a 32-bit DLL');
  142. sys.FileSystem.deleteFile(fullPath);
  143. }
  144. }
  145. }
  146. }
  147. static function is64BitDll(path:String)
  148. {
  149. if (!sys.FileSystem.exists(path))
  150. {
  151. throw 'The DLL at path $path was not found';
  152. }
  153. var file = sys.io.File.read(path);
  154. if (file.readByte() != 'M'.code || file.readByte() != 'Z'.code)
  155. {
  156. throw 'The DLL at path $path is invalid: Invalid MZ magic header';
  157. }
  158. file.seek(0x3c, SeekBegin);
  159. var peSigOffset = file.readInt32();
  160. file.seek(peSigOffset, SeekBegin);
  161. if (file.readByte() != 'P'.code || file.readByte() != 'E'.code || file.readByte() != 0 || file.readByte() != 0)
  162. {
  163. throw 'Invalid PE header signature: PE expected';
  164. }
  165. // coff header
  166. file.readString(20);
  167. // pe header
  168. var peKind = file.readUInt16();
  169. file.close();
  170. switch(peKind)
  171. {
  172. case 0x20b: // 64 bit
  173. return true;
  174. case 0x10b: // 32 bit
  175. return false;
  176. case 0x107: // rom
  177. return false;
  178. case _:
  179. throw 'Unknown PE header kind $peKind';
  180. }
  181. }
  182. /**
  183. Deploy source package to hxbuilds s3
  184. */
  185. static function deployNightlies():Void {
  186. changeDirectory(repoDir);
  187. switch (systemName) {
  188. case "Linux":
  189. runCommand("make", ["-s", "package_unix"]);// source
  190. for (file in sys.FileSystem.readDirectory('out')) {
  191. if (file.startsWith('haxe') && file.endsWith('_src.tar.gz')) {
  192. submitToS3("source", 'out/$file');
  193. break;
  194. }
  195. }
  196. for (file in sys.FileSystem.readDirectory('out')) {
  197. if (file.startsWith('haxe')) {
  198. if (file.endsWith('_bin.tar.gz')) {
  199. submitToS3('linux64', 'out/$file');
  200. }
  201. }
  202. }
  203. case "Mac":
  204. runCommand("make", ["-s", 'package_unix', 'package_installer_mac']);
  205. for (file in sys.FileSystem.readDirectory('out')) {
  206. if (file.startsWith('haxe')) {
  207. if (file.endsWith('_bin.tar.gz')) {
  208. submitToS3('mac', 'out/$file');
  209. } else if (file.endsWith('_installer.tar.gz')) {
  210. submitToS3('mac-installer', 'out/$file');
  211. }
  212. }
  213. }
  214. case "Windows":
  215. var kind = switch (Sys.getEnv("ARCH")) {
  216. case null:
  217. throw "ARCH is not set";
  218. case "32":
  219. "windows";
  220. case "64":
  221. cleanup32BitDlls();
  222. "windows64";
  223. case _:
  224. throw "unknown ARCH";
  225. }
  226. var cygRoot = Sys.getEnv("CYG_ROOT");
  227. if (cygRoot != null) {
  228. runCommand('$cygRoot/bin/bash', ['-lc', "cd \"$OLDPWD\" && make -s -f Makefile.win package_installer_win"]);
  229. } else {
  230. runCommand("make", ['-f', 'Makefile.win', "-s", 'package_installer_win']);
  231. }
  232. for (file in sys.FileSystem.readDirectory('out')) {
  233. if (file.startsWith('haxe')) {
  234. if (file.endsWith('_bin.zip')) {
  235. submitToS3(kind, 'out/$file');
  236. } else if (file.endsWith('_installer.zip')) {
  237. submitToS3('${kind}-installer', 'out/$file');
  238. }
  239. }
  240. }
  241. case _:
  242. throw "unknown system";
  243. }
  244. }
  245. static function fileExtension(file:String) {
  246. file = haxe.io.Path.withoutDirectory(file);
  247. var idx = file.indexOf('.');
  248. if (idx < 0) {
  249. return '';
  250. } else {
  251. return file.substr(idx);
  252. }
  253. }
  254. static function submitToS3(kind:String, sourceFile:String) {
  255. switch ([
  256. Sys.getEnv("HXBUILDS_AWS_ACCESS_KEY_ID"),
  257. Sys.getEnv("HXBUILDS_AWS_SECRET_ACCESS_KEY")
  258. ]) {
  259. case [null, _] | [_, null]:
  260. infoMsg("Missing HXBUILDS_AWS_*, skip submit to S3");
  261. case [accessKeyId, secretAccessKey]:
  262. var date = DateTools.format(Date.now(), '%Y-%m-%d');
  263. var ext = fileExtension(sourceFile);
  264. var fileName = 'haxe_${date}_${gitInfo.branch}_${gitInfo.commit.substr(0,7)}${ext}';
  265. var changeLatest = gitInfo.branch == "development";
  266. Sys.putEnv('AWS_ACCESS_KEY_ID', accessKeyId);
  267. Sys.putEnv('AWS_SECRET_ACCESS_KEY', secretAccessKey);
  268. runCommand('aws s3 cp --region us-east-1 "$sourceFile" "$S3_HXBUILDS_ADDR/$kind/$fileName"');
  269. if (changeLatest) {
  270. runCommand('aws s3 cp --region us-east-1 "$sourceFile" "$S3_HXBUILDS_ADDR/$kind/haxe_latest$ext"');
  271. }
  272. Indexer.index('$S3_HXBUILDS_ADDR/$kind/');
  273. runCommand('aws s3 cp --region us-east-1 index.html "$S3_HXBUILDS_ADDR/$kind/index.html"');
  274. Indexer.index('$S3_HXBUILDS_ADDR/');
  275. runCommand('aws s3 cp --region us-east-1 index.html "$S3_HXBUILDS_ADDR/index.html"');
  276. }
  277. }
  278. /**
  279. Deploy source package to ppa:haxe/snapshots.
  280. */
  281. static function deployPPA():Void {
  282. if (
  283. gitInfo.branch == "development" &&
  284. Sys.getEnv("DEPLOY") != null &&
  285. Sys.getEnv("haxeci_decrypt") != null
  286. ) {
  287. // setup deb info
  288. runCommand("git config --global user.name \"${DEBFULLNAME}\"");
  289. runCommand("git config --global user.email \"${DEBEMAIL}\"");
  290. // setup haxeci_ssh
  291. runCommand("openssl aes-256-cbc -k \"$haxeci_decrypt\" -in extra/haxeci_ssh.enc -out extra/haxeci_ssh -d");
  292. runCommand("chmod 600 extra/haxeci_ssh");
  293. runCommand("ssh-add extra/haxeci_ssh");
  294. // setup haxeci_sec.gpg
  295. runCommand("openssl aes-256-cbc -k \"$haxeci_decrypt\" -in extra/haxeci_sec.gpg.enc -out extra/haxeci_sec.gpg -d");
  296. runCommand("gpg --allow-secret-key-import --import extra/haxeci_sec.gpg");
  297. runCommand("sudo apt-get install devscripts git-buildpackage ubuntu-dev-tools dh-make -y");
  298. var compatDate = ~/[^0-9]/g.replace(gitInfo.date, "");
  299. var SNAPSHOT_VERSION = '${haxeVerFull}+1SNAPSHOT${compatDate}+${gitInfo.commit.substr(0,7)}';
  300. runCommand('cp out/haxe*_src.tar.gz "../haxe_${SNAPSHOT_VERSION}.orig.tar.gz"');
  301. changeDirectory("..");
  302. runCommand("git clone https://github.com/HaxeFoundation/haxe-debian.git");
  303. changeDirectory("haxe-debian");
  304. runCommand("git checkout upstream");
  305. runCommand("git checkout next");
  306. runCommand('gbp import-orig "../haxe_${SNAPSHOT_VERSION}.orig.tar.gz" -u "${SNAPSHOT_VERSION}" --debian-branch=next');
  307. runCommand('dch -v "1:${SNAPSHOT_VERSION}-1" --urgency low "snapshot build"');
  308. runCommand("debuild -S -sa");
  309. runCommand("backportpackage -d yakkety --upload ${PPA} --yes ../haxe_*.dsc");
  310. runCommand("backportpackage -d xenial --upload ${PPA} --yes ../haxe_*.dsc");
  311. runCommand("backportpackage -d vivid --upload ${PPA} --yes ../haxe_*.dsc");
  312. runCommand("backportpackage -d trusty --upload ${PPA} --yes ../haxe_*.dsc");
  313. runCommand("git checkout debian/changelog");
  314. runCommand("git merge -X ours --no-edit origin/next-precise");
  315. runCommand('dch -v "1:${SNAPSHOT_VERSION}-1" --urgency low "snapshot build"');
  316. runCommand("debuild -S -sa");
  317. runCommand("backportpackage -d precise --upload ${PPA} --yes ../haxe_*.dsc");
  318. }
  319. }
  320. static var haxeVer(default, never) = {
  321. var haxe_ver = haxe.macro.Compiler.getDefine("haxe_ver");
  322. switch (haxe_ver.split(".")) {
  323. case [major]:
  324. major;
  325. case [major, minor] if (minor.length == 1):
  326. '${major}.${minor}';
  327. case [major, minor] if (minor.length > 1):
  328. var patch = Std.parseInt(minor.substr(1));
  329. var minor = minor.charAt(0);
  330. '${major}.${minor}.${patch}';
  331. case _:
  332. throw haxe_ver;
  333. }
  334. }
  335. static var haxeVerFull(default, never) = {
  336. var ver = haxeVer.split(".");
  337. while (ver.length < 3) {
  338. ver.push("0");
  339. }
  340. ver.join(".");
  341. }
  342. static public function deploy():Void {
  343. switch (ci) {
  344. case TravisCI:
  345. switch (Sys.getEnv("TRAVIS_PULL_REQUEST")) {
  346. case "false", null:
  347. // not a PR
  348. case _:
  349. infoMsg("Not deploying in PR builds.");
  350. return;
  351. }
  352. case AppVeyor:
  353. switch (Sys.getEnv("APPVEYOR_PULL_REQUEST_NUMBER")) {
  354. case null:
  355. // not a PR
  356. case _:
  357. infoMsg("Not deploying in PR builds.");
  358. return;
  359. }
  360. case _:
  361. // pass
  362. }
  363. if (isDeployApiDocsRequired()) {
  364. deployApiDoc();
  365. } else {
  366. infoMsg("Not deploying API doc");
  367. }
  368. if (isDeployNightlies()) {
  369. deployNightlies();
  370. } else {
  371. infoMsg("Not deploying nightlies");
  372. }
  373. }
  374. }