Monero depends 依赖构建系统:编写包配方(recipe)的完整指南
2026/9/23 17:52:43 网站建设 项目流程
  • 区块链
  • 金融科技

【免费下载链接】monero

Monero: the secure, private, untraceable cryptocurrency

项目地址:https://gitcode.com/gh_mirrors/mo/monero
点击查看免费下载

导读

Monero 仓库中的contrib/depends是一套用于跨平台构建并缓存第三方依赖的独立构建系统,它与 Monero 主工程的 CMake 体系解耦,专门负责把 Boost、OpenSSL、libsodium、libusb、hidapi、ZeroMQ 等外部库编译成适合交叉编译的静态库集合。本文以contrib/depends/packages.md为骨架,系统讲解"如何为 depends 系统编写一个新的包配方(recipe)":从标识符(Identifiers)、构建变量(Build Variables)到构建命令(Build Commands)三段式结构,并结合仓库内已有的boost.mkopenssl.mksodium.mk等真实配方与funcs.mk的实现细节,让你既能照着模板写出自己的配方,也能理解 depends 系统背后"哈希驱动的增量构建"机制。读完本文,你将掌握定义一个可被makemake download完整驱动、可跨平台交叉编译、可被缓存复用的 Monero 依赖包的全部要点。

一、配方(recipe)的总体结构

contrib/depends系统中,每个第三方依赖对应contrib/depends/packages/目录下的一个*.mk文件(make 片段)。每个配方由三大部分组成:

  1. 标识符(Identifiers):定义包名、版本、源码下载地址、文件名、SHA256 校验和、依赖关系与补丁等元信息;
  2. 构建变量(Build Variables):在$(package)_set_vars函数中集中设置编译器、链接器、各类 flags 与配置选项,并支持按主机平台、架构、debug/release 精细化定制;
  3. 构建命令(Build Commands):以$(package)_fetch_cmds$(package)_extract_cmds$(package)_preprocess_cmds$(package)_config_cmds$(package)_build_cmds$(package)_stage_cmds等钩子定义从取源到安装的完整流程。

下文以mylib作为示例包名,贯穿讲解。一个通用技巧是:配方内所有mylib_xxx变量在文档与生成规则中都写作$(package)_xxx,这样整个配方可以脱离具体包名复用——这正是funcs.mk通过$(1)参数化所有规则的根本原因(见 funcs.mk)。

二、标识符(Identifiers):定义包的身份与来源

每个包至少必须定义以下四个变量:

$(package)_version: Version of the upstream library or program. If there is no version, a placeholder such as 1.0 can be used. $(package)_download_path: Location of the upstream source, without the file-name. Usually http or ftp. $(package)_file_name: The upstream source filename available at the download path. $(package)_sha256_hash: The sha256 hash of the upstream file
  • $(package)_version:上游库或程序的版本号。若上游没有正式版本号,可以用1.0之类的占位值。版本号不仅用于标识源码,还深度参与构建目录命名与缓存 ID 的计算(见下文"构建 ID")。
  • $(package)_download_path:上游源码的下载地址,不含文件名,通常是 http 或 ftp。
  • $(package)_file_name:上游在下载地址处提供的源码文件名。
  • $(package)_sha256_hash:上游文件的 SHA256 校验和,用于取源后的完整性校验。

真实配方示例

以 sodium.mk 为例,四要素一目了然:

package=sodium $(package)_version=1.0.18 $(package)_download_path=https://github.com/jedisct1/libsodium/releases/download/$($(package)_version)-RELEASE $(package)_file_name=libsodium-$($(package)_version).tar.gz $(package)_sha256_hash=6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1

注意这里$(package)_version被嵌套引用进download_pathfile_name,这是 depends 配方的常见做法,避免了版本号在多处重复维护。

再看 boost.mk,其版本号还带了打包序号:

package=boost $(package)_version=1.91.0-1 $(package)_download_path=https://github.com/boostorg/boost/releases/download/boost-$($(package)_version) $(package)_file_name=boost-$($(package)_version)-b2-nodocs.tar.gz $(package)_sha256_hash=b5a3d1490118e012f8b12688240d981bcdfcd009fd35bc70d120fbc907df4f7c $(package)_patches=no-embed-absolute.patch

可选标识符

以下变量是可选的,按需定义:

$(package)_build_subdir: cd to this dir before running configure/build/stage commands. $(package)_download_file: The file-name of the upstream source if it differs from how it should be stored locally. This can be used to avoid storing file-names with strange characters. $(package)_dependencies: Names of any other packages that this one depends on. $(package)_patches: Filenames of any patches needed to build the package $(package)_extra_sources: Any extra files that will be fetched via $(package)_fetch_cmds. These are specified so that they can be fetched and verified via 'make download'.
  • $(package)_build_subdir:在执行 configure/build/stage 命令前进入的子目录,适用于源码根目录下还有一层目录结构的 tarball。
  • $(package)_download_file:上游源码文件名与本地存储文件名不一致时使用,例如用于规避包含特殊字符的文件名。
  • $(package)_dependencies:本包依赖的其他包名列表。依赖会递归展开,且依赖的构建结果会直接决定本包的构建 ID(详见后文)。
  • $(package)_patches:构建本包所需的补丁文件名。补丁存放在contrib/depends/patches/<package>/目录下。例如 boost.mk 引用no-embed-absolute.patch、openssl.mk 引用fix-android.patch。从funcs.mk可见,补丁文件的 SHA256 会参与配方的 recipe hash 计算(见 funcs.mk),补丁一旦改动,包会立即重建。
  • $(package)_extra_sources:除主源码外还需通过$(package)_fetch_cmds获取的额外文件。声明后它们才能被make download一并抓取并校验。

_dependencies还支持平台相关写法:在 hidapi.mk 中可以看到$(package)_linux_dependencies=libusb,即只在 Linux 主机下才依赖 libusb——这与funcs.mk$(1)_dependencies += $($(1)_$(host_arch)_$(host_os)_dependencies) $($(1)_$(host_os)_dependencies)的展开逻辑一一对应(见 funcs.mk)。

三、构建变量(Build Variables):用 set_vars 定制构建环境

定义完标识符后,可以在一个名为$(package)_set_vars的函数中集中设置或定制构建变量,函数体在配置阶段被funcs.mk通过$(call $(1)_set_vars,$(1))调用(见 funcs.mk):

define $(package)_set_vars ... endef

3.1 变量命名:主机/架构/发布类型维度

大部分变量都可以加上主机(host_os)、架构(host_arch)前缀,或两者组合前缀,使修改只对特定目标生效:

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

funcs.mkint_config_attach_build_config的展开顺序证实了这一点:它依次拼接$(release_type)$(host_arch)$(host_os)$(host_arch)_$(host_os)四个维度的 cflags/cxxflags/cppflags 等(见 funcs.mk)。以 openssl.mk 为例,OpenSSL 针对每种目标平台选择不同的 Configure target:

$(package)_config_opts_x86_64_linux=linux-x86_64 $(package)_config_opts_i686_linux=linux-generic32 $(package)_config_opts_aarch64_linux=linux-generic64 $(package)_config_opts_arm_android=--static android-arm $(package)_config_opts_aarch64_darwin=darwin64-arm64-cc $(package)_config_opts_riscv64_linux=linux64-riscv64 $(package)_config_opts_x86_64_mingw32=mingw64 $(package)_config_opts_i686_mingw32=mingw

3.2 可覆盖/追加的标准变量清单

以下变量可以被设置(覆盖默认值)或追加(在其默认值基础上增加内容):

$(package)_cc $(package)_cxx $(package)_objc $(package)_objcxx $(package)_ar $(package)_ranlib $(package)_libtool $(package)_nm $(package)_cflags $(package)_cxxflags $(package)_ldflags $(package)_cppflags $(package)_config_env $(package)_build_env $(package)_stage_env $(package)_build_opts $(package)_config_opts

其中*_env变量用于向对应的命令环境注入环境变量。例如 openssl.mk:

$(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" $(package)_config_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" $(package)_build_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native"

这些编译工具变量(cc/cxx/ar 等)在funcs.mk中已有默认值:默认从构建类型对应的工具链导出,例如$(1)_cc=$$($$($(1)_type)_CC)(见 funcs.mk),配方中的设置是对默认值的覆盖或补充。

3.3 debug/release 后缀

许多变量还支持debug/release 后缀,从而只对特定构建配置生效。默认所有构建都视为 release,除非用户设置DEBUG=1

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

带后缀的变量会在无后缀变量之外追加使用。看 boost.mk 的真实用法:

$(package)_config_opts_release=variant=release $(package)_config_opts_debug=variant=debug

这样同一个配方在 release 与 debug 两种模式下会生成不同 build id($(release_type)参与 build_id 计算,见 funcs.mk),互不污染缓存。

3.4 其他按需自定义变量

除了上述清单,配方还可以自由定义其他变量。例如 boost 配方定义了工具集与库清单变量,供后续构建命令引用:

$(package)_toolset_$(host_os)=$(build_CC) $(package)_archiver_$(host_os)=$($(package)_ar) $(package)_config_libraries_$(host_os)="chrono,filesystem,program_options,thread,test,serialization" $(package)_config_libraries_mingw32="chrono,filesystem,program_options,thread,test,serialization,locale" $(package)_cxxflags_linux+=-fPIC $(package)_cxxflags_darwin+=-ffile-prefix-map=$($(package)_extract_dir)=/usr

注意+=表示追加而非覆盖,这使得基础 flags(如 sysroot 头文件路径-I$(prefix)/include,见 funcs.mk)能保留下来。

四、构建命令(Build Commands):六大钩子与生命周期

对每次构建,depends 系统都会创建独立的构建目录(build dir)与暂存目录(staging dir)。例如对 mylib 1.0 的某次构建:

  • 构建目录:work/build/mylib/1.0-1adac830f6e
  • 暂存目录:work/staging/mylib/1.0-1adac830f6e

目录名中的1adac830f6e就是该包的 build id(取自 build_id 哈希的前若干字符,HASH_LENGTH定义于 funcs.mk,见 funcs.mk)。目录布局的完整定义在 funcs.mk:extract_dirbuild_dir(= extract_dir/build_subdir)、staging_dirpatch_dir等路径全部由系统根据 host、版本、build_id 自动计算。

每个配方可用以下构建命令钩子:

$(package)_fetch_cmds: Runs from: build dir Fetch the source file. If undefined, it will be fetched and verified against its hash. $(package)_extract_cmds: Runs from: build dir Verify the source file against its hash and extract it. If undefined, the source is assumed to be a tarball. $(package)_preprocess_cmds: Runs from: build dir/$(package)_build_subdir Preprocess the source as necessary. If undefined, does nothing. $(package)_config_cmds: Runs from: build dir/$(package)_build_subdir Configure the source. If undefined, does nothing. $(package)_build_cmds: Runs from: build dir/$(package)_build_subdir Build the source. If undefined, does nothing. $(package)_stage_cmds: Runs from: build dir/$(package)_build_subdir Stage the build results. If undefined, does nothing.
  • fetch_cmds:在构建目录中抓取源码。若未定义,系统会执行默认逻辑:按download_path/download_file下载并用sha256_hash校验,校验失败则构建失败;若设置了FALLBACK_DOWNLOAD_PATH,还会先尝试主路径、失败后回退到该路径(见 funcs.mk)。
  • extract_cmds:先校验源码 SHA256 再解压。若未定义,默认按 tarball 处理:tar --no-same-owner --strip-components=1 -xf(见 funcs.mk)。
  • preprocess_cmds:在build_subdir中做源码预处理,如打补丁、复制config.guess/config.sub、删除多余文件。
  • config_cmds:在build_subdir中配置源码(autotools 的./configure、CMake 的cmake、Boost 的bootstrap.sh等)。
  • build_cmds:实际编译。
  • stage_cmds:把构建产物安装到暂存目录(sysroot),供其他包与最终 Monero 构建使用。

4.1 每个配方可用的路径变量

构建命令中可以引用以下路径变量:

$(1)_staging_dir: package's destination sysroot path $(1)_staging_prefix_dir: prefix path inside of the package's staging dir $(1)_extract_dir: path to the package's extracted sources $(1)_build_dir: path where configure/build/stage commands will be run $(1)_patch_dir: path where the package's patches (if any) are found

其中$(1)_staging_prefix_dir是暂存目录内的 prefix 路径,安装产物最终会被打包进缓存;$(1)_patch_dir是补丁目录,例如 boost 的preprocess_cmds$($(package)_patch_dir)/no-embed-absolute.patch定位补丁(见 boost.mk)。

4.2 默认命令的自动推导

在 funcs.mk 中可以看到所有钩子的默认值(?=):

$(1)_fetch_cmds ?= $(call fetch_file,...) $(1)_extract_cmds ?= mkdir -p ... && sha256sum -c ... && tar --no-same-owner --strip-components=1 -xf ... $(1)_preprocess_cmds ?= $(1)_build_cmds ?= $(1)_config_cmds ?= $(1)_stage_cmds ?= $(1)_set_vars ?=

也就是说:绝大多数包只需覆盖 preprocess/config/build/stage 四个钩子,fetch 与 extract 直接继承默认实现即可,这大大降低了编写新配方的成本。例如 sodium.mk 只实现了后四个钩子:

define $(package)_config_cmds $($(package)_autoconf) AR_FLAGS=$($(package)_arflags) endef define $(package)_build_cmds $(MAKE) endef define $(package)_stage_cmds $(MAKE) DESTDIR=$($(package)_staging_dir) install endef

4.3 autotools 项目的快捷写法

对于 autotools 项目,可以在 configure 步骤使用$($(package)_autoconf),它通常能自动完成正确的 configure(会追加所有$($(package)_config_opts)):

define $(package)_config_cmds $($(package)_autoconf) AR_FLAGS=$($(package)_arflags) endef

libusb.mk、zeromq.mk 均采用此写法。而大部分 autotools 项目可以用下面这条命令完成 stage(把产物安装进暂存 sysroot,供后续打包缓存):

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

4.4 非 autotools 项目示例

  • Boost(b2/Boost.Build):config 用bootstrap.sh --with-toolset=... --without-icu --with-libraries=...,build 用./b2 ... stage,stage 用./b2 ... install(见 boost.mk)。
  • OpenSSL(Configure + make build_libs):config 用./Configure $($(package)_config_opts) ARFLAGS=...,build 用$(MAKE) build_libs,stage 用$(MAKE) DESTDIR=... install_sw(见 openssl.mk)。
  • hidapi(CMake):config 用$($(package)_cmake) .,build/stage 直接用$(MAKE)$(MAKE) install(见 hidapi.mk)。

4.5 额外的 postprocess 钩子

实际仓库中的配方还普遍使用$(package)_postprocess_cmds在 stage 之后、缓存打包之前精简产物。例如:

  • openssl.mk 删除share bin etc目录;
  • sodium.mk 与 zeromq.mk 删除lib/*.la(libtool 归档文件);
  • boost.mk 在 release 构建下裁剪include/boost中未使用的头文件目录(仅保留 algorithm、asio、serialization、thread 等 Monero 实际用到的子库)。

这类瘦身对减小跨平台缓存体积、缩短 CI 分发时间很有价值,是编写生产级配方时值得借鉴的实践。

五、深入 depends 引擎:build id、缓存与确定性(原理支撑)

理解了配方结构后,再看 depends 引擎如何消费这些配方,能帮你写出更正确的配方。相关机制完整记录在 description.md 中,其要点与配方编写直接相关:

5.1 构建 ID:哈希驱动的增量构建

每个包在构建前都会生成一个唯一的 build id,它由以下内容共同哈希而成:

  • 本包配方的所有文件哈希(packages/<pkg>.mk、主 Makefile、funcs.mk 等 meta 文件、以及patches/<pkg>/下的补丁);
  • 每个递归依赖的package-version-recipe_hash组合;
  • 构建类型(release/debug)与工具链 id。

相关计算逻辑见 funcs.mk(int_get_build_recipe_hashint_get_build_id)。这意味着:任何配方、补丁或依赖的改动都会自动触发本包及其所有下游依赖的重建;而主 Makefile / funcs.mk 等 meta 文件一旦变化,则所有包全部重建。这与 description.md 中"如果配方任何部分改变,本包及依赖它的包都会被重建"的描述完全一致。

5.2 缓存与确定性

构建完成后,产物会以 tarball 形式缓存(路径形如$(BASE_CACHE)/$(host)/$(pkg)/$(pkg)-$(version)-$(build_id).tar.gz,见 funcs.mk),可被后续构建复用与分发。构建与暂存目录在每次构建后会被清理,任何旧版本的缓存结果也会在成功构建后被移除(自清理,见 description.md)。同时系统不依赖时间戳判断是否需要构建,而是用文件存在性(各种.stamp_*标记,见 funcs.mk)驱动,这使得结果可分发、便于自动化构建系统消化。

5.3 每次构建只暴露声明过的依赖

每个包构建时,sysroot 会被清空,然后只安装其(递归)依赖。这样构建是确定性的——不会有未知文件混入造成副作用(见 description.md)。因此编写配方时务必完整声明$(package)_dependencies,否则构建时找不到头文件或库。

5.4 源码自动抓取与校验

每个包必须定义来源与校验和,抓取的源码若与哈希不符,构建直接失败(见 description.md)。funcs.mkfetch_file实现还支持SOURCES_PATH预置与FALLBACK_DOWNLOAD_PATH回退(见 funcs.mk),方便离线或受限网络环境。

六、如何驱动配方:与 depends 系统的衔接

配方写好后,由contrib/depends/Makefile统一驱动。常用操作(详见 README.md):

  • 为当前架构/OS 构建全部依赖:在contrib/depends目录执行make
  • 为其他架构交叉编译:make HOST=host-platform-triplet,例如make HOST=x86_64-w64-mingw32 -j4
  • 生成可直接接入 Monero CMake 的工具链:构建完成后会生成contrib/depends/<triplet>/share/toolchain.cmake,然后在 Monero 源码树顶层执行:
mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE=$PWD/../contrib/depends/x86_64-w64-mingw32/share/toolchain.cmake ..
  • 常用交叉编译 triplet:i686-w64-mingw32(Win32)、x86_64-w64-mingw32(Win64)、x86_64-apple-darwin(macOS x86_64)、arm-linux-gnueabihf(Linux ARM 32 位)、aarch64-linux-gnu(Linux ARM 64 位)、riscv64-linux-gnu(Linux RISC-V 64 位);
  • 构建选项可通过make FOO=bar传入:SOURCES_PATH(下载源码存放位置)、BASE_CACHE(构建产物缓存位置)、FALLBACK_DOWNLOAD_PATH(取源失败时的回退路径)、DEBUG(关闭部分优化并开启更多运行时检查)、HOST_ID_SALT/BUILD_ID_SALT(生成 host/build 包 ID 时的盐值);
  • 只取源不构建:make downloadmake download-osxmake download-winmake download-linux,分别抓取全部或某平台所需源码——这正是$(package)_extra_sources会被一并抓取校验的场景(见 funcs.mk 的all_sources汇总逻辑);
  • Windows mingw 构建需要将 gcc/g++ 切换到 posix 模式:
update-alternatives --set x86_64-w64-mingw32-g++ x86_64-w64-mingw32-g++-posix update-alternatives --set x86_64-w64-mingw32-gcc x86_64-w64-mingw32-gcc-posix

七、编写新配方的完整流程(Checklist)

综合全文,为 Monero 的 depends 系统新增一个包mylib的推荐流程如下:

  1. 创建配方文件:新建contrib/depends/packages/mylib.mk,首行写package=mylib
  2. 定义标识符:填写$(package)_version$(package)_download_path$(package)_file_name$(package)_sha256_hash四个必填项;按需补充build_subdirdownload_filedependenciespatchesextra_sources
  3. 补充补丁与额外源码:把补丁放入contrib/depends/patches/mylib/,并在$(package)_patches中列出文件名;
  4. 实现 set_vars:用define $(package)_set_vars ... endef设置或追加 cflags/cxxflags/ldflags/cppflags/config_opts 等;需要平台差异化时使用_linux_x86_64_x86_64_linux等前缀;需要区分构建模式时使用_release/_debug后缀;
  5. 实现构建命令钩子:优先复用默认的 fetch/extract;autotools 项目用$($(package)_autoconf)做 configure,用$(MAKE) DESTDIR=$($(package)_staging_dir) install做 stage;CMake 项目用$($(package)_cmake) .;其他构建系统参照 boost/openssl 的写法自定义;
  6. 精简产物(可选):通过$(package)_postprocess_cmds删除.la文件、文档、无用头文件等,减小缓存体积;
  7. 验证:先make download确认取源与校验通过,再make(可带HOST=...)确认编译、暂存与缓存打包成功;修改配方后再次make应看到该包被自动重建(build id 变化)。

结语

Monero 的 depends 系统用一套简洁、统一的配方格式(标识符 + set_vars + 六钩子构建命令)封装了跨平台依赖构建的复杂性。掌握了 packages.md 中定义的这套规范,你不仅可以为 Monero 新增或升级任何第三方依赖,还能理解其背后"哈希驱动的增量构建、sysroot 隔离、tarball 缓存分发"的确定性构建哲学。结合实际配方(boost.mk、openssl.mk、sodium.mk、hidapi.mk、libusb.mk、zeromq.mk)与引擎实现(funcs.mk、Makefile、description.md)对照阅读,即可举一反三地应用到自己的交叉编译与依赖管理场景中。

  • 区块链
  • 金融科技

【免费下载链接】monero

Monero: the secure, private, untraceable cryptocurrency

项目地址:https://gitcode.com/gh_mirrors/mo/monero
点击查看免费下载

相关推荐

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

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

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

立即咨询