Zcash depends 系统包配方编写完全指南:从标识符到构建命令的深度剖析
2026/9/17 16:21:42 网站建设 项目流程

Zcash depends 系统包配方编写完全指南:从标识符到构建命令的深度剖析

【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash

本文以 Zcash 仓库中 depends/packages.md 为骨架,完整讲解 depends 依赖构建系统的配方(recipe)编写规范:如何定义包标识符、如何用构建变量精确控制编译行为、如何编排 fetch/extract/config/build/stage 全流程命令,并结合 depends/packages/ 目录下的真实配方(libsodium、libevent、boost、bdb、native_rust 等)与 depends/funcs.mk 的底层实现进行佐证。读完本文,你将具备为 Zcash 的可复现交叉编译环境新增或修改第三方依赖配方的完整实战能力。

一、背景:depends 系统解决什么问题

Zcash 的 depends 子系统是一套"构建并缓存依赖"的工具链,其设计目标(详见 depends/description.md)决定了配方编写时的所有约束:

  • 与构建机/目标机解耦:理论上任意构建机可为任意目标 OS/架构产出二进制,实际中构建架构以 x86_64 Linux 或 macOS 为准;
  • 不依赖时间戳:用"文件是否存在"判断哪些包需要构建,结果可分发、可被自动化构建器消费;
  • 构建环境干净:每次构建时 sysroot 被清空,只装入(递归)依赖,杜绝未知文件带来的副作用,保证确定性;
  • 按需重建与缓存:每个包有唯一 build-id,配方或主 Makefile 一旦变化即触发重建,结果打包成 tarball 缓存复用;
  • 源码自动获取与校验:每个包必须定义源码位置与校验和,抓取内容不匹配即构建失败;
  • 自清理:构建/暂存目录用后即清,缓存旧版本在成功构建后被移除。

使用方式见 depends/README.md:在当前 arch+OS 下直接make,交叉编译则make HOST=host-platform-triplet(例如make HOST=x86_64-w64-mingw32 -j4构建 Win64 依赖),产物是一个可供configure直接使用的前缀目录(./configure --prefix=$(pwd)/depends/x86_64-w64-mingw32)。make download可只抓取全部源码而不构建,另有download-osxdownload-windownload-linux等目标。

在 depends/packages/packages.mk 中可以看到 Zcash 当前维护的完整包清单:核心包boost libevent zeromq,Zcash 特有包libsodium rustcxx utfcpp tl_expectedgoogletest,钱包依赖bdb,以及native_clang native_ccache native_cmake native_fmt native_rust native_cxxbridge native_xxhash native_zstd等一批 native(构建期)工具包;Windows 交叉工具链由native_llvm_mingw提供,非 darwin 平台额外构建libcxx。每一个这样的包,都需要一份配方文件(depends/packages/<包名>.mk)。

二、配方的三大组成部分

每个配方由三大部分组成(对应 depends/packages.md 的目录结构):

  1. 定义标识符(Identifiers)——声明包是谁、源码从哪来、校验和是什么;
  2. 设置构建变量(Build Variables)——在$(package)_set_vars函数中定制编译器、flags 与选项;
  3. 定义构建命令(Build Commands)——用$(package)_fetch_cmds等命令编排构建流水线。

下文以文档中的示例包mylib为主线逐项展开。文档给出两条通用提示,贯穿始终:

  • 配方中写mylib_foo,在 make 层面写作$(package)_foo,以便配方模板更统一;
  • 相对于 Zcash 二进制/库的次要依赖(即不在contrib/devtools/symbol-check.pyALLOWED_LIBRARIES之列的库)应静态链接,理由见本文第六节。

三、标识符(Identifiers):包的身份证

每个包必须定义以下四个变量(在 depends/packages.md 中有明确说明):

变量含义说明
$(package)_version上游库/程序的版本若无版本号可用1.0之类占位值
$(package)_download_path上游源码所在位置(不含文件名)通常为 http/https/ftp,优先使用 https 等安全传输
$(package)_file_name下载路径下可取得的上游源码文件名本地保存的名字
$(package)_sha256_hash上游文件的 sha256 校验和抓取后强制校验,不符即构建失败

以真实配方 depends/packages/libsodium.mk 为例,四个必备标识符一目了然:

package=libsodium $(package)_version=1.0.20 $(package)_download_path=https://download.libsodium.org/libsodium/releases/ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=ebb65ef6ca439333c2bb41a0c1990587288da07f6c7fd07cb3a18cc18d30ce19

注意第一行package=libsodium:在 depends/funcs.mk 的int_vars/int_add_cmds等宏中,$(package)正是由每个配方文件顶部的package=赋值提供的,它串联起整个 make 层变量体系。

可选标识符

以下变量可选(继承自原文档的完整清单):

  • $(package)_build_subdir:在运行 configure/build/stage 命令前cd进入的子目录。典型例子是 depends/packages/bdb.mk:$(package)_build_subdir=build_unix,因为 Berkeley DB 要求从build_unix子目录中调用../dist/configure进行构建。
  • $(package)_download_file:当上游源码文件名与本地存储名不一致时使用,用于规避含奇怪字符的文件名。真实案例见 depends/packages/libevent.mk:上游发布的是release-2.1.12-stable.tar.gz(GitHub tag 归档),而本地希望存成libevent-2.1.12.tar.gz,于是:
    $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_download_file=release-$($(package)_version)-stable.tar.gz
  • $(package)_dependencies:本包依赖的其他包名。例如 boost 依赖native_b2(且非 darwin 时追加libcxx,见 depends/packages/boost.mk),bdb 非 darwin 时依赖libcxx。依赖关系会参与 build-id 计算并递归展开(见第五节)。
  • $(package)_patches:构建本包所需的补丁文件名列表。补丁文件存放在depends/patches/<包名>/目录下。libsodium 一口气挂了 4 个补丁(1.0.15-pubkey-validation.diff1.0.15-signature-validation.diff1.0.20-immintrin-conflict.diff1.0.22-evex512-fix.patch),并在preprocess_cmds中逐个patch -p1 < $($(package)_patch_dir)/...应用。
  • $(package)_extra_sources:需要通过$(package)_fetch_cmds额外抓取的文件,声明后会被make download一并抓取并校验。典型应用是 depends/packages/native_rust.mk:交叉编译时需要同时抓取构建机平台与目标平台的 Rust 发行包,其中目标平台包就是通过$(package)_extra_sources声明、由自定义fetch_cmds抓取的。

平台化标识符

从 depends/funcs.mk 的int_get_build_id宏可以看到,file_namesha256_hashdownload_filedownload_path均支持按主机平台覆盖:优先_exact_*,其次_$(host_arch)_$(host_os)、再_$(host_os)、最后是通用值。这正是 native_rust.mk 中$(package)_file_name_linux$(package)_file_name_darwin$(package)_file_name_freebsd$(package)_file_name_aarch64_linux各写一份的原因——Rust 官方按平台分发不同的工具链包,校验和也各不相同($(package)_sha256_hash_linux等)。

四、构建变量(Build Variables):精细控制编译行为

在定义完标识符后,通过一个名为$(package)_set_vars的函数来添加或定制构建变量:

define $(package)_set_vars ... endef

该函数由 depends/funcs.mk 的int_config_attach_build_config宏调用($(call $(1)_set_vars,$(1))),随后其内部会完成大量平台化拼接工作(见下文)。

主机/架构/系统前缀

大多数变量可以加hostarchitecture或两者前缀,使修改只对特定情况生效(原文档示例):

Universal: $(package)_cc=gcc Linux only: $(package)_linux_cc=gcc x86_64 only: $(package)_x86_64_cc = gcc x86_64 linux only: $(package)_x86_64_linux_cc = gcc

这些变量用于覆盖或追加默认值。int_config_attach_build_config宏会按$(release_type)$(host_arch)$(host_os)$(host_arch)_$(host_os)的顺序把带后缀的变量值逐级累加到基础变量上,因此配方里只需写差异化的片段。boost.mk 就是一套教科书式的平台化示例:

$(package)_config_opts_linux=target-os=linux threadapi=pthread runtime-link=shared $(package)_config_opts_mingw32=target-os=windows binary-format=pe threadapi=win32 runtime-link=static $(package)_config_opts_x86_64=architecture=x86 address-model=64 $(package)_config_opts_i686=architecture=x86 address-model=32 $(package)_config_opts_aarch64=address-model=64 $(package)_config_opts_armv7a=address-model=32

可覆盖的变量清单

原文档列出的可设置变量(完整继承):

变量作用
$(package)_cc/_cxx/_objc/_objcxxC/C++/Objective-C/ObjC++ 编译器
$(package)_ar/_ranlib/_libtool/_nm归档器、ranlib、libtool、nm 等工具
$(package)_cflags/_cxxflags/_ldflags/_cppflags编译/链接/预处理标志
$(package)_config_env/_build_env/_stage_env分别注入到 config/build/stage 命令的环境变量
$(package)_build_opts/_config_opts构建/配置选项

其中*_env系列变量用于给对应命令追加环境变量。这些变量在 depends/funcs.mk 中都有默认值:int_vars宏先把cc/cxx/ar/ranlib/...指向构建工具链的对应值,并把ldflags追加-L$(prefix)/libcppflags追加-I$(prefix)/includeint_config_attach_build_config再自动为config_env注入PKG_CONFIG_LIBDIRPKG_CONFIG_PATHCMAKE_MODULE_PATHPATH等,保证每个包只看得见自己的 sysroot。

debug/release 后缀

许多变量还支持 debug/release 后缀,以便只对对应构建配置生效(原文档示例):

$(package)_cflags_release = -O3 $(package)_cflags_i686_debug = -g $(package)_config_opts_release = --disable-debug

带后缀的值会叠加到不带后缀的值之上。所有构建默认视为 release,除非用户设置DEBUG=1(对应 depends/README.md 中的DEBUG选项)。funcs.mk$(1)_cflags+=$($(1)_cflags_$(release_type))这类累加即是对此的底层实现。真实案例:boost.mk 用$(package)_config_opts_release=variant=release$(package)_config_opts_debug=variant=debug分别驱动 b2 的构建变体;libevent.mk 用$(package)_config_opts_release=--disable-debug-mode在发布构建时关闭调试模式。

除以上变量外,配方可根据需要自行定义其他变量(文档明确说明"Other variables may be defined as needed"),例如 boost.mk 自定义了$(package)_config_libraries=chrono,filesystem,program_options,thread,test$(package)_toolset_$(host_os)等。

五、构建命令(Build Commands):六大阶段的流水线

每次构建都会创建独立的构建目录与暂存目录(原文档示例):work/build/mylib/1.0-1adac830f6ework/staging/mylib/1.0-1adac830f6e。目录名中的1adac830f6e即 build-id 前缀(depends/funcs.mk 中$(1)_build_id:=$(shell echo -n ... | SHA256SUM | cut -c-$(HASH_LENGTH)),由"包名-版本-配方哈希-release 类型-依赖链"共同哈希生成),任何配方或依赖变化都会产生新目录,从而自然实现缓存失效。

六个可用命令及其运行目录

原文档对每个命令的职责与运行目录有明确约定:

命令运行目录默认行为
$(package)_fetch_cmdsbuild dir抓取源文件;若未定义,则自动抓取并校验哈希
$(package)_extract_cmdsbuild dir校验哈希并解包;若未定义,假定源是 tarball
$(package)_preprocess_cmdsbuild dir/$(package)_build_subdir按需预处理源码(如打补丁、autogen);未定义则不做
$(package)_config_cmdsbuild dir/$(package)_build_subdir配置源码;未定义则不做
$(package)_build_cmdsbuild dir/$(package)_build_subdir编译;未定义则不做
$(package)_stage_cmdsbuild dir/$(package)_build_subdir暂存构建产物;未定义则不做

这些默认值在 depends/funcs.mk 中有精确对应:$(1)_fetch_cmds ?= $(call fetch_file,...)(默认按download_path/download_file抓取并写入哈希文件用SHA256SUM -c校验);$(1)_extract_cmds ?= ... tar --no-same-owner --strip-components=1 -xf $(source)(默认解 tarball,并先校验);preprocess/build/config/stage默认均为空。注意默认解包用--strip-components=1剥掉顶层目录。

每个配方可用的路径变量

变量含义
$(1)_staging_dir包的目标 sysroot 路径
$(1)_staging_prefix_dir包 staging 目录内部的 prefix 路径
$(1)_extract_dir包解包后的源码路径
$(1)_build_dir运行 configure/build/stage 命令的路径(即 extract_dir + build_subdir)
$(1)_patch_dir包补丁(如有)所在路径

funcs.mk中这些路径的推导为:staging_dir=$(base_staging_dir)/$(host)/$(1)/$(version)-$(build_id)staging_prefix_dir=$(staging_dir)$(prefix)extract_dir=$(base_build_dir)/$(host)/$(1)/$(version)-$(build_id)build_dir=$(extract_dir)/$(build_subdir)patch_dir=$(extract_dir)/.patches-$(build_id)。配方中通常写$($(package)_staging_prefix_dir)(即$(1)_staging_prefix_dir的包级形式)。

autotools 快捷方式

对 autotools 构建的包,配置阶段可直接使用$($(package)_autoconf),它通常能正确完成自动配置,配方中的$(package)_config_opts会被自动追加。funcs.mk$(1)_autoconf被展开为:

./configure --host=$(host) --prefix=$(prefix) $(config_opts) CC="$(cc)" CXX="$(cxx)" [NM=...] [RANLIB=...] [AR=...] [CFLAGS=...] [CXXFLAGS=...] [CPPFLAGS=...] [LDFLAGS=...]

真实用法见 libsodium.mk 的配置命令:$($(package)_autoconf) --enable-static --disable-shared

大多数 autotools 项目可用如下命令正确暂存(原文档推荐,亦是 libsodium/libevent 的 stage 命令):

$(MAKE) DESTDIR=$($(package)_staging_dir) install

一个完整配方的流水线串讲:libsodium

结合 depends/packages/libsodium.mk 看完整流水线:

  1. preprocess:依次应用 4 个补丁,然后cd $(build_subdir); DO_NOT_UPDATE_CONFIG_SCRIPTS=1 ./autogen.sh生成 configure 脚本(libsodium 未设build_subdir,默认取.);
  2. config$(autoconf) --enable-static --disable-shared——配合第六节的"次要依赖静态链接"原则,Zcash 需要的 libsodium 以静态库产出;
  3. build$(MAKE)
  4. stage$(MAKE) DESTDIR=$(staging_dir) install

类似地,libevent.mk 在 preprocess 中patch -p1 < .../0001-fix-windows-getaddrinfo.patch./autogen.sh,配置时通过--disable-shared --disable-openssl --disable-libevent-regress --disable-samples --disable-dependency-tracking裁剪功能面,并对 Windows 目标设置-D_WIN32_WINNT=0x0601(与 configure 侧保持一致的最低 Windows 版本,见配方注释与 depends/hosts/mingw32.mk)。boost.mk 则展示了更复杂的自定义:preprocess 中把编译器与各 flags 写入user-config.jam,config 用./bootstrap.sh选择库清单与工具集,build/stage 调用b2,并在 mingw32 下用postprocess_cmdslib/*.lib重命名为*.a以兼容 lld 链接器。

六、构建输出与次要依赖的静态链接策略

输出规范:拒绝 libtool 归档

依赖包的构建输出原则上不应包含任何 libtool 归档(.la文件),而应尽量输出.pc(pkg-config)文件。原文档引用 Gentoo Wiki 的解释:libtool 会把所有直接与间接依赖写进.la文件,导致严重的过度链接(overlinking),引发大量不必要重建。Zcash 的配方同样遵循该约束——libevent.mk 的postprocess_cmds就是rm lib/*.la,在暂存后显式删除 libtool 归档。

次要依赖(Secondary Dependencies)为何必须静态链接

所谓"次要依赖",指相对 Zcash 二进制/库而言、不在contrib/devtools/symbol-check.pyALLOWED_LIBRARIES集合中的库(该文件第 73 行定义该集合,见 contrib/devtools/symbol-check.py)。这类包应当静态链接,其理由与原文档一致:

  • 安全更新及时性:静态链接使安全修复随 Zcash 发布即时生效,不依赖系统包管理器的更新节奏;
  • 共识兼容:不同发行版/环境下行为一致,避免因库版本差异导致共识相关的细微分歧;
  • 调试与复现便利:用户问题可在单一二进制内复现,无需还原系统库组合;
  • 避免不兼容变更:不受上游 ABI/行为变化导致的意外破坏;
  • 跨发行版可移植:二进制不依赖目标系统特定版本的共享库。

原文档还给出了一个极具说服力的工程示例:将可执行文件链接到共享库libprimary(其自身又共享依赖libsecondary)时,必须在链接命令行中用-rpath/-rpath-link显式指定libsecondary的路径,仅写libprimary是不够的。而把静态的libsecondary链进共享的libprimary后,再链接libprimary时就不存在依赖链问题——尤其 Zcash 场景下"我们本来就是在链接一个用完即弃的 dummylibprimary",根本不关心终端用户系统里libsecondary是静态还是动态,静态libsecondarylibprimary自带全部符号,链接过程干净利落。这也是 depends/packages/packages.mk 中 libsodium、zeromq 等库的配方普遍采用--enable-static --disable-shared的原因。

七、底层机制补充:funcs.mk 如何驱动配方

对编写配方的人来说,理解 depends/funcs.mk 中的几个关键宏能显著减少踩坑:

  • int_get_build_recipe_hash:对主 Makefile(meta_depends)、packages/<包名>.mkpatches/<包名>/下所有补丁做 sha256,再汇总哈希得到recipe_hash。因此改配方或补丁即改哈希,必触发重建;改主 Makefile 则全部包重建(与 depends/description.md 的描述一致)。
  • int_get_build_id:把递归依赖(int_get_all_dependencies)展开成<dep>-<version>-<recipe_hash>链条,与自身信息拼成build_id_long后取 sha256 前HASH_LENGTH位作为 build-id。所以依赖树任一节点变化都会级联出新 build-id。
  • stamp 机制:每个阶段对应一个 stamp 文件(fetched/extracted/preprocessed/configured/built/staged/postprocessed/cached,见funcs.mk的 stamps 段),make 以文件存在性判定阶段是否完成,这正是"不依赖时间戳"原则的落地。
  • sysroot 隔离configured目标会rm -rf $(host_prefix)后把全部递归依赖的缓存 tarball 解包进去,保证"每个构建只能看到声明的依赖"。
  • native 包与 host 包的类型区分int_add_cmds段将native_*包标为build类型(跑在构建机上),普通包标为$(host_arch)_$(host_os)类型,二者工具链变量(CC/CXX等)来源不同。

八、编写配方:一份实操清单

综合 depends/packages.md 全文与仓库现状,新增一个包时按以下顺序自检:

  1. 定义标识符:在depends/packages/<name>.mk首行写package=<name>,随后必填$(package)_version_download_path(优先 https)、_file_name_sha256_hash;视需要补充_build_subdir_download_file_dependencies_patches_extra_sources
  2. 注册包:在 depends/packages/packages.mk 的packages/native_packages/wallet_packages列表中登记(并留意ifneq ($(host_os),darwin)之类平台条件块);
  3. 放置补丁:补丁放入depends/patches/<name>/,并在$(package)_patches中列出、在preprocess_cmds中应用;
  4. 编写set_vars:在define $(package)_set_vars ... endef中设置通用 flags,并按_linux/_mingw32/_aarch64等后缀与_release/_debug后缀做平台化、配置化定制;
  5. 编排六大命令:合理利用fetch_cmds/extract_cmds/preprocess_cmds/config_cmds/build_cmds/stage_cmds,autotools 项目优先使用$($(package)_autoconf)$(MAKE) DESTDIR=$($(package)_staging_dir) install;路径一律引用$($(package)_staging_dir)$($(package)_staging_prefix_dir)$($(package)_patch_dir)等预定义变量;
  6. 清理输出:若产物含 libtool 归档,在postprocess_cmds中删除,优先产出.pc文件;次要依赖务必静态链接(--enable-static --disable-shared),确保其不进入symbol-check.pyALLOWED_LIBRARIES之外的新动态库依赖;
  7. 验证:先make download确认抓取与哈希校验通过,再make完成构建;改配方后观察 build-id 变化确认缓存正确失效。

按照这套流程,无论是为 Zcash 引入新的系统级库、Rust crate 的 native 工具,还是调整既有依赖的版本与构建方式,都能在可复现、可缓存、可交叉编译的 depends 体系内稳定落地。

【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询