pkgs.callPackage
前面我们介绍并大量使用了 import xxx.nix
来导入 Nix 文件,这种语法只是单纯地返回该文件的执行结果,不会对该结果进行进一步处理。 比如说 xxx.nix
的内容是形如 {...}: {...}
,那么 import xxx.nix
的结果就是该文件中定义的这个函数。
pkgs.callPackage
也是用来导入 Nix 文件的,它的语法是 pkgs.callPackage xxx.nix { ... }
. 但跟 import
不同的是,它导入的 nix 文件内容必须是一个 Derivation 或者返回 Derivation 的函数,它的执行结果一定是一个 Derivation,也就是一个软件包。
那可以作为 pkgs.callPackge
参数的 nix 文件具体长啥样呢,可以去看看我们前面举例过的 hello.nix
fcitx5-rime.nix
vscode/with-extensions.nix
firefox/common.nix
,它们都可以被 pkgs.callPackage
导入。
当 pkgs.callPackge xxx.nix {...}
中的 xxx.nix
,其内容为一个函数时(绝大多数 nix 包都是如此),执行流程如下:
pkgs.callPackge xxx.nix {...}
会先import xxx.nix
,得到其中定义的函数,该函数的参数通常会有lib
,stdenv
,fetchurl
等参数,以及一些自定义参数,自定义参数通常都有默认值。- 接着
pkgs.callPackge
会首先从当前环境中查找名称匹配的值,作为将要传递给前述函数的参数。像lib
stdenv
fetchurl
这些都是 nixpkgs 中的函数,在这一步就会查找到它们。 - 接着
pkgs.callPackge
会将其第二个参数{...}
与前一步得到的参数集(attribute set)进行合并,得到一个新的参数列表,然后将其传递给该函数并执行。 - 函数执行结果是一个 Derivation,也就是一个软件包。
这个函数比较常见的用途是用来导入一些自定义的 Nix 包。
比如说我们自定义了一个 NixOS 内核配置 kernel.nix
,并且将开发版名称与内核源码作为了可变更参数:
{
lib,
stdenv,
linuxManualConfig,
src,
boardName,
...
}:
(linuxManualConfig {
version = "5.10.113-thead-1520";
modDirVersion = "5.10.113";
inherit src lib stdenv;
# file path to the generated kernel config file(the `.config` generated by make menuconfig)
#
# here is a special usage to generate a file path from a string
configfile = ./. + "${boardName}_config";
allowImportFromDerivation = true;
})
那么就可以在任意 Nix Module 中使用 pkgs.callPackage ./hello.nix {}
来导入并使用它,并且替换它的任意参数。
{ lib, pkgs, pkgsKernel, kernel-src, ... }:
{
# ......
boot = {
# ......
kernelPackages = pkgs.linuxPackagesFor (pkgs.callPackage ./pkgs/kernel {
src = kernel-src; # kernel source is passed as a `specialArgs` and injected into this module.
boardName = "licheepi4a"; # the board name, used to generate the kernel config file path.
});
# ......
}
就如上面所展示的,通过 pkgs.callPackage
我们可以给 kernel.nix
定义的函数传入不同的 src
与 boardName
,来生成不同的内核包,这样就可以使用同一份 kernel.nix
来适配不同的内核源码与不同的开发板了。