https://lafor.ge/feed.xml

Partie 2 : Première dérivation

2023-04-18
Les articles de la série

Bonjour à toutes et tous! 😀

Deuxième article sur Nix.

Cette fois-ci, on fait vraiment du Nix.

Mais d'une façon un peu différente de ce que j'ai pu trouver sur internet.

Ok, les dessins vus en partie 1 c'est rigolo, mais en vrai, comment je peux créer ces paquets ?

C'est là que les choses sérieuses débutent! 🥲

Le paquet que l'on veut créer

Nous allons faire le paquet le plus simple du monde.

Un hello-world en C.

Pour cela, il nous faut des sources.

#include "stdio.h"

void main() {
  printf("Hello World");
}

Normalement pour compiler on fait un coup de

gcc main.c -o result

Et bien allons-y ! Mais utilisons ce qu'on sait déjà faire.

nix-shell -p gcc

On obtient alors un shell avec gcc

[nix-shell:~/path]$ gcc --version
gcc (GCC) 12.2.0

Compilons et rendons exécutable le fichier

[nix-shell:~/path]$ gcc main.c -o result
[nix-shell:~/path]$ chmod +x result
[nix-shell:~/path]$ ./result
Hello World
[nix-shell:~/path]$ exit

Wouhou, on sait encore faire du C 😎

Découverte de nix-repl

Bon assez rigolé, attaquons les choses sérieuses.

Mais nous allons le faire très progressivement.

Tout d'abord et en dehors d'un nix-shell, nous allons utiliser le REPL "Real Event Print Loop" de Nix. Il s'agit d'un programme qui permet de manipuler Nix en mode interactif.

$ nix repl
Welcome to Nix 2.15.0. Type :? for help.

Si tout se passe bien, vous devriez avoir un affichage semblable.

Il permet de taper des expressions qui seront analysées et exécutées.

nix-repl > 1 + 2
3

La fonction derivation

Créons maintenant, une dérivation dont on a si souvent parlé dans la partie précédente.

nix-repl > derivation
«lambda @ //builtin/derivation.nix:5:1»

Ok, "lambda" ... Cela signifie qu'il s'agit d'une fonction.

En Nix, pour appeler une fonction on fait

expr {}

{% note(title=""() %} Pour les pros, oui je sais c'est un raccourci, j'explicite dans la partie 3 ^^ {% end %}

Allons-y pour la derivation.

nix-repl > derivation {}
error: attribute 'name' missing

Donc la fonction prend un attribut qui est name;

Les attributs sont les champs de structures de données comme ci-dessous:

{ x = 1; y = 2; }

En Nix on nomme ceci un set.

Remarquez le point virgule ; à la fin de chaque attribut

Nous allons donc donner à notre fonction un set comportant l'attribut name et qui a pour valeur "hello-world".

nix-repl > derivation { name = "hello-world"; }
error: required attribute 'builder' missing

Bon ça progresse ! Je vais pas vous faire tous les attributs manquants, un à un ^^

Voici un set complet

nix-repl > derivation { name = "hello-world"; builder = "fake-builder"; system = "fake-system";}
«derivation /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv»

Wouhou ! Vous venez de créer votre première dérivation !

Derivation Nix

Le retour de vous avez créé a tout l'air d'être un chemin sur l'ordi.

Sortons du REPL avec ctrl+d.

Voyons ce qu'il en est.

$ file /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv
/nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv: ASCII text, with no line terminators

C'est bien un fichier, visualisons son contenu.

$ cat /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv

Derive([("out","/nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world","","")],[],[],"fake-system","fake-builder",[],[("builder","fake-builder"),("name","hello-world"),("out","/nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world"),("system","fake-system")])

Nix propose une alternative plus sympatique pour le visualiser.

$ nix derivation show /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv

error: experimental Nix feature 'nix-command' is disabled; use '--extra-experimental-features nix-command' to override

Bon, il faut rajouter l'argument.

$ nix derivation show /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv --extra-experimental-features nix-command

{
  "/nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv": {
    "args": [],
    "builder": "fake-builder",
    "env": {
      "builder": "fake-builder",
      "name": "hello-world",
      "out": "/nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world",
      "system": "fake-system"
    },
    "inputDrvs": {},
    "inputSrcs": [],
    "name": "hello-world",
    "outputs": {
      "out": {
        "path": "/nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world"
      }
    },
    "system": "fake-system"
  }
}

{% note(title=""() %} Vu que c'est experimental mais pas complet cassé, nous allons désactiver les warnings.

$ vim ~/.config/nix/nix.conf
experimental-features = nix-command

{% end %}

Dans ce qui ressemble a du JSON, nous avons une clef avec le nom de notre dérivation <deriv> et dedans une clef <deriv>.env.out.

Celle-ci a pour valeur un autre chemin /nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world.

Si on tente de l'atteindre.

$ ls /nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world
ls: cannot access '/nix/store/vzv4g8bavybrp4682xidd5dn7r9w8lf6-hello-world': No such file or directory

Normal, nous avons déclaré notre dérivation mais nous ne l'avons pas encore réalisée.

C'est donc logique et plutôt rassurant que le paquet hello-world n'existe pas encore.

Réalisation de la dérivation

Réalisons notre dérivation !

Pour cela, nouvelle commande : nix-build.

$ nix-build /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv

this derivation will be built:
  /nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv
error: a 'fake-system' with features {} is required to build '/nix/store/z4lc26wzbma1c5s7spp98zz7xz99m5gh-hello-world.drv', but I am a 'x86_64-linux' with features {benchmark, big-parallel, kvm, nixos-test, uid-range}

Ok, notre dérivation est pas top, je crois que nous allons devoir repartir sur la table à dessins. 😢

On est de retour dans le REPL.

Changeons l'attribut system pour ce qu'il demande.

nix-repl> derivation { name = "hello-world"; builder = "fake-builder"; system = "x86_64-linux";}
«derivation /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv»

Le chemin est complètement différent, pour cette seconde dérivation.

$ nix derivation show /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv
{
  "/nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv": {
    "builder": "fake-builder",
    "env": {
      "system": "x86-64_linux"
    }
  }
}

Et notre système est à la bonne valeur.

On relance un build et on croise les doigts. 🤞

$ nix-build /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv
this derivation will be built:
  /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv
building '/nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv'...
error: executing 'fake-builder': No such file or directory
error: builder for '/nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv' failed with exit code 1;
       last 1 log lines:
       > error: executing 'fake-builder': No such file or directory
       For full logs, run 'nix-store -l /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv'.

Ah! De l'avancement. des erreurs mais on progresse.

Je trouve le chemin de modification un peu longuet: repl, derivation, exit repl, build.

Est-ce que l'on pourrait rester dans le REPL pour build ? Oui, sinon je poserai pas la question. 😝

Il suffit de rajouter :b devant la déclaration de la dérivation.

nix-repl> :b derivation { name = "hello-world"; builder = "fake-builder"; system = "x86_64-linux";}
error: builder for '/nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv' failed with exit code 1;
       last 1 log lines:
       > error: executing 'fake-builder': No such file or directory
       For full logs, run 'nix-store -l /nix/store/85pf9z8ps92lf115wpfy5k493l096g99-hello-world.drv'.

Bon il trouve pas le builder, et pour cause, il n'existe pas.

Le builder

Un builder est un exécutable qui a pour but de réaliser la dérivation et de mettre le contenu dans <deriv>.env.out celui du retour de la commande nix derivation show..

Pour cela nous allons rajouter un quatrième paramètre args qui va nous permettre de définir les arguments passés au builder.

Ici ça sera l'équivalent d'écrire:

/bin/sh -c "echo toto > $out"

-c pour lire le stdin et l'exécuter.

Et nous allons utiliser un builder qui existe, celui-ci sera /bin/sh.

J'explique dans la partie 3 pourquoi. ^^

Le env de <deriv>.env.out est ici pour signaler qu'une variable d'environnement "$out" est définie à la valeur du chemin de sortie de la dérivation.

Donc ici, notre builder se contente de venir écrire toto à ce chemin. Qui devient un fichier.

nix-repl> :b derivation {          
  name = "hello-world";    
  system = "x86_64-linux";
  builder = "/bin/sh";     
  args = [                 
    "-c"                                                                                                                               
    ''                       
    echo toto > $out         
    ''
  ];
}

This derivation produced the following outputs:
  out -> /nix/store/alrzzi9kljsqazv5a34hspx531wjknz7-hello-world

On peut s'en convaincre avec:

$ file /nix/store/alrzzi9kljsqazv5a34hspx531wjknz7-hello-world
/nix/store/alrzzi9kljsqazv5a34hspx531wjknz7-hello-world: ASCII text

$ cat /nix/store/alrzzi9kljsqazv5a34hspx531wjknz7-hello-world
toto

Félicitation ! Vous venez de réaliser votre première dérivation!! 🎉 🍾

Conclusion

Nous avons bien une dérivation, elle est fonctionnelle, ne compile pas encore de C.

On verra dans la partie 3 comment compiler.

Merci de votre lecture ❤️

avatar

Auteur: Akanoa

Je découvre, j'apprends, je comprends et j'explique ce que j'ai compris dans ce blog.

Ce travail est sous licence CC BY-NC-SA 4.0.