首页
/ Bitcoin Core depends 依赖构建系统深度解析:确定性构建、build-id 缓存与跨编译设计

Bitcoin Core depends 依赖构建系统深度解析:确定性构建、build-id 缓存与跨编译设计

2026-09-04 20:40:46作者:贡沫苏Truman

本文以 depends/description.md 为核心,系统拆解 Bitcoin Core depends 目录下的依赖构建与缓存系统的七项核心设计——构建机/目标机无关、无时间戳依赖、干净的 sysroot、基于 build-id 的增量缓存、相对确定性产物、源码自动抓取校验以及自清理机制,并结合 depends/Makefiledepends/funcs.mkdepends/gen_id 等源码说明每项设计的落地实现。读完后,你将理解这套系统如何让 Bitcoin Core 的依赖构建在不同机器上可复现、可分发、可被自动化构建机消费,并能熟练使用 make HOST=...NO_QT=1 等选项完成原生与交叉构建。

一、系统定位:为构建 Bitcoin Core 而生的依赖工厂

depends 是"一套构建并缓存构建 Bitcoin Core 所需依赖的系统"(原文首句)。它的职责是:把 Qt、Boost、SQLite、libevent、ZeroMQ、expat 等第三方库(以及构建侧所需的 native 工具)按统一的配方编译好,打进一个干净的 sysroot,最终生成供主构建系统 CMake 使用的 toolchain.cmake 工具链文件。

depends/README.md 可知,使用流程非常直接:

# 构建当前架构+操作系统的依赖
make
# 交叉构建
make HOST=host-platform-triplet
# 例如
make HOST=x86_64-w64-mingw32 -j4

构建完成后,在主仓库根目录配置 Bitcoin Core 时必须显式指定工具链文件,CMake 默认会忽略 depends 的产物:

cmake -B build --toolchain depends/x86_64-pc-linux-gnu/toolchain.cmake

depends/Makefile 在依赖全部就绪后还会打印一条提示(第 193 行):

To build Bitcoin Core with these packages, pass '--toolchain <dir>/toolchain.cmake' to the first CMake invocation.

description.md 强调,这套系统有若干特点,使其"与大多数同类系统不同"。下面逐条对照源码展开。

二、设计一:Builder 与 Host 无关(构建机与目标机解耦)

原文表述:

In theory, binaries for any target OS/architecture can be created, from a builder running any OS/architecture. In practice, build-side tools must be specified when the defaults don't fit, and packages must be amended to work on new hosts.

即理论上任意构建机都可以产出任意目标平台/架构的二进制。实现上,Makefile 用 config.guess/config.sub 两个脚本做规范化:

BUILD = $(shell unset CC && ./config.guess)        # 构建机三元组
canonical_host:=$(shell ./config.sub $(HOST))     # 目标机规范化三元组

随后按"两个维度、各自 include"的方式加载规则(depends/Makefile#L114-L118):

include hosts/$(host_os).mk
include hosts/default.mk
include builders/$(build_os).mk
include builders/default.mk
include packages/packages.mk
  • depends/hosts/default.mk 定义目标侧工具(default_host_CC = $(host_toolchain)gcc,交叉编译时 host_toolchain$(host)- 前缀),并提供 add_host_tool_func/add_host_flags_func 两级回退(命令行变量 > 平台变量 > 默认值);
  • depends/builders/default.mk 定义构建侧(native 工具,如 native_capnpnative_qt 用的)CC/CXX/AR/TAR/SHA256SUM/DOWNLOAD/TOUCH 等工具,默认 gcc/g++,各 BSD/macOS 构建机在 builders/*.mk 中覆盖为 clang 系。

这就是原文所说"实践中,当默认值不适用时需显式指定构建侧工具,新增 host 需修改包"的含义:新增一个目标平台,要新增/修改 hosts/<os>.mk;新增一种构建机,要适配 builders/<os>.mk

三、设计二:不依赖时间戳,只认文件存在性与内容哈希

原文表述:

File presence is used to determine what needs to be built. This makes the results distributable and easily digestible by automated builders.

depends 的增量判断不靠 mtime 比较,而是靠stamp 文件是否存在 + build-id 变化depends/funcs.mk 中每个包都生成一整套阶段目标,其阶段名固定为(funcs.mk 第 307 行):

stages = fetched extracted preprocessed configured built staged postprocessed cached cached_checksum

每个阶段以文件存在为完成标志,例如(funcs.mk 第 250–299 行):

$($(1)_fetched): ...      # 下载 + sha256 校验通过,落盘 .hash stamp
$($(1)_extracted): | $($(1)_fetched) ...   # 解包
$($(1)_configured): | $($(1)_dependencies) $($(1)_preprocessed) ...
$($(1)_built): | $($(1)_configured) ...
$($(1)_staged): | $($(1)_built) ...
$($(1)_cached): | $($(1)_dependencies) $($(1)_postprocessed) ...
$($(1)_cached_checksum): $($(1)_cached) ...

关键细节:build-id 直接编码在目录名里(funcs.mk 第 86–93 行):

$(1)_staging_dir=$(base_staging_dir)/$(host)/$(1)/$($(1)_version)-$($(1)_build_id)
$(1)_cached:=$(BASE_CACHE)/$(host)/$(1)/$(1)-$($(1)_version)-$($(1)_build_id).tar.gz

配方一变,build_id 变,目录/缓存文件名就变,旧产物自然失效——无需任何时间戳比较。这正是"结果可分发、易被自动化构建机消化"的底层原因:CI 只需拉取缓存 tarball 与校验文件即可复用。

四、设计三:每次构建只看到被声明的依赖(干净的 sysroot)

原文表述:

For each build, the sysroot is wiped and the (recursive) dependencies are installed. This makes each build deterministic, since there will never be any unknown files available to cause side-effects.

configured 阶段的规则中可以清楚看到"先清空、再只安装递归依赖"(funcs.mk 第 267–272 行):

$($(1)_configured): | $($(1)_dependencies) $($(1)_preprocessed)
	echo Configuring $(1)...
	rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), $(build_TAR) --no-same-owner -xf $($(package)_cached); )
  • rm -rf $(host_prefix):每个包开始 configure 前,sysroot(host prefix)被整体清掉;
  • 随后只把 $($(1)_all_dependencies)(由 int_get_all_dependencies 递归展开的直接+间接依赖)的缓存 tarball 解进去。

因此在编译包 A 时,sysroot 里有 A 声明的依赖,任何"环境里恰好存在某个头文件/库"造成的隐性副作用都被排除。递归依赖的计算在 funcs.mk 第 25–27 行的 int_get_all_dependencies(递归 foreach + sort 去重)中完成。

五、设计四:build-id 驱动缓存,改动自动级联重建

这是 description.md 中最核心的一条:

Before building, a unique build-id is generated for each package. This id consists of a hash of all files used to build the package (Makefiles, packages, etc), and as well as a hash of the same data for each recursive dependency. If any portion of a package's build recipe changes, it will be rebuilt as well as any other package that depends on it. If any of the main makefiles (Makefile, funcs.mk, etc) are changed, all packages will be rebuilt. After building, the results are cached into a tarball that can be reused and distributed.

源码中这条逻辑由三个函数实现:

  1. 配方哈希 recipe_hash(funcs.mk 第 65–75 行 int_get_build_recipe_hash):对主 Makefile 文件组 + 本包配方 + 补丁文件做 sha256:

    $(1)_all_file_checksums:=$(shell $(build_SHA256SUM) $(meta_depends) packages/$(1).mk ... $(addprefix $($(1)_patches_path)/,$($(1)_patches)) | cut -d" " -f1)
    

    其中 meta_dependsdepends/Makefile#L181 定义为:

    meta_depends = Makefile config.guess config.sub funcs.mk builders/default.mk hosts/default.mk hosts/$(host_os).mk builders/$(build_os).mk
    

    由于每个包的配方哈希都包含全部 meta_depends,所以主 Makefile/funcs.mk 任一变更,所有包 id 全变,触发全量重建——与原文描述完全一致。

  2. build-id 生成 int_get_build_id(funcs.mk 第 77–83 行):build_id_long 由"包名-版本-recipe_hash-release_type + 所有递归依赖的 包名-版本-recipe_hash + 工具链 id($($(1)_type)_id)"拼成,再 sha256 截断为 11 字符(Makefile 第 54 行 HASH_LENGTH:=11)。依赖的 build-id 变化会通过 $(dep)-$(version)-$(recipe_hash) 串级联进来,实现"依赖包变了,下游包跟着重建"。

  3. 工具链 id 来自 depends/gen_id:Makefile 第 143–152 行把 build_CC/CXX/AR/NM/RANLIB/STRIP/SHA256SUM、各 *FLAGSDEBUGLTO 及可选盐(BUILD_ID_SALT/HOST_ID_SALT,默认 salt)作为环境传给 gen_idgen_id 会把编译器 -v 输出、AR/NM/RANLIB/STRIP --versionld.lld --versionmold --version、标准版本等拼成预像做 sha256(第 27–100 行)。注释中特意说明两点工程考量:unset SOURCE_DATE_EPOCH 防止时间戳泄漏进工具输出、用 bash -c 调用确保"command not found"报错行号恒为 1,保证哈希稳定——"它不 set -e,因为 id 判定以机会主义为主:失败可以,但要失败得一致"。

缓存侧还有主动的一致性自检(Makefile 第 238–259 行):check-packages/check-sources 会对每个包的缓存 tarball 与源码重新执行 sha256sum -c,校验失败则删除缓存并提示 Checksum mismatch for $(package). Forcing rebuild.. / Checksum missing or mismatched ... Forcing re-download.install 目标显式依赖 check-packages(Makefile 第 267 行)。

此外,Makefile 第 185–186 行还计算了全局 final_build_id(所有包 build_id_longtoolchain.cmake.in 哈希后取前 11 位),.stamp_$(final_build_id) 目标负责把全部缓存 tarball 解包进最终 host 目录——整个依赖树的指纹,工具链文件的生成以它为前置(第 202 行)。

六、设计五:产物"相对"确定性

原文表述(注意其谨慎措辞):

Each package is configured and patched so that it will yield the same build-results with each consequent build, within a reasonable set of constraints. Some things like timestamp insertion are unavoidable, and are beyond the scope of this system. Additionally, the toolchain itself must be capable of deterministic results. When revisions are properly bumped, a cached build should represent an exact single payload.

配套措施在仓库中可见:

  • tarball 打包确定性(funcs.mk 第 290–292 行):find . ... | TZ=UTC xargs -0r $(build_TOUCH) 先统一 mtime,再 find . | LC_ALL=C sort | $(build_TAR) --numeric-owner --no-recursion -czf ... -T -——固定时区 touch、locale 无关排序、--numeric-owner 避免 uid/gid 差异,让缓存 tarball 本身可比对、可分发;
  • 构建机 touch 工具固化时间depends/builders/default.mk#L9 default_build_TOUCH = touch -h -m -t 200001011200,把文件时间钉死在固定值;
  • gen_id 排除时间因素unset SOURCE_DATE_EPOCH(gen_id 第 31–33 行),既防泄漏进工具输出,也最大化包缓存复用。

原文同时明确了边界:编译进二进制的版本字符串等时间戳注入不在本系统职责内,且工具链本身必须支持确定性输出。

七、设计六:源码自动抓取与强制校验

原文表述:

Each package must define its source location and checksum. The build will fail if the fetched source does not match. Sources may be pre-seeded and/or cached as desired.

每个包的"身份"四元组以 depends/packages/expat.mk 为例:

package=expat
$(package)_version=2.7.3
$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_2_7_3/
$(package)_file_name=$(package)-$($(package)_version).tar.gz
$(package)_sha256_hash=821ac9710d2c073eaf13e1b1895a9c9aa66c1157a99635c639fbff65cdbdd732

抓取与校验的默认实现在 funcs.mk 第 29–41 行:

define fetch_file_inner
    ( mkdir -p $$($(1)_download_dir) && echo Fetching $(3) from $(2) && \
    $(build_DOWNLOAD) "$$($(1)_download_dir)/$(4).temp" "$(2)/$(3)" && \
    echo "$(5)  $$($(1)_download_dir)/$(4).temp" > $$($(1)_download_dir)/.$(4).hash && \
    $(build_SHA256SUM) -c $$($(1)_download_dir)/.$(4).hash && \
    mv $$($(1)_download_dir)/$(4).temp $$($(1)_source_dir)/$(4) && \
    rm -rf $$($(1)_download_dir) )
endef

define fetch_file
    ( $(call fetch_file_inner,$(1),$(2),$(3),$(4),$(5)) || \
      $(call fetch_file_inner,$(1),$(FALLBACK_DOWNLOAD_PATH),$(4),$(4),$(5)))
endef

要点:

  • 下载后立即 sha256sum -c,不匹配即构建失败(对应原文 "The build will fail if the fetched source does not match");
  • 主源站失败后自动回退到 FALLBACK_DOWNLOAD_PATH(Makefile 第 46 行,默认 https://bitcoincore.org/depends-sources,可通过命令行覆盖);
  • download-one 目标(Makefile 第 270 行)支持"只下载不构建",另有 download-osx/download-linux/download-win 三个平台化快捷目标(第 272–278 行),源码可预置(pre-seed)到 SOURCES_PATH(Makefile 第 33 行,默认 $(BASEDIR)/sources);
  • 解包阶段同样再校验一次哈希(funcs.mk 第 129 行 extract_cmds 默认值);
  • 特殊地,定义 $(package)_local_dir 的"本地包"会把本地目录打成 tarball 并把其哈希纳入 build-id,目录内容变化即触发该包及下游重建(funcs.mk 第 47–63 行 fetch_local_dir_sha256,以及 depends/packages.md 的 "Local packages" 一节)。

各包变量的完整语义(_version/_download_path/_file_name/_sha256_hash 及可选的 _build_subdir/_dependencies/_patches/_extra_sources)在 depends/packages.md 中有逐条说明,是新增包时的规范文档。

八、设计七:自清理,面向自动化构建机

原文表述:

Build and staging dirs are wiped after use, and any previous version of a cached result is removed following a successful build. Automated builders should be able to build each revision and store the results with no further intervention.

对应实现分三层:

  1. 目录级清理staged 阶段完成后立即 rm -rf $($(1)_extract_dir)(funcs.mk 第 282 行);cached 阶段把 tarball 移入缓存目录后 rm -rf $($(1)_staging_dir)(第 296 行);

  2. 全局清理目标(Makefile 第 261–265 行):

    clean-all: clean
    	@rm -rf $(SOURCES_PATH) x86_64* i686* mips* arm* aarch64* powerpc* riscv32* riscv64* s390x*
    
    clean:
    	@rm -rf $(WORK_PATH) $(BASE_CACHE) $(BUILD) *.log
    

    clean 清掉 work/(WORK_PATH)、built/(BASE_CACHE,即缓存区)与 .logclean-all 再清源码目录与所有 host 前缀目录;

  3. 旧缓存淘汰:由于缓存文件名内嵌 build-id(见第五节),配方变更后新生成的缓存是新文件,而 check-packages 的哈希校验(sha256sum -c $($(package)_cached_checksum) || rm ...)会把损坏/不匹配旧缓存删掉并强制重建(Makefile 第 238–243 行 check_or_remove_cached)。

这三层共同保证:自动化构建机可以为每个 revision 干净地构建、存储结果,无需人工干预。

九、实操速查:目录结构、选项与常用命令

结合 depends/README.mddepends/Makefile,常用操作汇总如下。

9.1 默认路径布局(Makefile 第 33–36 行,均可命令行覆盖)

变量 默认值 用途
SOURCES_PATH $(BASEDIR)/sources 下载源码存放处
WORK_PATH $(BASEDIR)/work 构建/暂存目录(work/buildwork/stagingwork/download
BASE_CACHE $(BASEDIR)/built 缓存 tarball 存放处
SDK_PATH $(BASEDIR)/SDKs macOS SDK 目录(交叉编译用)
FALLBACK_DOWNLOAD_PATH https://bitcoincore.org/depends-sources 主源失败时的回退下载源

9.2 功能开关(make FOO=bar 形式)

  • NO_BOOST / NO_QT / NO_QR / NO_ZMQ / NO_WALLET / NO_USDT / NO_IPC:不下载/构建/缓存对应功能依赖。Makefile 中通过 boost_packages_$(NO_BOOST) 这类"按开关取变量"的写法实现(第 154–165 行),其中 NO_IPC 在 Windows 目标下默认为 1(第 44 行);
  • DEBUG:置空时为 release(release_type=release,第 60–64 行),非空时切 debug 并启用更多运行时检查,同时 gen_id 会把哈希预像打到 stderr 便于调试缓存问题(gen_id 第 101–104 行);
  • C_STANDARD/CXX_STANDARD:默认 c11 / c++20(Makefile 第 48–49 行);
  • HOST_ID_SALT/BUILD_ID_SALT:id 盐值,默认 salt(第 57–58 行),用于在共享缓存环境下隔离不同工具链配置;
  • LTO:开启 LTO 所需选项(不向 *FLAGS 追加 -flto);
  • LOG:为单个包启用文件日志,出错时自动打印日志内容。

README 还指出一个重要的联动行为:若跳过了某些包(如 make NO_WALLET=1),生成的 toolchain.cmake 会替主构建系统设置对应 CMake 缓存变量,例如 -DENABLE_WALLET=OFF。这一点可在 depends/toolchain.cmake.in 中逐条验证:@wallet_packages@ 为空则 set(ENABLE_WALLET OFF)@qt_packages@ 为空则 BUILD_GUI OFF@ipc_packages@ 为空则 ENABLE_IPC OFF(第 137–177 行)。同时该文件把 CMAKE_FIND_ROOT_PATH 指向 depends 输出目录并设置 CMAKE_FIND_ROOT_PATH_MODE_LIBRARY/INCLUDE/PACKAGEONLY(第 88–92 行),从机制上保证主构建只从 sysroot 找库,与第四节"每次构建只看到被声明的依赖"的设计互为表里。

9.3 单包调试目标

depends/packages.md "Build targets" 一节,调试单个包时可用:

make ${package}
make ${package}_fetched
make ${package}_extracted
make ${package}_preprocessed
make ${package}_configured
make ${package}_built
make ${package}_staged
make ${package}_postprocessed
make ${package}_cached
make ${package}_cached_checksum

这些目标由 funcs.mk 第 309–313 行 ext_add_stagesstages 列表批量生成,与第三节的九阶段管线一一对应。

9.4 交叉编译工具链准备

README 给出了各目标的常见 triplet(x86_64-w64-mingw32arm64-apple-darwinaarch64-linux-gnuriscv64-linux-gnu 等)及对应的 mingw/clang/交叉 g++ 安装命令,并说明 macOS 交叉编译需将 SDK 解压到 depends/SDKs/。这些命令属于"适用前提":发行版包名随系统而变,且交叉工具链必须真实存在——与 description.md 第一条"新 host 需要适配"的提示一致。

十、总结

depends/description.md 用七条特征概括了这套系统,源码逐条印证:

  1. 构建机/目标机无关——config.guess/config.sub + hosts/*.mkbuilders/*.mk 双维度规则(depends/Makefiledepends/hosts/default.mk);
  2. 无时间戳依赖——九阶段 stamp 目标 + 内嵌 build-id 的缓存文件名(depends/funcs.mk);
  3. 干净 sysroot——configure 前 rm -rf $(host_prefix) 再只解包递归依赖;
  4. build-id 缓存——meta_depends 全量哈希 + 递归依赖 id 级联 + gen_id 工具链指纹,改主 Makefile 全量重建、改单包配方级联重建;
  5. 相对确定性——固定 touch 时间、TZ=UTC + LC_ALL=C 排序的 tar、--numeric-owner,并明确声明时间戳注入等边界外事项;
  6. 源码自动抓取校验——四元组身份定义 + 下载/解包双重 sha256 校验 + FALLBACK_DOWNLOAD_PATH 回退 + download* 预取目标;
  7. 自清理——阶段间目录回收、clean/clean-all 目标、check-packages 哈希淘汰。

这套机制使 Bitcoin Core 的第三方依赖构建成为"可复现、可分发、可自动化"的工程资产:任何自动化构建机拉取代码、运行 make、按 build-id 取/放缓存 tarball 即可完成每个 revision 的依赖构建,无需人工干预。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384