using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using game; using UnityEditor; using UnityEditor.Compilation; using UnityEngine; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; #if UNITY_IOS using UnityEditor.iOS.Xcode; #endif public class EditorDevUtils { private static Process StartGamectlTask(string task) { var p = new Process(); //NOTE: Invoking command in shell on OSX to setup proper paths if (Environment.OSVersion.Platform == PlatformID.Unix) { PatchPATH(); p.StartInfo.FileName = "bash"; p.StartInfo.Arguments = "-c 'source ~/.bash_profile; php " + Application.dataPath + "/../gamectl " + task + "'"; } else { p.StartInfo.FileName = "php"; p.StartInfo.Arguments = Application.dataPath + "/../gamectl " + task; } p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.Start(); return p; } private static void PatchPATH() { string path = Environment.GetEnvironmentVariable("PATH"); Environment.SetEnvironmentVariable("PATH", "/usr/local/bin:" + path); } public static void ProcessAdbTask(string task) { var p = new Process(); if (Environment.OSVersion.Platform == PlatformID.Unix) { p.StartInfo.FileName = "bash"; p.StartInfo.Arguments = "-c 'source ~/.bash_profile; adb " + task + "'"; } else { p.StartInfo.FileName = "adb"; p.StartInfo.Arguments = task; } p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.Start(); p.WaitForExit(); Log.Debug($"Done running 'adb {task}', exit code: {p.ExitCode}"); if (p.ExitCode != 0) ShowProcError(p); } public static void ShowProcError(Process p) { string err_msg = p.StandardError.ReadToEnd(); string out_msg = p.StandardOutput.ReadToEnd(); //NOTE: showing error first so that it's visible immediately in the console log Debug.LogError(err_msg + "\n\n" + out_msg + err_msg); } public static void ProcessGamectlTask(string task, Action cb) { ClearCompilationErrors(); Process p = StartGamectlTask(task); StreamReader s = p.StandardOutput; p.WaitForExit(); cb(p, s); if (p.ExitCode == 0) return; string errMsg = p.StandardError.ReadToEnd(); string outMsg = p.StandardOutput.ReadToEnd(); if (TryGuessErrorFileAndLine(errMsg, out string file, out int line)) EmulateCompilationError(errMsg, file, line); else Debug.LogError(errMsg + "\n\n" + outMsg + errMsg); } private static bool TryGuessErrorFileAndLine(string err_msg, out string file, out int line) { file = ""; line = -1; var m = Regex.Match(err_msg, "File '([^']+)'"); if (!m.Success) return false; line = 0; file = m.Groups[1].ToString(); return true; } private static void EmulateCompilationError(string err_msg, string file, int line) { var m = typeof(CompilationPipeline).GetMethod("LogEditorCompilationError", BindingFlags.Static | BindingFlags.NonPublic); string rel_file = Path.GetFullPath(file) .Replace(Path.GetFullPath(Application.dataPath) + Path.DirectorySeparatorChar, $"Assets{Path.DirectorySeparatorChar}"); var asset = AssetDatabase.LoadAssetAtPath(rel_file); if (asset == null) return; m.Invoke(null, new object[] { rel_file + "(1,1): error: " + err_msg, asset.GetInstanceID() }); } private static void ClearCompilationErrors() { var m = typeof(CompilationPipeline).GetMethod("ClearEditorCompilationErrors", BindingFlags.Static | BindingFlags.NonPublic); m?.Invoke(null, null); } public static bool ProcessGamectlTask(string task) { var success = false; ProcessGamectlTask(task, (process, reader) => { success = process.ExitCode == 0; }); return success; } public static bool IsBatchMode() { string cli_opts = Environment.CommandLine; return cli_opts.Contains("-batchmode"); } public static string GameRoot() { char sep = Path.DirectorySeparatorChar; return Path.GetFullPath(Application.dataPath + $"{sep}..{sep}..{sep}"); } public static void DrawHorizontalSeparator(Color color, float padding = 4) { const float thickness = 1; const float padding_y = 3; GUILayout.Space(padding_y); var r = EditorGUILayout.GetControlRect(GUILayout.Height(thickness)); r.height = thickness; r.x += padding; r.width -= 2 * padding; EditorGUI.DrawRect(r, color); GUILayout.Space(padding_y); } public static string GetFilePath(ConfBase cb) { return AliasToFilePath(cb.strid); } public static string AliasToFilePath(string conf_alias) { return conf_alias.Replace("@", GameRoot() + "configs/") + ".conf.js"; } public static string FilePathToAlias(string file_path) { return file_path.Replace(GameRoot() + "configs/", "@").Replace(".conf.js", ""); } public static void WriteConfByAlias(string alias, string content) { string file_path = AliasToFilePath(alias); Directory.CreateDirectory(Path.GetDirectoryName(file_path)); WriteConfigPreserveHeader(file_path, content); } public static string ExtractJsonHeader(string json, out uint id, out string strid) { uint tmp_id = 0; var tmp_strid = ""; json = Regex.Replace(json, "^\\{\\s*/\\*\\s*proto_id\\s*=\\s*(\\d+)\\s*;\\s*alias\\s*=\\s*(\\S+)\\s*\\*/", delegate(Match m) { if (m.Success) { tmp_id = uint.Parse("" + m.Groups[1]); tmp_strid = "" + m.Groups[2]; return "{\n"; } return m.Value; } ); id = tmp_id; strid = tmp_strid; return json; } public static void RemoveConfigHeader(string path) { if (!File.Exists(path)) return; string[] lines = File.ReadAllLines(path); int idx = lines[0].IndexOf('/'); if (idx != -1) lines[0] = lines[0].Remove(idx); File.WriteAllLines(path, lines); } public static void WriteConfigPreserveHeader(string path, string json) { if (File.Exists(path)) { string prev_json = File.ReadAllText(path); //NOTE: let's preserve header info uint id = 0; var strid = ""; ExtractJsonHeader(prev_json, out id, out strid); if (id != 0) { int first_bracket = json.IndexOf("{"); Error.Assert(first_bracket != -1); json = "{ /* proto_id = " + id + " ; alias = " + strid + " */" + json.Substring(first_bracket + 1); } } File.WriteAllText(path, json); } //public static string ReplaceIdToStrid(string json, string field_name, Configs configs) where T : ConfBase //{ // for(int i = 0; i < configs.Count; ++i) // { // uint id = configs[i].id; // var conf = configs.Find(id); // if(conf != null && conf is T) // json = json.Replace($"\"{field_name}\": {conf.id}", $"\"{field_name}\": \"{conf.strid}\""); // } // return json; //} public static string ReplaceI18N(string json, string field_name) { json = json.Replace($"\"_{field_name}\": ", $"\"{field_name}\": "); json = Regex.Replace(json, $",(\n|\r|\r\n) .*\"__{field_name}\": \\[\\]", "", RegexOptions.Multiline); json = json.Replace(@"\r", ""); return json; } //public static string ReplaceStridToId(string json, string field_name, Configs configs) where T : ConfBase //{ // for(int i = 0; i < configs.Count; ++i) // { // uint id = configs[i].id; // var conf = configs.Find(id); // if(conf != null && conf is T) // json = json.Replace($"\"{field_name}\": \"{conf.strid}\"", $"\"{field_name}\": {conf.id}"); // } // return json; //} #if UNITY_IOS public static void IOSAddLocalFile(string buildPath, PBXProject proj, string targetName, string fileDir, string fileName) { string filePath = Path.Combine(fileDir, fileName); File.Copy(filePath, Path.Combine(buildPath, fileName)); proj.AddFileToBuild(targetName, proj.AddFile(fileName, fileName)); } #endif public static string ToShortName(string file, string path) { return file.Replace(path + "/", ""); } }