System.hx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package runci;
  2. import haxe.Timer;
  3. import sys.io.Process;
  4. import runci.Config.*;
  5. using StringTools;
  6. class CommandFailure extends haxe.Exception {
  7. public final exitCode:Int;
  8. public function new(exitCode:Int = 1) {
  9. super("Command failed: " + Std.string(exitCode));
  10. this.exitCode = exitCode;
  11. }
  12. }
  13. class System {
  14. static public function successMsg(msg:String):Void {
  15. Sys.println(colorSupported ? '\x1b[32m' + msg + '\x1b[0m' : msg);
  16. }
  17. static public function failMsg(msg:String):Void {
  18. Sys.println(colorSupported ? '\x1b[31m' + msg + '\x1b[0m' : msg);
  19. }
  20. static public function infoMsg(msg:String):Void {
  21. Sys.println(colorSupported ? '\x1b[36m' + msg + '\x1b[0m' : msg);
  22. }
  23. static public function commandSucceed(cmd:String, args:Array<String>):Bool {
  24. return try {
  25. final p = new Process(cmd, args);
  26. final succeed = p.exitCode() == 0;
  27. p.close();
  28. succeed;
  29. } catch(e:Dynamic) false;
  30. }
  31. static public function commandResult(cmd:String, args:Array<String>):{
  32. stdout:String,
  33. stderr:String,
  34. exitCode:Int
  35. } {
  36. final p = new Process(cmd, args);
  37. final out = {
  38. stdout: p.stdout.readAll().toString(),
  39. stderr: p.stderr.readAll().toString(),
  40. exitCode: p.exitCode()
  41. }
  42. p.close();
  43. return out;
  44. }
  45. static inline function getDisplayCmd(cmd:String, ?args:Array<String>) {
  46. return cmd + (args == null ? '' : ' $args');
  47. }
  48. /**
  49. Run a command using `Sys.command()`.
  50. If the command exits with non-zero code, throws `CommandFailure` with the same code.
  51. */
  52. static public function runCommand(cmd:String, ?args:Array<String>):Void {
  53. final exitCode = showAndRunCommand(cmd, args);
  54. if (exitCode != 0)
  55. throw new CommandFailure(exitCode);
  56. }
  57. /** Runs command using `Sys.command()` but ignores failures. **/
  58. static public function attemptCommand(cmd:String, ?args:Array<String>) {
  59. showAndRunCommand(cmd, args);
  60. }
  61. static function showAndRunCommand(cmd:String, args:Null<Array<String>>, ?displayed:String):Int {
  62. if (displayed == null)
  63. displayed = getDisplayCmd(cmd, args);
  64. infoMsg('Command: $displayed');
  65. final t = Timer.stamp();
  66. final exitCode = Sys.command(cmd, args);
  67. final dt = Math.round(Timer.stamp() - t);
  68. final msg = 'Command exited with $exitCode in ${dt}s: $displayed';
  69. if (exitCode != 0)
  70. failMsg(msg);
  71. else
  72. successMsg(msg);
  73. return exitCode;
  74. }
  75. static final TRIALS = 3;
  76. /**
  77. Run a command using `Sys.command()` with up to three attempts. Useful for running network-dependent commands.
  78. If all three attempts fail, throws a `CommandFailure` with the exit code of the last run.
  79. **/
  80. static public function runNetworkCommand(cmd:String, ?args:Array<String>) {
  81. final cmdStr = getDisplayCmd(cmd, args);
  82. for (trial in 1...TRIALS+1){
  83. final exitCode = showAndRunCommand(cmd, args, cmdStr);
  84. if (exitCode == 0)
  85. return;
  86. if (trial == TRIALS)
  87. throw new CommandFailure(exitCode);
  88. infoMsg('Command will be re-run...');
  89. }
  90. }
  91. /**
  92. * Recursively delete a directory.
  93. * @return Int Exit code of a system command executed to perform deletion.
  94. */
  95. static public function deleteDirectoryRecursively(dir:String):Int {
  96. return switch (Sys.systemName()) {
  97. case "Windows":
  98. Sys.command("rmdir", ["/S", "/Q", StringTools.replace(sys.FileSystem.fullPath(dir), "/", "\\")]);
  99. case _:
  100. Sys.command("rm", ["-rf", dir]);
  101. }
  102. }
  103. static public function addToPATH(path:String):Void {
  104. infoMsg('Prepending $path to PATH.');
  105. switch (systemName) {
  106. case "Windows":
  107. Sys.putEnv("PATH", path + ";" + Sys.getEnv("PATH"));
  108. case "Mac", "Linux":
  109. Sys.putEnv("PATH", path + ":" + Sys.getEnv("PATH"));
  110. }
  111. }
  112. static public function addToLIBPATH(path:String):Void {
  113. infoMsg('Prepending $path to loader path.');
  114. switch (systemName) {
  115. case "Windows": // pass
  116. case "Linux":
  117. Sys.putEnv("LD_LIBRARY_PATH", path + ":" + Sys.getEnv("LD_LIBRARY_PATH"));
  118. case "Mac":
  119. Sys.putEnv("DYLD_LIBRARY_PATH", path + ":" + Sys.getEnv("DYLD_LIBRARY_PATH"));
  120. }
  121. }
  122. static function isLibraryInstalled(library:String):Bool {
  123. return new Process("haxelib", ["path", library]).exitCode() == 0;
  124. }
  125. static public function haxelibInstallGit(account:String, repository:String, ?branch:String, ?srcPath:String, useRetry:Bool = false, ?altName:String):Void {
  126. final name = (altName == null) ? repository : altName;
  127. // It could only be installed already if we're on a local machine
  128. if (!isCi() && isLibraryInstalled(name))
  129. return infoMsg('$name has already been installed.');
  130. final args = ["git", name, 'https://github.com/$account/$repository'];
  131. if (branch != null)
  132. args.push(branch);
  133. if (srcPath != null)
  134. args.push(srcPath);
  135. if (useRetry)
  136. runNetworkCommand("haxelib", args);
  137. else
  138. runCommand("haxelib", args);
  139. }
  140. static public function haxelibInstall(library:String):Void {
  141. // It could only be installed already if we're on a local machine
  142. if (!isCi() && isLibraryInstalled(library))
  143. return infoMsg('$library has already been installed.');
  144. runCommand("haxelib", ["install", library]);
  145. }
  146. static public function haxelibDev(library:String, path:String):Void {
  147. // It could only be installed already if we're on a local machine
  148. if (!isCi() && isLibraryInstalled(library))
  149. return infoMsg('$library has already been installed.');
  150. runCommand("haxelib", ["dev", library, path]);
  151. }
  152. static public function haxelibRun(args:Array<String>, useRetry:Bool = false):Void {
  153. final allArgs = ["run"].concat(args);
  154. if (useRetry)
  155. runNetworkCommand("haxelib", allArgs);
  156. else
  157. runCommand("haxelib", allArgs);
  158. }
  159. static public function getHaxelibPath(libName:String) {
  160. final proc = new Process("haxelib", ["path", libName]);
  161. final code = proc.exitCode();
  162. var result;
  163. do {
  164. result = proc.stdout.readLine();
  165. if (!result.startsWith("-L")) {
  166. break;
  167. }
  168. } while(true);
  169. proc.close();
  170. if (code != 0) {
  171. throw 'Failed to get haxelib path ($result)';
  172. }
  173. return result;
  174. }
  175. static public function changeDirectory(path:String) {
  176. Sys.println('Changing directory to $path');
  177. Sys.setCwd(path);
  178. }
  179. static function mergeArgs(cmd:String, args:Array<String>) {
  180. return switch (Sys.systemName()) {
  181. case "Windows":
  182. [StringTools.replace(cmd, "/", "\\")].concat(args).map(haxe.SysTools.quoteWinArg.bind(_, true)).join(" ");
  183. case _:
  184. [cmd].concat(args).map(haxe.SysTools.quoteUnixArg).join(" ");
  185. }
  186. }
  187. /* command for setting the environment variable to set before sys tests */
  188. static final setCommand = if (Sys.systemName() == "Windows") "set"; else "export";
  189. static final nameAndValue = "EXISTS=1";
  190. /** Prepares environment for system tests and runs `cmd` with `args` **/
  191. static public function runSysTest(cmd:String, ?args:Array<String>) {
  192. final toDisplay = getDisplayCmd(setCommand, [nameAndValue]) + ' && ' + getDisplayCmd(cmd, args);
  193. if (args != null)
  194. cmd = mergeArgs(cmd, args);
  195. final fullCmd = '$setCommand $nameAndValue && $cmd';
  196. final exitCode = showAndRunCommand(fullCmd, null, toDisplay);
  197. if (exitCode != 0)
  198. throw new CommandFailure(exitCode);
  199. }
  200. static final installPath = if (systemName == "Windows")
  201. Sys.getEnv("USERPROFILE") + "/haxe-ci";
  202. else
  203. Sys.getEnv("HOME") + "/haxe-ci";
  204. /** Returns path where packages should be installed. **/
  205. public static inline function getInstallPath():String {
  206. return installPath;
  207. }
  208. /** Returns path where downloads should be placed. **/
  209. public static inline function getDownloadPath():String {
  210. return installPath + "/downloads";
  211. }
  212. }