XCRemoteCache 是 Xcode 项目的远程缓存工具。它复用远程机器上生成的目标产物,这些产物通过一个简单的 REST 服务器提供。

Build Status License Docs

如何以及为什么?

缓存机制基于远程产物,这些产物应在 master 分支的每次提交时生成并上传到缓存服务器,最好作为 CI/CD 步骤的一部分。Xcode 产品在不同的 Xcode 版本之间不可移植,每个 XCRemoteCache 产物都与生成它的特定 Xcode 构建号相关联。为了支持多个 Xcode 版本,应为每个 Xcode 版本生成产物。

产物复用流程如下:XCRemoteCache 执行目标预检查(也称为预构建),如果本地源代码的指纹与生成端计算的指纹匹配,则几个编译步骤包装器(例如 xcswiftcxcccxclibtool)模拟相应的编译步骤,链接(或归档)操作将缓存的构建产物移动到预期位置。

具有相同目标源代码的多个提交在远程服务器上复用产物包。

精确的目标输入文件

找到精确的输入和依赖文件列表是一项非平凡的任务,因为 Xcode 严重依赖隐式目标依赖。这意味着 Xcode 尝试从提供的搜索路径中使用所需的依赖项,并在 DerivedData 的产品目录中查找。为了找到用于计算指纹哈希的狭窄文件列表,XCRemoteCache 从服务器获取 meta.json 文件,该文件包含远程生成的指纹哈希和构成指纹的输入文件列表。该输入文件列表是在产物生成过程中基于来自 clangswift 编译器的 .d 输出生成的。

在 Xcode 中构建项目之前,XCRemoteCache 需要找到最佳的 git 提交 sha,以便使用其产物。这作为 xcprepare 执行的一部分发生,应在每次合并或切换分支后调用 xcpreparexcprepare 使用 git 的 first-parent 策略查找与远程仓库分支的 10 个最新公共 sha 列表,并选择已上传所有产物的最新一个。

生成端负责在每次成功构建后调用 xcprepare mark 子命令。标记过程在远程缓存服务器上创建一个空标记文件,格式如下:#{commmmitSha}-#{TargetName}-#{Configuration}-#{Platform}-#{XcodeBuildNumber}-#{ContextBuildSettings}-#{SchemaID}.json

xcprepare 对所有已识别的 sha 发出 HEAD 请求,选择远程存在标记文件的最新一个,并将其以文本形式保存到 arc.rc 文件中。该文件告知预构建阶段应获取哪个元文件以获取目标依赖文件列表。

新文件添加到目标

如果在哈希指纹中仅考虑先前观察到的文件列表,则如果构建包含新的源文件,则可能会给出无效结果,因为它未在哈希中考虑。

对于仅 Swift 目标中的新 .swift 文件,xcswiftc 会自动识别这种情况,并强制对整个目标进行本地编译。对于 Objective-C 或混合目标,回退到本地编译更加困难,因为某些先前的调用(xcccxcswiftc)可能已经完成,而没有操作。为了缓解这种情况,每个包装器将调用添加到侧文件 (history.compile),以防其他进程需要本地编译整个目标。如果发生这种情况,新添加文件的编译将获得目标范围的锁,该锁会阻止其他包装器调用,逐个执行已模拟的步骤以回填已跳过的编译步骤。

调试符号

使用“调试符号:已启用”构建的二进制文件嵌入了源文件绝对路径,因此编译产物不能在具有不同源根的两台机器之间直接移植。否则,LLDB 调试器无法将一组当前正在执行的机器指令与生成它的本地文件相关联。为了缓解这种情况,XCRemoteCache 建议为所有 XCRemoteCache 构建添加自定义 C 和 Swift 调试标志 prefix-map。这些标志确保本地生成和从远程服务器下载的所有二进制文件都具有相同的调试符号绝对路径,这些路径在 LLDB 会话开始时转换为实际的本地路径。

性能优化

XCRemoteCache 涉及多种优化技术

聚焦目标

如果可以具有脏源的目标列表是有限的,则可以使用在 .rcinfo 中指定的聚焦目标配置 XCRemoteCache。

默认情况下,所有目标都是聚焦目标,这些目标将本地指纹与远程可用的指纹进行比较,并在不匹配时回退到本地编译。非聚焦目标,称为“瘦”目标,始终使用缓存的产物,从而消除了指纹计算。“瘦”目标应仅包含具有 thin_target_mock_filename 的单个编译文件,例如 standin.swiftstandin.m

如何将 XCRemoteCache 与您的 Xcode 项目集成?

要在现有 .xcodeproj 中启用 XCRemoteCache,您需要向要缓存的目标添加额外的构建设置和构建阶段。

您可以使用 XCRemoteCache 提供的集成命令以自动方式执行此操作,或手动修改您的 Xcode 项目。

1. 下载 XCRemoteCache

从 Github 发布页面,下载 XCRemoteCache 捆绑包 zip 文件。将捆绑包解压缩到 .xcodeproj 旁边的目录中。

以下步骤将假设捆绑包已解压缩到 xcremotecache 目录,放置在 .xcodeproj 旁边。

A. 自动集成

2. 创建一个最小化的 XCRemoteCache 配置

.xcodeproj 旁边创建 .rcinfo yaml 文件,其中包含最少的配置条目,例如

primary_repo: https://yourRepo.git
cache_addresses:
- https://xcremotecacheserver.com

3. 运行自动集成脚本

3a. 生产者端

执行修改 <yourProject.xcodeproj> 的命令

xcremotecache/xcprepare integrate --input <yourProject.xcodeproj> --mode producer --final-producer-target <YourMainTarget>
3b. 消费者端

执行修改 <yourProject.xcodeproj> 的命令

xcremotecache/xcprepare integrate --input <yourProject.xcodeproj> --mode consumer
xcprepare integrate 支持的完整选项列表
参数 描述 默认值 必需
--input .xcodeproj 位置 N/A
--mode 模式。支持的值:consumerproducerproducer-fast(实验性) N/A
--targets-include 逗号分隔的目标列表,用于集成 XCRemoteCache。 "" ⬜️
--targets-exclude 逗号分隔的目标列表,用于不集成 XCRemoteCache。优先于 --targets-include。 "" ⬜️
--configurations-include 逗号分隔的配置列表,用于集成 XCRemoteCache。 "" ⬜️
--configurations-exclude 逗号分隔的配置列表,用于不集成 XCRemoteCache。优先于 --configurations-include。 发布 ⬜️
--final-producer-target [仅生产者] 生成缓存产物的最终目标。一旦此目标完成,则不允许其他目标为给定的 sha、配置和平台上下文上传产物到远程服务器。 nil ⬜️
--consumer-eligible-configurations [仅消费者] 逗号分隔的配置列表,在使用给定 sha 之前,需要将所有产物上传到远程站点。 Debug ⬜️
--consumer-eligible-platforms [仅消费者] 逗号分隔的平台列表,在使用给定 sha 之前,需要将所有产物上传到远程站点 iphonesimulator ⬜️
--lldb-init LLDBInit 模式。将调试所需的命令附加到 .lldbinit。支持的值:“none”(不附加到 .lldbinit)、“user”(附加到 ~/.lldbinit) user ⬜️
--fake-src-root 生产者和消费者之间共享的任意源位置。对于项目应该是唯一的。 /xxxxxxxxxx ⬜️
--output 将集成了 XCRemoteCache 的项目保存到单独的位置。 N/A ⬜️
--sdks-exclude 逗号分隔的 SDK 列表,用于不集成 XCRemoteCache(例如“watchos*,watchsimulator*”)。(实验性) "" ⬜️

B. 手动集成

2. 配置 XCRemoteCache

根据参数列表,在 .xcodeproj 旁边创建 yaml 配置文件 .rcinfo,其中包含您的完整 XCRemoteCache 配置,例如

primary_repo: https://yourRepo.git
cache_addresses:
 - https://xcremotecacheserver.com
repo_root: "."
remote_commit_file: arc.rc
xccc_file: xcremotecache/xccc

3. 调用 xcprepare

在每次与主分支合并或 rebase 后,执行 xcprepare --configuration #Configuration# --platform #platform# 命令。否则,远程缓存产物可能已过时,最终命中率可能会很差。

xcprepare 应用程序将 arc.rc 文件保存到磁盘,并将摘要打印到标准输出。打印的 recommended_remote_address 只是建议使用的缓存远程服务器。是否采用取决于集成工具的决定。如果采用,则项目的 .rcinfo 应将该值定义为 recommended_remote_address 参数。

示例

$ xcremotecache/xcprepare --configuration Debug --platform iphonesimulator
result: true
commit: aabbccc00
age: 0
recommended_remote_address: https://xcremotecacheserver.com

4. 与 Xcode 项目集成

配置应使用 XCRemoteCache 的 Xcode 目标

  1. 覆盖构建设置
屏幕截图

build-settings

  1. 添加一个 Prebuild 构建阶段(在编译之前)
  1. 添加 Postbuild 构建阶段(在编译之后)
屏幕截图

build-phases

5. 配置 LLDB 源映射(可选)

重写源映射是支持调试和命中断点所必需的,请参阅调试符号

  1. 覆盖所有目标的以下构建设置
  1. settings set target.source-map /xxxxxxxxxx /Users/account/src/PathToTheProject 添加到最终构建项目的机器上的 ~/.lldbinit

XCRC_SRCROOT 任意路径应该是项目独有的,以避免冲突。

提示:在极少数情况下,Xcode 会缓存 ~/.lldbinit 内容,因此请确保在修改后重新启动 Xcode。

6. 生产者模式 - 产物生成

XCRemoteCache 可以在两种主要模式下运行:consumer(默认)尝试复用远程服务器上可用的产物,而 producer 用于生成所有产物 - 它在本地构建所有目标并将元文件和产物文件上传到远程缓存服务器。

6a. 配置生产者模式

要启用 producer 模式,请直接在 .rcinfo 文件中配置它。

可选地,您可以在 .rcinfo 中定义 extra_configuration_file,其中包含指向另一个 yaml 文件的路径,该文件将覆盖 .rcinfo 中的默认配置。如果您想跟踪主 .rcinfo 并将本地配置保留在 git 之外,则此方法可能很有用。

6b. 填充缓存

从 Xcode 或使用 xcodebuild 构建项目

6c. 标记提交 sha

一旦所有产物都已上传,使用 xcprepare mark 命令“标记构建”

$ xcremotecache/xcprepare mark --configuration Debug --platform iphonesimulator

该命令在远程服务器上创建一个空文件,该文件告知对于给定的 sha、配置、平台、Xcode 版本等,所有产物都可用。

请注意,对于 producer 模式,预构建构建阶段和 xcccxcldxcldplusplusxclibtoolxclipo 包装器变为无操作,因此建议不要为 producer 模式添加它们。

7. 通用化 -Swift.h(可选,仅当静态库的桥接头文件从 ObjC 公开 NS_ENUM 时才需要)

如果静态库目标包含混合目标,其桥接头文件在公共 Swift API 中公开来自 ObjC 的枚举,则将 *-Swift.h 移动到共享位置的自定义脚本也应将 *-Swift.h.md5 移动到其旁边。

示例

现有脚本(之前)
ditto "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}"

其中

正确脚本(之后)
ditto "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}"
[ -f "${SCRIPT_INPUT_FILE_1}" ] && ditto "${SCRIPT_INPUT_FILE_1}" "${SCRIPT_OUTPUT_FILE_1}" || rm -f "${SCRIPT_OUTPUT_FILE_1}"

其中

注意:如果以下至少一项为真,则不需要此步骤

完整的配置参数列表

属性 描述 默认值 必需
mode 构建模式。可能的值:consumerproducer consumer ⬜️
cache_addresses 所有远程缓存副本的地址。必须是非空字符串数组 N/A
recommended_cache_address 在消费者模式下使用的最佳远程缓存的地址。如果未指定,将使用 cache_addresses 中的第一项 N/A ⬜️
cache_health_path cache_addresses 的探测请求路径(相对于 cache_addresses 中的路径),用于确定要使用的最佳缓存 nginx-health ⬜️
cache_health_path_probe_count cacheAddresses 探测请求的数量 3 ⬜️
remote_commit_file 包含远程提交 sha 的文件路径 build/remote-cache/arc.rc ⬜️
xccc_file xccc 包装器的路径 build/bin/xccc ⬜️
prebuild_discovery_path 相对于 $TARGET_TEMP_DIR 的路径,指定预构建发现 .d 文件 prebuild.d ⬜️
postbuild_discovery_path 相对于 $TARGET_TEMP_DIR 的路径,指定后构建发现 .d 文件 postbuild.d ⬜️
mode_marker_path 相对于 $TARGET_TEMP_DIR 的路径,用于启用或禁用给定目标的远程缓存的标记文件。包含允许使用远程缓存的所有输入文件列表 rc.enabled ⬜️
clang_command 标准 C 编译回退的命令 clang ⬜️
swiftc_command 标准 Swift 编译回退的命令 swiftc ⬜️
primary_repo 生成缓存产物的主 git 仓库的地址(区分大小写) N/A
primary_branch primary_repo 上生成缓存产物的主(主要)分支 master ⬜️
repo_root git 仓库根目录的路径 "." ⬜️
cache_commit_history 查找缓存产物的历史 git 提交的数量 10 ⬜️
source_root Xcode 项目的源根目录 "" ⬜️
fingerprint_override_extension 指纹覆盖扩展名(示例覆盖 Module.swiftmodule/x86_64.swiftmodule.md5 md5 ⬜️
extra_configuration_file 覆盖项目配置的配置文件(此属性可以在不同的文件中多次覆盖,以链接额外的配置文件) user.rcinfo ⬜️
publishing_sha 发布产物的自定义提交 sha(仅生产者) nil ⬜️
artifact_maximum_age HTTP 响应在本地缓存中被逐出之前的最大天数 30 ⬜️
custom_fingerprint_envs 应卷积到环境指纹中的额外 ENV 键 [] ⬜️
stats_dir 存储所有 XCRemoteCache 统计信息(例如计数器)的目录 ~/.xccache ⬜️
download_retries 下载请求的重试次数 0 ⬜️
upload_retries 上传请求的重试次数 3 ⬜️
retry_delay 重试之间的延迟(秒) 10 ⬜️
upload_batch_size 最大并发请求数。0 表示无限制 0 ⬜️
request_custom_headers 所有远程服务器请求的额外 HTTP 标头的字典 [] ⬜️
thin_target_mock_filename 编译输入文件的文件名(不带扩展名),用作强制缓存目标(也称为瘦目标)的伪编译 standin ⬜️
focused_targets 所有非瘦目标的列表。如果为空,则所有目标均应为非瘦目标 [] ⬜️
disable_http_cache 禁用 http 请求以获取元数据和下载产物的缓存 false ⬜️
compilation_history_file 相对于 $TARGET_TEMP_DIR 的路径,用于收集如果目标切换到本地编译时应执行的所有编译命令。示例:新的 .swift 文件使远程产物无效,并触发本地编译。发生这种情况时,所有先前跳过的 clang 构建步骤都需要最终在本地调用 - 此文件列出了所有这些命令。 history.compile ⬜️
timeout_response_data_chunks_interval 远程响应数据间隔的超时时间(秒)。如果数据块之间的间隔长于超时时间,则请求失败。 20 ⬜️
turn_off_remote_cache_on_first_timeout 如果为 true,则观察到的任何请求超时都会关闭所有目标的远程缓存 false ⬜️
product_files_extensions_with_content_override 应携带源指纹的所有扩展名列表。应包含包含非确定性内容(绝对路径、时间戳等)的所有产品文件的扩展名。 ["swiftmodule"] ⬜️
thinning_enabled 如果为 true,则启用对瘦项目的支持 false ⬜️
thinning_target_module_name 充当瘦目标助手的目标的模块名称 "ThinningRemoteCacheModule" ⬜️
prettify_meta_files 一个布尔值,用于选择元文件的美化 JSON 格式 false ⬜️
aws_secret_key 用于 AWS V4 签名授权的密钥。如果将其设置为非空字符串 - 将根据此密钥和其他 aws_* 参数添加身份验证标头。 "" ⬜️
aws_access_key 用于 AWS V4 签名授权的访问密钥。 "" ⬜️
aws_security_token AWS Security Token Service 提供的临时安全令牌。 nil ⬜️
aws_region 用于 AWS V4 签名授权的区域。例如 eu "" ⬜️
aws_service 用于 AWS V4 签名授权的服务。例如 storage "" ⬜️
out_of_band_mappings 应应用于文件路径重映射字典,使其在依赖项列表上与绝对路径无关。如果项目引用了仓库根目录之外的文件(编译文件或预编译依赖项),则很有用。键表示通用替换,值是要替换的子字符串。示例:对于映射 ["COOL_LIBRARY": "/CoolLibrary"]/CoolLibrary/main.swift 将表示为 $(COOL_LIBRARY)/main.swift)。警告:重映射顺序是不确定的,因此请避免具有多个匹配项的重映射。 [:] ⬜️
disable_certificate_verification 一个布尔值,用于选择禁用 SSL 证书验证 false ⬜️
disable_vfs_overlay 禁用虚拟文件系统覆盖支持的功能标志(临时) false ⬜️
custom_rewrite_envs 应用作依赖项列表中占位符的额外 ENV 列表。ENV 重写过程是乐观的 - 如果在预/后构建过程中未定义 ENV,则不执行任何操作。 [] ⬜️
irrelevant_dependencies_paths 不应包含在依赖项列表中的文件正则表达式。警告!谨慎添加条目 - 排除相关的依赖项可能会导致目标过度缓存。正则表达式可以部分或完全匹配文件路径,例如 \\.modulemap$ 将排除所有 .modulemap 文件。 [] ⬜️
gracefully_handle_missing_common_sha 如果为 true,则如果找不到与主分支的最新公共提交,则不要使 prepare 失败。这在 CI 中可能很有用,其中使用浅克隆,并且克隆深度不足以从主分支获取提交 false ⬜️
enable_swift_driver_integration 启用与 Xcode 14 中添加的 swift driver 的实验性集成 false ⬜️

后端缓存服务器

作为缓存服务器,XCRemoteCache 可以使用任何支持 PUT、GET 和 HEAD 方法的 REST 服务器。

在开发阶段,您可以尝试最简单的缓存服务器,该服务器在 backend-example 中以 Docker 镜像的形式提供。对于生产环境,建议配置可靠、快速的服务器,最好位于靠近开发人员机器的位置。

开箱即用,XCRemoteCache 支持 Amazon S3 和 Google GCS 使用的 V4 签名授权。或者,如果您的服务器具有自定义身份验证过程,则可以使用 request_custom_headers 配置属性添加额外的 HTTP 请求标头。

来自 Docker 镜像的示例 REST 缓存服务器

要运行服务器的本地实例,请使用一个代码片段,该代码片段在 https://:8080/cache 下公开缓存端点

docker build -t xcremotecache-demo-server backend-example
docker run -it --rm -d -p 8080:8080 --name xcremotecache xcremotecache-demo-server

由于 Docker 镜像将所有文件保存在容器的非持久性存储中,要重置缓存的内容,只需重新启动它

# stop the container
docker kill xcremotecache
# run a new instance of the image
docker run -it --rm -d -p 8080:8080 --name xcremotecache xcremotecache-demo-server

要查看缓存服务器中存储的所有文件,请导航到容器的缓存根目录

docker exec -w /tmp/cache -it xcremotecache /bin/bash

Amazon S3 和 Google Cloud Storage

XCRemoteCache 支持使用 Amazon S3 和 Google Cloud Storage 存储桶作为缓存服务器,并使用 Amazon v4 签名授权。

要进行设置,请在 .rcinfo 文件中使用配置参数 aws_secret_keyaws_access_keyaws_regionaws_service。在同一文件中的 cache-addresses 字段中指定存储桶的 URL。

XCRemoteCache 还支持 AWS 临时访问密钥。将额外的 aws_security_token 参数与 aws_secret_keyaws_access_key 结合使用进行设置。此页面 描述了如何接收安全令牌。

示例

...
cache_addresses:
 - https://bucketname.s3.eu-central-1.amazonaws.com/
aws_secret_key: <SECRET_KEY>
aws_access_key: <ACCESS_KEY>
aws_region: eu-central-1
aws_service: s3
...

保留策略:存储桶通常具有保留策略选项,该选项确保对象保留一定的时间,并且不会被修改或删除。保持此选项较短或禁用它,以避免在生产者端为同一配置连续完成多个构建时出错。

CocoaPods 插件

前往我们的 cocoapods-plugin 文档,了解如何在您的 CocoaPods 项目中集成 XCRemoteCache。

Apple Silicon 支持

每个架构的产物(推荐)

如果您的所有机器(生产者和所有消费者都具有相同的架构,无论是 Intel 还是 Apple Silicon),则无需执行任何操作。

XCRemoteCache 支持为 Apple Silicon 消费者构建产物。建议为 x86_64arm64 架构分别构建,以获得单架构产物,而无需下载不相关的二进制文件。如果您想同时支持 Intel 和 Apple Silicon 消费者,则需要执行以下步骤。

xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO build ...
xcodebuild clean
xcodebuild ARCHS=arm64 ONLY_ACTIVE_ARCH=NO build ...

胖产物

如果您希望生成胖产物(包含 Intel 和 Apple Silicon 二进制文件),则可以在生产者端禁用“仅构建归档架构”,例如

xcodebuild ONLY_ACTIVE_ARCH=NO build ...

注意:不建议使用此设置,并且未来的 XCRemoteCache 版本可能不支持此设置。

要求

局限性

常见问题解答

请关注 FAQ 页面。

开发

请关注 Development 指南。它包含有关如何入门的所有信息。

架构设计

请关注 架构设计 文档,其中描述和记录了 XCRemoteCache 的设计和实现细节。

发布

要发布版本,请在 Releases 中草拟一个新版本,标签格式为 v0.3.0{-rc0}。带有二进制文件的软件包将自动上传到 GitHub Releases 页面。

发布 CocoaPods 插件

增加在 gem_version.rb 中定义的 gem 版本,并创建上述新版本。

如果给定版本尚不存在,插件将自动上传到 RubyGems

构建发布包

要为单个平台(例如 x86_64-apple-macosxarm64-apple-macosx)构建发布 zip 包,请调用

rake 'build[release, x86_64-apple-macosx]'

zip 包将在 releases/XCRemoteCache.zip 中生成。

支持

创建一个 新 issue,并尽可能提供详细信息。

贡献

我们认为一个友好的社区非常重要,我们要求您在与社区的所有互动中都遵守 Spotify 的 开源行为准则

行为准则

本项目遵守 开放行为准则。通过参与,您需要遵守此准则。

许可证

Copyright 2021 Spotify AB

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://apache.ac.cn/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

安全问题?

请通过 Spotify 的漏洞赏金计划 (https://hackerone.com/spotify) 而不是 GitHub 报告敏感的安全问题。