namespace Guru.Editor { using System.Linq; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO; using NUnit.Framework; /// /// 构建工具 /// public partial class AppBuilder { public static int AndroidTargetSdkVersion = 33; public static string IOSTargetOSVersion = "13.0"; public static string GuruIOSTeamId = "39253T242A"; public static string GuruKeystoreName => "guru_key.jks"; public static string GuruKeystorePass => "guru0622"; public static string GuruAliasName => "guru"; public static string GuruAliasPass => "guru0622"; public static string GuruKeystorePath => Application.dataPath + $"/Plugins/Android/{GuruKeystoreName}"; public static string ProguardName => "proguard-user.txt"; public static string ProguardPath => Application.dataPath + $"/Plugins/Android/{ProguardName}"; public static string OutputDirName => "BuildOutput"; #region 构建接口 /// /// 直接调用 Build 接口 /// /// /// public static string Build(AppBuildParam buildParam) { string outputPath = string.Empty; switch (buildParam.TargetName) { case AppBuildParam.TargetNameAndroid: SwitchBuildPlatform(BuildTarget.Android); outputPath = BuildAndroid(buildParam); break; case AppBuildParam.TargetNameIOS: SwitchBuildPlatform(BuildTarget.iOS); outputPath = BuildIOS(buildParam); break; default: Debug.Log($" Unsupported build target: {buildParam.TargetName}. Skip build..."); break; } return outputPath; } #endregion #region 构建 Android 接口 /// /// 构建 Android 包体 /// /// /// public static string BuildAndroid(AppBuildParam buildParam) { // 切换平台 SwitchBuildPlatform(BuildTarget.Android); // 打包通用设置 ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.Android); var isDebug = !buildParam.IsBuildRelease; var useMinify = buildParam.AndroidUseMinify; var buildNumber= GetBuildNumberString(BuildTarget.Android); if(buildParam.AutoSetBuildNumber) buildNumber = ChangeBuildNumber(BuildTarget.Android); // 保存版本信息 SaveBuildVersion(buildParam.BuildVersion, buildNumber); //android专用打包设置 EditorUserBuildSettings.buildAppBundle = buildParam.IsBuildAAB; EditorUserBuildSettings.development = isDebug; #if UNITY_2020_3 EditorUserBuildSettings.androidCreateSymbolsZip = buildParam.IsBuildSymbols; #elif UNITY_2021_3 EditorUserBuildSettings.androidCreateSymbols = buildParam.IsBuildSymbols? AndroidCreateSymbols.Public : AndroidCreateSymbols.Disabled; //Android 输出SymbolsZip的选项 #endif PlayerSettings.muteOtherAudioSources = false; // ---- 开启 Minify 后需要配置 proguard-user.txt 文件 ---- if (useMinify) DeployProguardTxt(); PlayerSettings.Android.minifyWithR8 = useMinify; PlayerSettings.Android.minifyRelease = useMinify; PlayerSettings.Android.minifyDebug = useMinify; // ---- 部署 Guru 专用的 Keystore ---- if (buildParam.UseGuruCerts && DeployAndroidKeystore()) { // ---- 使用 Guru 专用的 KeyStore ---- PlayerSettings.Android.useCustomKeystore = true; PlayerSettings.Android.keystoreName = GuruKeystorePath; PlayerSettings.Android.keystorePass = GuruKeystorePass; PlayerSettings.Android.keyaliasName = GuruAliasName; PlayerSettings.Android.keyaliasPass = GuruAliasPass; } else if(!string.IsNullOrEmpty(buildParam.AndroidKeystorePath)) { // ---- 使用 Custom 的 KeyStore ---- PlayerSettings.Android.useCustomKeystore = true; PlayerSettings.Android.keystoreName = buildParam.AndroidKeystorePath; PlayerSettings.Android.keystorePass = buildParam.AndroidKeystorePass; PlayerSettings.Android.keyaliasName = buildParam.AndroidAlias; PlayerSettings.Android.keyaliasPass = buildParam.AndroidAliasPass; } PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64; //只构建 armv7 和 arm64 PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel22; if (buildParam.AndroidTargetVersion > 0) AndroidTargetSdkVersion = buildParam.AndroidTargetVersion; PlayerSettings.Android.targetSdkVersion = (AndroidSdkVersions)AndroidTargetSdkVersion; // 默认设置API为33 //打包 string symbolDefine = buildParam.IsBuildRelease ? GameDefine.MACRO_RELEASE : GameDefine.MACRO_DEBUG; string apkPath = string.Empty; string version = Application.version; string extension = buildParam.IsBuildAAB ? ".aab" : ".apk"; if (EditorUserBuildSettings.exportAsGoogleAndroidProject) extension = ""; // 输出工程 string outputDir = Path.GetFullPath($"{Application.dataPath }/../{OutputDirName}/Android"); apkPath = $"{outputDir}/{Application.productName.Replace(" ","_")}_{symbolDefine}_{version}_{buildNumber}{extension}"; if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir); BuildOptions opts = isDebug ? BuildOptions.Development : BuildOptions.None; BuildPipeline.BuildPlayer(GetBuildScenes(), apkPath, BuildTarget.Android, opts); if (buildParam.BuilderType == AppBuilderType.Editor) { Open(outputDir); } if (buildParam.AutoPublish) { GuruPublishHelper.Publish(apkPath, buildParam.PgyerAPIKey); // 直接发布版本 } return apkPath; } /// /// 部署 Guru 专用的 Keystore /// private static bool DeployAndroidKeystore() { var dir = GetWorkingDir(); var from = $"{dir}/{GuruKeystoreName}"; var to = GuruKeystorePath; if (File.Exists(to)) return true; if (File.Exists(from)) { File.Copy(from, to); return true; } return false; } /// /// 部署混淆用配置 /// private static bool DeployProguardTxt() { var dir = GetWorkingDir(); var from = $"{dir}/{ProguardName}"; var to = ProguardPath; if (File.Exists(to)) return true; if (File.Exists(from)) { File.Copy(from, to); return true; } return false; } #endregion #region 构建 IOS 接口 public static string BuildIOS(AppBuildParam buildParam) { //切换平台 SwitchBuildPlatform(BuildTarget.iOS); //打包通用设置 ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.iOS); //修改打包版本号 var buildNumber= GetBuildNumberString(BuildTarget.Android); if(buildParam.AutoSetBuildNumber) buildNumber = ChangeBuildNumber(BuildTarget.iOS); // 保存版本信息 SaveBuildVersion(buildParam.BuildVersion, buildNumber); var isDebug = !buildParam.IsBuildRelease; //ios专用打包设置 PlayerSettings.muteOtherAudioSources = false; PlayerSettings.iOS.appInBackgroundBehavior = iOSAppInBackgroundBehavior.Custom; PlayerSettings.iOS.backgroundModes = iOSBackgroundMode.RemoteNotification | iOSBackgroundMode.Fetch; // 后台启动配置 PlayerSettings.iOS.targetDevice = iOSTargetDevice.iPhoneAndiPad; PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK; var targetVersion = IOSTargetOSVersion; if (!string.IsNullOrEmpty(buildParam.IOSTargetVersion)) targetVersion = buildParam.IOSTargetVersion; PlayerSettings.iOS.targetOSVersionString = targetVersion; var teamId = buildParam.IOSTeamId; if (buildParam.UseGuruCerts) teamId = GuruIOSTeamId; if (!string.IsNullOrEmpty(teamId)) { PlayerSettings.iOS.appleEnableAutomaticSigning = true; PlayerSettings.iOS.appleDeveloperTeamID = teamId; } //打包 string outputDir = Path.GetFullPath($"{Application.dataPath }/../{OutputDirName}/Xcode"); if (Directory.Exists(outputDir)) { Directory.Delete(outputDir, true); } // 构建后打开路径 try { BuildOptions opts = isDebug ? BuildOptions.Development : BuildOptions.None; BuildPipeline.BuildPlayer(GetBuildScenes(), outputDir, BuildTarget.iOS, BuildOptions.None); if (buildParam.BuilderType == AppBuilderType.Editor) { Open(outputDir); } } catch (Exception e) { Debug.LogError(e.Message); } return outputDir; } #endregion #region 通用接口 /// /// 获取工作目录 /// /// private static string GetWorkingDir() { var guids = AssetDatabase.FindAssets($"{nameof(AppBuilder)} t:Script"); if (guids.Length > 0) { var path = ""; foreach (var guid in guids) { path = AssetDatabase.GUIDToAssetPath(guids[0]); if (path.Contains($"Editor/BuildTool/{nameof(AppBuilder)}")) { return Directory.GetParent(path)!.FullName; } } } return Path.GetFullPath("Packages/com.guru.unity.sdk.core/Editor/BuildTool/"); } /// /// 平台切换 /// /// private static void SwitchBuildPlatform(BuildTarget targetPlatform) { if (EditorUserBuildSettings.activeBuildTarget != targetPlatform) { EditorUserBuildSettings.SwitchActiveBuildTarget(BuildPipeline.GetBuildTargetGroup(targetPlatform), targetPlatform); AssetDatabase.Refresh(); } } private static void ChangeBuildPlayerCommonSetting(AppBuildParam buildParam, BuildTargetGroup buildTargetGroup) { EditorUserBuildSettings.development = !buildParam.IsBuildRelease; EditorUserBuildSettings.allowDebugging = false; EditorUserBuildSettings.connectProfiler = false; EditorUserBuildSettings.buildScriptsOnly = false; var backend = ScriptingImplementation.IL2CPP; if (buildTargetGroup == BuildTargetGroup.Android && !buildParam.IsBuildRelease && buildParam.DebugWithMono) { backend = ScriptingImplementation.Mono2x; } PlayerSettings.SetScriptingBackend(buildTargetGroup, backend); var companyName = buildParam.CompanyName; if(string.IsNullOrEmpty(companyName)) companyName = GameDefine.CompanyName; PlayerSettings.companyName = companyName; var bundleVersion = buildParam.BuildVersion; if(!string.IsNullOrEmpty(bundleVersion)) PlayerSettings.bundleVersion = bundleVersion; // -------- Defines -------- List defines = new List(); var str = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); if (!string.IsNullOrEmpty(str)) { defines = str.Split(';').ToList(); } if (defines.Count > 0) { defines.Remove(GameDefine.MACRO_RELEASE); defines.Remove(GameDefine.MACRO_DEBUG); } defines.Add(buildParam.IsBuildRelease ? GameDefine.MACRO_RELEASE : GameDefine.MACRO_DEBUG); if (!buildParam.IsBuildRelease || buildParam.IsBuildShowLog) { defines.Add(GameDefine.MACRO_LOG); } // defines.Add("mopub_manager"); PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines.ToArray()); PlayerSettings.stripEngineCode = true; PlayerSettings.SetManagedStrippingLevel(buildTargetGroup, ManagedStrippingLevel.Low); PlayerSettings.SetApiCompatibilityLevel(buildTargetGroup, ApiCompatibilityLevel.NET_4_6); } /// /// 修改打包版本号 /// /// /// private static string ChangeBuildNumber(BuildTarget buildTarget) { var nowDate = DateTime.Now; string strBuildNumber = $"{nowDate.Year - 2000}{nowDate.Month:00}{nowDate.Day:00}{(nowDate.Hour * 60 + nowDate.Minute) / 15}"; int buildNumber = int.Parse(strBuildNumber); if (buildTarget == BuildTarget.iOS) { PlayerSettings.iOS.buildNumber = buildNumber.ToString(); } else if (buildTarget == BuildTarget.Android) { PlayerSettings.Android.bundleVersionCode = buildNumber; } return strBuildNumber; } /// /// 获取构建数变量 /// /// private static string GetBuildNumberString(BuildTarget buildTarget) { if (buildTarget == BuildTarget.iOS) { return PlayerSettings.iOS.buildNumber; } if (buildTarget == BuildTarget.Android) { return PlayerSettings.Android.bundleVersionCode.ToString(); } return ""; } private static void SaveBuildVersion(string version, string code) { GuruAppVersion.SaveToDisk(version, code); } /// /// 获取打包场景 /// /// public static string[] GetBuildScenes() { List names = new List(); foreach (var e in EditorBuildSettings.scenes) { if(e == null) continue; if(e.enabled) names.Add(e.path); } return names.ToArray(); } /// /// 打开路径 /// /// public static void Open(string path) { #if UNITY_EDITOR_OSX EditorUtility.RevealInFinder(path); #else Application.OpenURL($"file://{path}"); #endif } #endregion #region 单元测试 [Test] public static void TEST_GetWorkingDir() { var path = GetWorkingDir(); Debug.Log(path); if (Directory.Exists(path)) { Open(path); } else { Debug.LogError($"path not found: {path}"); } } #endregion } }