解决Xcode编译时因库文件CPU架构导致无法在 iOS Simulator运行的问题

最近在编译mlc-llm的iOS客户端时,遇到了一个无法编译通过的问题。具体来说,mlc-llm的iOS客户端在编译时需要多个a库文件,生成过程按照mlc-llm的README介绍(包括在iOS目录下的README)编译生成,库的具体作用在mlc-llm项目中有详细描述,与编译问题无关故这里不再赘述。

如果直接按照流程编译app在真机(我使用的是iPhone 14 Pro Max)上运行则没有问题,但如果是选择build target为Simulator时就会出现building for iOS Simulator, but linking in object file built for iOS, for architecture arm64的提示。

一开始的时候我直接在StackOverflow上进行搜索,大部分的答案都指向了这个链接https://stackoverflow.com/questions/63607158/xcode-building-for-ios-simulator-but-linking-in-an-object-file-built-for-ios-f?answertab=modifieddesc#tab-top,其中最完整的解释了这个问题的帖子是https://stackoverflow.com/a/76270278

简单来说,Xcode编译时iOS和iOS Simulator作为两种不同的build target,iOS Simulator运行时需要a文件同时包括x86_64和arm64两种架构的库函数,而iOS运行时仅需要arm64(因为所有的iOS设备的CPU都是ARM架构的)。从这个链接里可以看出来Xcode这种报错很常见了,大部分人提供的解决方案是:

1、开启Release编译模式的Build Active Architecture Only

2、设置EXCLUDED_ARCHS项,或者在Build选项中指定Excluded Architectures,对于DEBUG和RELEASE选项均设置Any iOS Simulator SDK中指定过滤掉arm64

如果我们在编译这些a文件的时候完全按照mlc-llm的教程,那么可以发现只包含了arm64(可以使用lipo -info来查看a文件信息),所以如果我们不做任何修改的话,那么由于不包含x86_64的,无法通过Xcode的编译。考虑使用上述提到的两种解决方案,编译确实能够通过,但是仍然无法运行。

这个时候我就想到了,因为我现在使用的是M2的Mac进行的编译,如果exclude掉了arm64,此时运行库里就并没有arm64的库了。遵循这个思路,那么是不是我们把exclude里的arm64换成x86_64就可以了呢?然而答案是否定的,Xcode仍然会提示缺少框架可用的库,并提示说需要联系vendor提供所有架构的函数库。也就是说运行在Apple Silicon上的iOS Simulator一定要保证APP所需的库包含x86_64和ARM64两种。这个地方我仍然有疑问,按道理说我在M2芯片的Mac上的iOS Simulator应该也是ARM64架构的(已经通过活动监视器确认),那么为什么会一定需要intel架构的库呢?

再次搜索相关的信息,发现有人提到使用Rosetta来运行Xcode可以解决这个问题,我没有去尝试这个方案,一是觉得Rosetta运行的速度会大打折扣,二是觉得我本来就是一个Apple Silicon的机器,为何需需要Rosetta来运行Xcode的x86部分然后再转成一个ARM来运行。更何况,我们编译得到的库本来就缺少x86。

最终,我决定更换思路,不考虑从Xcode编译这边来解决问题,而是按照提示把a库文件的x86部分补充上。分析mlc-llm的源代码,发现大部分库文件是由C++编写而成,因此只需要替换掉prepare_libs.sh文件中的运行CMake的CMAKE_OSX_ARCHITECTURES选项,由arm64修改为arm64;x86_64即可得到包含两种架构的库文件。

然而,仅仅修改了这里并不能完全解决问题,虽然我们从Xcode的编译结果来看报错的库减少为只有两个了,但是仍然是有两个库报错,仔细查看CMakeList文件,可以发现这两个库中的libtokenizers_c.a这个文件是单独由rust编译而来,因此需要单独处理,我这边是在编译脚本文件中添加了rustup target add x86_64-apple-ios,然后重新编译后再用lipo -create aarch64-apple-ios/release/libtokenizers_c.a ../../x86_64-apple-ios/release/libtokenizers_c.a -output libtokenizers_c.a将两种架构的库整合为一个。这样操作后解决了问题,APP也成功在iOS Simulator中成功运行。不过,这里又留下了一个疑问,我使用lipo查看libmodel_iphone.a文件时发现这个库文件其实仅仅包含了arm64,但是这一次我没有做任何处理Xcode也没有报错了,合理推测是这个库用到的函数仅仅只被某些特定LLM模型使用,或者是Simulator运行过程中暂时没有用到。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注