com.guru.unity.sdk.core/Editor/BuildTool/AppBuilder.cs

442 lines
14 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 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);
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;
}
/// <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
}
}