Xcode 12.3:构建 iOS 模拟器应用,但链接和嵌入的框架是为 iOS + iOS 模拟器构建的。

187

我有一个应用程序,使用了一个链接和嵌入的自定义框架。在Xcode 12.2之前,该应用程序可以正确地构建iOS设备和模拟器版本。但是从Xcode 12.3开始,我遇到了以下错误:

构建iOS模拟器版本时,链接并嵌入的框架'My.framework'是为iOS + iOS模拟器版本构建的。

框架已经为设备和模拟器版本构建好(正如错误信息所说),并且使用了lipo进行了合并,因此它应该能够在任何地方运行而不会出现问题。

这里有什么我忽略的东西吗? Xcode 12.3有相关的更改吗?


8
一个临时的解决方案(对我有效)是使用传统的构建系统。https://dev59.com/o1UK5IYBdhLWcg3w2i3o#54058682 - Tom Knapen
132
另一个解决方法是将BuildSettings -> "Validate Workspace"设置为Yes。这样仍会显示警告,但将能够构建项目。 - o15a3d4l11s2
6
将“Validate Workspace”更改为“是”对我有用!但是当我再次将其更改为“否”时它仍然有效。有什么想法吗? - Hen Shabat
4
使用这个选项会有什么影响? - Nic Hubbard
5
@NicHubbard,这是一个相当棘手的情况。将其切换为“是”会引起一个预构建验证过程。在这种特殊情况下,它发现框架“臃肿”(包含iOS和模拟器架构),并生成一个验证警告。为什么这有帮助 - 因为现在这个问题成为了一个警告,不会再次出现为错误。只有错误会停止构建过程。总体而言,我建议这是一个临时的解决方法,而不是一个永久的解决方案。 - o15a3d4l11s2
显示剩余4条评论
4个回答

74
很抱歉,这是正确的错误,框架不应同时包含iOS和iOS模拟器代码。苹果试图强制我们使用XCFramework来解决这个问题。他们从Xcode 11开始推出,并加强了限制。
唯一正确的解决方法是将框架重建为XCFramework。这很容易实现:
xcrun xcodebuild -create-xcframework \
    -framework /path/to/ios.framework \
    -framework /path/to/sim.framework \
    -output combined.xcframework

你可以从一个组合的.framework开始。复制框架两次,并使用lipo从与不同SDK相关联的二进制文件中删除切片。
它基于苹果这里的原始答案。
我的特殊情况是我在使用Rome时遇到了这个错误,它产生了这些框架(可能的解决方案在这里)。此外,在Carthage方面也存在许多困难。

xcrun xcodebuild -create-xcframework -framework /path/to/ios.framework -framework /path/to/sim.framework -output combined.xcframework - Cameron Lowell Palmer
@alex-shubin 我面临以下问题,我同时使用了架构 arm64 和 armv7。ravihmalviya@Mac-mini linphone-sdk % xcrun xcodebuild -create-xcframework
-framework x86_64-apple-darwin.ios/Frameworks/bctoolbox-ios.framework
-framework arm64-apple-darwin.ios/Frameworks/bctoolbox-ios.framework
-framework armv7-apple-darwin.ios/Frameworks/bctoolbox-ios.framework
-output bctoolbox-ios.xcframework错误 -> ios-arm64 和 ios-armv7 表示两个等效的库定义。
- Ravi Kumar
获取此错误:不支持具有多个平台的二进制文件。 - Muhammad Shauket
非常有用,这里有一个仅3分钟的快速视频:https://youtu.be/TCnhvHUcjrY - Mushrankhan
关于 Rome,我们离添加对 XCFrameworks 的支持非常近了! - Mark
显示剩余2条评论

37
在构建模拟器时,您必须排除设备架构,在构建设备时,您必须排除模拟器的架构。
要做到这一点,请导航到项目的“构建设置”->“排除的架构”->选择配置(Debug / Release / Etc ...)->点击+->“任何iOS模拟器SDK”->添加“arm64”,“arm64e”,“armv7”。
同样,将“x86_64”,“i386”添加到“任何iOS SDK”。

enter image description here

PS:您可以通过运行file <path_to_framework_binary>lipo -info <path_to_framework_binary>来检查框架中存在的所有体系结构。

例如:file /Users/srikanth.kv/MyProject/MyLibrary.framework/MyLibrary


21
看起来很有前途,而且很有道理,但是当我实施这个解决方案时仍然会收到相同的错误信息 :-( - Arik Segal
2
对我来说不起作用。我猜还有其他问题,因为我同时运行着xcode 12.3和12.2。我可以在12.2上为设备和模拟器构建,但是在12.3上构建设备或模拟器时会出现错误。 - Chris
1
在我的 M1 Mac 上构建 iOS 应用时,使用“我的 Mac 设计为 iPad”作为目标时不起作用,但“验证工作区”技巧是一个很好的解决办法。而同样的项目在我基于 Intel 的 iMac Pro 上使用“排除架构”技巧确实有效。 - lifjoy
4
只需在TARGET(而非PROJECT)部分选择“任何iOS模拟器SDK”,不需要插入任何值,就能解决我的问题。 - CularBytes
10
在“构建设置(Build Settings)”中的“工程(PROJECT)”下,搜索“验证工作区(Validate Workspace)”,将“Debug”选项设置为“是(Yes)”。 - CularBytes
显示剩余3条评论

28

我有一个框架,其中包含 x86_64arm64 的通用二进制文件,我使用自定义脚本通过 lipo 合并二进制文件。我在 Xcode 12.3 中遇到了同样的问题,并已经创建了一个解决方法。希望这个问题可以很快得到修复,但在修复之前,一种快速解决方法是减少体系结构并使用所需的框架。

请参见我在这里的答案,了解如何开始生成 .xcframeworks,这是框架作者的长期解决方案

例如,假设我在终端中处于工作目录,在那里我的通用框架some_framework.framework存在。如果我想在实际的物理设备上运行,我执行以下命令:

lipo -thin arm64 some_framework.framework/some_framework -output some_framework
使用上述命令,您提取arm64二进制文件。然后,将当前的some_framework.framework/some_framework替换为新生成的arm64二进制文件。
mv some_framework some_framework.framework

如果你的通用框架只由Objective-C源代码构建,那么你的工作就完成了。但是如果你还有Swift代码,那么你需要更新some_framework.framework/Modules/some_framework.swiftmodule,以便它不包含任何对不是arm64体系结构的引用。

在模拟器上运行时,你需要使用x86_64,你将按照类似的过程进行操作。目前我维护两个版本的框架,直到这个问题被解决。每当我在模拟器和设备之间切换时,我只需切换项目中使用的框架即可。


@spassas,除非有其他证据表明,否则我会认为这是最新Xcode中的一个错误,很快就会得到解决。除了不便之外,您认为还有哪些“许多限制”? - mistahenry
1
是的,我也认为这是Xcode的一个bug。首要的限制是当不同的团队并行地在框架和主应用程序上工作,并且对代码进行隔离访问时,协调变得非常麻烦。另一个限制是它会破坏CI测试和分发流程。 - spassas
1
好的,如果我也有这些限制,那肯定是很糟糕的。希望苹果能够快速解决 :) - mistahenry
这是对我有效的答案。在我的项目中,我正在导入一个名为Intercom的框架,该框架支持iOS+iOS模拟器。令人失望的是,这就是答案...新一组Xcode更新绝对糟糕。无论如何,感谢@mistahenry。 - Declan Land
这是对我有用的答案。感谢 @mistahenry。 - xyzlast
显示剩余2条评论

22

除了mistahenry的回答外,您还可以使用此解决方法在项目中自动处理。

  1. 将不适用于Xcode 12.3的通用框架设置为不嵌入(在常规框架、库和嵌入内容中)

  2. 在“构建阶段”中添加这个“新运行脚本阶段”:

FRAMEWORK_APP_PATH="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"

# 1. Copying FRAMEWORK to FRAMEWORK_APP_PATH
find "$SRCROOT" -name '*.framework' -type d | while read -r FRAMEWORK
do
if [[ $FRAMEWORK == *"MY_WONDERFUL_UNIVERSAL_FRAMEWORK.framework" ]]
then
    echo "Copying $FRAMEWORK into $FRAMEWORK_APP_PATH"
    cp -r $FRAMEWORK "$FRAMEWORK_APP_PATH"
fi
done
# 2. Loops through the frameworks embedded in the application and removes unused architectures.
find "$FRAMEWORK_APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
if [[ $FRAMEWORK == *"MY_WONDERFUL_UNIVERSAL_FRAMEWORK.framework" ]]
then

    echo "Strip invalid archs on: $FRAMEWORK"
    FRAMEWORK_EXECUTABLE_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleExecutable" "$FRAMEWORK/Info.plist")
    FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
    echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
    EXTRACTED_ARCHS=()
    for ARCH in $ARCHS
    do
    echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
    lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
    EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
    done
    echo "Merging extracted architectures: ${ARCHS}"
    lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
    rm "${EXTRACTED_ARCHS[@]}"
    echo "Replacing original executable with thinned version"
    rm "$FRAMEWORK_EXECUTABLE_PATH"
    mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
    codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements $FRAMEWORK_EXECUTABLE_PATH
else
    echo "Ignored strip on: $FRAMEWORK"
fi
done
  • MY_WONDERFUL_UNIVERSAL_FRAMEWORK 替换为你的框架名称,并确保它位于 SRCROOT 目录下。

酷!有没有办法也自动完成第一步呢? - Rubycon
有没有一种方法可以在Xcode 12.3中为所有(先前)嵌入的框架执行此操作,而不是为每个框架单独编写这些解决方案运行脚本? - jxd
11
我现在使用这个解决方案,它更加简单方便:https://dev59.com/SlIG5IYBdhLWcg3wtjy2#65306886。 - jxd
为什么不直接通过“输入文件列表”绑定框架名称呢? - user28434'mstep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接