namespace Guru.Editor { using System.IO; using UnityEditor; using UnityEngine; using System; public class PgyerAPI { public const string Version = "1.0.0"; internal static readonly string DefaultBashPathWin = "C:\\Program Files\\Git\\bin\\bash.exe"; internal static readonly string DefaultBashPathMac = "/bin/bash"; internal static readonly string ShellFile = "pgyer_upload.sh"; private static readonly string GuruAPIKey = "20a3d1106b802abbd84ec687eedf17eb"; private static readonly string PgyerHost = "https://www.pgyer.com"; internal static string WorkingDir => $"{Application.dataPath.Replace("Assets", "Library")}/guru_publish"; public static string GetDownloadUrl(string shortUrl) => $"{PgyerHost}/{shortUrl}"; /// /// 发布产品到蒲公英平台 /// /// /// /// /// public static void PublishToPgyer(string packagePath, string apiKey = "", string bashPath = "", Action callback = null) { if (File.Exists(packagePath)) { Debug.Log($"=== START PUBLISH APP: {packagePath}"); CheckWorkingDir(); CallPublishShell(packagePath, apiKey, bashPath, callback); } } private static void CheckWorkingDir() { if (!Directory.Exists(WorkingDir)) { Directory.CreateDirectory(WorkingDir); } var file = $"{WorkingDir}/{ShellFile}"; if (!File.Exists(file)) { var from = GetShellPath(); if (File.Exists(from)) { File.Copy(from, file); #if UNITY_EDITOR_OSX RunCmd("chmod", $"+x {file}", workpath: WorkingDir); #endif } else { Debug.LogError($"[Publisher] Source shell file not found :{from}"); } } } /// /// 获取 CMD 命令路径 /// /// private static string GetShellPath() { var path = ""; var guids = AssetDatabase.FindAssets($"{nameof(PgyerAPI)} t:script"); if (guids.Length > 0) { path = Path.Combine(Directory.GetParent(AssetDatabase.GUIDToAssetPath(guids[0])).FullName, ShellFile); return Path.GetFullPath(path); } path = Path.GetFullPath( $"{Application.dataPath.Replace("Assets", "Packages")}/com.guru.unity.sdk.core/Editor/BuildTool/{ShellFile}"); return path; } private static void RunCmd(string cmd, string args, Action callback = null, string workpath = "") { System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = cmd; process.StartInfo.Arguments = args; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; if (!string.IsNullOrEmpty(workpath)) process.StartInfo.WorkingDirectory = workpath; process.Start(); string log = process.StandardOutput.ReadToEnd(); callback?.Invoke(log); process.Close(); } /// /// 在 mac 下进行发布 /// /// /// /// /// private static void CallPublishShell(string packagePath, string apiKey = "", string bashPath = "", Action callback = null) { if (string.IsNullOrEmpty(bashPath)) { #if UNITY_EDITOR_OSX bashPath = DefaultBashPathMac; #elif UNITY_EDITOR_WIN bashPath = DefaultBashPathWin.Replace("\\", "/"); #endif } if (!File.Exists(bashPath)) { string msg = $"Error: Bash file not found at path: {bashPath}! skip publishing!"; Debug.LogError(msg); callback?.Invoke(msg); return; } packagePath = packagePath.Replace("\\", "/"); if (string.IsNullOrEmpty(apiKey)) apiKey = GuruAPIKey; var args = $"-c \"./{ShellFile} -k {apiKey} {packagePath}\""; // Debug.Log(bashPath); // Debug.Log(args); // Debug.Log(WorkingDir); RunCmd(bashPath, args, callback, WorkingDir); } } /// /// Guru 包体上传工具 /// public class GuruPublishHelper { // Check Env and Exe files private static string EvnCheck() { if (!Directory.Exists(PgyerAPI.WorkingDir)) Directory.CreateDirectory(PgyerAPI.WorkingDir); // #1 --- read from cached file with available path string bash_path = ""; var envFile = $"{PgyerAPI.WorkingDir}/.env"; if (File.Exists(envFile)) { bash_path = File.ReadAllText(envFile); return bash_path; } // #2 --- Try to find bash exe file from default path bash_path = PgyerAPI.DefaultBashPathMac; #if UNITY_EDITOR_WIN bash_path = PgyerAPI.DefaultBashPathWin; #endif if (File.Exists(bash_path)) { bash_path = bash_path.Replace("\\", "/"); File.WriteAllText(envFile, bash_path); return bash_path; } // #3 --- Try to let user select bash exe file from disk string title = "选择 bash 可执行文件"; string despath = "/bin"; string exts = "*"; #if UNITY_EDITOR_WIN despath = "C:\\Program Files\\"; title = $"选择 bash 可执行文件, 例如: {despath}\\Git\\bin\\bash.exe"; exts = "exe"; #endif bash_path = EditorUtility.OpenFilePanel(title, despath, exts); if (File.Exists(bash_path)) { File.WriteAllText(envFile, bash_path.Replace("\\", "/")); } return bash_path; } [MenuItem("Guru/Publish/Android APK...")] private static void EditorPublishAPK() { SelectAndPublish(); } // [MenuItem("Guru/Publish/Publish Release AAB...")] // private static void EditorPublishAAB() // { // SelectAndPublish("aab"); // } /// /// 直接发布版本 /// /// /// public static void Publish(string appPath, string apiKey = "") { string bash_path = EvnCheck(); if (string.IsNullOrEmpty(bash_path)) { ShowDialog("找不到 Bash 执行文件", $"Bash文件不存在: {bash_path}!"); return; } if (!File.Exists(appPath)) { ShowDialog("找不到包体文件", $"包体文件不存在: {appPath}!"); return; } PgyerAPI.PublishToPgyer(appPath, apiKey, bash_path, OnResponse); } /// /// 选择文件及发布版本 /// /// /// public static void SelectAndPublish(string extension = "apk", string apiKey = "") { string file = EditorUtility.OpenFilePanel("选择包体", $"~/Downloads", extension); Publish(file, apiKey); } /// /// Show system dialogs /// /// /// /// /// /// /// private static void ShowDialog(string title, string body, Action callback = null, Action cancelAction = null, string okName= "OK", string cancelName = "") { if (EditorUtility.DisplayDialog(title, body, okName, cancelName)) { callback?.Invoke(); } else { cancelAction?.Invoke(); } } /// /// On pgyer response callback /// /// private static void OnResponse(string log) { var logPath = $"{PgyerAPI.WorkingDir}/log.txt"; File.WriteAllText(logPath, log); bool success = log.Contains(ResponseObject.HeadTag); if (success) { var json = log.Substring(log.IndexOf(ResponseObject.HeadTag, StringComparison.Ordinal)); var res = ResponseObject.Parse(json); if (res != null) { var url = PgyerAPI.GetDownloadUrl(res.ShortUrl()); ShowDialog($"==== 上传成功 ({PgyerAPI.Version}) ====", $"包体 {res.BuildVersion()} ({res.BuildVersionCode()}) 上传成功!\n下载链接:{url}", () => { Debug.Log($"INSTALL URL:{url}"); // output url Application.OpenURL(url); }); return; } } ShowDialog($"==== 上传失败 ({PgyerAPI.Version}) ====", $"上传文件失败, 查看详细日志: \n{logPath}", () => { #if UNITY_EDITOR_OSX EditorUtility.RevealInFinder(PgyerAPI.WorkingDir); return; #endif Application.OpenURL(PgyerAPI.WorkingDir); }); } } [Serializable] internal class ResponseObject { public int code; public string message; public PublishData data; public static readonly string HeadTag = "{\"code\":"; public string BuildVersion() => data?.buildVersion ?? "0.0.0"; public string BuildVersionCode() => data?.buildVersionNo ?? "0"; public string ShortUrl() => data?.buildShortcutUrl ?? "#"; public static bool IsValid(string json) { return json.Contains(HeadTag); } public static ResponseObject Parse(string json) { if (string.IsNullOrEmpty(json)) return null; if (!IsValid(json)) return null; return JsonUtility.FromJson(json); } } [Serializable] internal class PublishData { public string buildIdentifier; public string buildQRCodeURL; public string buildShortcutUrl; public string buildName; public string buildVersion; public string buildVersionNo; public string buildUpdated; } }