Today I Learned

Enable Bitcode flag in Unity

When building an application with XCode, we have an option to set the Enable Bitcode flag to either YES or NO via Build Settings > Build Options. By embedding bitcode (setting the flag to YES), we give Apple an opportunity to rebuild our application on their side and optimize it further, without being required to upload a new build. It can happen, if some bugs in the compiler are fixed, or a new device type with a new architecture type is introduced.

There are some situations, however, that we might want to disable this functionality altogether.

  • The project with bitcode enabled requires all of its frameworks to be compiled with bitcode support as well. Sometimes, the third-party dependencies that we use, are compiled without it, or are created with newer versions of the compiler, that we cannot support yet in our project for some reason.
  • Another scenario is related to dSYM files and crash reporting services, which require these files to present a crash report that is informative and readable for us. Now, as the bitcode is enabled, the final dSYMs are produced by Apple. It means that we would have to download them from the portal and then upload them to the third-party service of our choice.

In the Unity game engine, the bitcode is enabled by default with no options in project settings to disable it and it is also suggested to leave it enabled. However, due to the situations described above, we might actually have to disable it. To do so, we would have to generate a XCode project and then change it in build settings manually every time we create a build - which is inefficient. Fortunately, we have Scriptable Build Pipeline (SBP) at our disposal, which will allow us to automatically modify projects generated by XCode whenever we trigger a build. Create a new file in Assets > Scripts > Editor directory called IOSBitcodePostprocessor.cs and paste the following as the content:

using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEditor;
 
public sealed class IOSBitcodePostprocessor
{
   public static bool enableBitcode = true;
  
   [PostProcessBuildAttribute]
   public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) {
       switch(target) {
           case BuildTarget.iOS:
               setupBitcode(pathToBuiltProject);
               break;
           default: break;
       }
   }
 
   private static void setupBitcode(string pathToBuiltProject) {
       var project = new PBXProject();
       var pbxPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
       project.ReadFromFile(pbxPath);
       setupBitcodeFramework(project);
       setupBitcodeMain(project);
       project.WriteToFile(pbxPath);
   }
 
   private static void setupBitcodeFramework(PBXProject project) {
       setupBitcode(project, project.GetUnityFrameworkTargetGuid());
   }
 
   private static void setupBitcodeMain(PBXProject project) {
       setupBitcode(project, project.GetUnityMainTargetGuid());
   }
 
   private static void setupBitcode(PBXProject project, string targetGUID) {
       project.SetBuildProperty(targetGUID, "ENABLE_BITCODE", enableBitcode ? "YES" : "NO");
   }
}

This code will modify every iOS build triggered by the Unity game engine by applying the desired value to the Enable Bitcode flag to both targets (main & framework). We can now change it easily, by modifying enableBitcode variable on top of the script. We use an attribute called PostProcessBuildAttribute to mark a method as being our entry point for the Scriptable Build Pipeline post processor, in which we check what platform we are currently building for and act accordingly.