如何修复我的构建:具有 CocoaPods 依赖项和 2 个目标(iOS 和 tvOS)的 .framework 文件

How to fix my build: .framework file with CocoaPods dependencies and 2 targets (iOS and tvOS)

我有一个需求稍微复杂的项目,所以让我们从期望的目标开始:

现在,然后:我已经完成了所有这些工作。这花了很长时间并进行了大量试验,但我有一个满足上述所有要求的项目。我的 Podfile 看起来像这样:

播客文件

use_frameworks!

def common()
    pod 'CocoaLumberjack', '~> 3.5'
    pod 'JSONModel', '~> 1.8'
end

target 'MyFramework-iOS' do
    platform :ios, '9.0'
    common()
    pod 'GoogleAds-IMA-iOS-SDK', '~> 3.9'
end

target 'MyFramework-tvOS' do
    platform :tvos, '9.1'
    common()
    pod 'GoogleAds-IMA-tvOS-SDK', '~> 4.0'
end

我的 .podspec 看起来像这样:

MyFramework.podspec

Pod::Spec.new do |spec|
  spec.name         = "MyFramework"
  spec.version      = "1.0.0"
  spec.summary      = "A framework for interfacing with our back-end"

  spec.description  = <<-DESC
    A longer description
  DESC

  spec.homepage     = "https://example.com/"
  spec.license      = "MIT"

  spec.authors             = {
    "Steven Barnett" => "xxx@example.com"
  }

  spec.ios.deployment_target = "9.0"
  spec.tvos.deployment_target = "9.1"

  spec.source       = { :git => "https://bitbucket.org/xxx/MyFramework.git", :tag => "#{spec.version}" }

  spec.source_files  = "Sources/Common/**/*.{h,m}", "Sources/MyFramework.h"
  spec.ios.source_files  = "Sources/iOS/**/*.{h,m}"
  spec.tvos.source_files = "Sources/tvOS/**/*.{h,m}"

  spec.ios.resource_bundles = {
    "MyFramework" => ["Assets.xcassets", "Sources/iOS/**/*.{xib}"]
  }
  spec.tvos.resource_bundles = {
    "MyFramework" => ["Assets.xcassets", "Sources/tvOS/**/*.{xib}"]
  }

  spec.frameworks = "Foundation", "UIKit", "AVKit"

  spec.requires_arc = true

  spec.dependency "CocoaLumberjack", "~> 3.5"
  spec.dependency "JSONModel", "~> 1.8"
  spec.ios.dependency "GoogleAds-IMA-iOS-SDK", "~> 3.9"
  spec.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "~> 4.0"
end

我的源代码分为三个文件夹:

在我项目的构建阶段中,我使用以下脚本添加了一个 Run Script 阶段:

运行 脚本

UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

if [ "true" == ${ALREADYINVOKED:-false} ]; then
    echo "RECURSION: Detected, stopping"
else
    export ALREADYINVOKED="true"

    # make sure the output directory exists
    mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

    # Step "0". Build our project twice
    echo "Building for AppleTV Device (redundantly... sigh)"
    xcodebuild -workspace "${PROJECT_DIR}/${PROJECT}.xcworkspace" -scheme "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk appletvos ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" OBJROOT="${OBJROOT}/DependentBuilds" OTHER_CFLAGS="-fembed-bitcode"

    echo "Building for AppleTV Simulator"
    xcodebuild -workspace "${PROJECT_DIR}/${PROJECT}.xcworkspace" -scheme "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk appletvsimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" OBJROOT="${OBJROOT}/DependentBuilds" OTHER_CFLAGS="-fembed-bitcode"

    # Step 1. Copy the framework structure (from iphoneos build) to the universal folder
    echo "Copying to output folder"
    cp -R "${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/"

    # Step 2. Create universal binary file using lipo and place the combined executable in the copied framework directory
    echo "Combining executables"
    lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${EXECUTABLE_PATH}" "${BUILD_DIR}/${CONFIGURATION}-appletvsimulator/${EXECUTABLE_PATH}" "${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}"

    # Step 3. Create universal binaries for embedded frameworks
    for FRAMEWORK in $( find "${TARGET_BUILD_DIR}" -type d -name "*.framework" ); do
        FRAMEWORK_PATH="${FRAMEWORK##$TARGET_BUILD_DIR}"
        BINARY_NAME="${FRAMEWORK##*/}"
        BINARY_NAME="${BINARY_NAME%.*}"
        # I don't know how to do "if not" in Bash. Sue me.
        if [[ $BINARY_NAME == Pods_* ]] || [[ $BINARY_NAME == $TARGET_NAME ]]; then
            echo "Skipping framework: $FRAMEWORK_PATH"
        else
            echo "Creating fat binary for framework: $FRAMEWORK_PATH"
            cp -r "${FRAMEWORK}" "${UNIVERSAL_OUTPUTFOLDER}/"
            lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${BINARY_NAME}.framework/${BINARY_NAME}" "${BUILD_DIR}/${CONFIGURATION}-appletvsimulator/${FRAMEWORK_PATH}/${BINARY_NAME}" "${TARGET_BUILD_DIR}/${FRAMEWORK_PATH}/${BINARY_NAME}"
        fi
    done

    # Step 4. Manually copy some files to the output dir
    cp -r "${SOURCE_ROOT}/Pods/GoogleAds-IMA-tvOS-SDK/GoogleInteractiveMediaAds.framework" "${UNIVERSAL_OUTPUTFOLDER}"
    cp "${SOURCE_ROOT}/generate-xamarin-bindings.py" "${UNIVERSAL_OUTPUTFOLDER}"

    # Step 5. Convenience step
    open "${UNIVERSAL_OUTPUTFOLDER}"
fi

此脚本是我在网上找到的类似脚本的修改版。它基本上执行以下操作:

  1. 为嵌入了位码的设备(ARM 处理器)构建我的项目
  2. 为嵌入了位码的模拟器(x86 处理器)构建我的项目
  3. 使用 lipo 为我的项目创建一个 fat 二进制文件
  4. 迭代使用 lipo 为我依赖的所有框架创建胖二进制文件
  5. 将我的框架和我依赖的所有框架复制到 "universal" 输出目录

您可能还会注意到 generate-xamarin-bindings.py 文件。出于我的目的,这几乎可以忽略。它只是使用 Objective Sharpie 来自动创建 Xamarin 绑定,然后使用 JSONModel 修复一些已知错误(定义为 @property (retain, nonatomic, nullable) NSString <Optional> *test; 的变量将作为 Optional test { get; set; } 而不是 string test { get; set; })

除此之外,我不记得必须对所有脚本、构建设置和配置文件进行哪些额外的配置和调整 - 但如果出现任何问题,我可以查看代码并回答它们。

我有一个使用 CocoaPods 拉入我的 SDK 的测试项目,如下所示:

Examples/tvOS/Podfile

use_frameworks!
platform :tvos, '9.1'

target 'MyFramework Example tvOS App' do
    pod 'MyFramework', :path => '../../'
end

此测试项目构建完美,我可以 运行 在模拟器或真实设备上构建它。几周前,我还能够将我的测试项目部署到 Apple Store (TestFlight),但是从那以后我做了一些更改(包括从标准构建切换到胖二进制文件),所以我不确定它是否正确仍然有效(我会测试这个)

此外,当我们构建分析和创建 Xamarin 绑定时,我们可以嵌入所有四个框架(MyFramework.frameworkCocoaLumberjack.frameworkJSONModel.frameworkGoogleInteractiveMediaAds.framework)进入我们的 Xamarin 应用程序,它们将在模拟器和真实设备上构建和 运行。

我们 运行 遇到的问题是当我们试图将其中一个基于 Xamarin 的应用程序推送到 Apple Store (TestFlight) 时。我们收到 11 个错误:

  • ERROR ITMS-90166: "Missing Code Signing Entitlements. No entitlements found in bundle 'com.xxx.MyFramework' for executable 'Payload/InternalApp.app/Frameworks/MyFramework-tvOS.framework/MyFramework.bundle/MyFramework'."
  • ERROR ITMS-90668: "Invalid Bundle Executable. The executable file 'InternalApp.app/Frameworks/MyFramework-tvOS.framework/MyFramework-tvOS' contains incomplete bitcode. To compile binaries with complete bitcode, open Xcode and choose Archive in the Product menu"
  • ERROR ITMS-90668: "Invalid Bundle Executable. The executable file 'InternalApp.app/Frameworks/CocoaLumberjack.framework/CocoaLumberjack' contains incomplete bitcode. To compile binaries with complete bitcode, open Xcode and choose Archive in the Product menu"
  • ERROR ITMS-90668: "Invalid Bundle Executable. The executable file 'InternalApp.app/Frameworks/JSONModel.framework/JSONModel' contains incomplete bitcode. To compile binaries with complete bitcode, open Xcode and choose Archive in the Product menu"
  • ERROR ITMS-90635: "Invalid Mach-O Format. The Mach-O in bundle "InternalApp.app/Frameworks/CocoaLumberjack.framework" isn't consistent with the Mach-O in the main bundle. The main bundle Mach-O contains arm64(bitcode), while the nested bundle Mach-O contains arm64(machine code). Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting."
  • ERROR ITMS-90635: "Invalid Mach-O Format. The Mach-O in bundle "InternalApp.app/Frameworks/MyFramework-tvOS.framework" isn't consistent with the Mach-O in the main bundle. The main bundle Mach-O contains arm64(bitcode), while the nested bundle Mach-O contains arm64(machine code). Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting."
  • ERROR ITMS-90635: "Invalid Mach-O Format. The Mach-O in bundle "InternalApp.app/Frameworks/JSONModel.framework" isn't consistent with the Mach-O in the main bundle. The main bundle Mach-O contains arm64(bitcode), while the nested bundle Mach-O contains arm64(machine code). Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting."
  • ERROR ITMS-90171: "Invalid Bundle Structure - The binary file 'InternalApp.app/Frameworks/MyFramework-tvOS.framework/MyFramework.bundle/MyFramework' is not permitted. Your app can't contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. Refer to the Bundle Programming Guide at https://developer.apple.com/go/?id=bundle-structure for information on the tvOS app bundle structure."
  • ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at 'InternalApp.app/Frameworks/MyFramework-tvOS.framework/MyFramework-tvOS' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
  • ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at 'InternalApp.app/Frameworks/CocoaLumberjack.framework/CocoaLumberjack' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
  • ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at 'InternalApp.app/Frameworks/JSONModel.framework/JSONModel' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."

所以我开始尝试手动跟踪构建过程以找出哪里出了问题或出了什么问题。我已经在使用最新的 Xcode,并且我们已经尝试了 Product > ArchiveProduct > Build For > Profiling,所以我不认为 ITMS-90668 的建议解决方案ITMS-90209 是准确的。

在手动查看构建过程时,我注意到一些东西是 st运行ge:

  1. 当我构建我的应用程序的 tvOS 版本时,在 ~/Library/Developer/Xcode/DerivedData/MyFramework-xxxxxx/Build/Products(此后:${BUILD_ROOT})iOS 正在构建我的依赖项的 tvOS 版本:
    • ${BUILD_ROOT}/Release-appletvos/JSONModel-iOS.framework
    • ${BUILD_ROOT}/Release-appletvos/JSONModel-tvOS.framework
    • ${BUILD_ROOT}/Release-appletvsimulator/JSONModel-iOS.framework
    • ${BUILD_ROOT}/Release-appletvsimulator/JSONModel-tvOS.framework
    • 等(与 CocoaLumberjack 相同)
  2. 果然,如果我查看 ${BUILD_ROOT}/Release-universal/MyFramework-tvOS.framework/MyFramework.bundle,里面有一个名为 MyFramework 的可执行文件。我 no 知道是谁或什么在创建这个可执行文件,它来自哪里,或者为什么它被包含在我的包中。在任何构建阶段中都没有 引用它,但这似乎是 ITMS-90171[ 的原因=240=]
  3. GoogleInteractiveMediaAds 框架是唯一没有启用位码的框架,这让我怀疑位码不是 向商店提交应用程序所必需的,因为它也是唯一没有抛出任何错误的框架(尽管我之前被告知您需要包含位码)

我正在尝试做的事情听起来并不难。我只是在创建一个库(如 CocoaLumberjack),它可以是 运行 在 iOS 或 tvOS(如 CocoaLumberjack)上,但它具有依赖性(不像 CocoaLumberjack)和它可以作为 .framework 文件而不是仅仅作为 CocoaPod 提供(不确定 CocoaLumberjack 是否有这个?)。但是我找不到我遇到的问题的任何答案或一些奇怪的构建行为的任何解释。

具体来说:

  • 为什么我的 .bundle 中出现一个可执行文件?
  • 当我尝试构建我的 tvOS 版本时,为什么它会构建依赖框架的 iOS 版本?
  • 我需要位码吗? 或者我应该禁用它吗?

更新

  • Google 互动媒体广告框架 有位码,只是没有 __LLVM
  • 从我的 .bundle 中删除二进制文件将 ITMS-90171 替换为一个关于 Info.plist 引用不存在的 CFBundleExecutable 的新错误(我忘记了 ITMS-X 代码)。删除此二进制文件 处理了 ITMS-90166
  • 通过转到资源包的目标并选择 "Info" 我可以删除 "Executable file" 键以删除输出 Info.plist 中的这一行,但是它没有修复正在运行的可执行文件首先创建。当手动删除二进制文件时,它确实防止了新错误

因此,通过修改 Info.plist 并删除该可执行文件(目前手动删除,因为我不知道是什么生成了它),11 个错误中的 2 个消失了,留下 3 个错误的 3 个实例(总共共 9 个):

  • CocoaLumberjack(ITMS-90668、ITMS-90635 和 ITMS-90209)
  • JSONModel(ITMS-90668、ITMS-90635 和 ITMS-90209)
  • MyFramework(ITMS-90668、ITMS-90635 和 ITMS-90209)

更新 2

  • BITCODE_GENERATION_MODE=bitcode 标志添加到我的 xcodebuild 命令中 似乎 可以改善一些事情。我的输出二进制文件从 1.2 MB 变为 3.4 MB(大概包含 "complete" 位码)。然而奇怪的事情发生了。 ITMS-90668 的三个实例并没有消失,所有三个错误(ITMS-90668、ITMS-90635 和 ITMS-90209)都消失了 对于单个框架:JSONModel

我现在看到 6 个错误(CocoaLumberjack 和 MyFramework 各抛出 3 个错误)。但我不明白 JSONModel 是如何或为什么被修复的,而其他问题却没有

另一个附录:当我添加位码生成模式标志时,我的包中的可执行文件开始出现链接器错误(它不应该存在,我仍然不知道是什么在创建它)。我注意到 OTHER_CFLAGS 被传递给 clang 的每次调用,而不仅仅是相关的调用,所以我删除了 OTHER_CFLAGS="-fembed-bitcode" 并修复了链接器错误

一个最后的附录:我注意到当我使用我的运行脚本来生成一个胖二进制文件时,只有*-tvOS版本我的依赖关系已经建立。我的 运行 脚本正在构建 *-iOS 版本。这似乎对我在 Apple Store 看到的错误没有影响,但它会减慢我的构建速度,创建额外的文件,并发出一些关于隐式依赖检测发现两个候选者的警告。我不知道如何解决这个问题。

更新 3

我已经解决了一个问题!

因此出现在我的包中的 st运行ge 二进制文件是由于 VERSIONING_SYSTEM 构建设置默认为 apple-generic。 Apple Generic Versioning 系统创建一个源文件调用 <ProjectName>_vers.c,对其进行编译,然后将此文件链接到某些库(我猜是系统库?)

这是 this 文件,当我有 OTHER_CFLAGS="-fembed-bitcode" 时导致我的链接器错误,因为这个二进制文件正在设置 -bundle 这是不兼容的。通过将版本控制系统设置为 None 我能够在没有任何链接器错误的情况下恢复此标志!

我刚刚编译并尝试再次推送到 Apple store 这次一切正常!

总结

  • 为了修复 ITMS-90166 和 ITMS-90171,我必须将任何资源包目标的 Versioning System 设置为 None
  • 修复此问题后,您必须确保资源包的 info.plist 包含 Executable File 键(如果存在则将其删除)
  • 为了修复 ITMS-90668、ITMS-90635 和 ITMS-90209,我必须将标志 BITCODE_GENERATION_MODE=bitcodeENABLE_BITCODE=YES 添加到我的 xcodebuild 命令(连同现有的 OTHER_CFLAGS="-fembed-bitcode")

在所有这一切之后,我的构建系统仍然有一个奇怪的怪癖,但它只会抛出警告(而不是错误),我可以短期忍受它。这是事实,当我 运行 xcodebuild 而不是仅仅构建 tvOS 版本时,它正在构建我的依赖项的 iOS 版本。

我不知道这个问题/答案对其他偶然发现它的人有多大用处,因为我有很多问题和非常具体的设置。但我想继续 post 我 确实 找到了我的问题的解决方案。

首先,我需要设置 ENABLE_BITCODE=YESBITCODE_GENERATION_MODE=bitcode 标志。但是,设置这两个标志可防止我的代码因链接器错误而构建。此错误是因为我的资源包目标使用的是 Apple 通用版本控制 -- 资源包不支持

通过将 Versioning System 构建设置设置为 "None",它修复了所有问题