Atelier Nix les fondements de nix : store et derivation Nix Langage Base

Atelier Nix

  • nix : présentation à partir de la base
  • atelier : avec des exemples pratiques bas niveau

Objectifs

  • comprendre l’intérêt
  • se mettre le pied à l’étrier
  • se repérer

nix***

Nix est …

  • un gestionnaire de paquetages logiciels, comme dpkg/rpm
    • stocke tout dans /nix/store, appelé le store
    • commande nix ou nix-?????? : C++/rust
  • un langage permettant de décrire comment bâtir un paquetage à partir des sources
  • nixpkgs : un dépot git regroupant 120000 paquetages
  • nixos : un OS reposant sur nix

Store

abstraction pour gérer des données immutable et les références entre elles

les données : des paquetages logiciels, des fichiers/répertoire

La réalisation la plus concrète est le store local dans /nix/store => la représentation par fichiers est facilement accessible

store-path

Chaque objet stocké dans le store a une référence unique, obtenue quand on insère l’objet dans le store.

Sous forme de chemin de fichiers :

  /nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1
  |--------| |------------------------------| |----------|
    store                 digest                  name
  directory

/nix/store : stocke des store objects

Un objet est composé de :

  1. Des choses qui ressemble à des fichiers :

    • un fichier simple, avec un flag pour le bit d’exécution
    • un répertoire
    • un lien

    => serialisés au format NAR Nix Archive

  2. un tableau de références (store-path)

ajouter un fichier

echo "just a normal file" > toto
❯ nix store add-file ./toto
/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto
STORE_PATH=$(nix store add-file ./toto)cat /nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto
just a normal file
❯ nix store dump-path /nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto
nix-archive-1(typeregulacontentsjust a normal file
)
nix path-info --json $STORE_PATH |jq .
[
  {
    "ca": "fixed:sha256:1sjjyg9yl0vc50shz7isk1qqhmn3n8xfkaknihqj9hh5f84f727p",
    "narHash": "sha256-d6VAeTwXBMWyN0ae/ax4wgRVp3IsnB7Ty7o/DV5PTYY=",
    "narSize": 136,
    "path": "/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto",
    "references": [],
    "registrationTime": 1746964972,
    "valid": true
  }
]

ajouter un répertoire

mkdir totodir
❯ echo toto > totodir/truc
❯ nix store add-path totodir
/nix/store/cbldlwca16l022dpxz37knlhyp22l68p-totodir
❯ tree /nix/store/cbldlwca16l022dpxz37knlhyp22l68p-totodir
/nix/store/cbldlwca16l022dpxz37knlhyp22l68p-totodir
└── truc

1 directory, 1 file
❯ cat /nix/store/cbldlwca16l022dpxz37knlhyp22l68p-totodir/truc
toto

Derivation :

  • object du store décrivant comment construire dans le store
  • une recette de fabrication, stockée sous la forme d’un fichier .drv
  • va contenir des références à d’autres objets !

Créer une dérivation à la main

Je pars d’un objet json vide

{}

J’essaye de l’ajouter au store comme une dérivation, et je suis les erreurs …

nix derivation add < drv.json
Expected JSON object to contain key 'name' but it doesn't: {}
Expected JSON object to contain key 'outputs' but it doesn't: {"name":"alamano"}
Expected JSON value to be of type 'object' but it is of type 'null': null
Expected JSON object to contain key 'inputSrcs' but it doesn't: {"name":"alamano","outputs":{}}
Expected JSON value to be of type 'array' but it is of type 'null': null
Expected JSON object to contain key 'inputDrvs' but it doesn't: {"inputSrcs":[],"name":"alamano","outputs":{}}
Expected JSON value to be of type 'object' but it is of type 'null': null
Expected JSON object to contain key 'system' but it doesn't: {"inputDrvs":{},"inputSrcs":[],"name":"alamano","outputs":{}}
Expected JSON value to be of type 'string' but it is of type 'null': null
Expected JSON object to contain key 'builder' but it doesn't: {"inputDrvs":{},"inputSrcs":[],"name":"alamano","outputs":{},"system":""}
Expected JSON value to be of type 'string' but it is of type 'null': null
Expected JSON object to contain key 'args' but it doesn't: {"builder":"","inputDrvs":{},"inputSrcs":[],"name":"alamano","outputs":{},"system":""}
Expected JSON value to be of type 'array' but it is of type 'null': null
Expected JSON object to contain key 'env' but it doesn't: {"args":[],"builder":"","inputDrvs":{},"inputSrcs":[],"name":"alamano","outputs":{},"system":""}
Expected JSON value to be of type 'object' but it is of type 'null': null
must have at least one output
error: Expected JSON value to be of type 'object' but it is of type 'null': null

La dérivation minimale acceptée :

{
  "name": "alamano",
  "outputs": {
    "out": {}
  },
  "inputSrcs" : [],
  "inputDrvs" : {},
  "system": "",
  "builder": "",
  "args": [],
  "env": {}
}

On regarde le format pour compléter

{
  "name": "alamano",
  "outputs": {
    "out": {
      "path": "/nix/store/00000000000000000000000000000000-alamano"
    }
  },
  "inputSrcs" : [],
  "inputDrvs" : {},
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ],
  "env": {
    "out": "/nix/store/-alamano"
  }
}
nix derivation add < drv.json
error: derivation '/nix/store/7ggp83qm62y9h36dz0bl0f0v34hg3x1j-alamano.drv' has 
incorrect output '/nix/store/00000000000000000000000000000000-alamano', should b
e '/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano'

On corrige le hash

{
  "name": "alamano",
  "outputs": {
    "out": {
      "path": "/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano"
    }
  },
  "inputSrcs" : [],
  "inputDrvs" : {},
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ],
  "env": {
    "out": "/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano"
  }
}
nix derivation add < drv2.json
/nix/store/fbxzr0anq9qd8x1m1gvan2zflis6c452-alamano.drv
nix path-info --json $drv | jq .
{
  "/nix/store/fbxzr0anq9qd8x1m1gvan2zflis6c452-alamano.drv": {
    "ca": "text:sha256:1v35gb6kam0lgalzcisnfb6sfyxfhnd9hs2v6g37dgc81n4jzqln",
    "deriver": null,
    "narHash": "sha256-bFU5sTRnIUK3FWU94qJkFbWjvyilHEsoQvTVsjKnc10=",
    "narSize": 320,
    "references": [],
    "registrationTime": 1747929825,
    "signatures": [],
    "ultimate": false
  }
}
  • On ajoute le fichier toto /nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto comme source
  • on rend visible la valeur par le builder par une variable d’environnement
  • on corrige le hash final
{
  "name": "alamano",
  "outputs": {
    "out": {
      "path": "/nix/store/6y8zi6npfciizkml15c99hqamiw05r4d-alamano"
    }
  },
  "inputSrcs" : [ "/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"],
  "inputDrvs" : {},
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "args": [
    "-c",
    "echo 'hello world'> $out; echo $toto >> $out"
  ],
  "env": {
    "out": "/nix/store/6y8zi6npfciizkml15c99hqamiw05r4d-alamano",
    "toto": "/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"
  }
}
nix derivation add < drv3.json
/nix/store/ivwizaslbcg7s9rc2in0aq6zp10kh2af-alamano.drv
nix path-info --json $drv | jq .
{
  "/nix/store/ivwizaslbcg7s9rc2in0aq6zp10kh2af-alamano.drv": {
    "ca": "text:sha256:0fajbhpm4vlbdwii3k9l872z3b98mgbdhll5mvxy4fc3c6l9sf4g",
    "deriver": null,
    "narHash": "sha256-Qm7VaRLdzHuucJwARxI85+qCHhQ1bETJAPrEtu2M5qY=",
    "narSize": 448,
    "references": [
      "/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"
    ],
    "registrationTime": 1747929826,
    "signatures": [],
    "ultimate": false
  }
}

=> on a un bien un objet du store qui en référence un autre !

Construire une dérivation

nix-build

  • évalue le builder
  • génère de nouveaux objets dans le store
  • par défaut crée un lien result vers le résultat
❯ nix-build /nix/store/fbxzr0anq9qd8x1m1gvan2zflis6c452-alamano.drv
this derivation will be built:
  /nix/store/fbxzr0anq9qd8x1m1gvan2zflis6c452-alamano.drv
building '/nix/store/fbxzr0anq9qd8x1m1gvan2zflis6c452-alamano.drv'...
/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano

❯ cat /nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano
hello world
❯ cat result
hello world

À vous

  1. Faites marcher l’exemple précédent sur votre machine. En fonction de votre système, il faudra modifier : suivez les messages d’erreur
  2. ajoutez dans le store un fichier de votre choix, et utiliser le dans le builder. Pour cela, ajoutez une variable d’environnement.
  3. Construisez la dérivation ainsi obtenue et vérifier le résultat
{
  "name": "alamano",
  "outputs": {
    "out": {
      "path": "/nix/store/fy9v0v14x316ylh5jww2d780ppqhwr96-alamano"
    }
  },
  "inputSrcs" : ["/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"],
  "inputDrvs" : {},
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "args": [
    "-c",
    "echo $toto > $out"
  ],
  "env": {
    "out": "/nix/store/fy9v0v14x316ylh5jww2d780ppqhwr96-alamano",
    "toto": "/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"
  }
}

Notion à retenir

  • objets du store : des fichiers
  • chaque objet a un chemin ~ hash(contenu, depenpances directes, nom)

=> similaire au modèle objet de git

  • derivation : un objet enrobant un builder et une liste de dépendances

Bilan store/derivation

Il faut un langage au-dessus du store et des derivations :

  • automatiser la gestion des hash
  • gérer automatiquement les dépendances
  • enrober la création d’une dérivation
  • et assurer qu’elles sont correctes

Transition avec la suite : une derivation est l’objet de base du langage nix

instancier une dérivation : créer le drv dans le store

réaliser/bâtir : exécuter le builder défini dans la dérivation, construire dans le store les objets

faire à la main c’est bien, mais un peu fastidieux

nix comme gestionnaire de paquetages

nix ne gère que le contenu de /nix

comment un utilisateur peut-il utiliser des logiciels du store ?

  • en utilisant le PATH complet
  • automatiquement par un profil : un paquetage spécifique qui va contenir des liens vers d’autres du store nix
❯ which bash
/home/gamba/.nix-profile/bin/bash
❯ realpath ~/.nix-profile
/nix/store/q5p0pjdkzv81prxy6s1vqd65rsqjbcph-profile
❯ ls -d ~/.nix-profile
lrwxrwxrwx - gamba 29 août   2022 /home/gamba/.nix-profile ->
/nix/var/nix/profiles/per-user/gamba/profile
❯ ls -ld /nix/var/nix/profiles/per-user/gamba/profile
lrwxrwxrwx - gamba 18 mai   17:14 /nix/var/nix/profiles/per-user/gamba/profile ->
profile-610-link
❯ ls -ld /nix/var/nix/profiles/per-user/gamba/profile-610-link
lrwxrwxrwx - gamba 18 mai   17:14 /nix/var/nix/profiles/per-user/gamba/profile-610-link ->
/nix/store/q5p0pjdkzv81prxy6s1vqd65rsqjbcph-profile

Modification du profil courant

❯ hello
The program 'hello' is currently not installed. It is provided by
several packages. You can install it by typing one of the following:
  nix profile install nixpkgs#haskellPackages.hello.out
  nix profile install nixpkgs#mbedtls.out
  nix profile install nixpkgs#mbedtls_2.out
  nix profile install nixpkgs#hello.out
  nix profile install nixpkgs#fyne.out
  nix profile install nixpkgs#fltk14.bin
  nix profile install nixpkgs#fltk.bin

Or run it once with:
  nix shell nixpkgs#haskellPackages.hello.out -c hello ...
  nix shell nixpkgs#mbedtls.out -c hello ...
  nix shell nixpkgs#mbedtls_2.out -c hello ...
  nix shell nixpkgs#hello.out -c hello ...
  nix shell nixpkgs#fyne.out -c hello ...
  nix shell nixpkgs#fltk14.bin -c hello ...
  nix shell nixpkgs#fltk.bin -c hello ...
❯ nix profile install nixpkgs#hello
❯ hello
Bonjour, le monde !
❯ which hello
/home/gamba/.nix-profile/bin/hello
❯ realpath $(which hello)
/nix/store/lz9gfg6iybsh0hiignpk55w99a3bj4vb-hello-2.12.1/bin/hello

exploration des liens :

   ~/.nix-profile
-> /nix/var/nix/profiles/per-user/gamba/profile
#  ~/.local/state/nix/profiles/profile
-> profile-612-link
-> /nix/store/4z8abywmnbi2spz057j266178gw4iscj-profile
❯ nix profile remove hello
removing 'flake:nixpkgs#legacyPackages.x86_64-linux.hello'
❯ which hello
which: no hello in (/home/gamba/.nix-profile/bin:/home/gamba/.nix-profile/bin:/home/gamba/.nix-profile/bin:/run/wrappers/bin:/home/gamba/.local/share/flatpak/exports/bin:/var/lib/flatpak/exports/bin:/home/gamba/.nix-profile/bin:/home/gamba/.local/state/nix/profile/bin:/home/gamba/.local/state/nix/profile/bin:/etc/profiles/per-user/gamba/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin)
❯ /nix/store/lz9gfg6iybsh0hiignpk55w99a3bj4vb-hello-2.12.1/bin/hello
Bonjour, le monde !
# le paquetage n'a pas été supprimé du store

Gestion des versions

Chaque modification du profil est historisée.

nix profile history
nix profile rollback
nix profile rollback --to N

Tester sans modifier son profil

nix run nixpkgs#hello
# un raccourci bien pratique avec un cache
nix profile install nixpkgs#comma
nix run 'nixpkgs#nix-index' # indexe tous les binaires de nixpkgs !
, hello

Ouvrir un shell temporaire

Pour rendre plusieurs commandes disponibles pour une session de travail, sans modifier son profil.

nix shell nixpkgs#bat nixpkgs#fzf

TP

À vous :

  • cherchez un logiciel sur https://search.nixos.org, par ex cowsay
  • installer le dans votre profil
  • tester le logiciel : cowsay Nix is good
  • trouver le vrai chemin complet : realpath $(which cowsay)
  • faites un rollback : le logiciel devrait disparaître de votre PATH
  • mais il est toujours dans le store !

nix-collect-garbage

nettoie du store les objets qui ne sont plus référencés

nix-collect-garbage
# le paquetage installé devrait disparaitre

Les références racines =~ profils utilisateurs

valeur initiale des profils : profil de root

nix comme gestionnaires de paquetages

  • opération déléguables à l’utilisateur
  • caches binaires : si un objet n’est pas dans le store local, on essaye de le trouver dans le cache, et sinon on le construit
  • les manipulations de base ne nécessitent pas de connaissances sur le langage nix

nix : le langage

Le langage nix est un petit langage spécifique, dont les seuls effets possibles sont :

  • définir des dérivations
  • ajouter des fichiers au store

Nix, les bases

Pour faire l’équivalent de notre dérivation manuelle

{
  "name": "alamano",
  "outputs": {
    "out": {
      "path": "/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano"
    }
  },
  "inputSrcs" : ["/nix/store/w1adwykj2bs1m0h2cy7np5vnh7cyzpsf-toto"],
  "inputDrvs" : {},
  "system": "x86_64-linux",
  "builder": "/bin/sh",
  "args": [
    "-c",
    "echo 'hello world' > $out"
  ],
  "env": {
    "out": "/nix/store/51jp26dmb74zjy7sq7kz86zfimbjm9rh-alamano",
  }
}

nix repl

On utilise la repl de nix

  • derivation est une fonction primitive qui prend un Set
  • cela ajoute un fichier au store contenant la dérivation
  • on peut alors réaliser la dérivation avec nix-build ou la repl
# nix repl
nix-repl> dset = { name = "alamano"; system = builtins.currentSystem;
builder = "/bin/sh"; args = [ "-c" "echo 'hello world' > $out" ]; }
nix-repl> d = derivation dset
nix-repl> :t d # :t quel est le type de ... ?
a set
nix-repl> d.type
"derivation"
nix-repl> d
«derivation /nix/store/zcd1ilmz0bnxsn5rjaphj33c6dydcf9c-alamano.drv»
nix-repl> :b d # idem nix-build
# try do download from cahe 'https://cache.nixos.org/ll8b29blrqzf2ppm9v8z6hgq54wfcslc.narinfo'
This derivation produced the following outputs:
  out -> /nix/store/ll8b29blrqzf2ppm9v8z6hgq54wfcslc-alamano

Set/String/Identifiant

# !! attention au ;
{ clef = ... ; autre = ... ; }

a = 42; # a est un identifiant

builtins contient la librairie des fonctions intégrés à l’interpréteur nix

builtins.currentSystem : la chaîne qui décrit le système local, e.g. "x86_64-linux"

Observer le contenu d’une dérivation

nix-repl> builtins.attrNames d
[
  "all"
  "args"
  "builder"
  "drvAttrs"
  "drvPath"
  "name"
  "out"
  "outPath"
  "outputName"
  "system"
  "type"
]
nix-repl> :doc builtins.attrNames
Synopsis: builtins.attrNames set

    Return the names of the attributes in the set set in an alphabetically sorted list. For instance,
    builtins.attrNames { y = 1; x = "foo"; } evaluates to [ "x" "y" ].

Gérer les dépendances

nix-repl> :t <nixpkgs>
a path
nix-repl> <nixpkgs>
/nix/store/sqv0la3aklw5ygfikrn8gicwfgz52jzi-source
nix-repl> :t import <nixpkgs>
a function
nix-repl> pkgs = import <nixpkgs> {} # on évalue la fonction
  #            =(import <nixpkgs>){}
nix-repl> :t pkgs
a set
nix-repl> :t pkgs.bash
a set
nix-repl> :t pkgs.bash.type
a string
nix-repl> pkgs.bash.type
"derivation"
nix-repl> "${pkgs.bash}"
"/nix/store/41pi0jiyvlhxgpfhxzvb44w1skc8yp4z-bash-interactive-5.2p37"
nix-repl> "${pkgs.bash}/bin/bash"
"/nix/store/41pi0jiyvlhxgpfhxzvb44w1skc8yp4z-bash-interactive-5.2p37/bin/bash"

Une «vraie» dérivation

Un script bash sera le builder :

cat <<"EOF" > builder.sh
declare -xp
echo foo > $out
EOF
chmod +x builder.sh
pkgs = import <nixpkgs> {}
:t ./builder.sh # a path
d = derivation { name = "foo"; builder = "${pkgs.bash}/bin/bash"; args = [ ./builder.sh ]; system = builtins.currentSystem; } # /nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv
:b d
This derivation produced the following outputs:
  out -> /nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo
❯ nix path-info --json /nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv | jq .
{
  "/nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv": {
    "ca": "text:sha256:0dlymy9zj7sli8w14500w3abbhsaq4qwcfg4nm2nc6i3df4c02na",
    "deriver": null,
    "narHash": "sha256-NCOgJZpEejWyLopnVj+wFe1Iabf7egcSHGlizfUBwLY=",
    "narSize": 680,
    "references": [
      "/nix/store/fg8j7klr6mm1ki4qiq5a3fk5swp42b5f-bash-interactive-5.2p37.drv",
      "/nix/store/rkss115vqiwnbc19ksgwsn81v5c8c6pw-builder.sh"
    ],
    "registrationTime": 1747795069,
    "signatures": [],
    "ultimate": false
  }
}
❯ nix derivation show /nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv
{
  "/nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv": {
    "args": [
      "/nix/store/rkss115vqiwnbc19ksgwsn81v5c8c6pw-builder.sh"
    ],
    "builder": "/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash",
    "env": {
      "builder": "/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash",
      "name": "foo",
      "out": "/nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo",
      "system": "x86_64-linux"
    },
    "inputDrvs": {
      "/nix/store/fg8j7klr6mm1ki4qiq5a3fk5swp42b5f-bash-interactive-5.2p37.drv": {
        "dynamicOutputs": {},
        "outputs": [
          "out"
        ]
      }
    },
    "inputSrcs": [
      "/nix/store/rkss115vqiwnbc19ksgwsn81v5c8c6pw-builder.sh"
    ],
    "name": "foo",
    "outputs": {
      "out": {
        "path": "/nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo"
      }
    },
    "system": "x86_64-linux"
  }
}

Path

le langage nix a un type spécifique pour les chemins de fichiers

  • c’est une valeur de base
  • en sous-main, le fichier est ajouté au store, et la vraie valeur gérée est le chemin dans le store
nix-repl> :t ./builder.sh
a path

Environnement d’exécution

Notre builder nous liste tout l’environnement :

❯ nix log /nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo
declare -x HOME="/homeless-shelter"
declare -x NIX_BUILD_CORES="0"
declare -x NIX_BUILD_TOP="/build"
declare -x NIX_LOG_FD="2"
declare -x NIX_STORE="/nix/store"
declare -x OLDPWD
declare -x PATH="/path-not-set"
declare -x PWD="/build"
declare -x SHLVL="1"
declare -x TEMP="/build"
declare -x TEMPDIR="/build"
declare -x TERM="xterm-256color"
declare -x TMP="/build"
declare -x TMPDIR="/build"
declare -x builder="/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash"
declare -x name="foo"
declare -x out="/nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo"
declare -x system="x86_64-linux"

Reproductibilité

  • build en isolation complète du reste du système
  • les sources et les dépendances sont accessibles dans le store par une variable d’environnement

Utiliser un fichier .nix

# default.nix
let
  pkgs = import <nixpkgs> {};
in
  derivation {
    name = "foo";
    builder = "${pkgs.bash}/bin/bash";
    args = [ ./builder.sh ];
    system = builtins.currentSystem;
  }
❯ nix-build default.nix
this derivation will be built:
  /nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv
building '/nix/store/l6k4xzf478svf6nl6k67w73x9dydcvgy-foo.drv'...
declare -x HOME="/homeless-shelter"
declare -x NIX_BUILD_CORES="0"
declare -x NIX_BUILD_TOP="/build"
declare -x NIX_LOG_FD="2"
declare -x NIX_STORE="/nix/store"
declare -x OLDPWD
declare -x PATH="/path-not-set"
declare -x PWD="/build"
declare -x SHLVL="1"
declare -x TEMP="/build"
declare -x TEMPDIR="/build"
declare -x TERM="xterm-256color"
declare -x TMP="/build"
declare -x TMPDIR="/build"
declare -x builder="/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash"
declare -x name="foo"
declare -x out="/nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo"
declare -x system="x86_64-linux"
/nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo

cat /nix/store/slnmn2kjnf1pkzqjnw34za872l4j34fx-foo
foo

let … in

let ... in permet d’introduire des identifiants dans l’expression après le in

Modifier l’environnement du builder

rajouter des valeurs dans le set de la dérivation ajoute une variable d’environnement

On ajoute un répertoire local

# ssg-src.nix
let
  pkgs = import <nixpkgs> {};
in
  derivation {
    name = "foo";
    builder = "${pkgs.bash}/bin/bash";
    args = [ ./builder.sh ];
    system = builtins.currentSystem;
    src = ./content;
  }
❯ nix-build ssg-src.nix
this derivation will be built:
  /nix/store/1jihnh9afp50vf4pbiq0r3ii4f82dy4v-foo.drv
building '/nix/store/1jihnh9afp50vf4pbiq0r3ii4f82dy4v-foo.drv'...
declare -x HOME="/homeless-shelter"
...
declare -x builder="/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash"
declare -x name="foo"
declare -x out="/nix/store/855m8n1si9ddn0v28pm6hpvbz4dhk68v-foo"
declare -x src="/nix/store/2w43qdqspr50g24ijz3w1a5d7ccmdisq-content"
declare -x system="x86_64-linux"

on voit apparaître la variable d’environnement src

TP

modifiez le builder :

# builder-content.sh
declare -xp
echo loop over $src txt files
for content in $src/*.txt; do
  cat $content >> $out
done

Ajoutez des fichiers txt dans le répertoire ./content

Reconstruisez notre paquetage foo :

nix-build ssg-src.nix

declare -x HOME="/homeless-shelter"
declare -x PATH="/path-not-set"
loop over /nix/store/2w43qdqspr50g24ijz3w1a5d7ccmdisq-content txt files
/nix/store/9b7aqryar3mvwyyy6415l728npkb4kvc-builder-content.sh: line 3: cat: command not found
/nix/store/9b7aqryar3mvwyyy6415l728npkb4kvc-builder-content.sh: line 3: cat: command not found

build en isolation : tout ce qui n’est pas explicitement listé n’est pas accessible

ajouter des dépendances : on ajoute une variable d’environnement correspondant au paquetage

trouver cat : https://search.nixos.org -> coreutils

# out-from-files.nix
let
  pkgs = import <nixpkgs> {};
in
  derivation {
    name = "boo";
    builder = "${pkgs.bash}/bin/bash";
    args = [ ./builder-content.sh ];
    system = builtins.currentSystem;
    src = ./content;
    coreutils = pkgs.coreutils;
  }
declare -xp
echo loop over $src txt files
for content in $src/*.txt; do
  $coreutils/bin/cat $content >> $out
done

nix-shell

nix-shell crée un shell à partir d’une derivation correcpondant à l’environnement du builder

cela permet de faire à la main les opérations du builder

c’est très utile pour mettre un builder au point

nix-shell --pure out-from-files.nix
echo $coreutils
/nix/store/87fck6hm17chxjq7badb11mq036zbyv9-coreutils-9.7
echo $builder
/nix/store/1q9lw4r2mbap8rsr8cja46nap6wvrw2p-bash-interactive-5.2p37/bin/bash
echo $out
/nix/store/jvxjqg9hk92pay83la5drkhp1frhd3b2-boo
$builder -c ./builder-content.sh
# permission denied : seul root et le daemon nix peuvent écrire sur le store
export out=/tmp/test-boo
$builder -c ./builder-content.sh
$coreutils/bin/cat $out
content to be
displayed

nix : les fonctions

Rajouter des attributs dans la dérivation pour gérer les dépendances revient souvent. On va paramétrer cette pratique.

=> écrire une fonction en nix

nix-repl> x: x*2
«lambda @ «string»:1:1»
nix-repl> double = x: x*2
nix-repl> double
«lambda @ «string»:1:2»
nix-repl> double 21
42
  • fonction anonyme à un seul argument
  • appel en donnant l’argument sans parenthèses

fonction nix : plusieurs paramètres ?

nix-repl> mul = a: b: a*b
#         mul = a: (b: a*b)
nix-repl> mul
«lambda»
nix-repl> mul 3 # application partielle
«lambda»
nix-repl> (mul 3) 4
12

classique en programmation fonctionnelle :

  • curryfication : on construit les fonctions à plusieurs arguments à partir des fonctions à un seul argument
  • application

Application

# out-from-files.nix
let
  pkgs = import <nixpkgs> {};
in
  derivation {
    name = "boo";
    builder = "${pkgs.bash}/bin/bash";
    args = [ ./builder-content.sh ];
    system = builtins.currentSystem;
    src = ./content;
    coreutils = pkgs.coreutils;
  }
# builderFunc.nix
pkgs: attrs:
let defaultAttrs = {
      # name = "boo";
      builder = "${pkgs.bash}/bin/bash";
      args = [ ./builder-content.sh ];
      system = builtins.currentSystem;
      # src = ./content;
      # coreutils = pkgs.coreutils;
    };
    realAttrs = defaultAttrs // attrs;
in
  derivation realAttrs

set1 // set2 : donne un nouveau set avec le contenu de set1 auquel on ajoute set2

# default.nix
let
  pkgs = import <nixpkgs> { };
  mkDerivation = import ./builderFunc.nix pkgs;
in
mkDerivation {
  name = "boo";
  src = ./content;
  inherit (pkgs) coreutils;
  # coreutils = pkgs.coreutils;
}
nix-build # default.nix

TP

Notre builder actuel utilise cat.

Changer le pour utiliser banner :

for content in $src/*.txt; do
  $banner/bin/banner $($coreutils/bin/cat $content) >> $out
done
# default.nix
let
  pkgs = import <nixpkgs> { };
  mkDerivation = import ./builderFunc.nix pkgs;
in
mkDerivation {
  name = "boo";
  src = ./content;
  inherit (pkgs) coreutils banner;
}

Rajouter un attribut indiquant le programme à utiliser

# builderFunc.nix
pkgs: attrs:
let defaultAttrs = {
      builder = "${pkgs.bash}/bin/bash";
      args = [ ./builder-content.sh ];
      system = builtins.currentSystem;
    };
    realAttrs = defaultAttrs // attrs;
in
  derivation realAttrs
# default.nix
let
  pkgs = import <nixpkgs> { };
  mkDerivation = import ./builderFunc.nix pkgs;
in
mkDerivation {
  name = "boo";
  src = ./content;
  inherit (pkgs) coreutils;
  cmd = "${pkgs.banner}/bin/banner";
}
for content in $src/*.txt; do
  $cmd $($coreutils/bin/cat $content) >> $out
done
# default.nix
let
  pkgs = import <nixpkgs> { };
  mkDerivation = import ./builderFunc.nix pkgs;
in {
  bannerboo = mkDerivation {
    name = "bannerBoo";
    src = ./content;
    inherit (pkgs) coreutils;
    cmd = "${pkgs.banner}/bin/banner";
  };
  cowsayboo = mkDerivation {
    name = "cowsayBoo";
    src = ./content;
    inherit (pkgs) coreutils;
    cmd = "${pkgs.cowsay}/bin/cowsay";
  };
}

nix-build construit tous les attributs

pour restreindre : nix-build -AbannerBoo

nix : environnement standard

On utilise un wrapper au-dessus de derivation : pkgs.stdenv.mkDerivation bien expliqué dans nix-pills#19

Le builder associé comporte plusieurs phases définies dans un seul script shell setup.sh. De nombreux hooks permettent d’adapter le comportement.

Cet environnement sert de base à des versions spécialisés pour un langage, mais également à des versions «amoindries» appelées trivial builders.

pkgs.writeShellScriptBin

{writeShellScriptBin, banner, cowsay, moo ? false}:

let
  cmd = if moo then "${cowsay}/bin/cowsay"
        else "${banner}/bin/banner";
  name = if moo then "moo" else "boo";
in
writeShellScriptBin name ''
  echo do not use ${cowsay}
  ${cmd} $1
''
let
  pkgs = import <nixpkgs> { };
in {
  boo = pkgs.callPackage ./boo.nix { };
  moo = pkgs.callPackage ./boo.nix { moo = true;};
}

pkgs.callPackage

nix flake

Reproductible, mais …

  pkgs = import <nixpkgs> { }; # nixpkgs du système

les flakes sont une réponse à ce problème, et permettent de distribuer du code à partir d’un dépôt git.

Mais :

  • commande expérimentale standardisée par défaut

Exemple

# flake.nix
{
  description = "Boo et Moo flake";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
      forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
    in {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
          boo = pkgs.callPackage ./boo.nix { };
          moo = pkgs.callPackage ./boo.nix { moo = true; };
        in {
          boo = boo;
          moo = moo;
          default = boo;
        }
      );
    };
}

Utilisation

# Pour construire boo
nix build .#boo

# Pour construire moo
nix build .#moo

# Pour exécuter boo (en utilisant le chemin du binaire)
./result/bin/boo "texte"

# Pour exécuter moo (en utilisant le chemin du binaire)
nix build .#moo && ./result/bin/moo "texte"

Auteur: Pierre Gambarotto (IMT)

Created: 2025-05-22 jeu. 18:09