Is it possible to use the Xcode Manipulation API to add files to the Embedded Binaries build step?

I need to add a framework to the Embed Binaries build step in Xcode. I’ve asked this before but I think maybe my question was badly phrased or incomplete.

If this is easy, ignore the detailed description below. Thank you!

Within Xcode this is as simple as dragging your framework into the Embedded Binaries area in the targets General page.

This creates a build phase called Embed Frameworks

The project.pbxproj file changes are…

PBXBuildFile section reference added

		C5E4C9731BBC78F4007C4CD8 /* MyEmbedded.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8FC40EFB7EB18859284D579 /* MyEmbedded.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };

PBXCopyFilesBuildPhase section added

C5E4C9741BBC78F4007C4CD8 /* Embed Frameworks */ = {
			isa = PBXCopyFilesBuildPhase;
			buildActionMask = 2147483647;
			dstPath = "";
			dstSubfolderSpec = 10;
			files = (
				C5E4C9731BBC78F4007C4CD8 /* MyEmbedded.framework in Embed Frameworks */,
			);
			name = "Embed Frameworks";
			runOnlyForDeploymentPostprocessing = 0;
		};

PBXNativeTarget section changed to include Embed Frameworks phase

1D6058900D05DD3D006BFB54 /* Unity-iPhone */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Unity-iPhone" */;
			buildPhases = (
				1D60588D0D05DD3D006BFB54 /* Resources */,
				83D0C1FB0E6C8D5900EBCE5D /* ShellScript */,
				83D0C1FD0E6C8D7700EBCE5D /* CopyFiles */,
				1D60588E0D05DD3D006BFB54 /* Sources */,
				1D60588F0D05DD3D006BFB54 /* Frameworks */,
				033966F41B18B03000ECD701 /* ShellScript */,
				C5E4C9741BBC78F4007C4CD8 /* Embed Frameworks */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = "Unity-iPhone";
			productName = "iPhone-target";
			productReference = 1D6058910D05DD3D006BFB54 /* awesumfree.app */;
			productType = "com.apple.product-type.application";
		};

Finally, and I’m not sure if this is required, XCBuildConfiguration section for all builds (Debug, Release etc.) sets this

LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

Hopefully this is detailed enough to get me an answer. I’d really love to be able to build this with Cloud Build but that will only work if I can set this stuff up in a PostProcessBuild function.

So after a lot of messing around I finally got this to work. This is basically what @tarasfromlviv wrote with a few modifications. Here’s how to do it:

  1. Clone Unity’s XcodeAPI: https://bitbucket.org/Unity-Technologies/xcodeapi (Note, it’s a mercurial repository, not Git). I also changed the namespace of all the files to include my prefix, so I can distinguish it from the build in version, something like “Custom.UnityEditor.iOS.Xcode”

  2. To the PBXProject.cs file add the following method:

    public void AddDynamicFrameworkToProject(string targetGuid, string frameworkPathInProject)
    {
        var fileGuid = FindFileGuidByProjectPath(frameworkPathInProject);
        if (fileGuid == null)
        {
            Debug.LogError("Framework not found: " + frameworkPathInProject);
            return;
        }
        // add file reference as embed framework
        PBXBuildFileData embedFrameworkFileData = PBXBuildFileData.CreateFromFile(fileGuid, false, "");
        BuildFilesAdd(targetGuid, embedFrameworkFileData);
        // add "Embed Frameworks" section
        // TODO: check if exists
        PBXCopyFilesBuildPhaseData embedFrameworksSection = PBXCopyFilesBuildPhaseData.Create("Embed Frameworks", "", "10");
        embedFrameworksSection.files.AddGUID(embedFrameworkFileData.guid);
        m_Data.copyFiles.AddEntry(embedFrameworksSection);
        // add "Embed Frameworks" section to "Build phases"
        PBXNativeTargetData target = nativeTargets[targetGuid];
        target.phases.AddGUID(embedFrameworksSection.guid);
    }
    
  3. Add this to your build script:

      [PostProcessBuild]
    

    public static void OnPostProcessBuild(BuildTarget buildTarget, string path)
    {
    if (buildTarget == BuildTarget.iOS)
    {
    string projectPath = path + “/Unity-iPhone.xcodeproj/project.pbxproj”;
    PBXProject pbxProject = new PBXProject();
    pbxProject.ReadFromFile(projectPath);
    string target = pbxProject.TargetGuidByName(“Unity-iPhone”);
    // pbxProject.SetBuildProperty(target, “ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES”, “YES”);
    pbxProject.SetBuildProperty(target, “LD_RUNPATH_SEARCH_PATHS”, “$(inherited) @executable_path/Frameworks”);

           AddDynamicFrameworks(ref pbxProject, target);
    
           pbxProject.WriteToFile(projectPath);
    
           string contents = File.ReadAllText(projectPath);
    
           // Enable CodeSignOnCopy for the framework
           contents = Regex.Replace(contents,
               "(?<=Embed Frameworks)(?:.*)(\\/\\* EXAMPLE\\.framework \\*\\/)(?=; };)",
               m => m.Value.Replace("/* EXAMPLE.framework */",
                   "/* EXAMPLE.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }"));
    
           File.WriteAllText(projectPath, contents);
       }
    

    }

    static void AddDynamicFrameworks(ref PBXProject project, string target)
    {
    const string defaultLocationInProj = “Frameworks/Plugins/iOS”;
    const string coreFrameworkName = “EXAMPLE.framework”;

       string relativeCoreFrameworkPath = Path.Combine(defaultLocationInProj, coreFrameworkName);
       project.AddDynamicFrameworkToProject(target, relativeCoreFrameworkPath);
    
       Debug.Log("Dynamic Frameworks added to Embedded binaries.");
    

    }

  4. Replace “EXAMPLE” (and nothing else) in the code above to the name of your framework.

And voila, Unity will now add your framework to Embedded Binaries automatically!

I still, for the life of me, can’t figure out why this is not natively supported…

So, clone Unity official XCode API and apply this modifications, this will basically add existing frameworks in your project to embedded binaries:

Inside PBXProject:

    public void AddDynamicFrameworkToProject(string targetGuid, string frameworkPathInProject)
    {
        var fileGuid = FindFileGuidByProjectPath(frameworkPathInProject);
        if (fileGuid == null)
        {
            Debug.LogError("GetSocial Framework not found: " + frameworkPathInProject);
            return;
        }
        // add file reference as embed framework
        PBXBuildFileData embedFrameworkFileData = PBXBuildFileData.CreateFromFramework(fileGuid);
        BuildFilesAdd(targetGuid, embedFrameworkFileData);

        // add "Embed Frameworks" section
        // TODO: check if exists
        PBXCopyFilesBuildPhaseData embedFrameworksSection = PBXCopyFilesBuildPhaseData.Create("Embed Frameworks", "10");
        embedFrameworksSection.files.AddGUID(embedFrameworkFileData.guid);
        m_Data.copyFiles.AddEntry(embedFrameworksSection);

        // add "Embed Frameworks" section to "Build phases"
        PBXNativeTargetData target = nativeTargets[targetGuid];
        target.phases.AddGUID(embedFrameworksSection.guid);
    }

Usage Example:

static void AddDynamicFrameworks(PBXProject project, string target)
{
    const string defaultLocationInProj = "Frameworks/Plugins/iOS";
    const string coreFrameworkName = "GetSocial/GetSocial.framework";
    const string uiFrameworkName = "GetSocialUI/GetSocialUI.framework";
    string relativeCoreFrameworkPath = Path.Combine(defaultLocationInProj, coreFrameworkName);
    string relativeUiFrameworkPath = Path.Combine(defaultLocationInProj, uiFrameworkName);
    project.AddDynamicFrameworkToProject(target, relativeCoreFrameworkPath);
    project.AddDynamicFrameworkToProject(target, relativeUiFrameworkPath);
    Debug.Log("GetSocial Dynamic Frameworks added to Embedded binaries.");
}

#if UNITY_EDITOR_OSX

using UnityEditor.iOS.Xcode;
using UnityEditor.iOS.Xcode.Extensions;

#endif

public class TestBuildPostprocessor {
	[PostProcessBuildAttribute(1)]
	public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) {
		
		if (target != BuildTarget.iOS) {
			UnityEngine.Debug.LogWarning ("Target is not iPhone. XCodePostProcess will not run");
			return;
		}
#if UNITY_EDITOR_OSX
		//EmbedFrameworks
		string projPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
		PBXProject proj = new PBXProject();
		proj.ReadFromString(File.ReadAllText(projPath));
		string targetGuid = proj.TargetGuidByName("Unity-iPhone");
		const string defaultLocationInProj = "Plugins/iOS";
		const string coreFrameworkName = "test.framework";
		string framework = Path.Combine(defaultLocationInProj, coreFrameworkName);
		string fileGuid = proj.AddFile(framework, "Frameworks/" + framework, PBXSourceTree.Sdk);
		PBXProjectExtensions.AddFileToEmbedFrameworks(proj, targetGuid, fileGuid);
		proj.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks");
		proj.WriteToFile (projPath);
		//EmbedFrameworks end
#endif
	}
}

this code work for me!

It does not look like this is possible yet via XCode Manipulation API, but it could be added as it’s open source.

Edit:
The forum thread is here: http://forum.unity3d.com/threads/is-it-possible-to-use-the-xcode-manipulation-api-to-add-files-to-the-embedded-binaries-build-step.358357/

In Unity 2017.1 I was able to use this:

string frameworkPath = project.AddFile(Application.dataPath + "PATH_TO_FRAMEWORK/test.framework", "Frameworks/test.framework", PBXSourceTree.Source);
project.AddFileToBuild(target, frameworkPath);

string embedPhase = project.AddCopyFilesBuildPhase (target, "Embed Frameworks", "", "10");
project.AddFileToBuildSection (target, embedPhase, frameworkPath);

project.AddBuildProperty (target, "FRAMEWORK_SEARCH_PATHS", "$(SRCROOT)/PATH_TO_FRAMEWORK/");

Hope it helps!

In Unity 2017.2 following code works for me:

public static void OnPostprocessBuild(BuildTarget target, string projectPath) {
		
	string pbxProjPath = PBXProject.GetPBXProjectPath(projectPath);
	PBXProject pbxProject = new PBXProject();
	pbxProject.ReadFromFile(pbxProjPath);
	
	string targetName = PBXProject.GetUnityTargetName();
	string targetGuidName = pbxProject.TargetGuidByName(targetName);
	pbxProject.WriteToFile(pbxProjPath);

	string basePath = Application.dataPath + "/";
	string frameworkPath = "Plugins/iOS/";
	string []arrFrameworks = {"frameworkA.framework", "frameworkB.framework"};
	foreach(string framework in arrFrameworks) {
		AddEmbeddedFramework(ref pbxProject, targetGuidName, basePath + frameworkPath + framework, frameworkPath + framework);
	}

	pbxProject.WriteToFile (pbxProjPath);
	foreach(string framework in arrFrameworks) {
		string contents = File.ReadAllText(pbxProjPath);
		string pattern = "(?<=Embed Frameworks)(?:.*)(\\/\\* " + framework + "\\ \\*\\/)(?=; };)";
		string oldText = "/* " + framework + " */";
		string updatedText = "/* " + framework + " */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }";
		contents = Regex.Replace(contents, pattern, m => m.Value.Replace(oldText, updatedText));
		File.WriteAllText(pbxProjPath, contents);
	}
}

public static void AddEmbeddedFramework(ref PBXProject project, string target, string frameworkPath, string frameworkName) {
	string fileGuid = project.AddFile(frameworkPath, "Frameworks/" + frameworkName, PBXSourceTree.Source);
	string embedPhase = project.AddCopyFilesBuildPhase (target, "Embed Frameworks", "", "10");
	project.AddFileToBuildSection (target, embedPhase, fileGuid);
	PBXProjectExtensions.AddFileToEmbedFrameworks(project, target, fileGuid);
	project.AddBuildProperty(target, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks");
	project.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(SRCROOT)/PATH_TO_FRAMEWORK/");
}

For Unity 5.x https://forum.unity.com/threads/xcode-embedded-binaries.430750/#post-3979972

@mihakinova

I use your file for adding Embedded Binaries file. But recently I have a framework which used alias files. When xcodeproj got ready I see just one alias file on my framework folder. The framework which I tried to add is Pushwoosh. Do you have any idea how should I do it?

Thanks for your consideration