pkgs.callPackage
pkgs.callPackage se usa para parametrizar la construcción de una derivación de Nix. Para entender su propósito, primero veamos cómo definiríamos un paquete de Nix, también llamado derivación, sin usar pkgs.callPackage.
1. Sin pkgs.callPackage
Podemos definir un paquete de Nix con código como este:
pkgs.writeShellScriptBin "hello" ''echo "hello, ryan!"''Para comprobarlo, puedes usar nix repl, y verás que el resultado es efectivamente una derivación:
› 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»Aunque la definición de esta derivación es bastante concisa, la mayoría de las derivaciones en nixpkgs son mucho más complejas. En secciones anteriores introdujimos y usamos ampliamente el método import xxx.nix para importar expresiones de Nix desde otros archivos, lo que mejora la mantenibilidad del código.
- Para mejorar la mantenibilidad, puedes guardar la definición de la derivación en un archivo aparte, por ejemplo
hello.nix.- Sin embargo, el contexto dentro de
hello.nixno incluye la variablepkgs, así que tendrás que modificar su contenido para pasarpkgscomo parámetro ahello.nix.
- Sin embargo, el contexto dentro de
- En los lugares donde necesites usar esta derivación, puedes usar
import ./hello.nix pkgspara importarhello.nixy usarpkgscomo parámetro para ejecutar la función definida dentro.
Sigamos comprobándolo con nix repl, y verás que el resultado sigue siendo una derivación:
› cat hello.nix
pkgs:
pkgs.writeShellScriptBin "hello" '' echo "hello, xxx!" ''
› nix repl -f '<nixpkgs>'
Welcome to Nix 2.13.5. Type :? for help.
warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels' does not exist, ignoring
Loading installable ''...
Added 19203 variables.
nix-repl> import ./hello.nix pkgs
«derivation /nix/store/zhgar12vfhbajbchj36vbbl3mg6762s8-hello.drv»2. Usando pkgs.callPackage
En el ejemplo anterior sin pkgs.callPackage, pasamos pkgs directamente como parámetro a hello.nix. Sin embargo, este enfoque tiene algunas desventajas:
- Todas las demás dependencias de la derivación
helloquedan fuertemente acopladas conpkgs.- Si necesitamos dependencias personalizadas, tenemos que modificar
pkgso el contenido dehello.nix, lo cual puede ser engorroso.
- Si necesitamos dependencias personalizadas, tenemos que modificar
- Cuando
hello.nixse vuelve complejo, es difícil determinar de qué derivaciones depkgsdepende, lo que complica analizar las dependencias entre derivaciones.
pkgs.callPackage, como herramienta para parametrizar la construcción de derivaciones, resuelve estos problemas. Veamos su código fuente y sus comentarios en nixpkgs/lib/customisation.nix#L101-L121:
/* Llama a la función del paquete en el archivo `fn` con los
argumentos requeridos de forma automática. La función se invoca con
los argumentos `args`, pero cualquier argumento faltante se obtiene
de `autoArgs`. Esta función está pensada para parametrizarse
parcialmente, por ejemplo:
callPackage = callPackageWith pkgs;
pkgs = {
libfoo = callPackage ./foo.nix { };
libbar = callPackage ./bar.nix { };
};
Si la función `libbar` espera un argumento llamado `libfoo`, se le
pasa automáticamente. Las sobrescrituras o los argumentos faltantes
pueden proporcionarse en `args`, por ejemplo:
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;
# Todos los argumentos que se pasarán a la función
# Esto incluye los automáticos y los pasados explícitamente
allArgs = builtins.intersectAttrs fargs autoArgs // args;
# ......En esencia, pkgs.callPackage se usa como pkgs.callPackage fn args, donde el marcador fn es un archivo o función de Nix, y args es un conjunto de atributos. Así funciona:
pkgs.callPackage fn argsprimero comprueba sifnes una función o un archivo. Si es un archivo, importa la función definida dentro.- Después de este paso, tienes una función, normalmente con parámetros como
lib,stdenv,fetchurly posiblemente algunos parámetros personalizados.
- Después de este paso, tienes una función, normalmente con parámetros como
- Luego,
pkgs.callPackage fn argsfusionaargscon el conjunto de atributospkgs. Si hay conflictos, los parámetros deargssobrescriben los depkgs. - Después,
pkgs.callPackage fn argsextrae los parámetros de la funciónfndel conjunto de atributos fusionado y los usa para ejecutar la función. - El resultado de la ejecución de la función es una derivación, es decir, un paquete de Nix.
¿Cómo puede verse un archivo o función de Nix usado como argumento de pkgs.callPackage? Puedes revisar ejemplos que ya usamos en Uso avanzado de Nixpkgs - Introducción: hello.nix, fcitx5-rime.nix, vscode/with-extensions.nix y firefox/common.nix. Todos pueden importarse con pkgs.callPackage.
Por ejemplo, si definiste una configuración personalizada del kernel de NixOS en kernel.nix y hiciste configurable el nombre de la rama de desarrollo y el código fuente del kernel:
{
lib,
stdenv,
linuxManualConfig,
src,
boardName,
...
}:
(linuxManualConfig {
version = "5.10.113-thead-1520";
modDirVersion = "5.10.113";
inherit src lib stdenv;
# ruta del archivo de configuración del kernel generado (el `.config` creado por `make menuconfig`)
#
# aquí hay un uso especial para generar una ruta de archivo a partir de una cadena
configfile = ./. + "${boardName}_config";
allowImportFromDerivation = true;
})Puedes usar pkgs.callPackage ./hello.nix {} en cualquier módulo de Nix para importarlo y usarlo, sustituyendo cualquiera de sus parámetros según sea necesario:
{ lib, pkgs, pkgsKernel, kernel-src, ... }:
{
# ......
boot = {
# ......
kernelPackages = pkgs.linuxPackagesFor (pkgs.callPackage ./pkgs/kernel {
src = kernel-src; # el código fuente del kernel se pasa como `specialArgs` e inyecta en este módulo.
boardName = "licheepi4a"; # nombre de la placa, usado para generar la ruta del archivo de configuración del kernel.
});
# ......
}Como se muestra arriba, al usar pkgs.callPackage puedes pasar distintos valores de src y boardName a la función definida en kernel.nix para generar distintos paquetes del kernel. Esto permite adaptar el mismo kernel.nix a diferentes fuentes del kernel y placas de desarrollo.
Las ventajas de pkgs.callPackage son:
- Las definiciones de derivaciones quedan parametrizadas, y todas sus dependencias son los parámetros de la función en su definición. Esto facilita analizar dependencias entre derivaciones.
- Todas las dependencias y otros parámetros personalizados de la derivación pueden reemplazarse fácilmente usando el segundo parámetro de
pkgs.callPackage, lo que mejora mucho la reutilización. - Aunque consigue las dos funcionalidades anteriores, no aumenta la complejidad del código, porque todas las dependencias en
pkgspueden inyectarse automáticamente.
Por eso siempre se recomienda usar pkgs.callPackage para definir derivaciones.