Nix 动态派生功能存在查询缺陷,无法追溯初始派生文件
来源标题:Beer.drv.drv.drv
Nix 的动态派生(dynamic derivations)功能目前无法通过 `nix-store --query --deriver` 追溯动态生成派生文件的初始 .drv 来源,查询会返回 unknown-deriver。该功能仍属实验性特性,文档稀少,需开启 extra-experimental-feature 才能使用。动态派生允许构建过程动态生成派生文件,以对标 Bazel、buck2 的构建速度。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
你听说过动态派生(dynamic derivations)吗?这是 Nix 中的一个实验性特性,允许一个派生将其它派生作为输出生成出来。为什么要这样做呢?因为它让你能够动态地构建构建图,而不必在求值阶段就预先知道整个构建图。这是 Nix 为了追赶 Bazel 或 buck2 等构建系统的构建速度而给出的答案。
关于动态派生的资料不多,文档也很稀疏。我写过几篇相关的文章,所以偶尔会有人来问我相关问题。
最近,我收到 Evan Porter 在 LinkedIn(终极 bug 分诊网络)上的私信,问是否可以通过查询动态派生的 deriver 来获取最初的派生。
对于动态派生,假设我有一个 .drv.drv 文件。这个文件会输出一个派生。
通常是否可以通过查询输出派生的 deriver 来获得最初的派生?
$ nix-store --query --deriver \
$(nix-store --query --deriver ./result)
unknown-deriver
这应该是可行的。🤨 构建注册的每个路径都带有一个 deriver,也就是产生它的那个 .drv,而构建产出的 .drv 同样也是构建注册的一个路径。
你需要在 nix.conf 中设置 extra-experimental-features = dynamic-derivations ca-derivations recursive-nix 才能运行本文中的示例。
$ nix build -f default.nix \
--print-out-paths
/nix/store/v4hkwn8y4m083gsap6523c0m5r985ygr-result
$ nix-store --query --deriver \
/nix/store/v4hkwn8y4m083gsap6523c0m5r985ygr-result
/nix/store/6pniymfhwcyrmkns0np7ap1argjziqq2-result.drv
$ nix-store --query --deriver \
/nix/store/6pniymfhwcyrmkns0np7ap1argjziqq2-result.drv
/nix/store/m5x261fv29jkn0rvznqj2fzkaql01xiy-result.drv.drv
事实证明,确实存在一种情况会得到 unknown-deriver:如果你在生产端设置了 __contentAddressed = true。如果生产端是内容寻址的,它的输出路径在构建完成之前是未知的,而 Nix 不会为已经存在的路径补填 deriver。
我提交了 NixOS/nix#16823。
不过这个问题让我好奇:沿着动态派生的链条究竟能往回追溯多远?我必须试一试。😈
让我们选一个有趣的例子,看看能走多远。下面这个派生模仿经典歌曲《99 Bottles of Beer》,通过生成一串派生来工作,每一个派生输出下一段歌词。¹¹ 构建器手动用 ATerm 写出下一个派生,所以这里不涉及 recursive Nix,也不涉及 nix-instantiate。
export PATH=$busybox/bin
# A content-addressed derivation does not know its output path, so its
# "out" variable holds this placeholder and Nix swaps in the real path at
# build time. It is "/" + nix32(sha256("nix-output:out")), the same for
# every derivation with an output named "out".
OUT_PLACEHOLDER=/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9
bottles() {
case $1 in
0) echo "no more bottles" ;;
1) echo "1 bottle" ;;
*) echo "$1 bottles" ;;
esac
# The last derivation in the chain writes the punchline instead of a .drv.
if [ "$n" -eq 0 ]; then
echo "No more bottles of beer on the wall, no more bottles of beer." >&2
echo "Go to the store and buy some more." > $out
exit 0
fi
next=$((n - 1))
echo "$(bottles $n) of beer on the wall, $(bottles $n) of beer." >&2
echo "Take one down, pass it around, $(bottles $next) of beer on the wall." >&2
# Input sources must be sorted.
srcs=$(printf '"%s"\n' "$busybox" "$script" | sort | paste -sd, -)
# Write the derivation for the next verse. A .drv is a single line of
# ATerm, so strip the spaces and newlines that are only here for reading.
# Nix takes a derivation's name from its store path, so the next name is
# ours minus one ".drv".
tr -d ' \n' > $out <<EOF
Derive(
[("out", "", "text:sha256", "")],
[$srcs],
"$system",
"$busybox/bin/sh",
["$script"],
("builder", "$busybox/bin/sh"),
("busybox", "$busybox"),
("n", "$next"),
("name", "${name%.drv}"),
("out", "$OUT_PLACEHOLDER"),
("outputHashAlgo", "sha256"),
("outputHashMode", "text"),
("script", "$script"),
("system", "$system")
)
EOF
提示 输出为
.drv的派生必须设置outputHashMode = "text"。
在我们的例子中,我们使用的是内容寻址派生,但同样的思路也适用于输入寻址派生,只是在 shell 中计算输出路径会更麻烦一些,除非你使用 recursive Nix 让 nix-instantiate 替你完成。
Nix 这边构建第一个派生,然后按每瓶啤酒一次调用 builtins.outputOf 来沿着链条向下追溯。
let
pkgs = import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/6dadaf4375be35033e1c84395adbd53ff5fded96.tar.gz";
}) { };
inherit (pkgs) lib;
# maximum bottles of beer on the wall,
# 50 is the most that fits in a store path!?
bottles = 50;
top = derivation {
name = "beer" + lib.concatStrings (lib.replicate bottles ".drv");
system = builtins.currentSystem;
builder = "${pkgs.busybox}/bin/sh";
args = [ ./bottles.sh ];
script = ./bottles.sh;
busybox = pkgs.busybox;
n = bottles;
__contentAddressed = true;
outputHashMode = "text";
outputHashAlgo = "sha256";
};
in
# Follow the chain of outputs down to the last derivation.
lib.foldl' (drv: _: builtins.outputOf drv "out") top.outPath (lib.range 1 bottles)
在调试这个例子的过程中,我遇到了 Nix 的一个 bug,这迫使我必须对顶层派生设置 __contentAddressed = true。对于一个输出 .drv 的派生来说,这个设置并非必需,但它可以绕过那个 bug。我提交了 NixOS/nix#16830。
你可能注意到我们把啤酒数量限制在 50 瓶。为什么是 50 而不是 99 呢?
嗯,50 是 store path 能容纳的最大值。
error: path '…-beer.drv.drv.drv…' is not a valid store path:
name 'beer.drv.drv.drv…' must be no longer than 211 characters
store path 的最大长度为 211 个字符。一个派生的 store path 必须包含 .drv 后缀,而链条中每多一个 .drv 就会增加四个字符。顶层派生名为 beer,如果加上 51 个 .drv,我们就得到 208 个字符。再没有空间放更多的啤酒了。🍻
旁注 b+4n≤211⇒n=⌊211−b4⌋
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。