nix-env 是如何提前知道存储路径的?
How does nix-env knows the store path in advance?
如果我查询可用的 Go 包:
nix-env -qa go -b --put-path --json
我得到:
{
"nixpkgs.go_1_16": {
"name": "go-1.16.15",
"pname": "go",
"version": "1.16.15",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/nqi39ksavkfrxkrz3d0797n5wmzi9r30-go-1.16.15"
}
},
"nixpkgs.go": {
"name": "go-1.17.7",
"pname": "go",
"version": "1.17.7",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/3v2l94h7pllq6za9km3388cyd5agrln7-go-1.17.7"
}
},
"nixpkgs.go_1_18": {
"name": "go-1.18",
"pname": "go",
"version": "1.18",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/7jyfpb96xv3hr8dpfhnbb0f7zscwm7sr-go-1.18"
}
}
}
好奇,nix-env是怎么提前知道存储路径的?据我所知,存储路径是由 nix 在构建时生成的,但是 nix-env 已经知道它没有构建(例如,我看到没有从网络下载任何东西)。
构建过程不会直接将 .nix
代码(从 https://nixos.org/channels/nixos-21.11 等渠道下载)作为输入。相反,它需要 derivations 作为输入;这些推导是由用 Nix 语言编写的 评估 代码生成的,然后作为 .drv
文件写入 Nix 存储(可以很容易地解码为 JSON阅读;参见 nix show-derivation
)。这些 .drv
文件被用作实际构建过程的输入(运行 被 nixbld
沙盒用户帐户)。
这些推导完全指定了构建过程将执行的操作——所有输入和所有输出。因为输出(fixed-output 派生的输出除外)由用于生成它们的构建过程的 散列 寻址,实际上没有必要 运行在哈希已知之前构建。
我上面提到的异常是fixed-output推导;这些用于下载网络外构建过程将使用的资源。对于允许网络访问的 Nix 构建步骤,它需要断言 ahead-of-time 该构建步骤的输出将是什么 —— 如果该步骤产生的任何输出与声明的内容不匹配,则该步骤被视为失败。其中,内容本身的哈希值用于存储位置,因此下载过程的更改不需要 re-downloading 内容。
预测输出位置的能力 pre-build 是至关重要的,因为包是通过它们的哈希值 从 Hydra 二进制缓存中拉下来的 。如果我们在构建包之前不知道哈希值,那么缓存将毫无用处:除非我们已经拥有该二进制文件,否则我们将不知道在什么名称下查找二进制文件! (同样,如果我们不知道我们的输入的名称和哈希值而没有实际构建它们,我们将无法提前计划多于一步)。
举个实际的例子吧。假设在我写这篇文章的时候,我还没有通过 Nix 安装 GCC。
$ nix repl
Welcome to Nix version 2.3.16. Type :? for help.
nix-repl> :l <nixpkgs>
Added 15472 variables.
nix-repl> gcc8
«derivation /nix/store/9fpas3flqf424g46b8ldkbz7sgd9r7qk-gcc-wrapper-8.5.0.drv»
nix-repl> :b gcc8
...and either a download or a long build process runs here.
注意评估 gcc8
如何给我们一个/nix/store/*-gcc*.drv
文件。该文件包含一个计划,该计划描述了 Nix 将如何 构建 gcc 8(如果我们希望的话)。
我们可以证明该计划包括输出位置:
$ nix show-derivation /nix/store/9fpas3flqf424g46b8ldkbz7sgd9r7qk-gcc-wrapper-8.5.0.drv | jq '.[].outputs'
{
"info": {
"path": "/nix/store/znasm5jz3pp57ivspw5ahgn7rzfk791w-gcc-wrapper-8.5.0-info"
},
"man": {
"path": "/nix/store/j46y5mrppjw7nw9g8ckd3h438k8jjvkr-gcc-wrapper-8.5.0-man"
},
"out": {
"path": "/nix/store/v9pv2w7qiw1cpbjn4wjdkxkzld7pfki4-gcc-wrapper-8.5.0"
}
}
如果我查询可用的 Go 包:
nix-env -qa go -b --put-path --json
我得到:
{
"nixpkgs.go_1_16": {
"name": "go-1.16.15",
"pname": "go",
"version": "1.16.15",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/nqi39ksavkfrxkrz3d0797n5wmzi9r30-go-1.16.15"
}
},
"nixpkgs.go": {
"name": "go-1.17.7",
"pname": "go",
"version": "1.17.7",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/3v2l94h7pllq6za9km3388cyd5agrln7-go-1.17.7"
}
},
"nixpkgs.go_1_18": {
"name": "go-1.18",
"pname": "go",
"version": "1.18",
"system": "x86_64-linux",
"outputs": {
"out": "/nix/store/7jyfpb96xv3hr8dpfhnbb0f7zscwm7sr-go-1.18"
}
}
}
好奇,nix-env是怎么提前知道存储路径的?据我所知,存储路径是由 nix 在构建时生成的,但是 nix-env 已经知道它没有构建(例如,我看到没有从网络下载任何东西)。
构建过程不会直接将 .nix
代码(从 https://nixos.org/channels/nixos-21.11 等渠道下载)作为输入。相反,它需要 derivations 作为输入;这些推导是由用 Nix 语言编写的 评估 代码生成的,然后作为 .drv
文件写入 Nix 存储(可以很容易地解码为 JSON阅读;参见 nix show-derivation
)。这些 .drv
文件被用作实际构建过程的输入(运行 被 nixbld
沙盒用户帐户)。
这些推导完全指定了构建过程将执行的操作——所有输入和所有输出。因为输出(fixed-output 派生的输出除外)由用于生成它们的构建过程的 散列 寻址,实际上没有必要 运行在哈希已知之前构建。
我上面提到的异常是fixed-output推导;这些用于下载网络外构建过程将使用的资源。对于允许网络访问的 Nix 构建步骤,它需要断言 ahead-of-time 该构建步骤的输出将是什么 —— 如果该步骤产生的任何输出与声明的内容不匹配,则该步骤被视为失败。其中,内容本身的哈希值用于存储位置,因此下载过程的更改不需要 re-downloading 内容。
预测输出位置的能力 pre-build 是至关重要的,因为包是通过它们的哈希值 从 Hydra 二进制缓存中拉下来的 。如果我们在构建包之前不知道哈希值,那么缓存将毫无用处:除非我们已经拥有该二进制文件,否则我们将不知道在什么名称下查找二进制文件! (同样,如果我们不知道我们的输入的名称和哈希值而没有实际构建它们,我们将无法提前计划多于一步)。
举个实际的例子吧。假设在我写这篇文章的时候,我还没有通过 Nix 安装 GCC。
$ nix repl
Welcome to Nix version 2.3.16. Type :? for help.
nix-repl> :l <nixpkgs>
Added 15472 variables.
nix-repl> gcc8
«derivation /nix/store/9fpas3flqf424g46b8ldkbz7sgd9r7qk-gcc-wrapper-8.5.0.drv»
nix-repl> :b gcc8
...and either a download or a long build process runs here.
注意评估 gcc8
如何给我们一个/nix/store/*-gcc*.drv
文件。该文件包含一个计划,该计划描述了 Nix 将如何 构建 gcc 8(如果我们希望的话)。
我们可以证明该计划包括输出位置:
$ nix show-derivation /nix/store/9fpas3flqf424g46b8ldkbz7sgd9r7qk-gcc-wrapper-8.5.0.drv | jq '.[].outputs'
{
"info": {
"path": "/nix/store/znasm5jz3pp57ivspw5ahgn7rzfk791w-gcc-wrapper-8.5.0-info"
},
"man": {
"path": "/nix/store/j46y5mrppjw7nw9g8ckd3h438k8jjvkr-gcc-wrapper-8.5.0-man"
},
"out": {
"path": "/nix/store/v9pv2w7qiw1cpbjn4wjdkxkzld7pfki4-gcc-wrapper-8.5.0"
}
}