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 Obfuz.Settings; using UnityEditor; using UnityEngine; using BuildTarget = UnityEditor.BuildTarget; using Debug = UnityEngine.Debug; using Directory = System.IO.Directory; using YamlDotNet.Serialization; using Obfuz4HybridCLR; /// /// 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 Oversea_Release_PostUrl = "http://intl_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, isOversea = YooAssetBuildBase.isOversea, buildTicks = DateTime.Now.Ticks, buildCode = bundleVersionCode, pathRoot = "Build", phxhBiAccessKeyId = "yd9kWI798IWyyq1t", phxhBiScretKey = "iQLHTbOqeXtZZpxOZve-PDxTK9DkHPSKRbpXb30eS3g=", }; PackDataInst.CreateResourceJson(info); BuildTargetGroup currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentTargetGroup); // 写入打包信息到Yoo_Build_Cache bool isDebugBuild = false; #if DEVELOPMENT_BUILD || DEBUG isDebugBuild = true; #endif ExtBuildInfoHelper.CreateFirstExtBuildInfo(info.buildTicks, info.workSpace, info.version, info.buildCode, isDebugBuild, defines); BuildLauncher.SetEnableHotUpdate(enableHotUpdate); if (enableHotUpdate) BuildLauncher.BuildHotUpdateDlls(); YooAssetBuildBase.BuildContentAndPlayer(); // 写入打包信息到Yoo_Build_Cache ExtBuildInfoHelper.CreateVertifyBuildInfo(info.workSpace, info.version); // 还原宏 _RevertScriptDefines(); // 缓存打包时的文件 _CacheBuildFiles(info); PostLoginKey(info.version); // 上传Aot loginKey } catch (Exception e) { //Build Exception用于在日志文件中定位此次打包的异常 Debug.LogError($"[Build Exception]:{e.Message} \r\n {e.StackTrace}"); throw; } } private static void _CacheAotDlls(UpdateLocalInfo info) { 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); } } private static void _CacheObfuzFiles(UpdateLocalInfo info) { // 备份 Assets/Obfuz/SymbolObfus 文件夹 // 备份 Library/Obfuz/{buildTarget} 文件夹 var obfuzSymbolPath = Application.dataPath + "/Obfuz/SymbolObfus"; var obfuzBuildPath = Application.dataPath + $"/../Library/Obfuz/{EditorUserBuildSettings.activeBuildTarget}"; var cacheObfuzPath = Application.dataPath + $"/../Yoo_Build_Cache/{info.workSpace}/{info.version}/CacheObfuz"; var cacheObfuzSymbolPath = cacheObfuzPath + "/SymbolObfus";; var cacheObfuzBuildPath = cacheObfuzPath + $"/{EditorUserBuildSettings.activeBuildTarget}"; if (Directory.Exists(cacheObfuzPath)) Directory.Delete(cacheObfuzPath, true); Directory.CreateDirectory(cacheObfuzPath); DirectoryCopyHelper.CopyDirectory(obfuzSymbolPath, cacheObfuzSymbolPath); DirectoryCopyHelper.CopyDirectory(obfuzBuildPath, cacheObfuzBuildPath); } private static void _CacheBuildFiles(UpdateLocalInfo info) { _CacheAotDlls(info); if (ObfuzSettings.Instance.buildPipelineSettings.enable) { _CacheObfuzFiles(info); } } /// /// 批处理增量更新资源 /// jenkins打包增量更新时使用该方法 /// public static void UpdatePreviousBuild() { try { DebugUtil.Log($"初始化渠道配置:{DateTime.Now}"); InitChannelConfigFromYaml(); DebugUtil.Log($"初始化渠道配置完成:{DateTime.Now}"); var enableHotUpdate = YooAssetBuildBase.Init(); DebugUtil.Log($"YooAssetBuildBase初始化设置完成:{DateTime.Now}"); ExtBuildInfoHelper.VertifyExitBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion); DebugUtil.Log($"验证ExBuildInfo完成:{DateTime.Now}"); if (enableHotUpdate) { Debug.Log("开始检查AOT版本"); DebugUtil.Log($"开始检查AOT Dll:{DateTime.Now}"); if (CheckAotOutDate(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion)) { Debug.Log("AOT版本检查通过"); DebugUtil.Log($"检查AOT Dll完成:{DateTime.Now}"); BuildLauncher.BuildHotUpdateDlls(); } else { DebugUtil.Log($"检查AOT Dll完成:{DateTime.Now}"); Debug.LogError("AOT版本不匹配,请重新打AOT包"); } } else { Debug.Log("未开启热更"); } BuildTargetGroup currentTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentTargetGroup); DebugUtil.Log($"开始打包热更资源:{DateTime.Now}"); bool result = YooAssetBuildBase.BuildOnlyResUpdate(); DebugUtil.Log($"打包热更资源完成:{DateTime.Now}"); _RevertScriptDefines(); if (result) { // 写入打包信息到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 ExtBuildInfoHelper.CreateVertifyBuildInfo(YooAssetBuildBase.workSpace, YooAssetBuildBase.appVersion); } else { Debug.LogError("打包资源失败"); throw new Exception("YooAsset打包资源失败"); } } 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(); PrebuildCommandExt.CompileAndObfuscateDll(); // 编译混淆dll // 再检查编译出来的dll是否满足以前的Aotdll MissingMetadataChecker checker = new(stripedDllPath, SettingsUtil.HotUpdateAssemblyNamesIncludePreserved); 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({dllPath}),请重新打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 version) { string loginKey = PhxhSDK.Utils.LoginKeyMD5(version); Dictionary headData = PostHeader.PostHeaderInfo(new Dictionary { { "plaintext", version }, { "tag", loginKey }, { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() } }); LoginKeyData loginKeyData = new() { plaintext = version, tag = loginKey, }; try { if (PackDataInst.Inst.localInfo.isDev) { if (PackDataInst.Inst.localInfo.isOversea) HttpHelper.PostAsync($"{Oversea_Release_PostUrl}version", loginKeyData, headData).Forget(); // 暂时海外dev也使用Release else HttpHelper.PostAsync($"{Dev_PostUrl}version", loginKeyData, headData).Forget(); } else { if (PackDataInst.Inst.localInfo.isOversea) HttpHelper.PostAsync($"{Oversea_Release_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() { Dictionary dicHeadData = PostHeader.PostHeaderInfo(new Dictionary { { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() } }); HttpHelper.PostAsync($"{Dev_PostUrl}sendVersion", null, dicHeadData).Forget(); } private class LoginKeyData { public string plaintext; public string tag; 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 bool IsOversea { 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; } public TalkingDataConfig TalkingData { get; set; } } private class CrashSightConfig { [YamlMember(Alias = "id")] public string Id { get; set; } [YamlMember(Alias = "key")] public string Key { get; set; } } private class TalkingDataConfig { [YamlMember(Alias = "appid")] public string appId { get; set; } [YamlMember(Alias = "channel")] public string channel { get; set; } } private const string ChannelSettingYaml = "/../ChannelSetting.yaml"; /// /// 从YAML文件初始化渠道配置 /// private static void InitChannelConfigFromYaml() { 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; } } YooAssetBuildBase.isOversea = config.IsOversea; // 处理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_iOS配置 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); } if (config.TalkingData != null) { var talkingDataInitTool = new TalkingDataInitTool { appID = config.TalkingData.appId, channel = config.TalkingData.channel }; var talkingDataPath = "Assets/Resources/TalkingDataInitConfig.json"; var json = JsonUtility.ToJson(talkingDataInitTool); File.WriteAllText(talkingDataPath, json); Debug.Log("TalkingData配置已保存:" + talkingDataInitTool.appID + "|" + talkingDataInitTool.channel); } // 更新宏定义 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 (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}"); } } }