构建缓存¶
为了避免重新编译 Spack 软件包,可以将已安装的软件包推送到构建缓存中,然后供其他人下载和安装。
每当镜像源提供预构建的软件包时,Spack 会在具体化(concretization)和安装过程中将这些软件包考虑在内,从而显著加快 spack install 的速度。
注意
我们通常将“构建缓存(build cache)”和“镜像源(mirror)”互换使用。镜像源在安装过程中既用于获取源码,也用于获取预构建软件包。构建缓存则专门指代那些提供预构建软件包的镜像源。
创建构建缓存¶
可以通过以下方式创建构建缓存
$ spack buildcache push <path/url/mirror name> <spec>
此命令获取本地安装的 spec 及其依赖项,并为它们的安装前缀创建 tarball 压缩包。它还会生成由 GPG 签名的元数据文件。这些 tarball 和元数据文件随后会被推送到指定的构建缓存中,该缓存可以是本地目录,也可以是远程 URL。
以下是一个示例,我们在名为“spack-cache”的本地目录中创建构建缓存,并将“ninja” spec 推送到其中
$ spack buildcache push ./spack-cache ninja
==> Selected 30 specs to push to file:///home/spackuser/spack/spack-cache
...
==> [30/30] Pushed ninja@1.12.1/ngldn2k
请注意,要使此操作生效,必须先在本地安装 ninja。
一旦拥有了构建缓存,就可以将其添加为镜像源,具体讨论如下。
查找或安装构建缓存文件¶
要查找或安装构建缓存文件,必须使用以下方式配置 Spack 镜像源
$ spack mirror add <name> <url or path>
既可以指定 URL,也可以指定文件系统上的本地路径。在前面的示例中,您可以添加目录“spack-cache”并将其命名为 mymirror
$ spack mirror add mymirror ./spack-cache
您可以使用 spack mirror list 查看已添加的镜像源,如下所示
$ spack mirror list
mymirror file:///home/spackuser/spack/spack-cache
spack-public https://spack-llnl-mirror.s3-us-west-2.amazonaws.com/
此时,您已经创建了一个构建缓存,但 Spack 尚未对其进行索引,因此如果您运行 spack buildcache list,将看不到任何结果。您需要按如下方式为这个新的构建缓存建立索引
$ spack buildcache update-index ./spack-cache
现在您可以使用 list 了
$ spack buildcache list
==> 24 cached builds.
-- linux-ubuntu22.04-sapphirerapids / gcc@12.3.0 ----------------
[ ... ]
ninja@1.12.1
配置好 mymirror 并获得索引后,Spack 将在具体化和安装过程中自动使用它。这意味着您可以预期 spack install ninja 会从镜像源获取预构建软件包。让我们通过重新安装 ninja 来验证一下
$ spack uninstall ninja
$ spack install ninja
[ ... ]
==> Installing ninja-1.12.1-ngldn2kpvb6lqc44oqhhow7fzg7xu7lh [24/24]
gpg: Signature made Thu 06 Mar 2025 10:03:38 AM MST
gpg: using RSA key 75BC0528114909C076E2607418010FFAD73C9B07
gpg: Good signature from "example (GPG created for Spack) <example@example.com>" [ultimate]
==> Fetching file:///home/spackuser/spack/spack-cache/blobs/sha256/f0/f08eb62661ad159d2d258890127fc6053f5302a2f490c1c7f7bd677721010ee0
==> Fetching file:///home/spackuser/spack/spack-cache/blobs/sha256/c7/c79ac6e40dfdd01ac499b020e52e57aa91151febaea3ad183f90c0f78b64a31a
==> Extracting ninja-1.12.1-ngldn2kpvb6lqc44oqhhow7fzg7xu7lh from binary cache
==> ninja: Successfully installed ninja-1.12.1-ngldn2kpvb6lqc44oqhhow7fzg7xu7lh
Search: 0.00s. Fetch: 0.11s. Install: 0.11s. Extract: 0.10s. Relocate: 0.00s. Total: 0.22s
[+] /home/spackuser/spack/opt/spack/linux-ubuntu22.04-sapphirerapids/gcc-12.3.0/ninja-1.12.1-ngldn2kpvb6lqc44oqhhow7fzg7xu7lh
成功了!您刚刚完成了一个完整的示例:创建包含所需 spec 的构建缓存、将其添加为镜像源、更新其索引、列出内容,并最终从该缓存中安装。
默认情况下,当镜像源不可用或软件包尚未提供时,Spack 会退回到从源码构建。要强制 Spack 仅安装预构建软件包,您可以使用
$ spack install --use-buildcache only <package>
例如,要结合上述所有命令添加 E4S 构建缓存,并仅从中进行安装,您可以执行
$ spack mirror add E4S https://cache.e4s.io
$ spack buildcache keys --install --trust
$ spack install --use-buildcache only <package>
--install 和 --trust 标志会将密钥安装到密钥环中,并信任所有下载的密钥。
构建缓存索引视图¶
注意
Spack v1.2 引入。此功能的添加不会增加构建缓存版本 (v3)。
注意
OCI 构建缓存不支持构建缓存索引视图。
随着时间的推移,由于不断添加二进制文件,构建缓存可能会变得非常庞大且搜索效率低下。解决此问题的一种常见方法是将构建缓存拆分为针对特定应用程序或工作流的堆栈(stacks)。这允许将二进制文件作为较小的软件包集合进行管理,并推送到各自的镜像源中,从而使每个镜像源维护较小的搜索区域。然而,这种方法需要权衡,因为它会在堆栈之间复制通用依赖项,从而需要更大的存储空间和计算资源。拆分构建缓存还可能因为减少了单个镜像源中可用的二进制文件范围,而降低直接获取的命中率。
为了更好地解决大型搜索区域带来的问题,引入了构建缓存索引视图(本节中简称为“视图”)。视图是一个已命名的索引,它提供了对大型构建缓存的精选视角。这使得构建缓存维护者能够以堆栈拆分的方式提供构建缓存的粒度,而无需承担因重复依赖项所需的额外存储和计算成本。
视图可以使用活动环境或环境名称/路径列表来创建或更新。用于视图的 spack buildcache 命令是 spack buildcache update-index 命令的别名。
视图索引的存储方式与顶级构建缓存索引类似,但使用视图名称作为额外的前缀 <build cache prefix>/v3/manifests/index/my-stack/index.manifest.json。
创建构建缓存索引视图¶
以下是使用活动环境创建视图的示例。
$ spack env activate my-stack
$ spack install
$ spack buildcache push my-mirror
$ spack buildcache update-index --name my-view my-mirror
也可以通过传入环境名称或路径,从一个或多个环境的列表中创建视图。如果在活动环境内传入环境列表,则会忽略活动环境,仅考虑传入的环境。
$ spack buildcache update-index --name my-view my-mirror my-stack /path/to/environment/my-other-stack
更新构建缓存索引视图¶
为防止意外覆盖现有视图,必须指定更新视图的方式。更新视图索引有两种选项:--force 或 --append。使用 --force 选项将替换索引,就像之前的索引不存在一样。--append 选项将首先读取现有索引,然后向其中添加新的 spec。
$ spack buildcache push my-mirror
$ spack buildcache update-index --append --name my-view my-mirror my-stack
警告
将 --append 选项与构建缓存索引视图一起使用属于非原子操作。如果多个写入者同时追加到同一个视图,结果将仅包含最后一次写入的状态。在构建缓存工作流中使用 --append 时,用户需自行负责正确序列化更新操作。
流行构建缓存列表¶
创建并信任 GPG 密钥¶
spack gpg¶
Spack 支持使用 GPG 密钥对软件包进行签名和验证。Spack 使用单独的密钥环,因此用户主目录中已有的密钥不会被使用。
spack gpg init¶
当第一次安装 Spack 时,其密钥环是空的。存储在 var/spack/gpg 中的密钥是 Spack 安装的默认密钥。可以通过运行 spack gpg init 来导入这些密钥。这将把默认密钥作为受信任密钥导入到密钥环中。
信任密钥¶
可以使用以下命令将其他密钥添加到密钥环中
$ spack gpg trust <keyfile>
一旦密钥受到信任,就可以安装由该密钥所有者签名的软件包。
要从密钥环中删除密钥,请使用
$ spack gpg untrust <keyid>
密钥 ID 可以是电子邮件地址、名称或(最好是)指纹。
创建密钥¶
您还可以创建自己的密钥,以便使用以下命令签署自己的软件包
$ spack gpg create <name> <email>
默认情况下,密钥没有过期时间,但可以通过 --expires <date> 标志进行设置。建议使用 --comment <comment> 标志添加关于密钥用途的注释。密钥的公钥部分也可以导出以便与他人共享,以便他们能够使用您已签名的软件包,具体使用 --export <keyfile> 标志。私钥稍后也可以使用 spack gpg export <location> [<key>...] 命令导出。
密钥创建速度
生成新的 GPG 密钥需要生成大量的随机数。根据您系统产生的熵,整个过程可能需要很长时间(甚至看起来像是挂起)。虚拟机和云实例特别容易出现这种行为。
为了加速此过程,您可以安装诸如 rngd 之类的工具,它通常作为主机操作系统中的一个软件包提供。另一个选择是 haveged,它可以在 RHEL/CentOS 机器上安装。
这篇 Digital Ocean 教程对随机源提供了很好的概述。
构建缓存签名¶
默认情况下,Spack 会为推送到构建缓存的每个软件包添加加密签名,并在从构建缓存安装时验证该签名。
用于签名的密钥可以通过 spack gpg 命令以及上述的 spack buildcache keys 进行管理。
您可以通过 spack buildcache push --unsigned 在推送时禁用签名,并通过 spack install --no-check-signature 在从任何构建缓存安装时禁用验证。
或者,可以基于每个构建缓存来启用或禁用签名和验证
$ spack mirror add --signed <name> <url> # enable signing and verification
$ spack mirror add --unsigned <name> <url> # disable signing and verification
$ spack mirror set --signed <name> # enable signing and verification for an existing mirror
$ spack mirror set --unsigned <name> # disable signing and verification for an existing mirror
或者,您可以直接编辑 mirrors.yaml 配置文件
mirrors:
<name>:
url: <url>
signed: false # disable signing and verification
另请参阅 镜像源 (mirrors.yaml)。
在构建缓存中包含和排除 Specs¶
1.2 版本中添加。
在向构建缓存推送或从中获取时,您可以在镜像配置中指定包含和排除模式,以控制哪些 spec 被包含在构建缓存中或从中排除。如果一个 spec 同时满足包含和排除过滤器,则排除规则优先。默认情况下,包含所有 spec 且不排除任何 spec。
必须直接编辑 mirrors.yaml 以指定包含和排除模式
mirrors:
<name>:
url: <url>
include_binary:
- "%gcc" # include only specs that depend on gcc
exclude_binary:
- "dev_path=*" # except development specs
- "^mpich" # and any spec that depends on mpich
重定位¶
在不同的机器之间使用构建缓存时,安装根目录可能与构建二进制文件时使用的根目录不同。
为了解决这个问题,Spack 会在安装时自动将编码在二进制文件和脚本中的所有路径重定位到它们的新位置。
请注意,在某些情况下这是不可能的:如果二进制文件是在较短的路径中构建的,然后安装到较长的路径中,二进制文件中可能没有足够的空间来编码新路径。在这种情况下,Spack 将无法从构建缓存安装该软件包,需要进行源构建。
为了降低这种情况发生的可能性,强烈建议在构建期间向安装根目录添加填充(padding),如配置部分的 config 所述
config:
install_tree:
root: /opt/spack
padded_length: 128
自动推送到构建缓存¶
有时在软件包安装后立即将其推送到构建缓存是很方便的。Spack 可以通过在添加镜像源时设置 --autopush 标志来实现
$ spack mirror add --autopush <name> <url or path>
或者可以为现有的镜像源设置 --autopush 标志
$ spack mirror set --autopush <name> # enable automatic push for an existing mirror
$ spack mirror set --no-autopush <name> # disable automatic push for an existing mirror
然后,安装软件包后,它会自动推送到所有设置了 autopush: true 的镜像源。该命令
$ spack install <package>
将具有与以下相同的效果
$ spack install <package>
$ spack buildcache push <cache> <package> # for all caches with autopush: true
注意
软件包仅在从源构建时才会自动推送到构建缓存。
OCI / Docker V2 注册表作为构建缓存¶
Spack 还可以使用 OCI 或 Docker V2 注册表(如 Docker Hub、Quay.io、Amazon ECR、GitHub Packages、GitLab Container Registry、JFrog Artifactory 等)作为构建缓存。这是利用公共基础设施共享二进制文件,或在 GitHub Actions 和 GitLab CI 中缓存 Spack 构建的二进制文件的便捷方式。这些注册表不仅可用于共享 Spack 二进制文件,还可用于创建和分发可运行的容器镜像。
首先,使用 oci:// 作为方案配置 OCI 镜像源,并根据需要指定保存注册表用户名和密码(或个人访问令牌)的变量
$ spack mirror add --oci-username-variable REGISTRY_USER \
--oci-password-variable REGISTRY_TOKEN \
my_registry oci://example.com/my_image
这会在您的 mirrors.yaml 配置文件中注册一个如下所示的镜像源
mirrors:
my_registry:
url: oci://example.com/my_image
access_pair:
id_variable: REGISTRY_USER
secret_variable: REGISTRY_TOKEN
Spack 遵循 Docker 的命名约定,默认注册表为 Docker Hub。要使用 Docker Hub,可以省略注册表域
$ spack mirror add ... my_registry oci://username/my_image
在此之后,您可以像使用任何其他构建缓存一样使用该镜像源
$ export REGISTRY_USER=...
$ export REGISTRY_TOKEN=...
$ spack buildcache push my_registry <specs...> # push to the registry
$ spack install <specs...> # or install from the registry
注意
Spack 默认使用 https 连接 OCI 注册表,在失败时不会退回到 http。对于使用 http 而非 https 的本地注册表,您可以指定 oci+https://:5000/my_image。
使用流行容器注册表进行身份验证¶
以下是针对一些最流行的容器注册表进行身份验证的说明。在所有情况下,您都需要生成一个(临时)令牌用作密码——这与您的账户密码不同。
GHCR¶
要使用 GitHub 容器注册表 (GHCR) 进行身份验证,您可以使用您的 GitHub 用户名作为用户名。对于密码,您可以使用
具有
write:packages范围的个人访问令牌 (PAT)。具有
packages:write权限的 GitHub Actions 令牌 (GITHUB_TOKEN)。
另请参阅 GitHub 的文档 以及下方的 Spack GitHub Actions 构建缓存。
Docker Hub¶
要使用 Docker Hub 进行身份验证,您可以使用您的 Docker Hub 用户名作为用户名。对于密码,您需要在 Docker Hub 网站上生成个人访问令牌 (PAT)。有关详细信息,请参阅 Docker 文档。
Amazon ECR¶
要使用 Amazon ECR 进行身份验证,您可以使用 AWS CLI 生成临时密码。用户名始终是 AWS。
$ export AWS_ECR_PASSWORD=$(aws ecr get-login-password --region <region>)
$ spack mirror add \
--oci-username AWS \
--oci-password-variable AWS_ECR_PASSWORD \
my_registry \
oci://XXX.dkr.ecr.<region>.amazonaws.com/my/image
另请参阅 AWS 文档。
Azure 容器注册表¶
要使用启用了 RBAC 的 Azure 容器注册表进行身份验证,您可以使用 Azure CLI 为您的托管标识生成临时密码。用户名始终是 00000000-0000-0000-0000-000000000000。
$ export AZURE_ACR_PASSWORD=$(az acr login --name <registry-name> --expose-token --output tsv --query accessToken)
$ spack mirror add \
--oci-username 00000000-0000-0000-0000-000000000000 \
--oci-password-variable AZURE_ACR_PASSWORD \
my_registry \
oci://<registry-name>.azurecr.io/my/image
另请参阅 Azure 文档。
构建缓存与容器镜像¶
基于 OCI 注册表的构建缓存的一个独特功能是,生成带有已安装二进制文件的可运行容器镜像变得极其简单。这是一种让用户无需安装 Spack 即可使用应用程序的好方法——您只需要 Docker、Podman 或任何其他 OCI 兼容的容器运行时。
要生成容器镜像,您只需在推送到构建缓存时添加 --base-image 标志
$ spack buildcache push --base-image ubuntu:20.04 my_registry ninja
Pushed to example.com/my_image:ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spack
$ docker run -it example.com/my_image:ninja-1.11.1-yxferyhmrjkosgta5ei6b4lqf6bxbscz.spack
root@e4c2b6f6b3f4:/# ninja --version
1.11.1
如果未指定 --base-image,Spack 将生成 distroless 镜像。实际上,您将无法将它们作为容器运行,因为它们不附带 libc 和其他系统依赖项。但是,它们仍然兼容 skopeo、podman 和 docker 等用于拉取和推送的工具。
有关如何使用 Spack 创建容器镜像的详细信息,请参阅 将 Spack 安装导出为容器镜像 部分。
Spack GitHub Actions 构建缓存¶
为了显著加快 GitHub Actions 中的 Spack 速度,可以将二进制文件缓存在 GitHub Packages 中。此服务是一个 OCI 注册表,可以链接到 GitHub 存储库。
Spack 为 GitHub Actions 提供了一个包含一组常用软件包的公共构建缓存,可让您快速上手。有关详细信息,请参阅以下资源
spack/setup-spack 用于在 GitHub Actions 中设置 Spack
spack/github-actions-buildcache 关于公共构建缓存的更多详细信息
spack buildcache¶
spack buildcache push¶
创建已安装 Spack 软件包及其所有依赖项的 tarball。Tarball 和 specfile 会被压缩并校验;如果安装了 GPG2,还会对清单文件进行签名。诸如 spack buildcache install 之类的命令将搜索 Spack 镜像源以获取构建缓存列表。
参数 |
描述 |
|---|---|
|
部分 spec 列表,或以 |
|
创建 |
|
如果压缩的 tarball 和 spec 元数据文件已经存在,则覆盖它们 |
|
用于签署软件包的密钥。如果存在多个密钥,除非使用 |
|
在创建 tarball 之前使二进制文件中的路径变为相对路径 |
|
对有关创建未签名构建缓存的所有问题回答“是” |
spack buildcache list¶
检索 Spack 镜像源上可用构建缓存的所有 spec。
参数 |
描述 |
|---|---|
|
与为构建缓存下载的 spec 进行匹配的部分软件包 spec 列表 |
例如,spack buildcache list gcc 将仅打印安装 gcc 软件包的命令。
spack buildcache install¶
检索 Spack 镜像源上可用构建缓存的所有 spec,并安装与输入 spec 匹配的构建缓存。
参数 |
描述 |
|---|---|
|
要从构建缓存安装的部分软件包 spec 列表,或以 |
|
在解压 tarball 之前删除已存在的安装目录 |
|
对所有不使用 gpg 验证软件包的问题回答“是” |
spack buildcache keys¶
列出 Spack 镜像源上可用的公钥。
参数 |
描述 |
|---|---|
|
信任下载的密钥,并为每个密钥提示确认 |
|
对所有下载的密钥回答“是”以进行信任 |
构建缓存布局¶
本节描述了 URL 式构建缓存的结构和内容,与 OCI 式构建缓存不同。
二进制软件包的入口点是一个清单 JSON 文件,它引用至少两个存储为内容寻址 blob 的其他文件。这些文件包括一个 spec 元数据文件,以及作为压缩存档文件存储的软件包安装目录。二进制软件包清单文件的命名指示了软件包名称、版本以及具体 spec 的哈希值。例如
gcc-runtime-12.3.0-qyu2lvgt3nxh7izxycugdbgf5gsdpkjt.spec.manifest.json
将包含 gcc-runtime@12.3.0 的二进制软件包的清单。构建包的 ID 定义为具体 spec 的 DAG 哈希,也存在于文件名中。ID 将特定的二进制包与具有相同包名和版本的所有其他二进制包区分开来。以下是二进制软件包清单文件的示例。这样的文件将位于二进制镜像源的版本化 spec 清单目录中,例如 v3/manifests/spec/
{
"version": 3,
"data": [
{
"contentLength": 10731083,
"mediaType": "application/vnd.spack.install.v2.tar+gzip",
"compression": "gzip",
"checksumAlgorithm": "sha256",
"checksum": "0f24aa6b5dd7150067349865217acd3f6a383083f9eca111d2d2fed726c88210"
},
{
"contentLength": 1000,
"mediaType": "application/vnd.spack.spec.v5+json",
"compression": "gzip",
"checksumAlgorithm": "sha256",
"checksum": "fba751c4796536737c9acbb718dad7429be1fa485f5585d450ab8b25d12ae041"
}
]
}
该清单引用压缩的 tar 文件以及压缩的 spec 元数据文件,并包含两者的校验和。此校验和也用作关联文件的地址,因此必须已知才能在镜像源内定位 tarball 或 spec 文件。下载 tarball 或 spec 元数据文件后,应在本地计算校验和并将其与清单中的校验和进行比较,以确保自二进制软件包推送以来内容未更改。Spack 将所有数据文件(包括压缩的 tar 文件、spec 元数据、索引、公钥等)存储在 blobs/<hash-algorithm>/ 目录中,使用校验和的前两个字符作为子目录,以减少单个文件夹中的文件数量。以下是二进制镜像源内容组织的示意图
mirror_directory/
v3/
layout.json
manifests/
spec/
gcc-runtime/
gcc-runtime-12.3.0-s2nqujezsce4x6uhtvxscu7jhewqzztx.spec.manifest.json
gmake/
gmake-4.4.1-lpr4j77rcgkg5536tmiuzwzlcjsiomph.spec.manifest.json
compiler-wrapper/
compiler-wrapper-1.0-s7ieuyievp57vwhthczhaq2ogowf3ohe.spec.manifest.json
index/
index.manifest.json
key/
75BC0528114909C076E2607418010FFAD73C9B07.key.manifest.json
keys.manifest.json
blobs/
sha256/
0f/
0f24aa6b5dd7150067349865217acd3f6a383083f9eca111d2d2fed726c88210
fb/
fba751c4796536737c9acbb718dad7429be1fa485f5585d450ab8b25d12ae041
2a/
2a21836d206ccf0df780ab0be63fdf76d24501375306a35daa6683c409b7922f
...
manifests 目录中的文件按其代表的实体类型组织到子目录中。二进制软件包清单位于 spec/ 目录,构建缓存索引清单位于 index/ 目录,公钥及其索引的清单位于 key/ 子目录中。无论代表何种类型的实体,所有清单文件都命名为 .manifest.json 扩展名。
每个清单都包含一个 data 数组,其中每个元素都指代存储为内容寻址 blob 的关联文件。考虑到上面显示的示例 spec 清单,可以通过选择具有适当 mediaType 的数据 blob 来找到压缩的安装存档,在本例中为 application/vnd.spack.install.v2.tar+gzip。关联文件可以通过在 blobs/sha256/fb/ 下的 blobs 目录中查找命名为完整校验和值的文件来找到。
如上所述,构建缓存中的每个实体都存储为由清单指向的内容寻址 blob。虽然上面显示了示例 spec 清单(即二进制软件包的清单),但以下是构建缓存索引清单的样子
{
"version": 3,
"data": [
{
"contentLength": 6411,
"mediaType": "application/vnd.spack.db.v8+json",
"compression": "none",
"checksumAlgorithm": "sha256",
"checksum": "225a3e9da24d201fdf9d8247d66217f5b3f4d0fc160db1498afd998bfd115234"
}
]
}
关于此清单需要注意的一些事项是,它指向一个未压缩的 blob (compression: "none"),并且 mediaType 是我们以前没见过的,即 application/vnd.spack.db.v8+json。决定不对构建缓存索引进行压缩,是因为 Spack 尚未对构建缓存索引清单进行签名。一旦更改,您可能会开始看到这些索引存储为压缩 blob。
为了完整起见,以下是您可能在 Spack 构建缓存中找到的其他两种实体类型的清单示例。首先,公钥清单
{
"version": 3,
"data": [
{
"contentLength": 2472,
"mediaType": "application/pgp-keys",
"compression": "none",
"checksumAlgorithm": "sha256",
"checksum": "9fc18374aebc84deb2f27898da77d4d4410e5fb44c60c6238cb57fb36147e5c7"
}
]
}
注意 application/pgp-keys 的 mediaType。最后,公钥索引清单
{
"version": 3,
"data": [
{
"contentLength": 56,
"mediaType": "application/vnd.spack.keyindex.v1+json",
"compression": "none",
"checksumAlgorithm": "sha256",
"checksum": "29b3a0eb6064fd588543bc43ac7d42d708a69058dafe4be0859e3200091a9a1c"
}
]
}
同样,请注意 application/vnd.spack.keyindex.v1+json 的 mediaType。还要注意,上述两个清单示例都引用未压缩的 blob;原因与 Spack 尚未压缩构建缓存索引 blob 的原因相同。