Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 81 additions & 47 deletions Editor/IosLinkOrderDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ public static class IosLinkOrderDiagnostics
{
private const string Prefix = "LiveKit";
private const string Libilivekit = "liblivekit_ffi.a in Frameworks";
private const string Libiphone = "libiPhone-lib.a in Frameworks";
private static readonly string[] ConflictingLibiPhoneMembers =
// Unity exports the engine as Libraries/libiPhone-lib.a up to 6000.4 and as a static
// Frameworks/UnityRuntime.framework from 6000.5; both bundle the old CELT objects.
private static readonly string[] EngineLibraryEntries =
{
"libiPhone-lib.a in Frameworks",
"UnityRuntime.framework in Frameworks"
};
private static readonly string[] ConflictingEngineArchiveMembers =
{
"bands.o",
"celt.o",
Expand Down Expand Up @@ -50,12 +56,14 @@ public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProj
}

var projectText = File.ReadAllText(projectPath);
var fixedProjectText = EnsureSafeUnityFrameworkLinkOrder(projectText, out var wasModified);
var engineEntry = EngineLibraryEntries.FirstOrDefault(entry => projectText.Contains(entry));
var engineName = engineEntry?.Replace(" in Frameworks", string.Empty);
var fixedProjectText = EnsureSafeUnityFrameworkLinkOrder(projectText, engineEntry, out var wasModified);
if (wasModified)
{
File.WriteAllText(projectPath, fixedProjectText);
projectText = fixedProjectText;
Debug.Log($"{Prefix}: iOS link-order fix applied. Moved libiPhone-lib.a after liblivekit_ffi.a in UnityFramework -> Frameworks and Libraries.");
Debug.Log($"{Prefix}: iOS link-order fix applied. Moved {engineName} after liblivekit_ffi.a in UnityFramework -> Frameworks and Libraries.");
}

StripConflictingCodecObjects(pathToBuiltProject);
Expand All @@ -68,64 +76,67 @@ public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProj
}

var livekitIndex = frameworkSection.IndexOf(Libilivekit, StringComparison.Ordinal);
var iphoneIndex = frameworkSection.IndexOf(Libiphone, StringComparison.Ordinal);
if (livekitIndex < 0 || iphoneIndex < 0)
var engineIndex = engineEntry == null ? -1 : frameworkSection.IndexOf(engineEntry, StringComparison.Ordinal);
if (livekitIndex < 0 || engineIndex < 0)
{
Debug.LogWarning($"{Prefix}: iOS link-order diagnostic could not locate both {Libilivekit} and {Libiphone} in UnityFramework -> Frameworks and Libraries.");
Debug.LogWarning($"{Prefix}: iOS link-order diagnostic could not locate both {Libilivekit} and a Unity engine library in UnityFramework -> Frameworks and Libraries.");
return;
}

if (livekitIndex < iphoneIndex)
if (livekitIndex < engineIndex)
{
Debug.Log($"{Prefix}: iOS link-order diagnostic OK. UnityFramework links liblivekit_ffi.a before libiPhone-lib.a.");
Debug.Log($"{Prefix}: iOS link-order diagnostic OK. UnityFramework links liblivekit_ffi.a before {engineName}.");
return;
}

Debug.LogWarning(
$"{Prefix}: iOS link-order diagnostic found a risky order in UnityFramework -> Frameworks and Libraries. " +
"libiPhone-lib.a appears before liblivekit_ffi.a. This repo documents that this can crash Opus/CELT on iOS. " +
$"{engineName} appears before liblivekit_ffi.a. This repo documents that this can crash Opus/CELT on iOS. " +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have script to auto fix the problem and make sure we load liblivekit_ffi.a before libiPhone-lib.a ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the script works until Unity 6.4, but in 6.5 Unity apparently changed their lib composition and naming.

A customer reported the crash: #359
They also already confirmed the fix works for them as well. I will now test if it still works in Unity 2022 or Unity 6.3, but that behaviour should still be intact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the old way of building is not touched in lower Unity versions.

"The post-process fix could not rewrite the exported project automatically."
);
}

private static string EnsureSafeUnityFrameworkLinkOrder(string projectText, out bool wasModified)
private static string EnsureSafeUnityFrameworkLinkOrder(string projectText, string engineEntry, out bool wasModified)
{
wasModified = false;

if (engineEntry == null)
return projectText;

if (!TryGetUnityFrameworkSectionBounds(projectText, out var sectionStart, out var sectionEnd))
return projectText;

var frameworkSection = projectText.Substring(sectionStart, sectionEnd - sectionStart);
var livekitIndex = frameworkSection.IndexOf(Libilivekit, StringComparison.Ordinal);
var iphoneIndex = frameworkSection.IndexOf(Libiphone, StringComparison.Ordinal);
if (livekitIndex < 0 || iphoneIndex < 0 || livekitIndex < iphoneIndex)
var engineIndex = frameworkSection.IndexOf(engineEntry, StringComparison.Ordinal);
if (livekitIndex < 0 || engineIndex < 0 || livekitIndex < engineIndex)
return projectText;

var iphoneLineStart = frameworkSection.LastIndexOf('\n', iphoneIndex);
if (iphoneLineStart < 0)
iphoneLineStart = 0;
var engineLineStart = frameworkSection.LastIndexOf('\n', engineIndex);
if (engineLineStart < 0)
engineLineStart = 0;
else
iphoneLineStart += 1;
engineLineStart += 1;

var iphoneLineEnd = frameworkSection.IndexOf('\n', iphoneIndex);
if (iphoneLineEnd < 0)
iphoneLineEnd = frameworkSection.Length;
var engineLineEnd = frameworkSection.IndexOf('\n', engineIndex);
if (engineLineEnd < 0)
engineLineEnd = frameworkSection.Length;
else
iphoneLineEnd += 1;
engineLineEnd += 1;

var iphoneLine = frameworkSection.Substring(iphoneLineStart, iphoneLineEnd - iphoneLineStart);
var sectionWithoutIphone = frameworkSection.Remove(iphoneLineStart, iphoneLineEnd - iphoneLineStart);
var engineLine = frameworkSection.Substring(engineLineStart, engineLineEnd - engineLineStart);
var sectionWithoutEngine = frameworkSection.Remove(engineLineStart, engineLineEnd - engineLineStart);

var livekitIndexAfterRemoval = sectionWithoutIphone.IndexOf(Libilivekit, StringComparison.Ordinal);
var livekitIndexAfterRemoval = sectionWithoutEngine.IndexOf(Libilivekit, StringComparison.Ordinal);
if (livekitIndexAfterRemoval < 0)
return projectText;

var insertIndex = sectionWithoutIphone.IndexOf('\n', livekitIndexAfterRemoval);
var insertIndex = sectionWithoutEngine.IndexOf('\n', livekitIndexAfterRemoval);
if (insertIndex < 0)
return projectText;

insertIndex += 1;
var reorderedSection = sectionWithoutIphone.Insert(insertIndex, iphoneLine);
var reorderedSection = sectionWithoutEngine.Insert(insertIndex, engineLine);
wasModified = !string.Equals(frameworkSection, reorderedSection, StringComparison.Ordinal);
if (!wasModified)
return projectText;
Expand All @@ -146,36 +157,49 @@ private static bool TryGetUnityFrameworkSectionBounds(string projectText, out in
sectionStart = -1;
sectionEnd = -1;

const string marker = "/* UnityFramework */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;";
var markerIndex = projectText.IndexOf(marker, StringComparison.Ordinal);
if (markerIndex < 0)
return false;
// The frameworks build phase is not labeled consistently across Unity versions,
// so identify it as the one that links liblivekit_ffi.a.
const string marker = "isa = PBXFrameworksBuildPhase;";
var searchIndex = 0;
while (true)
{
var markerIndex = projectText.IndexOf(marker, searchIndex, StringComparison.Ordinal);
if (markerIndex < 0)
return false;

var filesIndex = projectText.IndexOf("\t\t\tfiles = (", markerIndex, StringComparison.Ordinal);
if (filesIndex < 0)
return false;
var filesIndex = projectText.IndexOf("\t\t\tfiles = (", markerIndex, StringComparison.Ordinal);
if (filesIndex < 0)
return false;

var endIndex = projectText.IndexOf("\t\t\t);", filesIndex, StringComparison.Ordinal);
if (endIndex < 0)
return false;
var endIndex = projectText.IndexOf("\t\t\t);", filesIndex, StringComparison.Ordinal);
if (endIndex < 0)
return false;

sectionStart = filesIndex;
sectionEnd = endIndex;
return true;
var section = projectText.Substring(filesIndex, endIndex - filesIndex);
if (section.Contains(Libilivekit))
{
sectionStart = filesIndex;
sectionEnd = endIndex;
return true;
}

searchIndex = markerIndex + marker.Length;
}
}

private static void StripConflictingCodecObjects(string pathToBuiltProject)
{
var archivePath = Path.Combine(pathToBuiltProject, "Libraries", "libiPhone-lib.a");
if (!File.Exists(archivePath))
var archivePath = FindEngineArchivePath(pathToBuiltProject);
if (archivePath == null)
{
Debug.Log($"{Prefix}: iOS archive fix skipped because {archivePath} was not found.");
Debug.Log($"{Prefix}: iOS archive fix skipped because no Unity engine archive was found in the exported project.");
return;
}

var archiveName = Path.GetFileName(archivePath);
if (!TryRunProcess("/usr/bin/ar", $" -t \"{archivePath}\"", out var memberOutput, out var listError))
{
Debug.LogWarning($"{Prefix}: iOS archive fix could not inspect libiPhone-lib.a members. {listError}");
Debug.LogWarning($"{Prefix}: iOS archive fix could not inspect {archiveName} members. {listError}");
return;
}

Expand All @@ -186,17 +210,17 @@ private static void StripConflictingCodecObjects(string pathToBuiltProject)
StringComparer.Ordinal
);

var membersToRemove = ConflictingLibiPhoneMembers.Where(members.Contains).ToArray();
var membersToRemove = ConflictingEngineArchiveMembers.Where(members.Contains).ToArray();
if (membersToRemove.Length == 0)
{
Debug.Log($"{Prefix}: iOS archive fix found no conflicting CELT objects in libiPhone-lib.a.");
Debug.Log($"{Prefix}: iOS archive fix found no conflicting CELT objects in {archiveName}.");
return;
}

var deleteArguments = $" -d \"{archivePath}\" {string.Join(" ", membersToRemove.Select(QuoteArgument))}";
if (!TryRunProcess("/usr/bin/ar", deleteArguments, out _, out var deleteError))
{
Debug.LogWarning($"{Prefix}: iOS archive fix could not strip conflicting objects from libiPhone-lib.a. {deleteError}");
Debug.LogWarning($"{Prefix}: iOS archive fix could not strip conflicting objects from {archiveName}. {deleteError}");
return;
}

Expand All @@ -206,7 +230,17 @@ private static void StripConflictingCodecObjects(string pathToBuiltProject)
return;
}

Debug.Log($"{Prefix}: iOS archive fix stripped {membersToRemove.Length} conflicting CELT objects from exported libiPhone-lib.a.");
Debug.Log($"{Prefix}: iOS archive fix stripped {membersToRemove.Length} conflicting CELT objects from exported {archiveName}.");
}

private static string FindEngineArchivePath(string pathToBuiltProject)
{
var candidates = new[]
{
Path.Combine(pathToBuiltProject, "Libraries", "libiPhone-lib.a"),
Path.Combine(pathToBuiltProject, "Frameworks", "UnityRuntime.framework", "UnityRuntime")
};
return candidates.FirstOrDefault(File.Exists);
}

private static bool TryRunProcess(string fileName, string arguments, out string stdout, out string error)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ add other linker flags to `UnityFramework`:

`-ObjC`

Since `libiPhone-lib.a` has built-in old versions of `celt` and `libvpx` (This will cause the opus and vp8/vp9 codecs to not be called correctly and cause a crash.), you need to ensure that `liblivekit_ffi.a` is linked before `libiPhone-lib.a`.
Unity's engine library — exported as `Libraries/libiPhone-lib.a` up to Unity 6000.4 and as the static `Frameworks/UnityRuntime.framework` from Unity 6000.5 — has built-in old versions of `celt` and `libvpx` (This will cause the opus and vp8/vp9 codecs to not be called correctly and cause a crash.), so you need to ensure that `liblivekit_ffi.a` is linked before the engine library.

The package now applies an iOS post-build fix that rewrites the exported Xcode project so `libiPhone-lib.a` is moved after `liblivekit_ffi.a` in `UnityFramework -> Frameworks and Libraries`.
The package now applies an iOS post-build fix that rewrites the exported Xcode project so the engine library is moved after `liblivekit_ffi.a` in `UnityFramework -> Frameworks and Libraries`.

It also strips the old CELT object cluster from the exported `Libraries/libiPhone-lib.a` so Xcode cannot resolve those codec symbols from Unity's archive.
It also strips the old CELT object cluster from the exported engine archive so Xcode cannot resolve those codec symbols from Unity's archive.

If your project disables package editor scripts or uses a custom Xcode export pipeline that overwrites `project.pbxproj` after LiveKit runs, you may still need to adjust the order manually by removing and re-adding `libiPhone-lib.a`.
If your project disables package editor scripts or uses a custom Xcode export pipeline that overwrites `project.pbxproj` after LiveKit runs, you may still need to adjust the order manually by removing and re-adding the engine library.

## Examples

Expand Down
8 changes: 6 additions & 2 deletions Samples~/Agents/Assets/Editor/BuildPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuil
string guid = proj.TargetGuidByName("Unity-iPhone");
#endif
proj.AddBuildProperty(guid, "OTHER_LDFLAGS", "-ObjC");
// Unity 6000.5+ no longer exports Libraries/libiPhone-lib.a.
string fileGuid = proj.FindFileGuidByProjectPath("Libraries/libiPhone-lib.a");
proj.RemoveFileFromBuild(guid, fileGuid);
proj.AddFileToBuild(guid, fileGuid);
if (fileGuid != null)
{
proj.RemoveFileFromBuild(guid, fileGuid);
proj.AddFileToBuild(guid, fileGuid);
}
proj.WriteToFile(projPath);
}
}
Expand Down
8 changes: 6 additions & 2 deletions Samples~/Meet/Assets/Editor/BuildPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuil
string guid = proj.TargetGuidByName("Unity-iPhone");
#endif
proj.AddBuildProperty(guid, "OTHER_LDFLAGS", "-ObjC");
// Unity 6000.5+ no longer exports Libraries/libiPhone-lib.a.
string fileGuid = proj.FindFileGuidByProjectPath("Libraries/libiPhone-lib.a");
proj.RemoveFileFromBuild(guid, fileGuid);
proj.AddFileToBuild(guid, fileGuid);
if (fileGuid != null)
{
proj.RemoveFileFromBuild(guid, fileGuid);
proj.AddFileToBuild(guid, fileGuid);
}

ApplyAutomaticSigning(proj);

Expand Down
Loading