Search Unity

How do I add XCFramwork from code?

Discussion in 'iOS and tvOS' started by Duobix, Mar 4, 2020.

  1. Duobix

    Duobix

    Joined:
    Dec 6, 2014
    Posts:
    52
    Recently I've stumbled upon some solutions that require me to add an xcframework directly to xcode project by dragging that straight to the xcode window. While that's simple and elegant, I'd like to streamline the build process and include the xcframework directly from Unity. That is a bit of a problem, because - I know I can integrate frameworks by just putting them in Assets/Plugins/iOS, this one just doesn't seem to be working the same when I put it in Assets/Plugins/iOS and when I drag it into Xcode.

    Could someone point me in the direction of doing the xcframework integration from code? It doesn't even needs to be by means of Unity, any solutions regarding such integration is welcome.
     
  2. SanderPaladin

    SanderPaladin

    Joined:
    Sep 12, 2018
    Posts:
    2
    Unfortunately I am now faced with the same issue, have you managed to resolve this issue Duobix?
     
  3. Neonlyte

    Neonlyte

    Joined:
    Oct 17, 2013
    Posts:
    515
    You can create a post-build processing script by writing C# file in Unity and annotate it with [PostProcessBuildAttribute]. In that script, you can use PBXProject API to manipulate the generated Xcode project.

    https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.html

    Do the following:
    1. Query "FRAMEWORK_SEARCH_PATHS" property with PBXProject.GetBuildPropertyForAnyConfig. This is a list of directories Xcode uses to look for frameworks in the "Link Binary with Library" build phase.

    2. Append the directory path that your framework resides to the queried string in quotes and a space before the quotation as a separator (refer to the link in step 1). Do try to search the string for that path first before appending because neither Xcode or Unity filter duplicated entries.

    3. Set the modified property back to PBXProject with PBXProject.SetBuildProperty.

    4. Use PBXProject.ContainsFramework to query your framework file name. If the name does not exist, use PBXProject.AddFrameworkToProject to add the file name. This is to add entry to the "Link Binary with Library" build phase, which in turn instructs the compiler to look for the framework in said name among the search paths.

    Here's a skeleton for a post build script
    Code (CSharp):
    1. [PostProcessBuild]
    2. public static void XcodePostProcess(BuildTarget buildTarget, string pathToBuiltProject)
    3. {
    4.     string projectPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
    5.     PBXProject project = new PBXProject();
    6.     project.ReadFromString(File.ReadAllText(projectPath));
    7.     string target = project.TargetGuidByName("Unity-iPhone");
    8.     //...
    9. }
     
    SanderPaladin likes this.
  4. SanderPaladin

    SanderPaladin

    Joined:
    Sep 12, 2018
    Posts:
    2
    Hello Neonlyte, thank you for your reply. Thanks to your reply I got to our solution step-by-step. The target location of the framework was already in the search paths (added by another plugin) and in our case we needed to 'Embed & Sign' the xcframework instead of adding it like a regular framework. Note that some extension methods have been deprecated and replaced in newer versions but we are still on 2018 (like GetUnityTargetName).

    Code (CSharp):
    1. public static class TestiOSPostProcessor
    2. {
    3.     private const string FRAMEWORK_ORIGIN_PATH =
    4.         "NativeAssets"; // relative to project folder
    5.     private const string FRAMEWORK_TARGET_PATH =
    6.         "Frameworks"; // relative to build folder
    7.  
    8.     private const string FRAMEWORK_NAME = "test.xcframework";
    9.  
    10.     [PostProcessBuild]
    11.     public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    12.     {
    13.         if (buildTarget != BuildTarget.iOS)
    14.             return;
    15.  
    16.         string sourcePath = Path.Combine(FRAMEWORK_ORIGIN_PATH, FRAMEWORK_NAME);
    17.         string destPath = Path.Combine(FRAMEWORK_TARGET_PATH, FRAMEWORK_NAME);
    18.  
    19.         CopyDirectory(sourcePath, Path.Combine(path, destPath));
    20.  
    21.         // obtain the xcode project
    22.         string pbxProjectPath = PBXProject.GetPBXProjectPath(path);
    23.         PBXProject project = new PBXProject();
    24.         project.ReadFromFile(pbxProjectPath);
    25.  
    26.         string targetGuid =
    27.             project.TargetGuidByName(PBXProject.GetUnityTargetName());
    28.  
    29.         // add the framework to the project and enable 'Embed & Sign' for it
    30.         string fileGuid = project.AddFile(destPath, destPath);
    31.         project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
    32.  
    33.         project.WriteToFile(pbxProjectPath);
    34.     }
    35.  
    36.     private static void CopyDirectory(string sourcePath, string destPath)
    37.     {
    38.         Assert.IsFalse(Directory.Exists(destPath));
    39.         Directory.CreateDirectory(destPath);
    40.  
    41.         foreach (string file in Directory.GetFiles(sourcePath))
    42.             File.Copy(file, Path.Combine(destPath, Path.GetFileName(file)));
    43.  
    44.         foreach (string dir in Directory.GetDirectories(sourcePath))
    45.             CopyDirectory(dir, Path.Combine(destPath, Path.GetFileName(dir)));
    46.     }
    47. }
    To add the path to the search one could use the following code, note that you'll need to include the quotation marks if your path has spaces in it (untested though because we didn't need it):
    Code (CSharp):
    1. // add the target path to the search path
    2. string searchPath = $" $(PROJECT_DIR)/{FRAMEWORK_TARGET_PATH}";
    3. string property =
    4.     project.GetBuildPropertyForAnyConfig(targetGuid, FRAMEWORK_SEARCH_PATH_PROPERTY);
    5.  
    6. if (!(property.Contains(searchPath + " ") || property.EndsWith(searchPath)))
    7. {
    8.     property += searchPath;
    9.     project.SetBuildProperty(targetGuid, FRAMEWORK_SEARCH_PATH_PROPERTY, property);
    10. }
     
    Neonlyte likes this.
  5. Deleted User

    Deleted User

    Guest

    Wasn't it supposed to be supported without the workaround? Tried the solution but putting xcframework within the project says file collision since there are two framework within xcframework. Also selecting different CPU build type for each framework is not a solution as m1 simulator behave different to intel. Unity should add feature to automatically select required build from xcframework file depending on export build settings in future updates.
     
    Last edited by a moderator: Dec 24, 2020
  6. Blinkq

    Blinkq

    Joined:
    Apr 19, 2015
    Posts:
    21
    Hello guys, probably someone faced same situation as me. Working in 2018, on my own SDK, and trying to provide SDK with Simulator and Device frameworks.
    I am no using cocoapods, and dont want to. Fat framework looks like out of date, its cant be embed in XCode, on apple forum devs writing that apple trying to force use us xcframework, meanwhile when you puting xcframework in Unity in Plugins/IOS, both dev and sim frameworks includes to xcode project and we need to do extra steps to remove unused framework.

    So, I made this script with TargetSDK dependency


    Code (CSharp):
    1.  
    2. #if UNITY_EDITOR
    3. using System;
    4. using UnityEngine;
    5. using UnityEditor;
    6. using UnityEditor.Callbacks;
    7. using UnityEditor.iOS.Xcode;
    8. using System.IO;
    9. using System.Linq;
    10. using System.Collections.Generic;
    11. using System.Text.RegularExpressions;
    12.  
    13. public static class FrameworkResolver
    14. {
    15. private const string FRAMEWORK_ORIGIN_PATH =
    16. "Assets/SuperPuper/Plugins/IOS"; // relative to project folder
    17. private const string FRAMEWORK_TARGET_PATH =
    18. "Frameworks"; // relative to build folder
    19.  
    20. [PostProcessBuild]
    21. public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    22. {
    23.  
    24. if (buildTarget != BuildTarget.iOS)
    25. return;
    26.  
    27. string sourcePath = Path.Combine(FRAMEWORK_ORIGIN_PATH, "SuperPuperSDK.xcframework");
    28. string destPath = Path.Combine(FRAMEWORK_TARGET_PATH, "SuperPuperSDK.framework");
    29.  
    30. string deviceFrameworkPath = "ios-arm64_armv7/SuperPuperSDK.framework";
    31. string simulatorFrameworkPath = "ios-i386_x86_64-simulator/SuperPuperSDK.framework";
    32.  
    33. iOSSdkVersion target = PlayerSettings.iOS.sdkVersion;
    34. if( target == iOSSdkVersion.DeviceSDK){
    35. sourcePath = Path.Combine(sourcePath, deviceFrameworkPath);
    36. } else if (target == iOSSdkVersion.SimulatorSDK){
    37. sourcePath = Path.Combine(sourcePath, simulatorFrameworkPath);
    38. }
    39.  
    40. CopyAndReplaceDirectory(sourcePath, Path.Combine(path, destPath));
    41.  
    42. string pbxProjectPath = PBXProject.GetPBXProjectPath(path);
    43. PBXProject project = new PBXProject();
    44.  
    45. project.ReadFromFile(pbxProjectPath);
    46.  
    47. string targetGuid = project.TargetGuidByName(PBXProject.GetUnityTargetName());
    48.  
    49. string fileGuid = project.AddFile(destPath, destPath, PBXSourceTree.Source);
    50.  
    51. project.AddFileToBuild(targetGuid, fileGuid);
    52.  
    53. project.AddFrameworkToProject(targetGuid, "SuperPuperSDK.framework", false);
    54.  
    55. project.SetBuildProperty(targetGuid, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
    56. project.AddBuildProperty(targetGuid, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Libraries");
    57.  
    58. project.SetBuildProperty(targetGuid, "FRAMEWORK_SEARCH_PATHS", "$(SRCROOT)/Frameworks");
    59. project.AddBuildProperty(targetGuid, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
    60.  
    61. project.AddBuildProperty(targetGuid, "OTHER_LDFLAGS", "-ObjC");
    62.  
    63. //var phaseGUID = project.GetFrameworksBuildPhaseByTarget(targetGuid);
    64. //project.AddFileToBuildSection(targetGuid, phaseGUID, fileGuid);
    65.  
    66. UnityEditor.iOS.Xcode.Extensions.PBXProjectExtensions.AddFileToEmbedFrameworks(project, targetGuid, fileGuid);
    67.  
    68. project.WriteToFile(pbxProjectPath);
    69. }
    70.  
    71. private static void CopyAndReplaceDirectory (string srcPath, string dstPath)
    72. {
    73. if (Directory.Exists (dstPath))
    74. Directory.Delete (dstPath);
    75. if (File.Exists (dstPath))
    76. File.Delete (dstPath);
    77.  
    78. Directory.CreateDirectory (dstPath);
    79.  
    80. foreach (var file in Directory.GetFiles(srcPath))
    81. File.Copy (file, Path.Combine (dstPath, Path.GetFileName (file)));
    82.  
    83. foreach (var dir in Directory.GetDirectories(srcPath))
    84. CopyAndReplaceDirectory (dir, Path.Combine (dstPath, Path.GetFileName (dir)));
    85. }
    86.  
    87. }
    88. #endif
    89.  
     
    daogework likes this.
  7. priyanka10tyagi1990

    priyanka10tyagi1990

    Joined:
    Feb 5, 2021
    Posts:
    1
    @Blinkq -> I tried your approach. But still facing below error :-
    Plugin 'AppInvokeSDK.framework' is used from several locations:
    Assets/AppInvokeUnity/Plugins/ios/Source/AppInvokeSDK.xcframework/ios-i386_x86_64-simulator/AppInvokeSDK.framework would be copied to <PluginPath>/AppInvokeSDK.framework
    Assets/AppInvokeUnity/Plugins/ios/Source/AppInvokeSDK.xcframework/ios-armv7_arm64/AppInvokeSDK.framework would be copied to <PluginPath>/AppInvokeSDK.framework
    Please fix plugin settings and try again.

    UnityEditor.Modules.DefaultPluginImporterExtension:CheckFileCollisions(String)
    UnityEditorInternal.PluginsHelper:CheckFileCollisions(BuildTarget) (at /Users/bokken/buildslave/unity/build/Editor/Mono/Plugins/PluginsHelper.cs:25)
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr) (at /Users/bokken/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:197)


    Requirement ->
    1. We are having Unity Project and we are adding AppInvokeSDK.xcFramework(this is our third party SDK).
    2. I have made all the changes as you specified in our SwiftPostProcess File
    3. After that trying to build but still facing the above error that AppInvokeSDK.framework is used(
    Plugins colliding with each other.) error. It is due to AppInvokeSDK.xcFramework. As xcframework having two different folders in it one for device and other for simulator and inside that folder they are having there own AppInvokeSDK.framework.

    Please suggest some way or guide me how to integrate xcFramework in Unity. As, I am new to Unity development
     
  8. hantaoyang

    hantaoyang

    Joined:
    Mar 30, 2022
    Posts:
    1
    You can have a post build process script and use GetFrameworksBuildPhaseByTarget() from https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.html to get guid for "Link binary with libraries" and then add xcframework file to it using AddFileToBuildSection().
     
  9. varikatla

    varikatla

    Joined:
    May 24, 2022
    Posts:
    4
    @hantaoyang
    I have tried the above approach with the below code, but it did not work. Can you please help me on this?

    Code (CSharp):
    1. PBXProject proj = new PBXProject();
    2. string target = proj.GetUnityMainTargetGuid();
    3. string targetxc = proj.GetFrameworksBuildPhaseByTarget(target);
    4. string fGuid = proj.AddFile(destPath, destPath);
    5. proj.AddFileToBuild(target, fGuid);
     
    Last edited: Jul 19, 2022
    gbittencourt likes this.
  10. tarasfromlviv

    tarasfromlviv

    Joined:
    Jun 6, 2013
    Posts:
    25
    Here is what I do, from the answers above it didn't work for me, the key part was to use AddFileToBuildSection to add the framework to linking frameworks phase.

    Code (CSharp):
    1.         private static void AddGoogleMapsFrameworks(string path, PBXProject project, string targetGuid)
    2.         {
    3.             string[] frameworks = { "GoogleMaps.xcframework", "GoogleMapsBase.xcframework", "GoogleMapsCore.xcframework" };
    4.             const string xcodeFrameworksFolder = "Frameworks";
    5.             foreach (var framework in frameworks)
    6.             {
    7.                 var destPath = Path.Combine(path, xcodeFrameworksFolder, framework);
    8.                 var frameworkAssets = AssetDatabase.FindAssets(framework);
    9.                 if (frameworkAssets.Length == 0)
    10.                 {
    11.                     Debug.LogError($"Could not find {framework} in the project");
    12.                 }
    13.                 CopyDirectory(AssetDatabase.GUIDToAssetPath(frameworkAssets.First()), destPath);
    14.                 var fileGuid = project.AddFile(destPath, $"Frameworks/{framework}");
    15.                 var frameworksBuildPhaseGuid = project.GetFrameworksBuildPhaseByTarget(targetGuid);
    16.                 project.AddFileToBuildSection(targetGuid, frameworksBuildPhaseGuid, fileGuid);
    17.                 project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
    18.             }
    19.         }
    20.  
     
    munkiki7 likes this.