using AutoBuild.Helper; using Cysharp.Threading.Tasks; using HybridCLR.Editor; using HybridCLR.Editor.Commands; using HybridCLR.Editor.HotUpdate; using PhxhSDK.AOT.VersionUpdate; using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using BuildTarget = UnityEditor.BuildTarget; using Debug = UnityEngine.Debug; using Directory = System.IO.Directory; using YamlDotNet.Serialization; /// /// Jenkins打包 /// internal class BatchBuild { /// /// 批处理更新设置 /// jenkins打包新包时使用该方法 /// public static void ApplyPlayerSetting() { YooAssetBuildBase.Init(); } public static string _oldDefines; /// /// 只在DEBUG下有 /// private const string DEBUG = "DEBUG"; /// /// dev上传地址 /// private const string Dev_PostUrl = "http://dev_openapi.kedrgame.com/api/v1/"; /// /// Pre上传地址 /// private const string Pre_PostUrl = "http://pre_openapi.kedrgame.com/api/v1/"; /// /// Release上传地址 /// private const string Release_PostUrl = "http://openapi.kedrgame.com/api/v1/"; public static void _RevertScriptDefines() { var currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); PlayerSettings.SetScriptingDefineSymbolsForGroup(currentTargetGroup, _oldDefines); } /// /// 批处理构建Resources和Player /// jenkins打包新包时使用该方法 /// public static void BuildContentAndPlayer() { try { InitChannelConfigFromYaml(); bool enableHotUpdate = YooAssetBuildBase.Init(); string bundleVersionCode = GetBundleVersionCode(); // 写入版本信息到Resources/LocalInfo.json UpdateLocalInfo info = new() { workSpace = YooAssetBuildBase.workSpace, accessKeyId = YooAssetBuildBase.appConfigAccessKeyId, accessKeySecret = YooAssetBuildBase.appConfigAccessKeySecret, bucketName = YooAssetBuildBase.appConfigBucketName, configOriginalUrl = YooAssetBuildBase.appConfigURL, //httpUrl = YooAssetBuildBase.httpURL, httpAccessKey = YooAssetBuildBase.httpAccessKey, httpKey = YooAssetBuildBase.httpKey, version = YooAssetBuildBase.appVersion, isDev = YooAssetBuildBase.isDev, buildTicks = DateTime.Now.Ticks, buildCode = bundleVersionCode, }; PackDataInst.CreateResourceJson(info); BuildTargetGroup currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentTargetGroup); // 写入打包信息到Yoo_Build_Cache #if DEVELOPMENT_BUILD || DEBUG ExtBuildInfoHelper.CreateFirstExtBuildInfo(info.buildTicks, info.workSpace, info.version, info.buildCode, true, defines); #else ExtBuildInfoHelper.CreateFirstExtBuildInfo(info.buildTicks, info.workSpace, info.version, false, defines); #endif BuildLauncher.SetEnableHotUpdate(enableHotUpdate); if (enableHotUpdate) BuildLauncher.BuildHotUpdateDlls(); YooAssetBuildBase.BuildContentAndPlayer(); // 写入打包信息到Yoo_Build_Cache ExtBuildInfoHelper.CreateVertifyBuildInfo(info.workSpace, info.version); // 还原宏 _RevertScriptDefines(); #region 打整包时缓存当前版本的AotDll string aotDllStorePath = GetAotDllStorePath(info.workSpace, info.version); string aotDllPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget); DirectoryInfo dir = new(aotDllPath); if (Directory.Exists(aotDllStorePath)) Directory.Delete(aotDllStorePath, true); Directory.CreateDirectory(aotDllStorePath); FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) { file.CopyTo(Path.Combine(aotDllStorePath, file.Name), false); } #endregion PostLoginKey(info.workSpace, info.buildTicks, info.version); // 上传Aot loginKey } catch (Exception e) { //Build Exception用于在日志文件中定位此次打包的异常 Debug.LogError($"[Build Exception]:{e.Message} \r\n {e.StackTrace}"); throw; } } /// /// 批处理增量更新资源 /// jenkins打包增量更新时使用该方法 /// public static void UpdatePreviousBuild() { try { InitChannelConfigFromYaml(); var enableHotUpdate = YooAssetBuildBase.Init(); ExtBuildInfoHelper.VertifyExitBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion); if (enableHotUpdate) { Debug.Log("开始检查AOT版本"); if (CheckAotOutDate(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion)) { Debug.Log("AOT版本检查通过"); BuildLauncher.BuildHotUpdateDlls(); } else { Debug.LogError("AOT版本不匹配,请重新打AOT包"); } } else { Debug.Log("未开启热更"); } BuildTargetGroup currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentTargetGroup); // 写入打包信息到Yoo_Build_Cache #if DEVELOPMENT_BUILD || DEBUG ExtBuildInfoHelper.UpdateExtBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion, true, defines); #else ExtBuildInfoHelper.UpdateExtBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion, false, defines); #endif YooAssetBuildBase.BuildOnlyResUpdate(); ExtBuildInfoHelper.CreateVertifyBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion); _RevertScriptDefines(); } catch (Exception e) { //Build Exception用于在日志文件中定位此次打包的异常 Debug.LogError($"[Build Exception]:{e.Message} \r\n {e.StackTrace}"); throw; } } /// /// jenkins检查AOT裁剪代码 /// /// /// public static bool CheckAotOutDate(string workSpace, string version) { Debug.Log($"开始检查aot版本:{version}"); string stripedDllPath = GetAotDllStorePath(workSpace, version); // 先编译dll CompileDllCommand.CompileDllActiveBuildTarget(); // 再检查编译出来的dll是否满足以前的Aotdll MissingMetadataChecker checker = new(stripedDllPath, new List()); string hotUpdateDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(EditorUserBuildSettings.activeBuildTarget); foreach (string dll in SettingsUtil.HotUpdateAssemblyFilesExcludePreserved) { string dllPath = $"{hotUpdateDir}/{dll}"; bool notAnyMissing = checker.Check(dllPath); if (!notAnyMissing) throw new Exception("AOT缺失Dll,请重新打AOT包"); } return true; } /// /// 获取缓存的AOT Dll路径 /// /// public static string GetAotDllStorePath(string workSpace, string version) { return Application.dataPath + $"/../Yoo_Build_Cache/{workSpace}/{version}/CacheAOTDll"; } public static string GetBundleVersionCode() { #if UNITY_IOS return PlayerSettings.iOS.buildNumber; #elif UNITY_ANDROID return PlayerSettings.Android.bundleVersionCode.ToString(); #else return ""; #endif } /// /// 上传登录key /// public static void PostLoginKey(string workSpace, long buildTicks, string version) { string key = $"{workSpace}_{version}_{buildTicks}"; string loginKey = PhxhSDK.Utils.LoginKeyMD5(key); string platform = string.Empty; // 0-安卓,1-IOS #if UNITY_ANDROID platform = "0"; #elif UNITY_IOS platform = "1"; #endif Dictionary headData = PostHeader.PostHeaderInfo(new Dictionary { { "plaintext", key }, { "tag", loginKey }, { "platform", platform }, { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() } }); LoginKeyData loginKeyData = new() { plaintext = key, tag = loginKey, platform = platform, }; try { if (PackDataInst.Inst.localInfo.isDev) { HttpHelper.PostAsync($"{Dev_PostUrl}version", loginKeyData, headData).Forget(); } else { HttpHelper.PostAsync($"{Pre_PostUrl}version", loginKeyData, headData).Forget(); HttpHelper.PostAsync($"{Release_PostUrl}version", loginKeyData, headData).Forget(); } } catch (Exception e) { Debug.LogError($"[PostLoginKey Exception]:{e.Message} \r\n {e.StackTrace}"); throw; } } /// /// 自动发布debug loginKey /// public static void PostDebugSendKey() { string platform = string.Empty; // 0-安卓,1-IOS #if UNITY_ANDROID platform = "0"; #elif UNITY_IOS platform = "1"; #endif Dictionary dicHeadData = PostHeader.PostHeaderInfo(new Dictionary { { "platform", platform }, { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() } }); SendLoginKeyData sendLoginKeyData = new() { platform = platform, }; HttpHelper.PostAsync($"{Dev_PostUrl}sendVersion", sendLoginKeyData, dicHeadData).Forget(); } private class LoginKeyData { public string plaintext; public string tag; public string platform; } private class SendLoginKeyData { public string platform; } private class LoginKeyResData { public int code; public string message; public object data; } private class ChannelConfig { public List Define_list { get; set; } public string PackageName { get; set; } public int BuildCode { get; set; } public string ConfigAccessKeyId { get; set; } public string ConfigAccessKeySecret { get; set; } public string ConfigBucketName { get; set; } public string ConfigURL { get; set; } public string AppAccessKey { get; set; } public string AppKey { get; set; } public CrashSightConfig CrashSight_Android { get; set; } public CrashSightConfig CrashSight_iOS { get; set; } } private class CrashSightConfig { [YamlMember(Alias = "id")] public string Id { get; set; } [YamlMember(Alias = "key")] public string Key { get; set; } } private const string ChannelSettingYaml = "/../ChannelSetting.yaml"; /// /// 从YAML文件初始化渠道配置 /// private static void InitChannelConfigFromYaml() { var hasYooAsset = false; List appendDefineList = new(); // 判断是否为开发模式 if (EditorUserBuildSettings.development) { appendDefineList.Add(DEBUG); Debug.Log("追加宏定义:" + DEBUG); } string yamlPath = Application.dataPath + ChannelSettingYaml; if (File.Exists(yamlPath)) { try { // 忽略 YAML 中多余的字段 var deserializer = new DeserializerBuilder() .IgnoreUnmatchedProperties() .Build(); string yamlContent = File.ReadAllText(yamlPath); var config = deserializer.Deserialize(yamlContent); // 处理宏定义 if (config.Define_list != null) { foreach (string define in config.Define_list) { string cleanDefine = define.Trim(); if (!string.IsNullOrEmpty(cleanDefine)) { appendDefineList.Add(cleanDefine); Debug.Log("追加宏定义:" + cleanDefine); } } } // 处理包名 if (!string.IsNullOrEmpty(config.PackageName)) { Debug.Log("设置新包名:" + config.PackageName); if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { PlayerSettings.applicationIdentifier = config.PackageName; } } // 处理BuildCode if (config.BuildCode != 0) { Debug.Log("设置BuildCode:" + config.BuildCode); #if UNITY_IOS PlayerSettings.iOS.buildNumber = config.BuildCode.ToString(); #elif UNITY_ANDROID PlayerSettings.Android.bundleVersionCode = config.BuildCode; #endif } // 处理AppConfig上传id if (config.ConfigAccessKeyId != null) { YooAssetBuildBase.appConfigAccessKeyId = config.ConfigAccessKeyId; Debug.Log($"设置AppConfig上传id:{YooAssetBuildBase.appConfigAccessKeyId}"); } // 处理AppConfig上传key if (config.ConfigAccessKeySecret != null) { YooAssetBuildBase.appConfigAccessKeySecret = config.ConfigAccessKeySecret; Debug.Log($"设置AppConfig上传key:{YooAssetBuildBase.appConfigAccessKeySecret}"); } // 处理AppConfig上传桶名 if (config.ConfigBucketName != null) { YooAssetBuildBase.appConfigBucketName = config.ConfigBucketName; Debug.Log($"设置AppConfig上传桶名:{YooAssetBuildBase.appConfigBucketName}"); } // 处理AppConfig上传url if (config.ConfigURL != null) { YooAssetBuildBase.appConfigURL = config.ConfigURL; Debug.Log($"设置AppConfig上传url:{YooAssetBuildBase.appConfigURL}"); } // 处理http请求AccessKey if (config.AppAccessKey != null) { YooAssetBuildBase.httpAccessKey = config.AppAccessKey; Debug.Log($"设置http请求id:{YooAssetBuildBase.httpAccessKey}"); } // 处理http请求AppKey if (config.AppKey != null) { YooAssetBuildBase.httpKey = config.AppKey; Debug.Log($"设置http请求id:{YooAssetBuildBase.httpKey}"); } // 处理CrashSight_Android配置 if (config.CrashSight_Android != null && EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { var crashSightData = new PhxhSDK.Data.CrashSightData { id = config.CrashSight_Android.Id, key = config.CrashSight_Android.Key }; var crashSightPath = "Assets/Resources/CrashSightData.json"; var json = JsonUtility.ToJson(crashSightData); File.WriteAllText(crashSightPath, json); Debug.Log("CrashSight配置已保存:" + crashSightData.id + "|" + crashSightData.key); } // 处理CrashSight_Android配置 if (config.CrashSight_iOS != null && EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { var crashSightData = new PhxhSDK.Data.CrashSightData { id = config.CrashSight_iOS.Id, key = config.CrashSight_iOS.Key }; var crashSightPath = "Assets/Resources/CrashSightData.json"; var json = JsonUtility.ToJson(crashSightData); File.WriteAllText(crashSightPath, json); Debug.Log("CrashSight配置已保存:" + crashSightData.id + "|" + crashSightData.key); } // 更新宏定义 var currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); var oldDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentTargetGroup); _oldDefines = oldDefines; if (appendDefineList.Count > 0) { var newDefineList = new List(); newDefineList.AddRange(oldDefines.Split(';')); for (var i = 0; i < newDefineList.Count; i++) { var define = newDefineList[i]; if (string.IsNullOrEmpty(define)) continue; if (define == "USE_YOO") { hasYooAsset = true; } if (appendDefineList.Contains(define)) appendDefineList.Remove(define); } newDefineList.AddRange(appendDefineList); PlayerSettings.SetScriptingDefineSymbolsForGroup(currentTargetGroup, string.Join(";", newDefineList)); } // 关闭Unity闪屏 PlayerSettings.SplashScreen.showUnityLogo = false; } catch (Exception e) { Debug.LogError($"解析YAML配置文件失败: {e.Message}\n{e.StackTrace}"); throw; } } else { Debug.LogError($"找不到渠道配置文件: {yamlPath}"); } } }