import haxe.io.BytesOutput; import haxe.io.Eof; import haxe.io.Path; import sys.io.Process; import sys.FileSystem; #if haxe4 import sys.thread.Thread; #elseif neko import neko.vm.Thread; #else import cpp.vm.Thread; #end class ProcessManager { static function dup(inArgs:Array) { if (inArgs==null) return []; return inArgs.copy(); } // Command may be a pseudo command, like "xcrun --sdk abc", or 'python "some script"' // Here we split the first word into command and move the rest into args, being careful // to preserve quoted words static function combineCommand(command:String, args:Array) { var parts = new Array(); var c = command; var quoted = ~/^\s*"([^"]+)"(.*)/; var word = ~/^\s*(\S+)(.*)/; while(c.length>0) { if (quoted.match(c)) { parts.push( quoted.matched(1) ); c = quoted.matched(2); } else if (word.match(c)) { parts.push( word.matched(1) ); c = word.matched(2); } else break; } if (parts.length>1) { command = parts.shift(); while(parts.length>0) args.unshift( parts.pop() ); } return PathManager.escape(command); } private static function formatMessage(command:String, args:Array, colorize:Bool = true):String { var message = ""; if (colorize) { message = "\x1b[33;1m" + command + "\x1b[0m"; } else { message = command; } for (arg in args) { if (colorize) { var ext = Path.extension(arg); if (ext == "cpp" || ext == "c" || ext == "h" || ext == "hpp" || ext == "m" || ext == "mm") { var split = arg.split ("/"); if (split.length > 1) { arg = "\x1b[33m" + split.slice(0, split.length - 1).join("/") + "/\x1b[33;1m" + split[split.length - 1] + "\x1b[0m"; } else { arg = "\x1b[1m" + arg + "\x1b[0m"; } } else if (StringTools.startsWith(arg, "-D")) { arg = "\x1b[1m" + arg + "\x1b[0m"; } else { arg = "\x1b[0m" + arg + "\x1b[0m"; } } if (arg.indexOf(" ") > -1) { message += " \"" + arg + "\""; } else { message += " " + arg; } } return message; } public static function runCommand(path:String, command:String, args:Array, print:Bool = true, safeExecute:Bool = true, ignoreErrors:Bool = false,?inText:String):Int { args = dup(args); command = combineCommand(command,args); if (print && !Log.verbose && !Log.quiet) { Log.info(inText==null ? "" : inText,formatMessage(command, args)); } if (safeExecute) { try { if (path != null && path != "" && !FileSystem.exists(FileSystem.fullPath(path)) && !FileSystem.exists(FileSystem.fullPath(new Path(path).dir))) { Log.error("The specified target path \"" + path + "\" does not exist"); return 1; } return _runCommand(path, command, args, inText); } catch (e:Dynamic) { if (!ignoreErrors) { //var text = formatMessage(command, args); //Log.error("Error while running command\n" + text , e); if (Log.verbose) { Log.error ("", e); } return 1; } return 0; } } else { return _runCommand(path, command, args, inText); } } public static function readStderr(inCommand:String,inArgs:Array) { inArgs = dup(inArgs); inCommand = combineCommand(inCommand,inArgs); var result = new Array(); var proc = new Process(inCommand,inArgs); try { while(true) { var out = proc.stderr.readLine(); result.push(out); } } catch(e:Dynamic){} proc.close(); return result; } public static function readStdout(command:String,args:Array) { args = dup(args); command = combineCommand(command,args); var result = new Array(); var proc = new Process(command,args); try { while(true) { var out = proc.stdout.readLine(); result.push(out); } } catch(e:Dynamic){} proc.close(); return result; } public static function runProcess(path:String, command:String, args:Array, waitForOutput:Bool = true, print:Bool = true, safeExecute:Bool = true, ignoreErrors:Bool = false, ?text:String):String { args = dup(args); command = combineCommand(command,args); if (print && !Log.verbose) { Log.info(formatMessage(command, args)); } if (safeExecute) { try { if (path != null && path != "" && !FileSystem.exists(FileSystem.fullPath(path)) && !FileSystem.exists(FileSystem.fullPath(new Path(path).dir))) { Log.error("The specified target path \"" + path + "\" does not exist"); } return _runProcess(path, command, args, waitForOutput, ignoreErrors, text); } catch (e:Dynamic) { if (!ignoreErrors) { //Log.error("Error while running command\n" + formatMessage(command,args), e); if (Log.verbose) { Log.error ("", e); } } return null; } } else { return _runProcess(path, command, args, waitForOutput, ignoreErrors, text); } } public static function runProcessLine(path:String, command:String, args:Array, waitForOutput:Bool = true, print:Bool = true, safeExecute:Bool = true, ignoreErrors:Bool = false):String { var result = runProcess(path, command, args, waitForOutput, print, safeExecute, ignoreErrors); if (result!=null) return result.split("\n")[0]; return result; } private static function _runCommand(path:String, command:String, args:Array, inText:String):Int { var oldPath:String = ""; if (path != null && path != "") { Log.info("", " - \x1b[1mChanging directory:\x1b[0m " + path + ""); oldPath = Sys.getCwd(); Sys.setCwd(path); } if (Log.quiet && inText!=null) { Log.info(inText); } else { var text = inText==null ? "Running command" : inText; Log.info("", " - \x1b[1m" + text + ":\x1b[0m " + formatMessage(command, args)); } var result = 0; if (args != null && args.length > 0) { result = Sys.command(command, args); } else { result = Sys.command(command); } if (oldPath != "") { Sys.setCwd(oldPath); } if (result != 0) { throw ("Error while running command\n" + formatMessage(command, args) + (path != "" ? " [" + path + "]" : "")); } return result; } private static function _runProcess(path:String, command:String, args:Array, waitForOutput:Bool, ignoreErrors:Bool, inText:String):String { var oldPath:String = ""; if (path != null && path != "") { Log.info("", " - \x1b[1m - Changing directory:\x1b[0m " + path + ""); oldPath = Sys.getCwd(); Sys.setCwd(path); } if ( !Log.quiet) { var text = inText==null ? "Running process" : inText; Log.info("", " - \x1b[1m" + text + ":\x1b[0m " + formatMessage(command, args)); } else { Log.info("",inText); } var output = ""; var result = 0; var process:Process = null; try { process = new Process(command, args); } catch(e:Dynamic) { if (ignoreErrors) return null; Log.error(e+""); } var buffer = new BytesOutput(); if (waitForOutput) { var waiting = true; while (waiting) { try { var current = process.stdout.readAll(1024); buffer.write(current); if (current.length == 0) { waiting = false; } } catch (e:Eof) { waiting = false; } } result = process.exitCode(); process.close(); //if (result == 0) //{ output = buffer.getBytes().toString(); if (output == "") { var error = process.stderr.readAll().toString(); if (ignoreErrors) { output = error; } else { if (error==null || error=="") error = "Error while running command\n" + formatMessage(command, args); Log.error(error); } return null; } //} } if (oldPath != "") { Sys.setCwd(oldPath); } return output; } // This function will return 0 on success, or non-zero error code public static function runProcessThreaded(command:String, args:Array, inText:String = null):Int { args = dup(args); command = combineCommand(command,args); Log.lock(); // Other thread may have already thrown an error if (BuildTool.threadExitCode!=0) { Log.unlock(); return BuildTool.threadExitCode; } if (inText != null) Log.info(inText,""); if (!Log.quiet) Log.v(" - \x1b[1mRunning command:\x1b[0m " + formatMessage(command, args)); Log.unlock(); var output = new Array(); var process:Process = null; try { process = new Process(command, args); } catch(e:Dynamic) { Log.lock(); if (BuildTool.threadExitCode == 0) { Log.info('${Log.RED}${Log.BOLD}$e${Log.NORMAL}\n'); BuildTool.setThreadError(-1); } Log.unlock(); return -1; } var err = process.stderr; var out = process.stdout; var reader = BuildTool.helperThread.value; // Read stderr in separate thread to avoid blocking if (reader==null) { var controller = Thread.current(); BuildTool.helperThread.value = reader = Thread.create(function() { while(true) { var stream = Thread.readMessage(true); var output:Array = null; try { while(true) { var line = stream.readLine(); if (output==null) output = [ line ]; else output.push(line); } } catch(e:Dynamic){ } controller.sendMessage(output); } }); } // Start-up the error reader reader.sendMessage(err); try { while(true) { var line = out.readLine(); output.push(line); } } catch(e:Dynamic){ } if (output.length==1 && ~/^\S+\.(cpp|c|cc)$/.match(output[0])) { // Microsoft prints the name of the cpp file for some reason output = []; } var errOut:Array = Thread.readMessage(true); var code = process.exitCode(); process.close(); if (code != 0) { if (BuildTool.threadExitCode == 0) { Log.lock(); var message = ""; if (Log.verbose) { Log.println(""); message += "Error while running command\n"; message += formatMessage(command,args) + "\n\n"; } if (output.length > 0) { message += output.join("\n") + "\n"; } if (errOut != null) { message += errOut.join("\n") + '${Log.NORMAL}'; } Log.error(message,"",null,false); Log.unlock(); } return code; } if (errOut!=null && errOut.length>0) output = output.concat(errOut); if (output.length>0) { Log.info(output.join("\n")); } return 0; } }