using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; using UnityEditor.Build; using UnityEngine; using Application = UnityEngine.Application; using BuildResult = UnityEditor.Build.Reporting.BuildResult; // uncomment for addressables //using UnityEditor.AddressableAssets; //using UnityEditor.AddressableAssets.Settings; namespace FlutterUnityIntegration.Editor { public class Build : EditorWindow { private static readonly string ProjectPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); private static readonly string APKPath = Path.Combine(ProjectPath, "Builds/" + Application.productName + ".apk"); private static readonly string AndroidExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../android/unityLibrary")); private static readonly string WindowsExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../windows/unityLibrary/data")); private static readonly string IOSExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../ios/UnityLibrary")); private static readonly string WebExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../web/UnityLibrary")); private static readonly string IOSExportPluginPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../ios_xcode/UnityLibrary")); private bool _pluginMode = false; private static string _persistentKey = "flutter-unity-widget-pluginMode"; //#region GUI Member Methods [MenuItem("Flutter/Export Android (Debug) %&n", false, 101)] public static void DoBuildAndroidLibraryDebug() { DoBuildAndroid(Path.Combine(APKPath, "unityLibrary"), false, false); } [MenuItem("Flutter/Export Android (Release) %&m", false, 102)] public static void DoBuildAndroidLibraryRelease() { DoBuildAndroid(Path.Combine(APKPath, "unityLibrary"), false, true); } [MenuItem("Flutter/Export Android Plugin %&p", false, 103)] public static void DoBuildAndroidPlugin() { DoBuildAndroid(Path.Combine(APKPath, "unityLibrary"), true, true); } [MenuItem("Flutter/Export IOS (Debug) %&i", false, 201)] public static void DoBuildIOSDebug() { BuildIOS(IOSExportPath, false); } [MenuItem("Flutter/Export IOS (Release) %&i", false, 202)] public static void DoBuildIOSRelease() { BuildIOS(IOSExportPath, true); } [MenuItem("Flutter/Export IOS Plugin %&o", false, 203)] public static void DoBuildIOSPlugin() { BuildIOS(IOSExportPluginPath, true); // Automate so manual steps SetupIOSProjectForPlugin(); // Build Archive // BuildUnityFrameworkArchive(); } [MenuItem("Flutter/Export Web GL %&w", false, 301)] public static void DoBuildWebGL() { BuildWebGL(WebExportPath); } // Hide this button as windows isn't implemented in the Flutter plugin yet. // [MenuItem("Flutter/Export Windows %&d", false, 401)] public static void DoBuildWindowsOS() { BuildWindowsOS(WindowsExportPath); } [MenuItem("Flutter/Settings %&S", false, 501)] public static void PluginSettings() { EditorWindow.GetWindow(typeof(Build)); } private void OnGUI() { GUILayout.Label("Flutter Unity Widget Settings", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); _pluginMode = EditorGUILayout.Toggle("Plugin Mode", _pluginMode); if (EditorGUI.EndChangeCheck()) { EditorPrefs.SetBool(_persistentKey, _pluginMode); } } private void OnEnable() { _pluginMode = EditorPrefs.GetBool(_persistentKey, false); } //#endregion //#region Build Member Methods private static void BuildWindowsOS(String path) { // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); if (Directory.Exists(path)) Directory.Delete(path, true); if (Directory.Exists(WindowsExportPath)) Directory.Delete(WindowsExportPath, true); var playerOptions = new BuildPlayerOptions { scenes = GetEnabledScenes(), target = BuildTarget.StandaloneWindows64, locationPathName = path, options = BuildOptions.AllowDebugging }; // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64); // build addressable ExportAddressables(); var report = BuildPipeline.BuildPlayer(playerOptions); if (report.summary.result != BuildResult.Succeeded) throw new Exception("Build failed"); Debug.Log("-- Windows Build: SUCCESSFUL --"); } private static void BuildWebGL(String path) { // Check if the Unity project is in the expected location if (!IsProjectLocationValid(path, "web")) { return; } // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); if (Directory.Exists(path)) Directory.Delete(path, true); if (Directory.Exists(WebExportPath)) Directory.Delete(WebExportPath, true); // EditorUserBuildSettings. = true; var playerOptions = new BuildPlayerOptions(); playerOptions.scenes = GetEnabledScenes(); playerOptions.target = BuildTarget.WebGL; playerOptions.locationPathName = path; // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WebGL, BuildTarget.WebGL); // build addressable ExportAddressables(); var report = BuildPipeline.BuildPlayer(playerOptions); if (report.summary.result != BuildResult.Succeeded) throw new Exception("Build failed"); // Copy(path, WebExportPath); ModifyWebGLExport(); Debug.Log("-- WebGL Build: SUCCESSFUL --"); } private static void DoBuildAndroid(String buildPath, bool isPlugin, bool isReleaseBuild) { // Check if the Unity project is in the expected location if (!IsProjectLocationValid(AndroidExportPath, "android")) { return; } // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); if (Directory.Exists(APKPath)) Directory.Delete(APKPath, true); if (Directory.Exists(AndroidExportPath)) Directory.Delete(AndroidExportPath, true); EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; EditorUserBuildSettings.exportAsGoogleAndroidProject = true; var playerOptions = new BuildPlayerOptions(); playerOptions.scenes = GetEnabledScenes(); playerOptions.target = BuildTarget.Android; playerOptions.locationPathName = APKPath; if (!isReleaseBuild) { // remove this line if you don't use a debugger and you want to speed up the flutter build playerOptions.options = BuildOptions.AllowDebugging | BuildOptions.Development; } #if UNITY_2022_1_OR_NEWER PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, isReleaseBuild ? Il2CppCompilerConfiguration.Release : Il2CppCompilerConfiguration.Debug); PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.Android, isReleaseBuild ? Il2CppCodeGeneration.OptimizeSpeed : Il2CppCodeGeneration.OptimizeSize); #elif UNITY_2021_2_OR_NEWER PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, isReleaseBuild ? Il2CppCompilerConfiguration.Release : Il2CppCompilerConfiguration.Debug); EditorUserBuildSettings.il2CppCodeGeneration = isReleaseBuild ? Il2CppCodeGeneration.OptimizeSpeed : Il2CppCodeGeneration.OptimizeSize; #endif // Switch to Android standalone build. EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); // build addressable ExportAddressables(); var report = BuildPipeline.BuildPlayer(playerOptions); if (report.summary.result != BuildResult.Succeeded) throw new Exception("Build failed"); Copy(buildPath, AndroidExportPath); // Modify build.gradle ModifyAndroidGradle(isPlugin); if(isPlugin) { SetupAndroidProjectForPlugin(); } else { SetupAndroidProject(); } // Copy over resources from the launcher module that are used by the library, Avoid deleting the existing src/main/res contents. Copy(Path.Combine(APKPath + "/launcher/src/main/res"), Path.Combine(AndroidExportPath, "src/main/res"), false); if (isReleaseBuild) { Debug.Log($"-- Android Release Build: SUCCESSFUL --"); } else { Debug.Log($"-- Android Debug Build: SUCCESSFUL --"); } } private static void ModifyWebGLExport() { // Modify index.html var indexFile = Path.Combine(WebExportPath, "index.html"); var indexHtmlText = File.ReadAllText(indexFile); indexHtmlText = indexHtmlText.Replace("