Unity xcode api

I’m trying to figure out how to use this,

https://bitbucket.org/Unity-Technologies/xcodeapi

Has anyone figured out yet?

2 Likes

Nice, didn’t know that existed.

Looks like they’re using it here, for example: https://bitbucket.org/Unity-Technologies/iosnativecodesamples/src/a0bc90e7d6358e456caf25d717134864218740a7/NativeIntegration/Misc/UpdateXcodeProject/Assets/Editor/XcodeProjectUpdater.cs?at=stable

Someone wrote an example usage here:

http://cflat-inc.hatenablog.com/entry/2015/01/05/074442

Quoted code:

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode; // ←さっきいれたXcodeAPI
using System.Collections;
using System.IO;

public class XcodeProjectMod : MonoBehaviour
{

    // ちょっとしたユーティリティ関数(http://goo.gl/fzYig8を参考)
    internal static void CopyAndReplaceDirectory(string srcPath, string dstPath)
    {
        if (Directory.Exists(dstPath))
            Directory.Delete(dstPath);
        if (File.Exists(dstPath))
            File.Delete(dstPath);

        Directory.CreateDirectory(dstPath);

        foreach (var file in Directory.GetFiles(srcPath))
            File.Copy(file, Path.Combine(dstPath, Path.GetFileName(file)));

        foreach (var dir in Directory.GetDirectories(srcPath))
            CopyAndReplaceDirectory(dir, Path.Combine(dstPath, Path.GetFileName(dir)));
    }

    [PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget == BuildTarget.iPhone)
        {
            string projPath = PBXProject.GetPBXProjectPath(path);
            PBXProject proj = new PBXProject();
      
            proj.ReadFromString(File.ReadAllText(projPath));
            string target = proj.TargetGuidByName("Unity-iPhone");

            // システムのフレームワークを追加
            proj.AddFrameworkToProject(target, "AssetsLibrary.framework", false);
      
            // 自前のフレームワークを追加
            CopyAndReplaceDirectory("Assets/Lib/mylib.framework", Path.Combine(path, "Frameworks/mylib.framework"));
            proj.AddFileToBuild(target, proj.AddFile("Frameworks/mylib.framework", "Frameworks/mylib.framework", PBXSourceTree.Source));

            // ファイルを追加
            var fileName = "my_file.xml";
            var filePath = Path.Combine("Assets/Lib", fileName);
            File.Copy(filePath, Path.Combine(path, fileName));
            proj.AddFileToBuild(target, proj.AddFile(fileName, fileName, PBXSourceTree.Source));

            // Yosemiteでipaが書き出せないエラーに対応するための設定
            proj.SetBuildProperty(target, "CODE_SIGN_RESOURCE_RULES_PATH", "$(SDKROOT)/ResourceRules.plist");

            // フレームワークの検索パスを設定・追加
            proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
            proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks");
      
            // 書き出し
            File.WriteAllText(projPath, proj.WriteToString());
        }
    }
}

I’m trying to figure this all out at the moment. I need to add frameworks, edit the pch file, add additional linker flags, some files and directories, add url schemes, and so on… so far I could do some of it with help from above code. I might post more on it later in case I figure it all out…

Some things:

  • they got the “projPath” wrong for me, I had to change “Unity-iPhone” into “Unity-iPhone.xcodeproj” in “projPath”.

  • There is a class for handling plist in there, but I’ll just use plistbuddy anyway, as it’s part of OS X.

  • So far I fail at adding a folder “by reference” recursively… I have a folder which contains several subfolder of which some contains extra .framework:s and other libs and files. Somehow adding a folder always adds it as “real” folder… I need it to be a reference (yellow folder in Xcode, not blue).

  • I actually have to add the appcontroller.m (replace actual code inside didFinishLaunching… and more) file on every build. It’s a real pain in the neck and I guess it would be hard to pull that off even with these tools… sigh.

2 Likes

Hi @Umai , I am facing the same issues that you reported above. Did you find some answers? My main issues are:

  • Adding compiler flags to files like -fno-objc-arc;

  • Conditional compilation in order to add iPhoneOS or iPhoneSimulator platforms specific lib versions.

Did some of you guys try to use one of the next alternatives?

Use mod-pbxproj. It says it supports adding a folder “by reference” recursively. The Unity way is dumping everything into Assets/Plugins/iOS. You’d still have to add your frameworks to the project so they can be linked.

You can call mod-pbxproj using (shorthand, missing exception handling, logging output, etc.):
Process myProcess = new Process();
myProcess.StartInfo.FileName = “python”;
myProcess.StartInfo.Arguments = ?; // the path to your python file
myProcess.StartInfo.UseShellExecute = false;
// set anything else
myProcess.Start();
myProcess.WaitForExit();

For compiler flags per file, in your python script you need to make a filelist and can do:
project.apply_mods({‘compiler_flags’: {‘-fno-objc-arc’: filelist}})
To make the file list I start with filelist = os.listdir(srcDir) and add/remove. I wrote this for a one-off task so apologies it’s not generalized.

For adding code each time, you should patch it in using a postprocess.

Unity includes a different lib based on if you’re targeting iPhoneOS or iPhoneSimulator so just read that value and add the correct platform specific libs and defines.

If you like you can check out the Native Library Demo in the Unity Cloud Build Demos. It is not directly connected as it an example to add a native library to XCode via post process using the XCode Manipulation API, targeting Unity Cloud Build, but the general concept is the same. Although, there are differenced between Unity 4.x and 5.x as the plugin folders are handled differently.

Please don’t use mod-pbxproj. It does not handle project files correctly. The problem does not arise when opening the project with Xcode though, as it can read slightly corrupted files just fine. However when postprocess steps of various plugins interact with each another, the build sometimes breaks in very strange ways that are hard to understand.

Use Xcode API provided by Unity. Unfortunately its documentation is not available on the website for some reason at the moment. I’ll look into this issue and make sure the documentation is available there in the following days.

2 Likes

mod-pbxproj does handle project files correctly. I’d be happy to re-evaluate if you have an example for which it breaks. But we have over a dozen iOS build jobs using it without any issues.

When people use mod-pbxproj incorrectly, then it can break. But all those fixes are outside of the mod-pbxproj script. I’ve had to fix the postprocess steps of some plugins and it’s usually because they made bad assumptions.

The Xcode API provided by Unity is less clear than mod-pbxproj. Having the documentation would be a big help in re-evaluating if it’s a better solution. One thing that sticks out is that you have to specify more things when using it, which makes it more prone to breaking/being used incorrectly than mod-pbxproj.

1 Like

Any updates on this? Any plans on more documentation? I use mod-pbxproj as well but if that has the potential to cause problems then I’d like to switch over to a cleaner method.

I used the example files to add links to frameworks, which works fine, but now I tried copying a plugin (which doesn’t copy due to a bug in Unity 5 apparently) and the resulting project can’t link to the plugin. It builds when I drag the file in by hand however… any ideas? When copying by hand xcode copies the file and adds it to a group as well as assigns a target, i’m trying to do the same procedure in code but kind of stabbing in the dark…

var fileName = "metaiosdk";
var filePath = Path.Combine("Assets/Plugins/iOS", fileName);
File.Copy(filePath, Path.Combine(path, "Libraries/" +fileName));
proj.AddFileToBuild(target, proj.AddFile( "Libraries/" + fileName,  "Libraries/" + fileName, PBXSourceTree.Source));

I also have a question about the XCode API. For my project I have to include several libraries to build in Xcode, also the gpg framework which has a filesize of 116MB. This means that I cannot include that file directly into my github repo, cause it is too big. Is there a solution for handling such a case with Unity Cloud build and the XCode API?

Can I maybe download the framework file on the fly everytime Unity Cloud builds my app?
Download link is:

Play Games C++ SDK Version 1.4.1

The documentation of the Xcode API is now available here Unity - Scripting API: PBXProject

3 Likes

This looks very useful, thanks!

Haha, I love it! That Japanese-commented code example, of which I have no idea what they are trying to do or why, seems to be one of the best Unity code examples I’ve ever read!

Having my own problems to solve and advert plugin -specific XCode setup requirements to follow, I can just look at how the original programmer has used paths, property names and the methods to create solutions.

Yeah, sorry but the Unity Scripting Reference is once again quite unhelpful. I couldn’t have used the XCode API without looking at the abovementioned example code to see what kind of paths were expected, or the above forum comment mentioning the bad return value for PBXProject.GetProjectPath method, or what these property names are and where I can look them up.

I’m overjoyed to have seen this thread and that example!

I’ve just been trying to get to grips with this Unity Xcode API as well, and come up against the exact same issues as above. PBXProject.GetProjectPath does seem to return the wrong value. You have to change “Unity-iPhone” into “Unity-iPhone.xcodeproj” in “projPath” to get it to work.
The Unity Docs could seriously do with some good examples adding. This would be a hugely helpful api if it were easier to understand.
As it is, I’ve gone back to mod_pbxproj, which works and does the job I need it to.

I have started using the Xcode Manipulation API and overall it seems to be good. I do have a question regarding adding in a dynamic library. I am trying to add in “libz.1.2.5.dylib” or maybe just “libz.1.dylib”.

If I add them in through Xcode, I do it through the “Link Binary with Libraries” from Build Phases. If I try to add in the “lib.1.2.5.dylib” or "libz.1.dylib"using the PBXProject.addFrameworkToProject(), it adds the file, but it is in red in Xcode which would mean that it is missing.

proj.AddFrameworkToProject(target, “libz.1.dylib”, false);

I can copy it our of its location in the iOS SDK and add it to my Unity project, copy it to my BuildProject directory, then add it to the Frameworks, but this seems really ineloquent.

Any advise for the best way to add in the dynamic libraries to Xcode?

Thanks

I’m guessing that you need the full path of the lib, have you tried that?

Thanks for the advice. The full path doesn’t work with the AddFrameworkToProject() method, but it does work if I use the AddFileToBuild with the full path…

string libZPath = “/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libz.1.dylib”;

proj.AddFileToBuild(target, proj.AddFile(libZPath,“Frameworks/libz.1.dylib”,PBXSourceTree.Source));

AddFrameworkToProject is meant only for system frameworks, like “Gamekit.framework”. For any other libraries please use AddFile with correct source tree - PBXSourceTree.Source for libraries already copied to within the project folder and PBXSourceTree.Sdk for system libraries. For example,

proj.AddFileToBuild(target, proj.AddFile(“usr/lib/libz.1.dylib”, “Frameworks/libz.1.dylib”, PBXSourceTree.Sdk));

3 Likes

Thanks for the follow-up. That works for me.

I had a similar problem using the example files and at least solved it on my end.

The example file has the following two lines to set the Framework Search Path…

proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
            proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks");

The problem with that is that the first line ]proj.SetBuildProperty(target, “FRAMEWORK_SEARCH_PATHS”, “$(inherited)”); wipes out any existing framework search paths that may already have been added to the project by another plugin.

So in my case, I had a framework in my Unity Project under “Plugins/iOS/AdColony.framework”. If I don’t run the Post Processor it appears that Unity will automatically add “Plugins/iOS” to the Framework Search Path in Xcode when the project is built.

If I run the example post processor script above it obliterates the “Plugins/iOS/” from the Frameworks Search Path and I have a link compilation error trying to find the headers and I get this…

Lexical or Preprocessor Issue
UnityADC.mm:18:9: 'AdColony/AdColony.h' file not found

The solution for me and maybe for you… Just don’t use

proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");

It shouldn’t be necessary anyway as the default empty Unity Project already sets the Framework Search Paths to “$(inherited)” by default.

Currently it appears just to be a good way to introduce incompatibility between third party plugins and cause developers a lot of extra hours figuring it out. I wish they would revise the sample.