461 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
| 
 | |
| 
 | |
| 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;
 | |
| 
 | |
| 	/// <summary>
 | |
| 	/// 构建工具
 | |
| 	/// </summary>
 | |
|     public partial class AppBuilder
 | |
|     {
 | |
| 	    private const int DefaultAndroidTargetSdkVersion = 34;
 | |
| 	    private const string IOSTargetOSVersion = "13.0";
 | |
| 	    private const string GuruIOSTeamId = "39253T242A";
 | |
| 	    private const string GuruKeystoreName = "guru_key.jks";
 | |
| 	    private const string GuruKeystorePass = "guru0622";
 | |
| 	    private const string GuruAliasName = "guru";
 | |
| 	    private const string GuruAliasPass = "guru0622";
 | |
| 
 | |
| 	    private const string UnityGradlePath_2021_3_41 =
 | |
| 		    "/Applications/Unity/Hub/Editor/2021.3.41f1/PlaybackEngines/AndroidPlayer/Tools/gradle";
 | |
| 	    
 | |
| 	    private static string GuruKeystorePath => Application.dataPath + $"/Plugins/Android/{GuruKeystoreName}";
 | |
| 	    private static string ProguardName => "proguard-user.txt";
 | |
| 	    private static string ProguardPath => Application.dataPath + $"/Plugins/Android/{ProguardName}";
 | |
| 	    private static string OutputDirName => "BuildOutput";
 | |
| 
 | |
| 	    #region 构建接口
 | |
| 
 | |
| 	    /// <summary>
 | |
| 	    /// 直接调用 Build 接口
 | |
| 	    /// </summary>
 | |
| 	    /// <param name="buildParam"></param>
 | |
| 	    /// <returns></returns>
 | |
| 	    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($"<color=red> Unsupported build target: {buildParam.TargetName}. Skip build...</color>");
 | |
| 				    break;
 | |
| 		    }
 | |
| 
 | |
| 		    return outputPath;
 | |
| 	    }
 | |
| 
 | |
| 
 | |
| 	    #endregion
 | |
| 	    
 | |
|         #region 构建 Android 接口
 | |
| 
 | |
|         /// <summary>
 | |
|         /// 构建 Android 包体
 | |
|         /// </summary>
 | |
|         /// <param name="buildParam"></param>
 | |
|         /// <returns></returns>
 | |
|         public static string BuildAndroid(AppBuildParam buildParam)
 | |
| 	    {
 | |
| 		    // 切换平台
 | |
| 		    SwitchBuildPlatform(BuildTarget.Android);
 | |
| 		    // 打包通用设置
 | |
| 		    ChangeBuildPlayerCommonSetting(buildParam, BuildTargetGroup.Android);
 | |
| 		    // Set GradlePath
 | |
| 		    SetGradlePath();
 | |
| 		    
 | |
| 		    var isDebug = !buildParam.IsBuildRelease;
 | |
| 		    var useMinify = buildParam.AndroidUseMinify;
 | |
| 	        var buildNumber= GetBuildNumberString(BuildTarget.Android);
 | |
| 	        var androidTargetVersion = buildParam.AndroidTargetVersion == 0 ? DefaultAndroidTargetSdkVersion : buildParam.AndroidTargetVersion;
 | |
| 	        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.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;
 | |
| 	        PlayerSettings.Android.targetSdkVersion = (AndroidSdkVersions)androidTargetVersion;  // 设置 API Version
 | |
| 	        
 | |
| 	        //打包
 | |
| 	        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;
 | |
| 	    }
 | |
| 
 | |
| 	    private static void SetGradlePath()
 | |
| 	    {
 | |
| #if UNITY_ANDROID && UNITY_2021_3_41
 | |
| 		    if (Directory.Exists(UnityGradlePath_2021_3_41))
 | |
| 		    {
 | |
| 			    UnityEditor.Android.AndroidExternalToolsSettings.gradlePath = UnityGradlePath_2021_3_41;
 | |
| 		    }
 | |
| #endif
 | |
| 	    }
 | |
| 
 | |
| 
 | |
| 	    /// <summary>
 | |
| 		/// 部署 Guru 专用的 Keystore
 | |
| 		/// </summary> 
 | |
|         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;
 | |
|         }
 | |
| 
 | |
| 		/// <summary>
 | |
| 		/// 部署混淆用配置
 | |
| 		/// </summary> 
 | |
|         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 通用接口
 | |
| 
 | |
| 		/// <summary>
 | |
| 		/// 获取工作目录
 | |
| 		/// </summary>
 | |
| 		/// <returns></returns>
 | |
|         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/");
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
| 	    /// 平台切换
 | |
| 	    /// </summary>
 | |
| 	    /// <param name="targetPlatform"></param>
 | |
| 	    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<string> defines = new List<string>();
 | |
| 		    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);
 | |
| 	    }
 | |
| 	    
 | |
| 	    /// <summary>
 | |
| 	    /// 修改打包版本号
 | |
| 	    /// </summary>
 | |
| 	    /// <param name="buildTarget"></param>
 | |
| 	    /// <param name="isRelease"></param>
 | |
| 	    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;
 | |
| 	    }
 | |
| 		
 | |
| 	    /// <summary>
 | |
| 	    /// 获取构建数变量
 | |
| 	    /// </summary>
 | |
| 	    /// <returns></returns>
 | |
| 	    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);
 | |
| 	    }
 | |
| 
 | |
| 	    /// <summary>
 | |
| 	    /// 获取打包场景
 | |
| 	    /// </summary>
 | |
| 	    /// <returns></returns>
 | |
| 	    public static string[] GetBuildScenes()
 | |
| 	    {
 | |
| 		    List<string> names = new List<string>();
 | |
| 		    foreach (var e in EditorBuildSettings.scenes)
 | |
| 		    {
 | |
| 			    if(e == null)
 | |
| 				    continue;
 | |
| 			    if(e.enabled)
 | |
| 				    names.Add(e.path);
 | |
| 		    }
 | |
| 		    return names.ToArray();
 | |
| 	    }
 | |
| 
 | |
| 		/// <summary>
 | |
| 		/// 打开路径
 | |
| 		/// </summary>
 | |
| 		/// <param name="path"></param>
 | |
| 	    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
 | |
| 	    
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 |