343 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
| using System;
 | |
| using System.IO;
 | |
| using System.Linq;
 | |
| using System.Xml;
 | |
| using System.Collections.Generic;
 | |
| using System.Text.RegularExpressions;
 | |
| using UnityEngine;
 | |
| using UnityEditor;
 | |
| using UnityEditor.Callbacks;
 | |
| #if UNITY_IOS
 | |
| using UnityEditor.iOS.Xcode;
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
| using UnityEditor.iOS.Xcode.Extensions;
 | |
| #endif
 | |
| #endif
 | |
| 
 | |
| namespace AdjustSdk
 | |
| {
 | |
|     public class AdjustEditor : AssetPostprocessor
 | |
|     {
 | |
|         private const int AdjustEditorPostProcesssBuildPriority = 90;
 | |
|         private const string TargetUnityIphonePodfileLine = "target 'Unity-iPhone' do";
 | |
|         private const string UseFrameworksPodfileLine = "use_frameworks!";
 | |
|         private const string UseFrameworksDynamicPodfileLine = "use_frameworks! :linkage => :dynamic";
 | |
|         private const string UseFrameworksStaticPodfileLine = "use_frameworks! :linkage => :static";
 | |
|         
 | |
|         [PostProcessBuild(AdjustEditorPostProcesssBuildPriority)]
 | |
|         public static void OnPostprocessBuild(BuildTarget target, string projectPath)
 | |
|         {
 | |
|             RunPostBuildScript(target: target, projectPath: projectPath);
 | |
|         }
 | |
| 
 | |
|         private static void RunPostBuildScript(BuildTarget target, string projectPath = "")
 | |
|         {
 | |
|             if (target == BuildTarget.iOS)
 | |
|             {
 | |
|     #if UNITY_IOS
 | |
|                 Debug.Log("[Adjust]: Starting to perform post build tasks for iOS platform.");
 | |
| 
 | |
|                 string xcodeProjectPath = projectPath + "/Unity-iPhone.xcodeproj/project.pbxproj";
 | |
| 
 | |
|                 PBXProject xcodeProject = new PBXProject();
 | |
|                 xcodeProject.ReadFromFile(xcodeProjectPath);
 | |
| 
 | |
|     #if UNITY_2019_3_OR_NEWER
 | |
|                 string xcodeTarget = xcodeProject.GetUnityMainTargetGuid();
 | |
|     #else
 | |
|                 string xcodeTarget = xcodeProject.TargetGuidByName("Unity-iPhone");
 | |
|     #endif
 | |
|                 HandlePlistIosChanges(projectPath);
 | |
| 
 | |
|                 if (AdjustSettings.iOSUniversalLinksDomains.Length > 0)
 | |
|                 {
 | |
|                     AddUniversalLinkDomains(xcodeProject, xcodeProjectPath, xcodeTarget);
 | |
|                 }
 | |
| 
 | |
|                 // If enabled by the user, Adjust SDK will try to add following frameworks to your project:
 | |
|                 // - AdSupport.framework (needed for access to IDFA value)
 | |
|                 // - AdServices.framework (needed in case you are running ASA campaigns)
 | |
|                 // - StoreKit.framework (needed for communication with SKAdNetwork framework)
 | |
|                 // - AppTrackingTransparency.framework (needed for information about user's consent to be tracked)
 | |
|                 // In case you don't need any of these, feel free to remove them from your app.
 | |
| 
 | |
|                 if (AdjustSettings.iOSFrameworkAdSupport)
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Adding AdSupport.framework to Xcode project.");
 | |
|                     xcodeProject.AddFrameworkToProject(xcodeTarget, "AdSupport.framework", true);
 | |
|                     Debug.Log("[Adjust]: AdSupport.framework added successfully.");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Skipping AdSupport.framework linking.");
 | |
|                 }
 | |
|                 if (AdjustSettings.iOSFrameworkAdServices)
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Adding AdServices.framework to Xcode project.");
 | |
|                     xcodeProject.AddFrameworkToProject(xcodeTarget, "AdServices.framework", true);
 | |
|                     Debug.Log("[Adjust]: AdServices.framework added successfully.");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Skipping AdServices.framework linking.");
 | |
|                 }
 | |
|                 if (AdjustSettings.iOSFrameworkStoreKit)
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Adding StoreKit.framework to Xcode project.");
 | |
|                     xcodeProject.AddFrameworkToProject(xcodeTarget, "StoreKit.framework", true);
 | |
|                     Debug.Log("[Adjust]: StoreKit.framework added successfully.");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Skipping StoreKit.framework linking.");
 | |
|                 }
 | |
|                 if (AdjustSettings.iOSFrameworkAppTrackingTransparency)
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Adding AppTrackingTransparency.framework to Xcode project.");
 | |
|                     xcodeProject.AddFrameworkToProject(xcodeTarget, "AppTrackingTransparency.framework", true);
 | |
|                     Debug.Log("[Adjust]: AppTrackingTransparency.framework added successfully.");
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Skipping AppTrackingTransparency.framework linking.");
 | |
|                 }
 | |
| 
 | |
|                 // The Adjust SDK needs to have Obj-C exceptions enabled.
 | |
|                 // GCC_ENABLE_OBJC_EXCEPTIONS=YES
 | |
|                 string xcodeTargetUnityFramework = xcodeProject.TargetGuidByName("UnityFramework");
 | |
|                 Debug.Log("[Adjust]: Enabling Obj-C exceptions by setting GCC_ENABLE_OBJC_EXCEPTIONS value to YES.");
 | |
|                 xcodeProject.AddBuildProperty(xcodeTarget, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
 | |
|                 Debug.Log("[Adjust]: Obj-C exceptions enabled successfully.");
 | |
|                 if (!string.IsNullOrEmpty(xcodeTargetUnityFramework))
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Enabling Obj-C exceptions by setting GCC_ENABLE_OBJC_EXCEPTIONS value to YES.");
 | |
|                     xcodeProject.AddBuildProperty(xcodeTargetUnityFramework, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
 | |
|                     Debug.Log("[Adjust]: Obj-C exceptions enabled successfully.");
 | |
|                 }
 | |
| 
 | |
|                 // potential AdjustSigSdk.xcframework embedding
 | |
|                 Debug.Log("[Adjust]: Checking whether AdjustSigSdk.xcframework needs to be embedded or not...");
 | |
|                 EmbedAdjustSignatureIfNeeded(projectPath, xcodeProject, xcodeTarget);
 | |
| 
 | |
|                 // Save the changes to Xcode project file.
 | |
|                 xcodeProject.WriteToFile(xcodeProjectPath);
 | |
|     #endif
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     #if UNITY_IOS
 | |
|         // dynamic xcframework embedding logic adjusted and taken from:
 | |
|         // https://github.com/AppLovin/AppLovin-MAX-Unity-Plugin/blob/master/DemoApp/Assets/MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPostProcessiOS.cs
 | |
|         private static void EmbedAdjustSignatureIfNeeded(string buildPath, PBXProject project, string targetGuid)
 | |
|         {
 | |
|             var podsDirectory = Path.Combine(buildPath, "Pods");
 | |
| 
 | |
|             if (!Directory.Exists(podsDirectory) || !ShouldEmbedDynamicLibraries(buildPath))
 | |
|             {
 | |
|                 Debug.Log("[Adjust]: No need to embed AdjustSigSdk.xcframework.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var dynamicLibraryPathToEmbed = GetAdjustSignaturePathToEmbed(podsDirectory, buildPath);
 | |
|             if (dynamicLibraryPathToEmbed == null) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             Debug.Log("[Adjust]: It needs to be embedded. Starting the embedding process...");
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
|             var fileGuid = project.AddFile(dynamicLibraryPathToEmbed, dynamicLibraryPathToEmbed);
 | |
|             project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
 | |
| #else
 | |
|             string runpathSearchPaths;
 | |
|             runpathSearchPaths = project.GetBuildPropertyForAnyConfig(targetGuid, "LD_RUNPATH_SEARCH_PATHS");
 | |
|             runpathSearchPaths += string.IsNullOrEmpty(runpathSearchPaths) ? "" : " ";
 | |
| 
 | |
|             // check if runtime search paths already contains the required search paths for dynamic libraries
 | |
|             if (runpathSearchPaths.Contains("@executable_path/Frameworks")) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             runpathSearchPaths += "@executable_path/Frameworks";
 | |
|             project.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", runpathSearchPaths);
 | |
| #endif
 | |
|             Debug.Log("[Adjust]: Embedding process completed.");
 | |
|         }
 | |
| 
 | |
|         private static bool ShouldEmbedDynamicLibraries(string buildPath)
 | |
|         {
 | |
|             var podfilePath = Path.Combine(buildPath, "Podfile");
 | |
|             if (!File.Exists(podfilePath)) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             // if the Podfile doesn't have a `Unity-iPhone` target, we should embed the dynamic libraries
 | |
|             var lines = File.ReadAllLines(podfilePath);
 | |
|             var containsUnityIphoneTarget = lines.Any(line => line.Contains(TargetUnityIphonePodfileLine));
 | |
|             if (!containsUnityIphoneTarget) {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             // if the Podfile does not have a `use_frameworks! :linkage => static` line, we should not embed the dynamic libraries
 | |
|             var useFrameworksStaticLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksStaticPodfileLine));
 | |
|             if (useFrameworksStaticLineIndex == -1) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             // if more than one of the `use_frameworks!` lines are present, CocoaPods will use the last one
 | |
|             var useFrameworksLineIndex = Array.FindIndex(lines, line => line.Trim() == UseFrameworksPodfileLine); // check for exact line to avoid matching `use_frameworks! :linkage => static/dynamic`
 | |
|             var useFrameworksDynamicLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksDynamicPodfileLine));
 | |
| 
 | |
|             // check if `use_frameworks! :linkage => :static` is the last line of the three
 | |
|             // if it is, we should embed the dynamic libraries
 | |
|             return useFrameworksLineIndex < useFrameworksStaticLineIndex && useFrameworksDynamicLineIndex < useFrameworksStaticLineIndex;
 | |
|         }
 | |
| 
 | |
|         private static string GetAdjustSignaturePathToEmbed(string podsDirectory, string buildPath)
 | |
|         {
 | |
|             var adjustSignatureFrameworkToEmbed = "AdjustSigSdk.xcframework";
 | |
| 
 | |
|             // both .framework and .xcframework are directories, not files
 | |
|             var directories = Directory.GetDirectories(podsDirectory, adjustSignatureFrameworkToEmbed, SearchOption.AllDirectories);
 | |
|             if (directories.Length <= 0) {
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|             var dynamicLibraryAbsolutePath = directories[0];
 | |
|             var relativePath = GetDynamicLibraryRelativePath(dynamicLibraryAbsolutePath);
 | |
|             return relativePath;
 | |
|         }
 | |
| 
 | |
|         private static string GetDynamicLibraryRelativePath(string dynamicLibraryAbsolutePath)
 | |
|         {
 | |
|             var index = dynamicLibraryAbsolutePath.LastIndexOf("Pods", StringComparison.Ordinal);
 | |
|             return dynamicLibraryAbsolutePath.Substring(index);
 | |
|         }
 | |
| 
 | |
|         private static void HandlePlistIosChanges(string projectPath)
 | |
|         {
 | |
|             const string UserTrackingUsageDescriptionKey = "NSUserTrackingUsageDescription";
 | |
| 
 | |
|             // Check if needs to do any info plist change.
 | |
|             bool hasUserTrackingDescription =
 | |
|                 !string.IsNullOrEmpty(AdjustSettings.iOSUserTrackingUsageDescription);
 | |
|             bool hasUrlSchemesDeepLinksEnabled = AdjustSettings.iOSUrlSchemes.Length > 0;
 | |
| 
 | |
|             if (!hasUserTrackingDescription && !hasUrlSchemesDeepLinksEnabled)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Get and read info plist.
 | |
|             var plistPath = Path.Combine(projectPath, "Info.plist");
 | |
|             var plist = new PlistDocument();
 | |
|             plist.ReadFromFile(plistPath);
 | |
|             var plistRoot = plist.root;
 | |
| 
 | |
|             // Do the info plist changes.
 | |
|             if (hasUserTrackingDescription)
 | |
|             {
 | |
|                 if (plistRoot[UserTrackingUsageDescriptionKey] != null)
 | |
|                 {
 | |
|                     Debug.Log("[Adjust]: Overwritting User Tracking Usage Description.");
 | |
|                 }
 | |
|                 plistRoot.SetString(UserTrackingUsageDescriptionKey,
 | |
|                     AdjustSettings.iOSUserTrackingUsageDescription);
 | |
|             }
 | |
| 
 | |
|             if (hasUrlSchemesDeepLinksEnabled)
 | |
|             {
 | |
|                 AddUrlSchemesIOS(plistRoot, AdjustSettings.iOSUrlIdentifier, AdjustSettings.iOSUrlSchemes);
 | |
|             }
 | |
| 
 | |
|             // Write any info plist change.
 | |
|             File.WriteAllText(plistPath, plist.WriteToString());
 | |
|         }
 | |
| 
 | |
|         private static void AddUrlSchemesIOS(PlistElementDict plistRoot, string urlIdentifier, string[] urlSchemes)
 | |
|         {
 | |
|             // Set Array for futher deeplink values.
 | |
|             var urlTypesArray = CreatePlistElementArray(plistRoot, "CFBundleURLTypes");
 | |
| 
 | |
|             // Array will contain just one deeplink dictionary
 | |
|             var urlSchemesItems = CreatePlistElementDict(urlTypesArray);
 | |
|             urlSchemesItems.SetString("CFBundleURLName", urlIdentifier);
 | |
|             var urlSchemesArray = CreatePlistElementArray(urlSchemesItems, "CFBundleURLSchemes");
 | |
| 
 | |
|             // Delete old deferred deeplinks URIs
 | |
|             Debug.Log("[Adjust]: Removing deeplinks that already exist in the array to avoid duplicates.");
 | |
|             foreach (var link in urlSchemes)
 | |
|             {
 | |
|                 urlSchemesArray.values.RemoveAll(
 | |
|                     element => element != null && element.AsString().Equals(link));
 | |
|             }
 | |
| 
 | |
|             Debug.Log("[Adjust]: Adding new deep links.");
 | |
|             foreach (var link in urlSchemes.Distinct())
 | |
|             {
 | |
|                 urlSchemesArray.AddString(link);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static PlistElementArray CreatePlistElementArray(PlistElementDict root, string key)
 | |
|         {
 | |
|             if (!root.values.ContainsKey(key))
 | |
|             {
 | |
|                 Debug.Log(string.Format("[Adjust]: {0} not found in Info.plist. Creating a new one.", key));
 | |
|                 return root.CreateArray(key);
 | |
|             }
 | |
|             var result = root.values[key].AsArray();
 | |
|             return result != null ? result : root.CreateArray(key);
 | |
|         }
 | |
| 
 | |
|         private static PlistElementDict CreatePlistElementDict(PlistElementArray rootArray)
 | |
|         {
 | |
|             if (rootArray.values.Count == 0)
 | |
|             {
 | |
|                 Debug.Log("[Adjust]: Deeplinks array doesn't contain dictionary for deeplinks. Creating a new one.");
 | |
|                 return rootArray.AddDict();
 | |
|             }
 | |
| 
 | |
|             var urlSchemesItems = rootArray.values[0].AsDict();
 | |
|             Debug.Log("[Adjust]: Reading deeplinks array");
 | |
|             if (urlSchemesItems == null)
 | |
|             {
 | |
|                 Debug.Log("[Adjust]: Deeplinks array doesn't contain dictionary for deeplinks. Creating a new one.");
 | |
|                 urlSchemesItems = rootArray.AddDict();
 | |
|             }
 | |
| 
 | |
|             return urlSchemesItems;
 | |
|         }
 | |
| 
 | |
|         private static void AddUniversalLinkDomains(PBXProject project, string xCodeProjectPath, string xCodeTarget)
 | |
|         {
 | |
|             string entitlementsFileName = project.GetBuildPropertyForAnyConfig(xCodeTarget, "CODE_SIGN_ENTITLEMENTS");
 | |
|             if (entitlementsFileName == null)
 | |
|             {
 | |
|                 entitlementsFileName = "Unity-iPhone.entitlements";
 | |
|             }
 | |
| 
 | |
|             Debug.Log("[Adjust]: Adding associated domains to entitlements file.");
 | |
|     #if UNITY_2019_3_OR_NEWER
 | |
|             var projectCapabilityManager = new ProjectCapabilityManager(xCodeProjectPath, entitlementsFileName, null, project.GetUnityMainTargetGuid());
 | |
|     #else
 | |
|             var projectCapabilityManager = new ProjectCapabilityManager(xCodeProjectPath, entitlementsFileName, PBXProject.GetUnityTargetName());
 | |
|     #endif
 | |
|             var uniqueDomains = AdjustSettings.iOSUniversalLinksDomains.Distinct().ToArray();
 | |
|             const string applinksPrefix = "applinks:";
 | |
|             for (int i = 0; i < uniqueDomains.Length; i++)
 | |
|             {
 | |
|                 if (!uniqueDomains[i].StartsWith(applinksPrefix))
 | |
|                 {
 | |
|                     uniqueDomains[i] = applinksPrefix + uniqueDomains[i];
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             projectCapabilityManager.AddAssociatedDomains(uniqueDomains);
 | |
|             projectCapabilityManager.WriteToFile();
 | |
| 
 | |
|             Debug.Log("[Adjust]: Enabling Associated Domains capability with created entitlements file.");
 | |
|             project.AddCapability(xCodeTarget, PBXCapabilityType.AssociatedDomains, entitlementsFileName);
 | |
|         }
 | |
|     #endif
 | |
|     }
 | |
| } |