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"
  }
}