本页介绍了如何使用 Bazel 构建或测试 Xcode 项目。本文介绍了 Xcode 和 Bazel 之间的区别,并提供了将 Xcode 项目转换为 Bazel 项目的步骤。此外,还提供了用于解决常见错误的问题排查方案。
Xcode 和 Bazel 之间的区别
Bazel 要求您通过构建规则明确指定每个构建目标及其依赖项,以及相应的构建设置。
Bazel 要求项目所依赖的所有文件都位于工作区目录中,或者在
WORKSPACE
文件中指定为导入项。使用 Bazel 构建 Xcode 项目时,
BUILD
文件会成为事实来源。如果您在 Xcode 中处理项目,则每次更新BUILD
文件时,都必须使用 Tulsi 生成与BUILD
文件匹配的新版 Xcode 项目。如果您不使用 Xcode,bazel build
和bazel test
命令可提供构建和测试功能,但存在一些限制,本指南稍后会对此进行说明。由于 build 配置架构(例如目录布局或 build 标志)存在差异,Xcode 可能无法完全了解 build 的“全局”情况,因此某些 Xcode 功能可能无法正常运行。具体而言:
根据您在 Tulsi 中为转化选择的目标,Xcode 可能无法正确为项目来源编制索引。这会影响 Xcode 中的代码补全和导航,因为 Xcode 将无法看到项目的所有源代码。
静态分析、地址清理器和线程清理器可能无法正常运行,因为 Bazel 不会生成 Xcode 针对这些功能所需的输出。
如果您使用 Tulsi 生成 Xcode 项目,并使用该项目从 Xcode 内部运行测试,则 Xcode 将运行测试,而不是 Bazel。如需使用 Bazel 运行测试,请手动运行
bazel test
命令。
准备工作
在开始之前,请执行以下操作:
如果您尚未安装 Bazel,请执行此操作。
如果您不熟悉 Bazel 及其概念,请完成 iOS 应用教程。您应该了解 Bazel 工作区,包括
WORKSPACE
和BUILD
文件,以及目标、构建规则和 Bazel 软件包的概念。分析并了解项目的依赖项。
分析项目依赖项
与 Xcode 不同,Bazel 要求您在 BUILD
文件中为每个目标明确声明所有依赖项。
如需详细了解外部依赖项,请参阅使用外部依赖项。
使用 Bazel 构建或测试 Xcode 项目
如需使用 Bazel 构建或测试 Xcode 项目,请执行以下操作:
-
a. 添加应用目标
b. (可选)添加测试目标
c. 添加库目标平台
第 1 步:创建 WORKSPACE
文件
在新目录中创建一个 WORKSPACE
文件。此目录将成为 Bazel 工作区根目录。如果项目不使用任何外部依赖项,则此文件可以为空。如果项目依赖于不在项目目录中的文件或软件包,请在 WORKSPACE
文件中指定这些外部依赖项。
第 2 步:(实验性)集成 CocoaPods 依赖项
如需将 CocoaPods 依赖项集成到 Bazel 工作区中,您必须按照转换 CocoaPods 依赖项中所述,将它们转换为 Bazel 软件包。
第 3 步:创建 BUILD
文件
定义工作区和外部依赖项后,您需要创建一个 BUILD
文件,告知 Bazel 项目的结构。在 Bazel 工作区的根目录下创建 BUILD
文件,并将其配置为按如下方式对项目进行初始构建:
提示:如需详细了解软件包和其他 Bazel 概念,请参阅工作区、软件包和目标。
第 3a 步:添加应用目标
添加 macos_application
或 ios_application
规则目标。此目标会分别构建 macOS 或 iOS 应用软件包。
在目标中,至少指定以下内容:
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 build 规则支持在 iOS 和 macOS 上运行基于库的单元测试,以及在 macOS 上运行基于应用的测试。对于 iOS 上的基于应用的测试或任一平台上的界面测试,Bazel 将构建测试输出,但测试必须在 Xcode 中通过使用 Tulsi 生成的项目运行。按如下方式添加测试目标:
macos_unit_test
在 macOS 上运行基于库和基于应用的单元测试。ios_unit_test
在 iOS 上运行基于库的单元测试。对于需要 iOS 模拟器的测试,Bazel 将构建测试输出,但不会运行测试。您必须使用 Tulsi 生成 Xcode 项目,并在 Xcode 中运行测试。ios_ui_test
:用于构建在 iOS 模拟器中使用 Xcode 运行界面测试所需的输出。您必须使用 Tulsi 生成 Xcode 项目,并在 Xcode 中运行测试。Bazel 本身无法运行界面测试。
至少应为 minimum_os_version
属性指定一个值。虽然其他打包属性(例如 bundle_identifier
和 infoplists
)默认采用最常用的值,但请确保这些默认值与项目兼容,并根据需要进行调整。对于需要 iOS 模拟器的测试,还需将 ios_application
目标名称指定为 test_host
属性的值。
第 3c 步:添加库目标平台
为每个 Objective-C 库添加一个 objc_library
目标,并为应用和/或测试所依赖的每个 Swift 库添加一个 swift_library
目标。
按如下方式添加库目标:
将应用库目标作为依赖项添加到应用目标。
将测试库目标添加为测试目标的依赖项。
在
srcs
属性中列出实现来源。列出
hdrs
属性中的标头。
如需详细了解 build 规则,请参阅 Apple Rules for Bazel。
此时,最好测试一下 build:
bazel build //:<application_target>
第 4 步:(可选)细化 build
如果项目很大,或者随着项目规模的扩大,请考虑将其分块为多个 Bazel 软件包。这种精细度可带来以下好处:
提高 build 的增量性,
提高了构建任务的并行化程度,
为未来的用户提供更好的可维护性,
更好地控制目标平台和软件包之间的源代码可见性。这样可以防止库中包含的实现细节泄露到公共 API 中等问题。
细化项目的提示:
将每个库放在自己的 Bazel 软件包中。从依赖项最少的模块开始,然后逐步向上处理依赖树。
在添加
BUILD
文件并指定目标时,请将这些新目标添加到依赖于它们的目标的deps
属性中。glob()
函数不会跨越软件包边界,因此随着软件包数量的增加,glob()
匹配的文件会减少。向
main
目录添加BUILD
文件时,还要向相应的test
目录添加BUILD
文件。在各个软件包中强制执行合理的可见性限制。
在对
BUILD
文件做出每次重大更改后,构建项目并修复遇到的构建错误。
第 5 步:运行 build
运行完全迁移的 build,确保其完成且没有出现错误或警告。 单独运行每个应用和测试目标,以便更轻松地找到发生任何错误的来源。
例如:
bazel build //:my-target
第 6 步:使用 Tulsi 生成 Xcode 项目
使用 Bazel 进行构建时,WORKSPACE
和 BUILD
文件会成为构建的真实来源。为了让 Xcode 识别这一点,您必须使用 Tulsi 生成与 Bazel 兼容的 Xcode 项目。
问题排查
当 Bazel 与所选 Xcode 版本不同步时(例如在应用更新时),可能会出现错误。如果您在使用 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
。