nixos-and-flakes-book/docs/zh/nixpkgs/callpackage.md

168 lines
7.4 KiB
Markdown
Raw Normal View History

2023-06-30 09:00:03 +00:00
# pkgs.callPackage
2023-10-04 06:13:49 +00:00
`pkgs.callPackage` 被用于参数化构建 Nix 包,为了理解它的用处,我们首先考虑下不使用 `pkgs.callPakcage` 的情况下,我们要如何定义一个 Nix 包(也就是 Derivation
2023-10-04 06:13:49 +00:00
## 1. 不使用 `pkgs.callPackage` 的情况
2023-10-04 06:13:49 +00:00
我们可以使用如下代码来定义一个 Nix 包:
```nix
pkgs.writeShellScriptBin "hello" ''echo "hello, ryan!"''
```
使用 `nix repl` 来验证一下,能看到它的执行结果确实是一个 Derivation
```shell
nix repl -f '<nixpkgs>'
Welcome to Nix 2.13.5. Type :? for help.
Loading installable ''...
Added 19203 variables.
nix-repl> pkgs.writeShellScriptBin "hello" '' echo "hello, xxx!" ''
«derivation /nix/store/zhgar12vfhbajbchj36vbbl3mg6762s8-hello.drv»
```
上面这个 Derivation 的定义很短,就一行,但 nixpkgs 中大部分的 Derivation 的定义都要比这复杂很多。
前面我们介绍并大量使用了 `import xxx.nix` 来从其他 Nix 文件中导入 Nix 表达式,我们可以在这里也使用这种方法来提升代码的可维护性:
1. 将上面这一行 Derivation 的定义存放到单独的文件 `hello.nix` 中。
1.`hello.nix` 自身的上下文中不包含 `pkgs` 这个变量,所以需要修改下其内容,将 `pkgs` 作为参数传递给 `hello.nix`
1. 在需要使用这个 Derivation 的地方,使用 `import ./hello.nix pkgs` 来导入它并使用 `pkgs` 作为参数来执行其中定义的函数。
仍然使用 `nix repl` 来验证一下,能看到它的执行结果仍然是一个 Derivation
```shell
cat hello.nix
pkgs:
pkgs.writeShellScriptBin "hello" '' echo "hello, xxx!" ''
nix repl -f '<nixpkgs>'
Welcome to Nix 2.13.5. Type :? for help.
2023-10-04 06:13:49 +00:00
warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels' does not exist, ignoring
Loading installable ''...
Added 19203 variables.
2023-10-04 06:13:49 +00:00
nix-repl> import ./hello.nix pkgs
«derivation /nix/store/zhgar12vfhbajbchj36vbbl3mg6762s8-hello.drv»
```
## 2. 使用 `pkgs.callPackage` 的情况
在前面不使用 `pkgs.callPackage` 的例子中,我们直接将 `pkgs` 作为参数传到了 `hello.nix` 中,这样做的缺点有:
1. `hello` 这个 derivation 的所有其他依赖项都只能从 `pkgs` 中获取,耦合度太高。
1. 比如说我们如果需要其他自定义依赖项,就必须修改 `pkgs` 或者修改 `hello.nix` 的内容,而这两个都很麻烦。
1.`hello.nix` 变复杂的情况下,很难判断 `hello.nix` 到底依赖了 `pkgs` 中的哪些 Derivation很难分析 Derivation 之间的依赖关系。
`pkgs.callPackage` 作为一个参数化构建 Derivation 的工具函数,可解决上述两个问题。
首先看看源码中此函数的定义与注释 [nixpkgs/lib/customisation.nix#L101-L121](https://github.com/NixOS/nixpkgs/blob/fe138d3/lib/customisation.nix#L101-L121
)
```nix
/* Call the package function in the file `fn` with the required
arguments automatically. The function is called with the
arguments `args`, but any missing arguments are obtained from
`autoArgs`. This function is intended to be partially
parameterised, e.g.,
callPackage = callPackageWith pkgs;
pkgs = {
libfoo = callPackage ./foo.nix { };
libbar = callPackage ./bar.nix { };
};
If the `libbar` function expects an argument named `libfoo`, it is
automatically passed as an argument. Overrides or missing
arguments can be supplied in `args`, e.g.
libbar = callPackage ./bar.nix {
libfoo = null;
enableX11 = true;
};
*/
callPackageWith = autoArgs: fn: args:
let
f = if lib.isFunction fn then fn else import fn;
fargs = lib.functionArgs f;
# All arguments that will be passed to the function
# This includes automatic ones and ones passed explicitly
allArgs = builtins.intersectAttrs fargs autoArgs // args;
# ...... 省略后面的内容 ......
```
简单的说,它的使用格式是 `pkgs.callPackage fn args`,其中 `fn` 是一个 nix 文件或者函数,`args` 是一个 attribute set它的工作流程是
2023-10-04 06:13:49 +00:00
1. `pkgs.callPackge fn args` 会先判断 `fn` 是一个函数还是一个文件,如果是文件就先通过 `import xxx.nix` 导入其中定义的函数。
1. 第一步执行完毕得到的是一个函数,其参数通常会有 `lib`, `stdenv`, `fetchurl` 等参数,可能还会带有一些自定义参数。
2. 之后,`pkgs.callPackge fn args` 会将 `args``pkgs` 这个 attribute set 合并。如果存在冲突,`args` 中的参数会覆盖 `pkgs` 中的参数。
3. 再之后,`pkgs.callPackge fn args` 会从上一步得到的 attribute set 中提取出 `fn` 函数的参数,并使用它们来执行 `fn` 函数。
4. 函数执行结果是一个 Derivation也就是一个 Nix 包。
那可以作为 `pkgs.callPackge` 参数的 nix 文件具体长啥样呢,可以去看看我们前面举例过的 `hello.nix` `fcitx5-rime.nix` `vscode/with-extensions.nix` `firefox/common.nix`,它们都可以被 `pkgs.callPackage` 导入。
2023-06-27 03:31:47 +00:00
比如说我们自定义了一个 NixOS 内核配置 `kernel.nix`,并且将开发版名称与内核源码作为了可变更参数:
```nix
{
lib,
stdenv,
linuxManualConfig,
src,
boardName,
...
}:
(linuxManualConfig {
version = "5.10.113-thead-1520";
modDirVersion = "5.10.113";
inherit src lib stdenv;
2023-06-30 09:00:03 +00:00
2023-06-27 03:31:47 +00:00
# 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 {}` 来导入并使用它,并且替换它的任意参数。
```nix
2023-06-30 09:00:03 +00:00
{ lib, pkgs, pkgsKernel, kernel-src, ... }:
2023-06-27 03:31:47 +00:00
{
# ......
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` 来适配不同的内核源码与不同的开发板了。
2023-10-04 06:13:49 +00:00
`pkgs.callPackage` 的优势在于:
1. Derivation 的定义被参数化,定义中的所有函数参数就是 Derivation 的所有依赖项,这样就可以很方便的分析 Derivation 之间的依赖关系。
2. Derivation 的所有依赖项与其他自定义参数都可以很方便地被替换(通过使用 `pkgs.callPackage` 的第二个参数Derivation 定义的可复用性大大提升。
3. 在实现了前两条功能的情况下,并未增加代码的复杂度,所有 `pkgs` 中的依赖项都可以被自动注入,不需要手动传递。
因此我们总是推荐使用 `pkgs.callPackage` 来定义 Derivation。
## 参考
2023-06-30 09:00:03 +00:00
- [Chapter 13. Callpackage Design Pattern - Nix Pills](https://nixos.org/guides/nix-pills/callpackage-design-pattern.html)
2023-10-04 06:13:49 +00:00
- [callPackage, a tool for the lazy - The Summer of Nix](https://summer.nixos.org/blog/callpackage-a-tool-for-the-lazy/)
- [Document what callPackage does and its preconditions - Nixpkgs Issues](https://github.com/NixOS/nixpkgs/issues/36354)