using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using UnityEditor; using UnityEngine; namespace SingularityGroup.HotReload.Editor { struct BuildInfoInput { public readonly string allDefineSymbols; public readonly BuildTarget activeBuildTarget; public readonly string[] omittedProjects; public readonly bool batchMode; public BuildInfoInput(string allDefineSymbols, BuildTarget activeBuildTarget, string[] omittedProjects, bool batchMode) { this.allDefineSymbols = allDefineSymbols; this.activeBuildTarget = activeBuildTarget; this.omittedProjects = omittedProjects; this.batchMode = batchMode; } } static class BuildInfoHelper { public static async Task GetGenerateBuildInfoInput() { var buildTarget = EditorUserBuildSettings.activeBuildTarget; var activeDefineSymbols = EditorUserBuildSettings.activeScriptCompilationDefines; var batchMode = Application.isBatchMode; var allDefineSymbols = await Task.Run(() => { return GetAllAndroidMonoBuildDefineSymbolsThreaded(activeDefineSymbols); }); // cached so unexpensive most of the time var omittedProjects = AssemblyOmission.GetOmittedProjects(allDefineSymbols); return new BuildInfoInput( allDefineSymbols: allDefineSymbols, activeBuildTarget: buildTarget, omittedProjects: omittedProjects, batchMode: batchMode ); } public static BuildInfo GenerateBuildInfoMainThread() { return GenerateBuildInfoMainThread(EditorUserBuildSettings.activeBuildTarget); } public static BuildInfo GenerateBuildInfoMainThread(BuildTarget buildTarget) { var allDefineSymbols = GetAllAndroidMonoBuildDefineSymbolsThreaded(EditorUserBuildSettings.activeScriptCompilationDefines); return GenerateBuildInfoThreaded(new BuildInfoInput( allDefineSymbols: allDefineSymbols, activeBuildTarget: buildTarget, omittedProjects: AssemblyOmission.GetOmittedProjects(allDefineSymbols), batchMode: Application.isBatchMode )); } public static BuildInfo GenerateBuildInfoThreaded(BuildInfoInput input) { var omittedProjectRegex = String.Join("|", input.omittedProjects.Select(name => Regex.Escape(name))); var shortCommitHash = GitUtil.GetShortCommitHashOrFallback(); var hostname = IsHumanControllingUs(input.batchMode) ? IpHelper.GetIpAddress() : null; // Note: add a string to uniquely identify the Unity project. Could use filepath to /MyProject/Assets/ (editor Application.dataPath) // or application identifier (com.company.appname). // Do this when supporting multiple projects: SG-28807 // The matching code is in Runtime assembly which compares server response with built BuildInfo. return new BuildInfo { projectIdentifier = "SG-29580", commitHash = shortCommitHash, defineSymbols = input.allDefineSymbols, projectOmissionRegex = omittedProjectRegex, buildMachineHostName = hostname, buildMachinePort = RequestHelper.port, activeBuildTarget = input.activeBuildTarget.ToString(), buildMachineRequestOrigin = RequestHelper.origin, }; } public static bool IsHumanControllingUs(bool batchMode) { if (batchMode) { return false; } var isCI = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")); return !isCI; } private static readonly string[] editorSymbolsToRemove = { "PLATFORM_ARCH_64", "UNITY_64", "UNITY_INCLUDE_TESTS", "UNITY_EDITOR", "UNITY_EDITOR_64", "UNITY_EDITOR_WIN", "ENABLE_UNITY_COLLECTIONS_CHECKS", "ENABLE_BURST_AOT", "RENDER_SOFTWARE_CURSOR", "PLATFORM_STANDALONE_WIN", "PLATFORM_STANDALONE", "UNITY_STANDALONE_WIN", "UNITY_STANDALONE", "ENABLE_MOVIES", "ENABLE_OUT_OF_PROCESS_CRASH_HANDLER", "ENABLE_WEBSOCKET_HOST", "ENABLE_CLUSTER_SYNC", "ENABLE_CLUSTERINPUT", }; private static readonly string[] androidSymbolsToAdd = { "CSHARP_7_OR_LATER", "CSHARP_7_3_OR_NEWER", "PLATFORM_ANDROID", "UNITY_ANDROID", "UNITY_ANDROID_API", "ENABLE_EGL", "DEVELOPMENT_BUILD", "ENABLE_CLOUD_SERVICES_NATIVE_CRASH_REPORTING", "PLATFORM_SUPPORTS_ADS_ID", "UNITY_CAN_SHOW_SPLASH_SCREEN", "UNITY_HAS_GOOGLEVR", "UNITY_HAS_TANGO", "ENABLE_SPATIALTRACKING", "ENABLE_RUNTIME_PERMISSIONS", "ENABLE_ENGINE_CODE_STRIPPING", "UNITY_ASTC_ONLY_DECOMPRESS", "ANDROID_USE_SWAPPY", "ENABLE_ONSCREEN_KEYBOARD", "ENABLE_UNITYADS_RUNTIME", "UNITY_UNITYADS_API", }; // Currently there is no better way. Alternatively we could hook into unity's call to csc.exe and parse the /define: arguments. // Hardcoding the differences was less effort and is less error prone. // I also looked into it and tried all the Build interfaces like this one https://docs.unity3d.com/ScriptReference/Build.IPostBuildPlayerScriptDLLs.html // and logging EditorUserBuildSettings.activeScriptCompilationDefines in the callbacks - result: all same like editor, so I agree that hardcode is best. public static string GetAllAndroidMonoBuildDefineSymbolsThreaded(string[] defineSymbols) { var defines = new HashSet(defineSymbols); defines.ExceptWith(editorSymbolsToRemove); defines.UnionWith(androidSymbolsToAdd); // sort for consistency, must be deterministic var definesArray = defines.OrderBy(def => def).ToArray(); return String.Join(";", definesArray); } } }