从 Xcode 迁移到 Bazel

报告问题 查看源代码 每夜 build · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

本页介绍了如何使用 Bazel 构建或测试 Xcode 项目。该文档介绍了 Xcode 和 Bazel 之间的区别,并提供了将 Xcode 项目转换为 Bazel 项目的步骤。同时还提供了问题排查解决方案来解决常见错误。

Xcode 和 Bazel 之间的差异

  • Bazel 要求您通过构建规则明确指定每个构建目标及其依赖项,以及相应的构建设置。

  • Bazel 要求项目依赖的所有文件都位于工作区目录中,或者在 WORKSPACE 文件中指定为导入项。

  • 使用 Bazel 构建 Xcode 项目时,BUILD 文件会成为真相来源。如果您在 Xcode 中处理项目,则每当更新 BUILD 文件时,都必须使用 Tulsi 生成与 BUILD 文件匹配的新版 Xcode 项目。如果您不使用 Xcode,bazel buildbazel test 命令可提供构建和测试功能,但存在本文指南后面部分所述的某些限制。

  • 由于 build 配置架构(例如目录布局或 build 标志)存在差异,Xcode 可能无法完全了解 build 的“全局情况”,因此某些 Xcode 功能可能无法正常运行。即:

    • 根据您在 Tulsi 中选择的转换目标,Xcode 可能无法正确编入项目源代码的索引。这会影响 Xcode 中的代码补全和导航,因为 Xcode 将无法看到项目的所有源代码。

    • 静态分析、地址 sanitizer 和线程 sanitizer 可能无法正常运行,因为 Bazel 不会生成 Xcode 对这些功能预期的输出。

    • 如果您使用 Tulsi 生成 Xcode 项目,并使用该项目在 Xcode 中运行测试,则 Xcode 将运行测试,而不是 Bazel。如需使用 Bazel 运行测试,请手动运行 bazel test 命令。

准备工作

在开始之前,请执行以下操作:

  1. 安装 Bazel(如果尚未安装)。

  2. 如果您不熟悉 Bazel 及其概念,请完成 iOS 应用教程。您应了解 Bazel 工作区,包括 WORKSPACEBUILD 文件,以及目标、构建规则和 Bazel 软件包的概念。

  3. 分析并了解项目的依赖项。

分析项目依赖项

与 Xcode 不同,Bazel 要求您在 BUILD 文件中明确声明每个目标的所有依赖项。

如需详细了解外部依赖项,请参阅使用外部依赖项

使用 Bazel 构建或测试 Xcode 项目

如需使用 Bazel 构建或测试 Xcode 项目,请执行以下操作:

  1. 创建 WORKSPACE 文件

  2. (实验性)集成 CocoaPods 依赖项

  3. 创建 BUILD 文件:

    a. 添加应用目标

    b. (可选)添加测试目标

    c. 添加库目标

  4. (可选)对 build 进行细化

  5. 运行构建

  6. 使用 Tulsi 生成 Xcode 项目

第 1 步:创建 WORKSPACE 文件

在新目录中创建一个 WORKSPACE 文件。此目录会成为 Bazel 工作区根目录。如果项目不使用外部依赖项,则此文件可以为空。如果项目依赖的文件或软件包不在项目的任一目录中,请在 WORKSPACE 文件中指定这些外部依赖项。

第 2 步:(实验性)集成 CocoaPods 依赖项

如需将 CocoaPods 依赖项集成到 Bazel 工作区,您必须将其转换为 Bazel 软件包,如转换 CocoaPods 依赖项中所述。

第 3 步:创建 BUILD 文件

定义工作区和外部依赖项后,您需要创建一个 BUILD 文件,以告知 Bazel 项目的结构。在 Bazel 工作区的根目录下创建 BUILD 文件,并将其配置为执行项目的初始构建,如下所示:

提示:如需详细了解软件包和其他 Bazel 概念,请参阅工作区、软件包和目标

第 3a 步:添加应用目标

添加 macos_applicationios_application 规则目标。此目标分别构建 macOS 或 iOS 应用 bundle。 在目标中,至少指定以下内容:

  • bundle_id - 二进制文件的软件包 ID(反向 DNS 路径后跟应用名称)。

  • provisioning_profile - 您的 Apple 开发者账号中的配置文件(如果是为 iOS 设备构建)。

  • families(仅限 iOS)- 是针对 iPhone 和/或 iPad 构建应用。

  • infoplists - 要合并到最终 Info.plist 文件中的 .plist 文件列表。

  • minimum_os_version - 应用支持的最低 macOS 或 iOS 版本。这样可确保 Bazel 使用正确的 API 级别构建应用。

第 3b 步:(可选)添加测试目标

Bazel 的 Apple 构建规则支持在 iOS 和 macOS 上运行基于库的单元测试,以及在 macOS 上运行基于应用的测试。对于 iOS 上的基于应用的测试或任一平台上的界面测试,Bazel 将构建测试输出,但测试必须通过使用 Tulsi 生成的项目在 Xcode 中运行。添加测试目标,如下所示:

  • macos_unit_test,用于在 macOS 上运行基于库和基于应用的单元测试。

  • ios_unit_test 在 iOS 上运行基于库的单元测试。对于需要 iOS 模拟器的测试,Bazel 将构建测试输出,但不会运行测试。您必须使用 Tulsi 生成 Xcode 项目,并从 Xcode 中运行测试。

  • ios_ui_test 用于构建使用 Xcode 在 iOS 模拟器中运行界面测试所需的输出。您必须使用 Tulsi 生成 Xcode 项目,并在 Xcode 中运行测试。Bazel 无法原生运行界面测试。

至少应为 minimum_os_version 属性指定一个值。虽然其他打包属性(例如 bundle_identifierinfoplists)默认采用最常用的值,但请确保这些默认值与项目兼容,并根据需要进行调整。对于需要 iOS 模拟器的测试,还应将 ios_application 目标名称指定为 test_host 属性的值。

第 3c 步:添加库目标

为每个 Objective C 库添加一个 objc_library 目标,并为应用和/或测试所依赖的每个 Swift 库添加一个 swift_library 目标。

按照以下方式添加库目标:

  • 将应用库目标添加为应用目标的依赖项。

  • 将测试库目标添加为测试目标的依赖项。

  • srcs 属性中列出实现源代码。

  • hdrs 属性中列出标头。

如需详细了解构建规则,请参阅适用于 Bazel 的 Apple 规则

此时,最好测试一下 build:

bazel build //:<application_target>

第 4 步:(可选)细化 build

如果项目很大,或者随着项目的增长,请考虑将其拆分为多个 Bazel 软件包。这种更精细的粒度可带来以下好处:

  • 提高 build 的增量性

  • 提高了构建任务的并行化程度,

  • 为未来用户提供更好的可维护性

  • 更好地控制各个目标和软件包中的源代码可见性。这可防止包含实现细节的库泄露到公共 API 中等问题。

关于对项目进行细分的一些提示:

  • 将每个库放入自己的 Bazel 软件包中。从需要最少依赖项的依赖项开始,然后逐渐向上遍历依赖项树。

  • 在添加 BUILD 文件并指定目标时,请将这些新目标添加到依赖于这些文件的目标的 deps 属性中。

  • glob() 函数不会跨软件包边界,因此随着软件包数量的增加,glob() 匹配的文件数量将会减少。

  • BUILD 文件添加到 main 目录时,还应将 BUILD 文件添加到相应的 test 目录。

  • 对软件包强制执行健康的可见性限制。

  • 在对 BUILD 文件进行每次重大更改后,请构建项目,并在遇到 build 错误时加以修正。

第 5 步:运行 build

运行完全迁移的 build,确保其在完成时没有错误或警告。单独运行每个应用和测试目标,以便更轻松地找到出现任何错误的来源。

例如:

bazel build //:my-target

第 6 步:使用 Tulsi 生成 Xcode 项目

使用 Bazel 进行构建时,WORKSPACEBUILD 文件会成为构建的真实来源。如需让 Xcode 了解这一点,您必须使用 Tulsi 生成与 Bazel 兼容的 Xcode 项目。

问题排查

当 Bazel 与所选的 Xcode 版本不同步时(例如在您应用更新时),可能会出现 Bazel 错误。如果您在使用 Xcode 时遇到错误(例如“必须指定 Xcode 版本才能使用 Apple CROSSTOOL”),请尝试以下操作。

  • 手动运行 Xcode 并接受任何条款及条件。

  • 使用 Xcode 选择指明正确的版本、接受许可并清除 Bazel 的状态。

  sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
  sudo xcodebuild -license
  bazel sync --configure
  • 如果这样不起作用,您也可以尝试运行 bazel clean --expunge