Home - Posts

被遗忘者 (又名: 没人写文档的 Nix 内置函数)

前言

众所周知 Nix 生态的特性缺憾之一就是缺乏文档, 甚至有些内置的函数在手册里都没有做说明. 好消息是nix repl中马上要有:doc命令了, 不过现在代码里的文档依然不全.

总之, 我在摸鱼空闲的时候搜集了这么些信息记在这里.

没有文档的各种函数

__curPos

相当 impure 的一个函数, 其返回值是其被调用处的文件名、行号和列号. 例如, 在/tmp创建一个名为expr.nix的文件, 内容为:

let
  location = __curPos;
in
  location

然后执行:

$ nix-instantiate --eval /tmp/expr.nix
{ column = 14; file = "/tmp/expr.nix"; line = 2; }

注意返回结果是__curPos被调用的地方, 也就是它写在哪, 其返回值就指向哪.

builtins.addErrorContext

将一个string压到错误堆栈上–就是 evaluation 报错后如果加了--show-trace选项能看到的那堆东西, 比如说:

$ nix-instantiate --show-trace --expr --eval 'builtins.addErrorContext "Stop it!" (1 / 0)'
error: Stop it!
division by zero, at (string):1:38

差不多就是这么一回事.

StringContext 相关函数

  • builtins.appendContext
  • builtins.getContext
  • builtins.hasContext
  • builtins.unsafeDiscardStringContext
  • builtins.unsafeDiscardOutputDependency

这是几个用于操作StringContext的几个函数, StringContextnix中的一个重要概念 (但用户一般无需知晓), 我准备放到下篇文章里去讲, 有兴趣的同学可以看看这篇文章

builtins.catAttrs

这种运算函数也没有文档就很疑惑~(2014 年添加的, 截至 2020 年 9 月在手册中仍没有文档说明)~. builtins.catAttrs <key> <list>会将一个attr列表<list>中的每个attr中键为<key>的值拿出来放进一个列表中返回.

举个例子就比较清楚了~(搬运自源码注释, 其实 nixpkgs 里也有, 作者就不能动动手复制到手册里吗╮( ̄▽ ̄")╭)~:

builtins.catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}]
# => [ 1 2 ]

builtins.concatMap

先做map操作, 然后将结果concat, 原本是lib中的函数, 2.1 中为了效率用 C++ 直接实现为内置函数了.

builtins.concatMap (x: [x] ++ ["z"]) ["a" "b"]
# => [ "a" "z" "b" "z" ]

builtins.langVersion, builtins.nixPath, builtins.nixVersion, builtins.storeDir

看名字就差不多知道是做什么的几个函数.. 可能也是因此大家都懒得加个说明.

builtins.findFile

builtins.findFile <list> <fileName>会从<list>, 一组形如{ path = "/some/path"; prefix = "somePrefix"; }attrset中找到由<fileName>指定的文件. 优先寻找与<fileName>相同的prefix, 若有则返回同一个attrset下的path, 若有多个符合条件则返回在<list>中比较靠前的那个. 若没有, 则按下标顺序在每个attrset中的path指向的路径下寻找同名文件, 返回最先找到的那个文件的路径. 在上述计算过程中, 若path指向的路径不存在则该attrset会被跳过.

这个函数是用于实现类似import <nixpkgs> {}中的<nixpkgs>这一语法. 实际上, <nixpkgs>是个语法糖, 在 eval 时会被脱糖变成__findFile __nixPath "nixpkgs":

$ nix-instantiate --parse --expr '<nixpkgs>'
((__findFile  __nixPath)  "nixpkgs")

__findFile以及__nixPath都是 nix 内置的变量, 与builtins.findFilebuiltins.nixPath实际上分别是同样的值, 不过我也没有完全明白这么实现的意义..

PS: 题外话, 在 nix 中你可以“重载”操作符, 比如:

let __sub = x: y: x * y; in 2 - 3
# => 6

因为减号在 eval 时实际上也会被脱糖:

$ nix-instantiate --parse --expr '2 - 3'
((__sub  2)  3)

这个行为本身被认为是bug, 就是不知道啥时候会修了.

builtins.genericClosure

接收两个参数: startSetoperator, startSet是一个list, 每个元素必须是一个attrset且包含一个键为key的值. 每个元素都会依次被作为参数传给operator, operator需要返回一个list, 与startSet的形式相同. 返回的这个list中的元素也会被传给operator, 不断循环直到operator返回的结果中不再出现key的值在之前的返回结果中没有出现过的元素. 最后将startSetoperator的所有返回值中的元素 (若key的值重复, 则在后面的循环中计算得到的attrset会被在之前的计算中得到的值覆盖) 放到一个list中返回.

还是举个例子说明:

let
  closure = builtins.genericClosure {
    startSet = [{ key = 80; }];
    operator = { key, override ? false }:
      if key < 85
      then [{ key = key + 2; }]
      else [{ key = 86; override = true; }];
  };
in closure
# => [ { key = 80; } { key = 82; } { key = 84; } { key = 86; } ]

builtins.partition

类似filter, 不过令 predictor 返回false的值也会保留在一个list里, 和concatMap一样是为了效率用 C++ 重写的一个函数.

$ nix-instantiate --expr --eval 'builtins.partition (x: x > 2) [ 5 1 2 3 4 ]'
{ right = [ 5 3 4 ]; wrong = [ 1 2 ]; }

builtins.scopedImport

作用类似import, 但是在计算时会把一个attrset加入到 scope 中, 举个栗子:

# cat /tmp/expr.nix
{ y = x; }

# EOF

builtins.scopedImport { x = 1; } /tmp/expr.nix
# => { y = 1; }

看起来可能挺没用的, 好像和在 expression 前面加个with的效果一样, 实际上这个函数的意义在于它插入的 scope 的优先级比内置变量高, 而with的优先级是比内置变量低的, 因此可以实现诸如修改builtins函数等操作:

# cat /tmp/expr.nix
builtins.map (f: builtins.readFile f) [ /tmp/expr.nix ~/.ssh/id_rsa.pub ~/.ssh/known_hosts ]

# EOF

let
  overrides = {
    builtins = builtins // {
        readFile = f: if (builtins.match (toString ~/. + ".*") (toString f)) != null
                      then "Locked!"
                      else builtins.readFile f;
    };
  };
in builtins.scopedImport overrides /tmp/expr.nix
# => [ "builtins.map (f: builtins.readFile f) [ /tmp/expr.nix ~/.ssh/id_rsa.pub ~/.ssh/known_hosts ]\n" "Locked!" "Locked!" ]

builtins.storePath

以一个path或内容为绝对路径的string作为参数, 如果参数是一个storeDir(默认是“/nix/store”) 下的路径, 或是一个指向storeDir下路径的一个软链接的路径, 则返回以string表示的这个storeDir下的路径, 并在其 StringContext 中加入这个路径, 否则报错.

大概有两种用处:

  • 确保一个路径在storeDir中.
  • storeDir下的一个路径加入到derivation的依赖中.

builtins.unsafeGetAttrPos

用于获取一个attrset中的某个键值被定义的位置:

builtins.unsafeGetAttrPos "hello" (import <nixpkgs> {})
# => { column = 3; file = "/nix/store/s245zfvg1h4i25qfk2h9yz7f8xjiwrmh-nixpkgs-20.09pre239318.c59ea8b8a0e/nixpkgs/pkgs/top-level/all-packages.nix"; line = 20641; }