Nix 源码构建开发指南:从 devShell 到交叉编译的完整实践
【免费下载链接】nixNix, the purely functional package manager项目地址: https://gitcode.com/gh_mirrors/ni/nix
本文是 Nix(纯函数式包管理器)源码仓库的开发(Hacking)入门指南,基于仓库根目录的 HACKING.md 编写。文中完整覆盖两条主流开发路径——经典nix-shell与实验性的nix develop,并深入展开支持平台、系统类型(System type)、交叉编译、多种编译环境(stdenv)、编辑器 LSP 集成与代码格式化等实践内容,结合仓库内的 flake.nix、packaging/dev-shell.nix、meson.build 等源码佐证底层实现,帮助你从零搭建 Nix 自身源码的开发、构建、测试与提交环境。
前置准备
在开始之前,请确认环境满足以下条件:
- 克隆仓库:本指南假设你已经把 Nix 源码克隆到本地。克隆时需注意:仓库中包含符号链接(symlink),因此在 Windows 上克隆前必须启用 git 的
core.symlinks设置,否则检出会失败。 - 本地已安装 Nix:以下所有构建步骤都依赖 Nix 来搭建开发环境(下载并注入所有构建依赖、设置环境变量)。如果你还没有安装 Nix,请先按照安装指引完成安装(参见 scripts/install-multi-user.sh 等多用户安装脚本,以及 packaging/installer 目录下的安装器实现)。
经典构建方式:nix-shell
Nix 仓库在根目录提供了 shell.nix(通过 default.nix 使用 flake-compat 桥接自 flake 输出),因此可以直接用经典 Nix 进入开发环境:
$ nix-shell进入 shell 后,所有构建依赖的环境变量(编译器、头文件、库路径等)都已就绪。若想使用其他受支持的编译环境(见下文“编译环境”一节),可以指定对应的 devShell 属性:
$ nix-shell --attr devShells.x86_64-linux.native-clangStdenv提示:使用
native-ccacheStdenv可以大幅缩短重复编译的时间。默认情况下,ccache 的缓存产物保存在~/.cache/ccache/。
在 shell 中构建 Nix
在nix-shell中构建 Nix 到本地目录(而不是 Nix 存储),可以执行以下命令:
[nix-shell]$ out="$(pwd)/outputs/out" dev=$out debug=$out mesonFlags+=" --prefix=${out}" [nix-shell]$ dontAddPrefix=1 configurePhase [nix-shell]$ buildPhase这里通过把prefix指向当前目录下的outputs/out,并让 configure/build 阶段直接作用于源码树,从而在不污染 Nix 存储的前提下快速迭代。
运行测试:
[nix-shell]$ checkPhase安装到$(pwd)/outputs并验证版本:
[nix-shell]$ installPhase [nix-shell]$ ./outputs/out/bin/nix --version nix (Nix) 2.12直接构建发布版(产物进入 Nix 存储):
$ nix-build使用 flake 构建:nix develop
本节假设你的 Nix 已经启用了flakes与nix-command两个实验特性。这两个特性分别对应仓库文档中定义的实验特性开关(xp-feature-flakes与xp-feature-nix-command)。
进入开发环境:
$ nix develop该 shell 与nix-shell的区别在于:它还会把./outputs/bin/nix加入$PATH,因此构建完成后可以立刻直接执行nix命令。
选择其他编译环境的开发 shell:
$ nix develop .#native-clangStdenv提示:同样地,使用
ccacheStdenv变体(如.#native-ccacheStdenv)可大幅改善重复编译时间。
构建、测试与安装:
[nix-shell]$ configurePhase [nix-shell]$ buildPhase [nix-shell]$ checkPhase [nix-shell]$ installPhase [nix-shell]$ nix --version nix (Nix) 2.12注意:
nix develop路径下无需手动设置out/prefix,devShell 的shellHook已经接管了这些变量(见 packaging/dev-shell.nix 中的shellHook定义)。
构建发布版:
$ nix build关于如何运行与过滤各类测试(单元测试、功能测试、模糊测试等),可参考仓库中的测试运行文档doc/manual/source/development/testing.md,测试基础设施位于 tests/functional 与各*-tests组件目录。
devShell 的底层实现
理解nix develop背后发生了什么,有助于排查环境问题。从 flake.nix 可以看到:
devShells由makeShell(定义于 packaging/dev-shell.nix)针对每个系统与每种 stdenv 生成,并统一以native-<stdenv名>前缀命名,默认的native实际指向native-stdenv;- devShell 本质是一个
stdenv.mkDerivation(pname = "shell-for-nix"),其shellHook会依次设置PATH、清空PYTHONPATH、导出MANPATH,并重定义configurePhase/buildPhase/checkPhase/installPhase,使其默认进入build目录后调用 Meson/Ninja 对应的阶段函数; - 所有内部组件(
nix-util、nix-store、nix-expr等,见 meson.build 中的 subproject 列表)的buildInputs会通过buildInputsClosureCond做闭包去重后注入 shell,避免重复引入内部依赖; - 在 Linux 上,
CC_LD/CXX_LD被设为mold,即默认使用 mold 链接器加速链接;此外 shell 中还预装了pre-commit、nixfmt、shellcheck、include-what-you-use、gdb(Unix)、clang-tools(clang shell)等开发工具。
支持的平台
Nix 可构建的官方平台在 flake.nix 的systems列表与文档中均有列出:
x86_64-linuxi686-linuxaarch64-linuxaarch64-darwinarmv6l-linuxarmv7l-linuxpowerpc64-linux(ELFv1 ABI)powerpc64le-linuxriscv64-linux
注意:当前 flake.nix 中systems实际只包含i686-linux、x86_64-linux、aarch64-linux与aarch64-darwin,其余平台(如armv6l-linux、powerpc64-linux等)属于通过crossSystems提供的交叉编译目标。
为其他平台构建
要为与当前机器不同的平台构建 Nix,需要先让当前 Nix 能够生成目标平台的代码,常见方案有两种:
- 远程构建机(remote build machines):把构建任务分发到目标平台的机器上执行;
- 二进制格式模拟(binary format emulation):仅 NixOS 支持,通过 binfmt 在内核层面模拟其他架构的二进制。
具备上述条件后,只需选择对应属性即可构建。例如为aarch64-linux交叉编译:
$ nix-build --attr packages.aarch64-linux.default或在启用 flake 的 Nix 中:
$ nix build .#packages.aarch64-linux.default可用的交叉编译目标
文档与 flake.nix 的crossSystems列表共同确认,仓库提供了以下交叉编译目标:
armv6l-linuxarmv7l-linuxpowerpc64-linux(ELFv1 ABI)powerpc64le-linuxriscv64-linuxx86_64-freebsdx86_64-w64-mingw32(Windows 目标,flake 中为其配置了 wine 模拟器)
如果想要在尚未支持的平台上引导(bootstrap)Nix,可以向flake.nix中的crossSystems追加对应的系统类型字符串。
在 flake 中,这些交叉编译目标以组件 × 目标平台的矩阵形式暴露,例如:
nix build .#nix-everything-riscv64-unknown-linux-gnu nix build .#nix-everything-armv7l-unknown-linux-gnueabihf nix build .#nix-everything-x86_64-unknown-freebsd nix build .#nix-everything-x86_64-w64-mingw32(属性名形如<组件>-<triple>,由 flake.nix 中的flatMapAttrs构建矩阵生成。)
一次构建多个平台
同一个源码树可以同时进行多个原生/交叉构建,用于验证对某一平台的改动不会破坏其他平台。Meson 天然支持这一点:所有构建产物都被限制在各自的构建目录内,只需让多个构建目录共享同一份源码即可。
具体做法:
告诉 Nixpkgs 构建基础设施 Meson 构建目录的位置:
mesonBuildDir=build-my-variant-name正常配置:
configurePhase正常构建:
buildPhase
系统类型(System type)
Nix 使用如下格式的字符串标识它运行的系统类型(或平台):
<cpu>-<os>[-<abi>]该字符串在 Nix 针对特定系统编译时确定,其来源是 Meson 的host_machine信息(host_machine.cpu_family()、host_machine.endian()、host_machine.cpu()等)。
出于历史原因和向后兼容性,部分 CPU 与 OS 标识符会被翻译映射,对应关系如下:
host_machine.cpu_family() | host_machine.endian() | Nix 系统类型 |
|---|---|---|
x86 | i686 | |
arm | host_machine.cpu() | |
ppc | little | powerpcle |
ppc64 | little | powerpc64le |
ppc | big | powerpc |
ppc64 | big | powerpc64 |
mips | little | mipsel |
mips64 | little | mips64el |
mips | big | mips |
mips64 | big | mips64 |
使用 Meson 交叉编译的注意点
当用 Meson 为本地开发做交叉编译时,需要通过--cross-file指定一个 cross-file(它定义目标架构与工具链);而当“用 Nix 交叉编译 Nix”时,Nixpkgs 会自动处理这一切,无需手动提供 cross-file。
编译环境(Compilation environments)
Nix 可以用多种编译环境构建,这些环境由 flake.nix 中的stdenvs列表定义:
stdenv:默认环境;gccStdenv:强制使用 gcc 编译器;clangStdenv:强制使用 clang 编译器;ccacheStdenv:启用 ccache 编译器缓存以加速编译;- (flake 中还存在
libcxxStdenv,即使用 libc++ 标准库的变体)
使用 flake 构建:
$ nix build .#nix-cli-ccacheStdenv使用经典 Nix 构建:
$ nix-build --attr nix-cli-ccacheStdenvnix-cli-ccacheStdenv中的nix-cli是 CLI 组件的名称;换成任意其他受支持的环境(例如nix-cli-clangStdenv)同样有效。从 flake.nix 的构建矩阵可以看到,每个组件都会生成<组件>-<stdenv名>形式的属性,同时还有-static(静态链接)、-llvm(LLVM 工具链)等变体。
编辑器集成(Editor integration)
基于 clang 的 devShell(如.#native-clangStdenv)默认安装了clangdLSP 服务器。要让编辑器获得完整的补全、跳转与诊断能力,需要一份compile_commands.json告诉clangd每个文件是如何编译的——Meson 的 configure 步骤总会把它生成在构建目录内。
配置要点:
- 让编辑器使用
.#native-clangStdenvshell 中的clangd:可以在 devShell 内启动编辑器,也可以借助 nix-direnv 与对应的编辑器插件自动加载环境; - 部分编辑器需要额外插件:Visual Studio Code 需要安装 clangd 扩展;Emacs(如 lsp-mode)、Vim(如 vim-lsp)等则需要通用的 LSP 支持插件;
- 编辑器相关的具体配置因人而异,这里不再展开。
格式化与 pre-commit 钩子
一次性格式化
可以随时运行仓库自带的格式化脚本对整个代码库执行格式化:
./maintainers/format.sh该脚本(maintainers/format.sh)依赖pre-commit与_NIX_PRE_COMMIT_HOOKS_CONFIG环境变量,因此推荐通过nix develop -c ./maintainers/format.sh运行;脚本内部会循环执行pre-commit run --config "$_NIX_PRE_COMMIT_HOOKS_CONFIG" --all-files,还支持--until-stable参数(反复运行直到所有检查通过为止)。
安装 pre-commit 钩子
如果希望每次提交前自动运行格式化检查,可在 devShell 内安装钩子:
pre-commit-hooks-install该命令基于 cachix/git-hooks.nix)中启用的检查包括:
check-merge-conflicts及针对 mergify backport 的自定义冲突检测;meson-format:使用meson format格式化meson.build/meson.options文件;nixfmt:格式化 Nix 表达式(对tests/functional/lang中格式敏感的测试用例做了排除);clang-format:格式化 C/C++ 代码(排除测试数据与 vendored 代码);shellcheck:检查 shell 脚本;zizmor:静态分析 GitHub Actions 工作流。
提交时请留意控制台输出。若钩子失败,先运行git add --patch采纳其建议的修改,然后再次提交。
刷新钩子配置
当 pre-commit 配置(即 flake 中的pre-commit.settings)发生变化时,需要刷新本地钩子:
- 退出开发 shell,重新执行
nix develop; - 如果仍在使用 pre-commit 钩子,再执行一次
pre-commit-hooks-install。
VSCode 中的 nixfmt 配置
将以下 JSON 写入.vscode/settings.json,即可让 VSCode 使用nixfmt格式化 Nix 文件(Format Document命令、"editor.formatOnSave"等都会生效):
{ "nix.formatterPath": "nixfmt", "nix.serverSettings": { "nixd": { "formatting": { "command": [ "nixfmt" ], }, }, "nil": { "formatting": { "command": [ "nixfmt" ], }, }, }, }常见问题与要点回顾
- Windows 检出失败:克隆前务必开启
core.symlinks。 checkPhase跑什么:它执行 Meson 的测试阶段,覆盖单元测试(unit-tests)、功能测试(functional-tests,见 tests/functional)、JSON schema 校验(json-schema-checks,见 src/json-schema-checks)等;各开关的默认值可在根目录 meson.options 中查到。- 改代码后快速重编:优先使用
ccacheStdenv变体,并在 Linux 上享受默认的 mold 链接器加速。 - 构建产物位置:Meson 将产物限制在构建目录(
build或mesonBuildDir指定的目录)内,源码树保持干净,这也是支持多平台并行构建的基础。 - 想深入测试体系:参见
doc/manual/source/development/testing.md,并阅读 ci/gha/tests 了解 CI 中的测试编排方式。
通过本文的指引,你应该已经能够:用经典或 flake 方式进入 Nix 的开发环境、完成构建/测试/安装、为其他平台交叉编译、切换不同的编译器环境,并配置好编辑器与提交前的格式化检查,从而顺畅地参与到 Nix 自身的开发中。
【免费下载链接】nixNix, the purely functional package manager项目地址: https://gitcode.com/gh_mirrors/ni/nix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考