#if UNITY_IOS using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.iOS; using UnityEditor.iOS.Xcode; using UnityEditor.Rendering; using UnityEditor.XR.ARSubsystems; using UnityEditor.XR.Management; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.XR.ARKit; using Unity.EditorCoroutines.Editor; using OSVersion = UnityEngine.XR.ARKit.OSVersion; namespace UnityEditor.XR.ARKit { class ARKitBuildProcessor { class PostProcessor : IPostprocessBuildWithReport { // NB: Needs to be > 0 to make sure we remove the shader since the // Input System overwrites the preloaded assets array public int callbackOrder => 1; void PostprocessBuild(BuildReport report) { if (report.summary.platform != BuildTarget.iOS) { return; } BuildHelper.RemoveShaderFromProject(ARKitCameraSubsystem.backgroundShaderName); HandleARKitRequiredFlag(report.summary.outputPath); } public void OnPostprocessBuild(BuildReport report) { #if UNITY_XR_ARKIT_LOADER_ENABLED PostprocessBuild(report); #endif } static void HandleARKitRequiredFlag(string pathToBuiltProject) { var arkitSettings = ARKitSettings.GetOrCreateSettings(); string plistPath = Path.Combine(pathToBuiltProject, "Info.plist"); PlistDocument plist = new PlistDocument(); plist.ReadFromString(File.ReadAllText(plistPath)); PlistElementDict rootDict = plist.root; // Get or create array to manage device capabilities const string capsKey = "UIRequiredDeviceCapabilities"; PlistElementArray capsArray; PlistElement pel; if (rootDict.values.TryGetValue(capsKey, out pel)) { capsArray = pel.AsArray(); } else { capsArray = rootDict.CreateArray(capsKey); } // Remove any existing "arkit" plist entries const string arkitStr = "arkit"; capsArray.values.RemoveAll(x => arkitStr.Equals(x.AsString())); if (arkitSettings.requirement == ARKitSettings.Requirement.Required) { // Add "arkit" plist entry capsArray.AddString(arkitStr); } File.WriteAllText(plistPath, plist.WriteToString()); } } class Preprocessor : IPreprocessBuildWithReport, IPreprocessShaders { // Magic value according to // https://docs.unity3d.com/ScriptReference/PlayerSettings.GetArchitecture.html // "0 - None, 1 - ARM64, 2 - Universal." const int k_TargetArchitectureArm64 = 1; const int k_TargetArchitectureUniversal = 2; // The minimum target Xcode version for the plugin const int k_TargetMinimumMajorXcodeVersion = 11; const int k_TargetMinimumMinorXcodeVersion = 0; const int k_TargetMinimumPatchXcodeVersion = 0; void ProcessShader(Shader shader, ShaderSnippetData snippet, IList data) { // Remove shader variants for the camera background shader that will fail compilation because of package dependencies. string backgroundShaderName = ARKitCameraSubsystem.backgroundShaderName; if (backgroundShaderName.Equals(shader.name)) { foreach (var backgroundShaderKeywordToNotCompile in ARKitCameraSubsystem.backgroundShaderKeywordsToNotCompile) { ShaderKeyword shaderKeywordToNotCompile = new ShaderKeyword(shader, backgroundShaderKeywordToNotCompile); for (int i = (data.Count - 1); i >= 0; --i) { if (data[i].shaderKeywordSet.IsEnabled(shaderKeywordToNotCompile)) { data.RemoveAt(i); } } } } } public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList data) { #if UNITY_XR_ARKIT_LOADER_ENABLED ProcessShader(shader, snippet, data); #endif } void PreprocessBuild(BuildReport report) { if (Application.isBatchMode) { // by the time we reach the preprocessor in batch mode the AssetDatabase is ready to load our iOS XR // settings so we manually update the arkit defines here. LoaderEnabledCheck.UpdateARKitDefines(); } if (loaderEnabled && report.summary.platform != BuildTarget.iOS) return; if (string.IsNullOrEmpty(PlayerSettings.iOS.cameraUsageDescription)) throw new BuildFailedException("ARKit requires a Camera Usage Description (Player Settings > iOS > Other Settings > Camera Usage Description)"); EnsureMinimumXcodeVersion(); EnsureMetalIsFirstApi(); if(ARKitSettings.GetOrCreateSettings().requirement == ARKitSettings.Requirement.Required) { EnsureMinimumBuildTarget(); EnsureTargetArchitecturesAreSupported(report.summary.platformGroup); } else if (PlayerSettings.GetArchitecture(report.summary.platformGroup) == k_TargetArchitectureUniversal) { EnsureOpenGLIsUsed(); } BuildHelper.AddBackgroundShaderToProject(ARKitCameraSubsystem.backgroundShaderName); } public void OnPreprocessBuild(BuildReport report) { SetRuntimePluginCopyDelegate(); #if UNITY_XR_ARKIT_LOADER_ENABLED PreprocessBuild(report); #endif } void EnsureMinimumBuildTarget() { var userSetTargetVersion = OSVersion.Parse(PlayerSettings.iOS.targetOSVersionString); if (userSetTargetVersion < new OSVersion(11)) { throw new BuildFailedException($"You have selected a minimum target iOS version of {userSetTargetVersion} and have the ARKit package installed." + "ARKit requires at least iOS version 11.0 (See Player Settings > Other Settings > Target minimum iOS Version)."); } } void EnsureMinimumXcodeVersion() { #if UNITY_EDITOR_OSX var xcodeIndex = Math.Max(0, XcodeApplications.GetPreferedXcodeIndex()); var xcodeVersion = OSVersion.Parse(XcodeApplications.GetXcodeApplicationPublicName(xcodeIndex)); if (xcodeVersion == new OSVersion(0)) throw new BuildFailedException($"Could not determine which version of Xcode was selected in the Build Settings. Xcode app was computed as \"{XcodeApplications.GetXcodeApplicationPublicName(xcodeIndex)}\"."); if (xcodeVersion < new OSVersion( k_TargetMinimumMajorXcodeVersion, k_TargetMinimumMinorXcodeVersion, k_TargetMinimumPatchXcodeVersion)) throw new BuildFailedException($"The selected Xcode version: {xcodeVersion} is below the minimum Xcode required Xcode version for the Unity ARKit Plugin. Please target at least Xcode version {k_TargetMinimumMajorXcodeVersion}.{k_TargetMinimumMinorXcodeVersion}.{k_TargetMinimumPatchXcodeVersion}."); #endif } void EnsureTargetArchitecturesAreSupported(BuildTargetGroup buildTargetGroup) { if (PlayerSettings.GetArchitecture(buildTargetGroup) != k_TargetArchitectureArm64) throw new BuildFailedException("ARKit XR Plugin only supports the ARM64 architecture. See Player Settings > Other Settings > Architecture."); } void EnsureMetalIsFirstApi() { var graphicsApis = PlayerSettings.GetGraphicsAPIs(BuildTarget.iOS); if (graphicsApis.Length > 0) { var graphicsApi = graphicsApis[0]; if (graphicsApi != GraphicsDeviceType.Metal) throw new BuildFailedException($"You currently have {graphicsApi} at the top of the list of Graphics APis. However, Metal needs to be first in the list. (See Player Settings > Other Settings > Graphics APIs)"); } } void EnsureOpenGLIsUsed() { var graphicsApis = PlayerSettings.GetGraphicsAPIs(BuildTarget.iOS); if (graphicsApis.Length > 0) { if(!graphicsApis.Contains(GraphicsDeviceType.OpenGLES2)) throw new BuildFailedException("To build for 'Universal' architecture, OpenGLES2 is needed. (See Player Settings > Other Settings > Graphics APIs)"); } } readonly string[] runtimePluginNames = new string[] { "libUnityARKit.a", "UnityARKit.m", "libUnityARKitFaceTracking.a", }; bool ShouldIncludeRuntimePluginsInBuild(string path) { if (!loaderEnabled) return false; if (path.Contains("libUnityARKitFaceTracking.a")) return faceTrackingEnabled; return true; } void SetRuntimePluginCopyDelegate() { var allPlugins = PluginImporter.GetAllImporters(); foreach (var plugin in allPlugins) { if (plugin.isNativePlugin) { foreach (var pluginName in runtimePluginNames) { if (plugin.assetPath.Contains(pluginName)) { plugin.SetIncludeInBuildDelegate(ShouldIncludeRuntimePluginsInBuild); break; } } } } } public int callbackOrder => 0; } public static bool loaderEnabled; public static bool faceTrackingEnabled; } static class AddDefineSymbols { public static void Add(string define) { var definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); var allDefines = new HashSet(definesString.Split(';')); if (allDefines.Contains(define)) { return; } allDefines.Add(define); PlayerSettings.SetScriptingDefineSymbolsForGroup( BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget), string.Join(";", allDefines)); } public static void Remove(string define) { string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); var allDefines = new HashSet(definesString.Split(';')); allDefines.Remove(define); PlayerSettings.SetScriptingDefineSymbolsForGroup( BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget), string.Join(";", allDefines)); } } [InitializeOnLoad] class LoaderEnabledCheck { static LoaderEnabledCheck() { s_ARKitSettings = ARKitSettings.GetOrCreateSettings(); ARKitBuildProcessor.loaderEnabled = false; // in batch mode, the AssetDatabase may not yet be properly initialized and trying to load the xr settings // and check the loader won't work. This can leave us in a state where the build processor can't execute // properly down the line even though the loader is enabled in the settings if (Application.isBatchMode) return; UpdateARKitDefines(); EditorCoroutineUtility.StartCoroutineOwnerless(UpdateARKitDefinesCoroutine()); } static ARKitSettings s_ARKitSettings; static IEnumerator UpdateARKitDefinesCoroutine() { var waitObj = new EditorWaitForSeconds(.25f); while (true) { UpdateARKitDefines(); yield return waitObj; } } internal static void UpdateARKitDefines() { bool previousLoaderEnabled = ARKitBuildProcessor.loaderEnabled; var generalSettings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); if (generalSettings != null) { ARKitBuildProcessor.loaderEnabled = false; foreach (var loader in generalSettings.Manager.activeLoaders) { if (loader is ARKitLoader) { ARKitBuildProcessor.loaderEnabled = true; break; } } if (ARKitBuildProcessor.loaderEnabled && !previousLoaderEnabled) { AddDefineSymbols.Add("UNITY_XR_ARKIT_LOADER_ENABLED"); } else if (!ARKitBuildProcessor.loaderEnabled && previousLoaderEnabled) { AddDefineSymbols.Remove("UNITY_XR_ARKIT_LOADER_ENABLED"); } if (s_ARKitSettings.faceTracking && !ARKitBuildProcessor.faceTrackingEnabled) { AddDefineSymbols.Add("UNITY_XR_ARKIT_FACE_TRACKING_ENABLED"); } else if (!s_ARKitSettings.faceTracking && ARKitBuildProcessor.faceTrackingEnabled) { AddDefineSymbols.Remove("UNITY_XR_ARKIT_FACE_TRACKING_ENABLED"); } ARKitBuildProcessor.faceTrackingEnabled = s_ARKitSettings.faceTracking; } } } } #endif