XCode 15 Cycle inside Unity-iPhone; building could produce unreliable results

I had to update to XCode 15 for the new iOS17. I first ran into the phasescriptexecution issue and solved it by installed XCode Beta 5 along side in the applications folder (per this post: Upgraded to XCode 15, getting phasescriptexecution failed with a nonzero exit code ).

But now when I build I get this error: Cycle inside Unity-iPhone; building could produce unreliable results.

Any thoughts on a solution? Thanks.

Here is the full error:

Cycle inside Unity-iPhone; building could produce unreliable results.

Cycle details:

→ Target ‘Unity-iPhone’: CodeSign /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app

○ Target ‘Unity-iPhone’ has process command with output ‘/Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist’

○ Target ‘Unity-iPhone’ has copy command from ‘/Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/notificationservice.appex’ to ‘/Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/PlugIns/notificationservice.appex’

○ That command depends on command in Target ‘Unity-iPhone’: script phase “Unity Process symbols for Unity-iPhone”

○ Target ‘Unity-iPhone’ has a command with output ‘/Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app.dSYM’

Raw dependency cycle trace:

target: →

node: →

command: →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/_CodeSignature →

command: P0:target-Unity-iPhone--:smile:ebug:CodeSign /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist/ →

directoryTreeSignature: � →

directoryContents: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist →

CYCLE POINT →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist →

command: P0:target-Unity-iPhone--:smile:ebug:ProcessInfoPlistFile /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist /Users//Documents/GitHub.nosync/BSBJ-MobileApp/Build/iOS/Info.plist →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/PlugIns/notificationservice.appex →

command: P0:target-Unity-iPhone--:smile:ebug:Copy /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/PlugIns/notificationservice.appex /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/notificationservice.appex →

node: <target-Unity-iPhone-–fused-phase4-unity-process-symbols-for-unity-iphone> →

command: P0:::Gate target-Unity-iPhone-–fused-phase4-unity-process-symbols-for-unity-iphone →

node: <execute-shell-script-4d46f0999f4e53d6791a1877ccf164d7b4bb0224e304c7959f223177dc1c75b9-target-Unity-iPhone- → ->

command: P2:target-Unity-iPhone--:smile:ebug:PhaseScriptExecution Unity Process symbols for Unity-iPhone /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Intermediates.noindex/Unity-iPhone.build/Debug-iphoneos/Unity-iPhone.build/Script-D4BF5B853AEA9FBFC39E33E9.sh →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app.dSYM/Contents/Resources/DWARF/ →

command: P0:target-Unity-iPhone--:smile:ebug:GenerateDSYMFile /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app.dSYM /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/ →

node: /Users//Library/Developer/Xcode/DerivedData/Unity-iPhone-/Build/Products/Debug-iphoneos/.app/Info.plist

Solved! I moved Embed Frameworks and Embed App Extensions further up (2nd and 3rd) respectively) in build phases in xcode. Unfortunately, it seems you have to do this every time you generate a new build from Unity. But it works. The solution was in this post but I had scroll past the answer to find the fact that it may take moving both of those to work.

1 Like

we are working on a fix, if somebody comes here from search: you can move “Unity Process symbols for Unity-iPhone” to be last (you can drag and drop in xcode):

Hi.
Some code:

namespace YourNameSpace
{
    internal static class FixBuildPhasesOrderForUnityIPhone
    {
        private const string _sectionSearchKey = "Build configuration list for PBXNativeTarget \"Unity-iPhone\"";
        private const string _targetBuildPhaseKey = "Unity Process symbols";

        [PostProcessBuild(45)]
        private static void FixBuildPhasesOrderInProject(BuildTarget target, string pathToBuiltProject)
        {
            if (target != BuildTarget.iOS)
            {
                return;
            }

            // Search for order array.
            var path = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcodeproj", "project.pbxproj");
            var allLines = File.ReadAllLines(path);

            // Search for first line.
            var firstLine = -1;
            for (var i = 0; i + 2 < allLines.Length; i++)
            {
                if (allLines[i].Contains(_sectionSearchKey) && allLines[i + 1].Contains("buildPhases = ("))
                {
                    firstLine = i + 2;
                    break;
                }
            }

            // Check for error.
            if (firstLine <= 0)
            {
                return;
            }

            // Search for last line.
            var lastLine = -1;
            for (var i = firstLine; i + 1 < allLines.Length && i - firstLine < 30; i++)
            {
                if (allLines[i + 1].Contains(");"))
                {
                    lastLine = i;
                    break;
                }
            }

            // Check for error.
            if (lastLine <= 0 || lastLine == firstLine)
            {
                return;
            }

            var buffer = new List<string>();
            for (var i = firstLine; i <= lastLine; i++)
            {
                buffer.Add(allLines[i]);
            }

            var index = buffer.FindIndex(x => x.Contains(_targetBuildPhaseKey));
            if (index == -1)
            {
                return;
            }

            var line = buffer[index];
            buffer.RemoveAt(index);
            buffer.Add(line);

            // Replace old lines with new ones.
            for (var i = 0; i < buffer.Count; i++)
            {
                allLines[firstLine + i] = buffer[i];
            }

            // Save results.
            File.WriteAllLines(path, allLines);
            Debug.Log("BuildPhases order were reordered in xCode project.");
        }
    }
}
2 Likes

Hi! Do you have any ETA for this fix for Unity 2021/2022?

Any updates in this issue?

Unity version 2022.3.14f1 that released today has the following release note:

iOS: Added phony postprocess buildphase in xcode, and make sure that app extensions add copy buildphases before it. (UUM-53588)

Hello! Is there is any news about fix? Solution suggested by vkhylchuk doesn’t work for me.

I managed to get @vkhylchuk 's solution working on Unity 2020 by modifying the target build phase key to:

private const string _targetBuildPhaseKey = "ShellScript";
1 Like

it works!

Hi.
Here is updated version that do search for build phase id and than works with it.

Important notice: xCode do change some coments after oppening project. Example: coment to script phase changed to phase name.
03974A0FAE43BE1C251689E9 /* ShellScript */,
to
03974A0FAE43BE1C251689E9 /* Unity Process symbols */,

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

namespace XCodeBuildFixes
{
internal static class FixBuildPhasesOrderForUnityIPhone
{
private const string _sectionSearchKey = "Build configuration list for PBXNativeTarget \"Unity-iPhone\"";
private const string _targetShellScriptSectionKey = "Begin PBXShellScriptBuildPhase section";
private const string _targetEndShellScriptSectionKey = "End PBXShellScriptBuildPhase section";

[PostProcessBuild(999)]
private static void FixBuildPhasesOrderInProject(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS)
{
return;
}

var path = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcodeproj", "project.pbxproj");
var allLines = File.ReadAllLines(path);

var id = FindShellScriptId(allLines);
if (string.IsNullOrEmpty(id))
{
Debug.LogError("Can not find ID for 'Unity Process symbols'.");
return;
}

// Search for order array.
// Search for first line.
var firstLine = -1;
for (var i = 0; i + 2 < allLines.Length; i++)
{
if (allLines[i].Contains(_sectionSearchKey) && allLines[i + 1].Contains("buildPhases = ("))
{
firstLine = i + 2;
break;
}
}

// Check for error.
if (firstLine <= 0)
{
return;
}

// Search for last line.
var lastLine = -1;
for (var i = firstLine; i + 1 < allLines.Length && i - firstLine < 30; i++)
{
if (allLines[i + 1].Contains(");"))
{
lastLine = i;
break;
}
}

// Check for error.
if (lastLine <= 0 || lastLine == firstLine)
{
return;
}

var buffer = new List<string>();
for (var i = firstLine; i <= lastLine; i++)
{
buffer.Add(allLines[i]);
}

// Check order of Sources.
var index = buffer.FindIndex(x => x.Contains(id));
if (index == -1)
{
return;
}

var line = buffer[index];
buffer.RemoveAt(index);
buffer.Add(line);

// Replace old lines with new ones.
for (var i = 0; i < buffer.Count; i++)
{
allLines[firstLine + i] = buffer[i];
}

// Save results.
File.WriteAllLines(path, allLines);
Debug.Log("BuildPhases order were reordered in xCode project.");
}

private static string FindShellScriptId(string[] allLines)
{
var section = FindShellScriptSection(allLines);
if (section == null)
{
Debug.LogError("Can not find PBXShellScriptBuildPhase section.");
return null;
}

// posible encounters:
// name = Unity Process symbols - old Unity ~2020
// name = "Unity Process symbols for Unity-iPhone"; - newer Unity ~2022
// name = "Unity Process symbols for UnityFramework"; - newer Unity ~2022
var nameIndex = -1;
for (var i = section.Value.SectionStart; i < section.Value.SectionEnd; i++)
{
if (allLines[i].Contains("Unity Process symbols") && !allLines[i].Contains("UnityFramework"))
{
nameIndex = i;
break;
}
}

if (nameIndex == -1)
{
Debug.LogError("Can not find Unity Process symbols for Unity-iPhone phase.");
return null;
}

var idLine = (string)null;
for (var i = nameIndex - 1; i > section.Value.SectionStart; i--)
{
if (allLines[i].Contains(" ShellScript "))
{
idLine = allLines[i];
break;
}
}

if (string.IsNullOrEmpty(idLine))
{
Debug.LogError("Can not find phase ID.");
return null;
}

// line example: D4BF5B853AEA9FBFC39E33E9 /* ShellScript */ = {
var parts = idLine.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || string.IsNullOrWhiteSpace(parts[0]))
{
return null;
}

return parts[0];
}

private static (int SectionStart, int SectionEnd)? FindShellScriptSection(string[] allLines)
{
var firstLine = -1;
for (var i = 0; i + 2 < allLines.Length; i++)
{
if (allLines[i].Contains(_targetShellScriptSectionKey))
{
firstLine = i + 2;
break;
}
}

if (firstLine <= 0)
{
return null;
}

// Search for last line.
var lastLine = -1;
for (var i = firstLine + 1; i < allLines.Length; i++)
{
if (allLines[i].Contains(_targetEndShellScriptSectionKey))
{
lastLine = i - 1;
break;
}
}

return lastLine <= 0 || lastLine == firstLine ? null : (firstLine, lastLine);
}
}
}

Edited:
Thanks to @DavidJakszta

if (allLines[i].Contains("Unity Process symbols for Unity-iPhone"))
-->
if (allLines[i].Contains("Unity Process symbols"))

Edited:

if (allLines[i].Contains("Unity Process symbols"))
-->
if (allLines[i].Contains("Unity Process symbols") && !allLines[i].Contains("UnityFramework"))

@vky

How do I know what to put in ? I assume this is all I need to configure. Specifically _sectionSearchKey

_targetEndShellScriptSectionKey will probably be “Unity Process Symbols since i want to end with them”
_targetShellScriptSectionKey I have no clue

private const string _sectionSearchKey = “Build configuration list for PBXNativeTarget "Unity-iPhone"”;
private const string _targetShellScriptSectionKey = “Begin PBXShellScriptBuildPhase section”;
private const string _targetEndShellScriptSectionKey = “End PBXShellScriptBuildPhase section”;

Edit: The script is causing a thread to be prematurely finalized and nothing happens

@DavidJakszta You need to change nothing. Just add this script into editor folder and Unity will run it automatically after build is done. In case you run it after you open xCode project, it will not longer work because of notice I posted.

Hmm it doesn’t work for me vkhylchuk. I dont quite understand. So when i open the Xcode project the new script will still work or not? Also on line 110 unless i change “Unity Process symbols for Unity-iPhone” to “Unity Process symbols” the IDs can not be found. What do I do if i only want this behaviour on specific builds? When i try running the method I get Thread 0x3286c7000 may have been prematurely finalized.

Maybe we have different build phase name text because we are using different Unity version.
If changing "Unity Process symbols for Unity-iPhone" to "Unity Process symbols" is working for you thats great! I will correct my script.

In our case after update Xcode to 15.3 and Firebase Unity SDK to 11.7.0 we’ve got an error

error: Cycle inside Unity-iPhone; building could produce unreliable results.
Cycle details:
→ Target 'Unity-iPhone' has process command with output '/Users/nhs/builds/XCodeDerivedData/0/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/InstallationBuildProductsLocation/Applications/MyApp.app/Info.plist'
Target 'Unity-iPhone' has copy command from '/Users/nhs/builds/XCodeDerivedData/0/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/RichPushNotifications.appex' to '/Users/nhs/builds/XCodeDerivedData/0/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/InstallationBuildProductsLocation/Applications/MyApp.app/PlugIns/RichPushNotifications.appex'
That command depends on command in Target 'Unity-iPhone': script phase “Crashlytics Run Script”

The error was fixed by moving “Crashlytics Run Script” build phase below “Embed App Extensions” build phase.
To automate the fix a simple post process build script was written:

#if UNITY_IOS
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace murka_core.ProjectBuilderApi.Editor.iOS
{
    internal class FixXcode15BuildPhasesOrder : IPostprocessBuildWithReport
    {
        private const string CrashlyticsShellScriptPhaseName = "Crashlytics Run Script";
        private const string EmbedAppExtensionsPhaseName = "Embed App Extensions";

        public int callbackOrder => 10001;

        public void OnPostprocessBuild(BuildReport report)
        {
            if (report.summary.platform != BuildTarget.iOS)
            {
                return;
            }

            Process(report.summary.outputPath);
        }

        private static void Process(string path)
        {
            string projectPath = PBXProject.GetPBXProjectPath(path);
            PBXProject project = new PBXProject();
            project.ReadFromFile(projectPath);

            string mainTargetGuild = project.GetUnityMainTargetGuid();
            string[] buildPhases = project.GetAllBuildPhasesForTarget(mainTargetGuild);

            string crashlyticsShellScriptPhaseGuid = GetBuildPhaseGuid(CrashlyticsShellScriptPhaseName);
            if (string.IsNullOrEmpty(crashlyticsShellScriptPhaseGuid))
            {
                DebugLog($"\"{CrashlyticsShellScriptPhaseName}\" phase guid not found.");
                return;
            }

            string embedAppExtensionsPhaseGuid = GetBuildPhaseGuid(EmbedAppExtensionsPhaseName);
            if (string.IsNullOrEmpty(embedAppExtensionsPhaseGuid))
            {
                DebugLog($"\"{EmbedAppExtensionsPhaseName}\" phase guid not found.");
                return;
            }

            string GetBuildPhaseGuid(string buildPhaseName)
            {
                foreach (string buildPhaseGuid in buildPhases)
                {
                    if (project.GetBuildPhaseName(buildPhaseGuid) == buildPhaseName)
                    {
                        return buildPhaseGuid;
                    }
                }

                return null;
            }

            Type projectType = project.GetType();
            PropertyInfo nativeTargetsProperty = projectType.GetProperty("nativeTargets", BindingFlags.NonPublic | BindingFlags.Instance);
            object nativeTargets = nativeTargetsProperty?.GetValue(project);
            if (nativeTargets == null)
            {
                DebugLog($"Property 'nativeTargets' not found.");
                return;
            }

            Type nativeTargetsType = nativeTargets.GetType();
            MethodInfo indexerMethod = nativeTargetsType.GetMethod("get_Item");
            if (indexerMethod == null)
            {
                DebugLog($"Method 'get_Item' of {nativeTargetsType.FullName} type not found.");
                return;
            }

            object pbxNativeTargetData = indexerMethod.Invoke(nativeTargets, new object[] { mainTargetGuild });
            FieldInfo phasesField = pbxNativeTargetData.GetType().GetField("phases");
            object phases = phasesField?.GetValue(pbxNativeTargetData);

            FieldInfo listField = phases?.GetType().GetField("m_List", BindingFlags.NonPublic | BindingFlags.Instance);
            if (listField?.GetValue(phases) is not List<string> guidList)
            {
                DebugLog($"Field 'm_List' not found.");
                return;
            }

            guidList.Remove(crashlyticsShellScriptPhaseGuid);
            guidList.Insert(guidList.IndexOf(embedAppExtensionsPhaseGuid) + 1, crashlyticsShellScriptPhaseGuid);
            DebugLog($"Insert {CrashlyticsShellScriptPhaseName} phase {crashlyticsShellScriptPhaseGuid} after {EmbedAppExtensionsPhaseName} phase {embedAppExtensionsPhaseGuid}");

            project.WriteToFile(projectPath);

            void DebugLog(string message) => Debug.Log($"[{nameof(FixXcode15BuildPhasesOrder)}] {message}");
        }
    }
}
#endif
1 Like

@vkhylchuk 's solution didn’t work for me - it wasn’t finding the correct build phase ID. So I’ve modified @stdev33 's to move the Unity Process Symbols phase to the bottom of the list.

I’m on Unity 2021.3.15f1, XCode 15.4.

#if UNITY_IOS
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace Editor.BuildPostProcess
{
    internal class FixXcode15BuildPhasesOrder : IPostprocessBuildWithReport
    {
        private const string UnityProcessSymbolsPhaseName = "Unity Process symbols";

        public int callbackOrder => 10001;

        public void OnPostprocessBuild(BuildReport report)
        {
            if (report.summary.platform != BuildTarget.iOS)
            {
                return;
            }

            string projectPath = PBXProject.GetPBXProjectPath(report.summary.outputPath);
            PBXProject project = new();
            project.ReadFromFile(projectPath);

            string mainTargetGuid = project.GetUnityMainTargetGuid();
            string[] buildPhaseGuids = project.GetAllBuildPhasesForTarget(mainTargetGuid);

            string unityProcessSymbolsPhaseGuid = GetBuildPhaseGuid(UnityProcessSymbolsPhaseName);
            if (string.IsNullOrEmpty(unityProcessSymbolsPhaseGuid))
            {
                DebugLog($"\"{UnityProcessSymbolsPhaseName}\" phase guid not found.");
                return;
            }

            string GetBuildPhaseGuid(string buildPhaseName)
            {
                foreach (string buildPhaseGuid in buildPhaseGuids)
                {
                    DebugLog($"BuildPhase [Name {project.GetBuildPhaseName(buildPhaseGuid)}] [GUID {buildPhaseGuid}]");
                    if (project.GetBuildPhaseName(buildPhaseGuid) == buildPhaseName)
                    {
                        return buildPhaseGuid;
                    }
                }

                return null;
            }

            Type projectType = project.GetType();
            PropertyInfo nativeTargetsProperty = projectType.GetProperty("nativeTargets", BindingFlags.NonPublic | BindingFlags.Instance);
            object nativeTargets = nativeTargetsProperty?.GetValue(project);
            if (nativeTargets == null)
            {
                DebugLog($"Property 'nativeTargets' not found.");
                return;
            }

            Type nativeTargetsType = nativeTargets.GetType();
            MethodInfo indexerMethod = nativeTargetsType.GetMethod("get_Item");
            if (indexerMethod == null)
            {
                DebugLog($"Method 'get_Item' of {nativeTargetsType.FullName} type not found.");
                return;
            }

            object pbxNativeTargetData = indexerMethod.Invoke(nativeTargets, new object[] { mainTargetGuid });
            FieldInfo phasesField = pbxNativeTargetData.GetType().GetField("phases");
            object phases = phasesField?.GetValue(pbxNativeTargetData);

            FieldInfo listField = phases?.GetType().GetField("m_List", BindingFlags.NonPublic | BindingFlags.Instance);
            if (listField?.GetValue(phases) is not List<string> guidList)
            {
                DebugLog($"Field 'm_List' not found.");
                return;
            }

            guidList.Remove(unityProcessSymbolsPhaseGuid);
            guidList.Add(unityProcessSymbolsPhaseGuid);
            DebugLog($"Insert {UnityProcessSymbolsPhaseName} phase {unityProcessSymbolsPhaseGuid} at end of list.");

            project.WriteToFile(projectPath);

            void DebugLog(string message) => Debug.Log($"[{nameof(FixXcode15BuildPhasesOrder)}] {message}");
        }
    }
}
#endif
1 Like