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; #endif public class AdjustEditor : AssetPostprocessor { [MenuItem("Assets/Adjust/Export Unity Package")] public static void ExportAdjustUnityPackage() { string exportedFileName = "Adjust.unitypackage"; string assetsPath = "Assets/Adjust"; List assetsToExport = new List(); // Adjust Assets. assetsToExport.Add(assetsPath + "/3rd Party/SimpleJSON.cs"); assetsToExport.Add(assetsPath + "/Android/adjust-android.jar"); assetsToExport.Add(assetsPath + "/Android/AdjustAndroid.cs"); assetsToExport.Add(assetsPath + "/Android/AdjustAndroidManifest.xml"); assetsToExport.Add(assetsPath + "/Editor/AdjustEditor.cs"); assetsToExport.Add(assetsPath + "/Editor/AdjustSettings.cs"); assetsToExport.Add(assetsPath + "/Editor/AdjustSettingsEditor.cs"); assetsToExport.Add(assetsPath + "/Editor/AdjustCustomEditor.cs"); assetsToExport.Add(assetsPath + "/Editor/AdjustEditorPreprocessor.cs"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.cs"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.prefab"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.unity"); assetsToExport.Add(assetsPath + "/iOS/ADJAttribution.h"); assetsToExport.Add(assetsPath + "/iOS/ADJConfig.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEvent.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEventFailure.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEventSuccess.h"); assetsToExport.Add(assetsPath + "/iOS/ADJLogger.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSessionFailure.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSessionSuccess.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSubscription.h"); assetsToExport.Add(assetsPath + "/iOS/Adjust.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustiOS.cs"); assetsToExport.Add(assetsPath + "/iOS/AdjustSdk.a"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnity.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnity.mm"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnityDelegate.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnityDelegate.mm"); assetsToExport.Add(assetsPath + "/Prefab/Adjust.prefab"); assetsToExport.Add(assetsPath + "/Unity/Adjust.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustAppStoreSubscription.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustAttribution.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustConfig.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEnvironment.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEvent.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEventFailure.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEventSuccess.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustLogLevel.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustPlayStoreSubscription.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustSessionFailure.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustSessionSuccess.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustUtils.cs"); assetsToExport.Add(assetsPath + "/Windows/AdjustWindows.cs"); assetsToExport.Add(assetsPath + "/Windows/WindowsPcl.dll"); assetsToExport.Add(assetsPath + "/Windows/WindowsUap.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/Win10Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/Win81Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/WinWsInterface.dll"); assetsToExport.Add(assetsPath + "/Windows/W81/AdjustWP81.dll"); assetsToExport.Add(assetsPath + "/Windows/W81/Win81Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/WS/AdjustWS.dll"); assetsToExport.Add(assetsPath + "/Windows/WS/WinWsInterface.dll"); assetsToExport.Add(assetsPath + "/Windows/WU10/AdjustUAP10.dll"); assetsToExport.Add(assetsPath + "/Windows/WU10/Win10Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Newtonsoft.Json.dll"); AssetDatabase.ExportPackage( assetsToExport.ToArray(), exportedFileName, ExportPackageOptions.IncludeDependencies | ExportPackageOptions.Interactive); } [PostProcessBuild] 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 -ObjC flag set in other linker flags section because of it's categories. // OTHER_LDFLAGS -ObjC // // Seems that in newer Unity IDE versions adding -ObjC flag to Unity-iPhone target doesn't do the trick. // Adding -ObjC to UnityFramework target however does make things work nicely again. // This happens because Unity is linking SDK's static library into UnityFramework target. // Check for presence of UnityFramework target and if there, include -ObjC flag inside of it. Debug.Log("[Adjust]: Adding -ObjC flag to other linker flags (OTHER_LDFLAGS) of Unity-iPhone target."); xcodeProject.AddBuildProperty(xcodeTarget, "OTHER_LDFLAGS", "-ObjC"); Debug.Log("[Adjust]: -ObjC successfully added to other linker flags."); string xcodeTargetUnityFramework = xcodeProject.TargetGuidByName("UnityFramework"); if (!string.IsNullOrEmpty(xcodeTargetUnityFramework)) { Debug.Log("[Adjust]: Adding -ObjC flag to other linker flags (OTHER_LDFLAGS) of UnityFramework target."); xcodeProject.AddBuildProperty(xcodeTargetUnityFramework, "OTHER_LDFLAGS", "-ObjC"); Debug.Log("[Adjust]: -ObjC successfully added to other linker flags."); } // The Adjust SDK needs to have Obj-C exceptions enabled. // GCC_ENABLE_OBJC_EXCEPTIONS=YES 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."); } if (xcodeProject.ContainsFileByProjectPath("Libraries/Adjust/iOS/AdjustSigSdk.a")) { if (!string.IsNullOrEmpty(xcodeTargetUnityFramework)) { xcodeProject.AddBuildProperty(xcodeTargetUnityFramework, "OTHER_LDFLAGS", "-force_load $(PROJECT_DIR)/Libraries/Adjust/iOS/AdjustSigSdk.a"); } else { xcodeProject.AddBuildProperty(xcodeTarget, "OTHER_LDFLAGS", "-force_load $(PROJECT_DIR)/Libraries/Adjust/iOS/AdjustSigSdk.a"); } } // Save the changes to Xcode project file. xcodeProject.WriteToFile(xcodeProjectPath); #endif } } #if UNITY_IOS 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 = "Unity-iPhone.entitlements"; #if UNITY_2021_3 entitlementsFileName = "Unity-iPhone/Unity-iPhone.entitlements"; #endif 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 }