本页介绍了如何使用 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 将无法看到项目的所有源代码。
静态分析、地址 sanitizer 和线程 sanitizer 可能无法正常运行,因为 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 工作区,您必须将其转换为 Bazel 软件包,如转换 CocoaPods 依赖项中所述。
第 3 步:创建 BUILD
文件
定义工作区和外部依赖项后,您需要创建一个 BUILD
文件,以告知 Bazel 项目的结构。在 Bazel 工作区的根目录下创建 BUILD
文件,并将其配置为执行项目的初始构建,如下所示:
提示:如需详细了解软件包和其他 Bazel 概念,请参阅工作区、软件包和目标。
第 3a 步:添加应用目标
添加 macos_application
或 ios_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_identifier
和 infoplists
)默认采用最常用的值,但请确保这些默认值与项目兼容,并根据需要进行调整。对于需要 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 进行构建时,WORKSPACE
和 BUILD
文件会成为构建的真实来源。如需让 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
。