hellbound/Assets/Editor/Build.cs

456 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
using UnityEngine;
using game;
#if RND_FACEBOOK
using Facebook.Unity.Settings;
#endif
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using UnityEngine.iOS;
#endif
#if PURCHASING_IS_CONNECT
using UnityEngine.Purchasing;
using UnityEditor.Purchasing;
#endif
[InitializeOnLoad]
public static class BuildUtils
{
private static readonly string[] _primaryLevels = { "Assets/Scenes/loader.unity", "Assets/Scenes/game.unity" };
private const string BUILD_LOCATION_PATH = "LOCATION_PATH";
[MenuItem("Game/SetUpBuiltInLevels (Editor)", false, 20)]
private static void SetUpBuiltInLevels()
{
string[] levels = GetBuiltInLevels();
var scenes = new List<EditorBuildSettingsScene>(levels.Length);
foreach (string lvl in levels)
scenes.Add(new EditorBuildSettingsScene(lvl, true));
EditorBuildSettings.scenes = scenes.ToArray();
}
private static string[] GetBuiltInLevels()
{
const string dir = "Assets/Scenes/Levels";
if (Directory.Exists(dir) == false)
{
Log.Warn($"Levels directory not found from path {dir}");
return _primaryLevels;
}
string[] files = Directory.GetFiles(dir).Where(file => file.EndsWith(".unity")).ToArray();
Log.Info($"Founded game levels count: {files?.Length}");
files.ForEach(lvl => Log.Info($"Level add to built in {lvl}"));
var levels = new List<string>(_primaryLevels);
levels.AddRange(files);
return levels.ToArray();
}
[MenuItem("Game/BuildAndPlay (Editor)", false, 20)]
public static void BuildAndPlay()
{
if (Build())
PlayGameScene();
}
private static void Recompile()
{
var editorAssembly = Assembly.GetAssembly(typeof(Editor));
Type editorCompilationInterfaceType =
editorAssembly.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface");
MethodInfo dirtyAllScriptsMethod =
editorCompilationInterfaceType.GetMethod("DirtyAllScripts", BindingFlags.Static | BindingFlags.Public);
dirtyAllScriptsMethod.Invoke(editorCompilationInterfaceType, null);
}
public static void BuildAndPlayRecompile()
{
Recompile();
BuildAndPlay();
}
public static void RecompileAndPlay()
{
Recompile();
PlayGameScene();
}
private static void PlayGameScene()
{
OpenGameScene();
EditorApplication.isPlaying = true;
}
private static void OpenGameScene()
{
Scene scene = SceneManager.GetActiveScene();
if (scene.name != "game")
EditorSceneManager.OpenScene("Assets/Scenes/game.unity");
}
private static BuildOptions GetBuildOptions()
{
//var arr = Settings.BUILD_CLIENT_OPTS.Split(',');
const BuildOptions opts = new BuildOptions();
//foreach(var name in arr)
// opts |= name.ParseEnum<BuildOptions>();
return opts;
}
[MenuItem("Game/SetupCommonPlayerSettings (Editor)", false, 20)]
private static void SetupCommonPlayerSettings()
{
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android ,Settings.PACKAGE_ID_FULL);
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS ,Settings.PACKAGE_ID_FULL);
PlayerSettings.bundleVersion = Settings.VERSION;
PlayerSettings.productName = Settings.CLIENT_APP_NAME;
PlayerSettings.companyName = Settings.GAME_COMPANY_NAME;
PlayerSettings.stripUnusedMeshComponents = true;
#if RND_FACEBOOK
FacebookSettings.AppLabels = new List<string>(){Settings.CLIENT_APP_NAME};
FacebookSettings.AppIds = new List<string>(){Settings.FACEBOOK_APP_ID};
FacebookSettings.AndroidKeystorePath = "user-key-store.keystore";
#endif
#if PURCHASING_IS_CONNECT
PurchasingSettings.enabled = Settings.UNITY_CONNECT_PURCHASING;
#endif
UpdateIconsForBuildTarget(BuildTargetGroup.Android, Settings.CLIENT_APP_ICON);
UpdateIconsForBuildTarget(BuildTargetGroup.iOS, Settings.CLIENT_APP_ICON);
}
static void UpdateIconsForBuildTarget(BuildTargetGroup target, string icon)
{
Log.Info($"UpdateIconsForBuildTarget: {target} - {icon}");
int[] iconSizes = PlayerSettings.GetIconSizesForTargetGroup(target);
var icons = new Texture2D[iconSizes.Length];
for (var i = 0; i < iconSizes.Length; i++)
icons[i] = AssetDatabase.LoadAssetAtPath<Texture2D>(icon);
PlayerSettings.SetIconsForTargetGroup(target, icons);
}
private static void EnableIL2CPP(BuildTargetGroup targetGroup, bool flag)
{
ScriptingImplementation backend = flag ? ScriptingImplementation.IL2CPP : ScriptingImplementation.Mono2x;
PlayerSettings.SetScriptingBackend(targetGroup, backend);
}
//[MenuItem("Game/Build/Android APK + Run %R", false, 21)]
public static void BuildAPKAndRun()
{
BuildAPK(opts => opts.Add(BuildOptions.AutoRunPlayer));
}
[MenuItem("Game/Build/Android APK %D", false, 22)]
public static void BuildAPKOnly()
{
BuildAPK(opts => opts.Remove(BuildOptions.AutoRunPlayer));
}
private static void BuildAPK(BuildOptionsTweak tweak)
{
BuildAPK(PrepareBuild(tweak));
}
public static void ExportAndroidProject()
{
ExportAndroidProject(opts => opts.Remove(BuildOptions.AutoRunPlayer));
}
private static void ExportAndroidProject(BuildOptionsTweak tweak)
{
ExportAndroidProject(PrepareBuild(tweak));
}
[MenuItem("Game/Build/iOS Project (Clean)", false, 24)]
public static void BuildXCodeProjectClean()
{
BuildXCode(opts => opts
.Remove(BuildOptions.AutoRunPlayer)
.Remove(BuildOptions.AcceptExternalModificationsToPlayer)
);
}
private static void BuildXCode(BuildOptionsTweak tweak)
{
BuildXCode(PrepareBuild(tweak));
}
private static void BuildXCode(BuildOptions buildOpts)
{
PlayerSettings.iOS.allowHTTPDownload = true;
PlayerSettings.iOS.targetOSVersionString = "10.0";
PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK; //TODO: simulator build support?
#if UNITY_IOS
PlayerSettings.iOS.deferSystemGesturesMode = SystemGestureDeferMode.TopEdge | SystemGestureDeferMode.BottomEdge;
#endif
PlayerSettings.iOS.appleEnableAutomaticSigning = true;
const BuildTarget target = BuildTarget.iOS;
PlayerSettings.SetUseDefaultGraphicsAPIs(target, automatic: true);
PlayerSettings.iOS.appleDeveloperTeamID = Settings.IOS_TEAM_ID;
PlayerSettings.iOS.buildNumber = $"{PlayerSettings.bundleVersion}.{Settings.VERSION_REV_NUMBER}";
#if UNITY_IOS
//IOSCredentialsPostProcess.ReplaceFirebasePlistAccordingToBundleID(); //TODO: Delete?
#endif
EnableIL2CPP(BuildTargetGroup.iOS, true);
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH); //TODO: To method?
if (string.IsNullOrEmpty(locationPath))
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL"); //TODO: With this
BuildPipeline.BuildPlayer(GetBuiltInLevels(), locationPath, target, buildOpts);
}
private static BuildReport BuildStandalone(BuildOptionsTweak tweak)
{
return BuildStandalone(PrepareBuild(tweak));
}
private static void ExportAndroidProject(BuildOptions buildOpts)
{
var ver = (int)Settings.VERSION_CODE;
AndroidArchitecture arch = ParseArch();
int bundleCode = MakeAndroidBundleVersionCode();
Log.Info($"==== GAME VERSION CODE: {ver}, BUNDLE VERSION CODE: {bundleCode} ({arch}) ====");
SetupAndroidBuildSettings(bundleCode, arch);
EnableIL2CPP(BuildTargetGroup.Android, Settings.ANDROID_USE_IL2CPP);
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH);
if (string.IsNullOrEmpty(locationPath))
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL");
BuildPipeline.BuildPlayer(GetBuiltInLevels(), string.Concat(locationPath, ".proj"), BuildTarget.Android, buildOpts);
AndroidRepackNativeDebugSymbols();
}
private static void BuildAPK(BuildOptions buildOpts)
{
var ver = (int)Settings.VERSION_CODE;
AndroidArchitecture arch = ParseArch();
int bundleCode = MakeAndroidBundleVersionCode();
Log.Info($"==== GAME VERSION CODE: {ver}, BUNDLE VERSION CODE: {bundleCode} ({arch}) ====");
SetupAndroidBuildSettings(bundleCode, arch);
EnableIL2CPP(BuildTargetGroup.Android, Settings.ANDROID_USE_IL2CPP);
string locationPath = Environment.GetEnvironmentVariable(BUILD_LOCATION_PATH);
if (string.IsNullOrEmpty(locationPath))
throw new InvalidDataException($"Environment variable {BUILD_LOCATION_PATH} is NULL");
if ((buildOpts & BuildOptions.AcceptExternalModificationsToPlayer) != 0)
locationPath += ".proj";
//EditorUserBuildSettings.androidCreateSymbolsZip = false; //TODO: ???
BuildPipeline.BuildPlayer(GetBuiltInLevels(), locationPath, BuildTarget.Android, buildOpts);
AndroidRepackNativeDebugSymbols();
}
[MenuItem("Game/SetupAndroidResolverDependencies (Editor)", false, 20)]
private static void SetupAndroidResolverDependencies()
{
const string ardPath = "ProjectSettings/AndroidResolverDependencies.xml";
if (File.Exists(ardPath))
{
Log.Info($"Android Resolver Dependencies loading existing file {ardPath}");
XDocument ardDoc = XDocument.Load(ardPath);
Log.Info("ARD_doc " +
ardDoc
.Element("dependencies")
.Element("settings")
.Elements("setting")
.Single(e => e.Attribute("name").Value == "bundleId").Attribute("value").Value
);
XElement bundleId = ardDoc
.Element("dependencies")
.Element("settings")
.Elements("setting")
.Single(e => e.Attribute("name").Value == "bundleId");
Log.Info($"bundleId {bundleId.Attribute("value").Value}");
bundleId.Attribute("value").Value = Settings.PACKAGE_ID;
ardDoc.Save(ardPath);
}
else
{
Log.Info($"Android Resolver Dependencies not found {ardPath} file");
}
}
private static void SetupAndroidBuildSettings(int bundleCode, AndroidArchitecture arch)
{
PlayerSettings.Android.bundleVersionCode = bundleCode;
PlayerSettings.Android.targetArchitectures = arch;
PlayerSettings.Android.keyaliasName = "android-build";
PlayerSettings.Android.keyaliasPass = "WV7fx6pRykCk";
PlayerSettings.Android.keystorePass = "WV7fx6pRykCk";
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel23;
PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
EditorUserBuildSettings.buildAppBundle = true;
SetupAndroidResolverDependencies();
//TODO: ?
//EditorUserBuildSettings.androidReleaseMinification = AndroidMinification.Gradle;
//EditorUserBuildSettings.androidDebugMinification = AndroidMinification.Gradle;
//EditorUserBuildSettings.androidCreateSymbolsZip = Settings.ANDROID_NATIVE_DEBUG_SYMBOLS_ZIP &&
// Settings.ANDROID_USE_IL2CPP;
AndroidSelectTargetStore();
}
private static void AndroidSelectTargetStore()
{
#if PURCHASING_IS_CONNECT
switch(Settings.ANDROID_TARGET_STORE)
{
case EnumAndroidStore.AMAZON:
UnityPurchasingEditor.TargetAndroidStore(AppStore.AmazonAppStore);
break;
case EnumAndroidStore.GPLAY:
UnityPurchasingEditor.TargetAndroidStore(AppStore.GooglePlay);
break;
default:
throw new ArgumentNullException($"Unknown Android store flavor: {Settings.ANDROID_TARGET_STORE}");
}
#endif
}
private static string AndroidFlavorFilenameSuffix()
{
switch(Settings.ANDROID_TARGET_STORE)
{
case EnumAndroidStore.GPLAY:
return "google_play";
default:
throw new ArgumentNullException($"Unknown Android store flavor: {Settings.ANDROID_TARGET_STORE}");
}
}
private static void AndroidRepackNativeDebugSymbols()
{
if (!EditorUserBuildSettings.androidCreateSymbolsZip)
return;
string ndkPath = EditorPrefs.GetString("AndroidNdkRootR16b", "");
if (string.IsNullOrEmpty(ndkPath))
{
Log.Error("Android NDK path not specified in Unity Editor Preferences - can't repack Android debug symbols");
return;
}
var prefix = $"{Settings.GAME_PROJECT_NAME}_{AndroidFlavorFilenameSuffix()}";
//E.g.: game_google_play-24.0.0-v3917800.symbols.zip
var zipName =
$"{prefix}-{PlayerSettings.bundleVersion}-v{PlayerSettings.Android.bundleVersionCode}.symbols.zip";
var outName = $"{prefix}.symbols.zip";
//EditorDevUtils.ProcessGamectlTask($"android_native_debug_symbols_repack {ndk_path} {zip_name} {out_name}"); //TODO: mem
}
private static BuildReport BuildStandalone(BuildOptions buildOpts)
{
PlayerSettings.fullScreenMode = FullScreenMode.Windowed;
var binName = $"{Settings.GAME_PROJECT_NAME}";
return BuildPipeline.BuildPlayer(GetBuiltInLevels(), binName, BuildTarget.StandaloneOSX, buildOpts);
}
private static AndroidArchitecture ParseArch()
{
var architecture = AndroidArchitecture.None;
//TODO: mem
//string[] arch_strs = Settings.ANDROID_TARGET_ARCHITECTURES.Trim().Split(',');
//for(int i = 0; i < arch_strs.Length; i++)
// architecture |= arch_strs[i].ParseEnum<AndroidArchitecture>();
architecture |= AndroidArchitecture.ARM64;
architecture |= AndroidArchitecture.ARMv7;
return architecture;
}
private static int MakeAndroidBundleVersionCode()
{
return (int)(Settings.VERSION_REV_NUMBER * 100); //Last two digits reserved for possible suffixes
}
private static bool Build()
{
if (ShouldAskToSaveScene() && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
return false;
Log.Info("=== Building Game ===");
return Urun();
}
public static bool Urun()
{
return EditorDevUtils.ProcessGamectlTask("urun");
}
private static bool ShouldAskToSaveScene()
{
return !Application.isPlaying &&
SceneManager.GetActiveScene().name.IndexOf("game.unity", StringComparison.Ordinal) == -1;
}
private static BuildOptions Add(this BuildOptions opts, BuildOptions newOpt)
{
return opts |= newOpt;
}
private static BuildOptions Remove(this BuildOptions opts, BuildOptions uselessOpt)
{
return opts &= ~uselessOpt;
}
private static BuildOptions PrepareBuild(BuildOptionsTweak tweak)
{
Build();
SetupCommonPlayerSettings();
return tweak(GetBuildOptions());
}
private delegate BuildOptions BuildOptionsTweak(BuildOptions rawOpts);
}