https://lafor.ge/feed.xml

Partie 1 : Premier pas avec Nix

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

Bonjour à toutes et tous! 😀

Premier article sur le gestionnaire de packages Nix et son langage.

Cela peut aussi être un système d'exploitation complet, mais chaque chose en son temps. 😅

Je ferai mon installation sur linux mais la documentation officielle vous permet de l'installer n'importe où même sur du windows.

Dans cette partie, je vous explique comment l'installer et quelle est la philosophie adoptée par Nix pour gérer les paquets.

La pratique viendra dans la partie 2.

Installation

Nix est installable via un scrip bash

curl -L https://nixos.org/nix/install | sh -s -- --daemon

Celui-ci va vous poser un certain nombre de questions qui vont vous permettre de définir le comportement d'installation.

Moi, j'ai choisi le multi-user et donc le besoin d'être sudo.

Pour vérifier que tout marche, rendez-vous dans un autre shell.

$ nix --version
nix (Nix) 2.15.0

Les environnements éphémères

Faisons un peu de magie noire.

Des fois nous avons besoin d'un logiciel à une certaine version pour faire des choses.

Mais c'est compliqué, il faut installer tout un tas de dépendances, ça "pourrit" le disque et on oublie de désinstaller après coup, ou on ne sait plus où on l'a installé.

Nix permet de créer des environnements éphémères pour tester des choses. Environnements qui peuvent facilement se nettoyer.

Exemple, je suis sur un système qui ne possède pas python.

$ python
Command 'python' not found

Fâcheux, 2 solutions, je fais un apt install de python même si j'en ai besoin que maintenant.

Ou ... J'utilise Nix, yes je vois que ça suit derrière 😛

Pour cela, on créé ce que l'on appelle un nix-shell.

Mais pas n'importe lequel, on lui donne des arguments, par exemple le python que l'on désire.

Pour cela, on va sur https://search.nixos.org !

On recherche le paquet que l'on désire, par exemple python 3.11.

Et cela nous donne le nom du paquet, ici python311.

Plus qu'à injecter ça dans nix-shell

$ nix-shell -p python311

Selon votre shell, vous devriez avoir une indication de changement de contexte.

[nix-shell:~/dir]$

Maintenant, si on retente le

[nix-shell:~/dir]$ python --version
Python 3.11.2

Si on exit

$ exit
$ python
Command 'python' not found

Le python n'est toujours pas installé à l'extérieur 😀

Et maintenant si l'on veut, 2 versions de python.

  • python 3.11
  • python 2.7
$ nix-shell -p python311 python27

Il va vous engueuler !

Known issues:
        - Python 2.7 has reached its end of life after 2020-01-01. See https://www.python.org/doc/sunset-python-2/.

       You can install it anyway by allowing this package, using the
       following methods:

       a) To temporarily allow all insecure packages, you can use an environment
          variable for a single invocation of the nix tools:

            $ export NIXPKGS_ALLOW_INSECURE=1

Bien Madame, tout de suite Madame.

$ NIXPKGS_ALLOW_INSECURE=1 nix-shell -p python311 python27

Après un certain temps, vu que l'on est en train de compiler les sources de python, vous entrerez dans un shell.

[nix-shell:~/dir]$ python --version
Python 3.11.2

[nix-shell:~/dir]$ python2 --version
Python 2.7.18.6

Plutôt cool non ? 😎

Et bien-sûr on peut le faire avec n'importe quoi, pourvu qu'une bonne âme l'ait déjà packagé.

nix-shell -p rustc cargo

Créons un projet rust.

[nix-shell:~/dir]$ rustc --version
rustc 1.68.2 (9eb3afe9e 2023-03-27) (built from a source tarball)

[nix-shell:~/dir]$ cargo init toto
Created binary (application) package

[nix-shell:~/dir]$ cd toto

[nix-shell:~/dir]$ cargo run
   Compiling toto v0.1.0 (/home/noa/Documents/Workshop/nix/toto)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/toto`
Hello, world!

Voilà! 30 secondes pour commencer à bosser.

Vous sortez et pouf, tout disparaît ^^

Je sais pas si vous avez remarqué mais, le premier environnement est long à installer mais les suivants c'est instantané.

Il y a sûrement du cache quelque part. 🤔

La réponse est oui ! 🙂

Pour s'en convaincre il suffit de faire un which.

[nix-shell:~/dir]$ which rustc
/nix/store/hd6l85dw0i9zy7j0h52w9s8smyymha3r-rustc-1.68.2/bin/rustc

$ exit
$ which rustc
/home/noa/.cargo/bin/rustc

A l’intérieur et au-dehors de l'environnement, ce ne sont pas les mêmes binaires.

Tout ce qui est utilisable dans l'environnement éphémère est stocké dans un des sous répertoires du dossier /nix/store.

La philosophie derrière Nix

Si vous n'êtes pas familier de la programmation fonctionnelle, nous allons devoir faire un peu de théorie.

Le pire cauchemar d'un fonctionnaliste c'est d'avoir des effets de bord dans son système.

Et qu'est ce qu'un effet de bord ?

Et bien c'est un paramètre non fixé qui fait varier de manière incontrôlée le système.

Imaginez que vous avez une fonction qui doit renvoyer la date d'aujourd'hui.

fun return_now() -> int
  return now()

display return_now();

Cette fonction est impure, car en fonction du moment où vous allez l'appeler, la valeur va varier.

Pour que celle-ci soit pure, il faut que le temps soit déterministe. Et donc que now() le soit.

fun return_now_pure(clock : time) -> int
  return clock.now()

clock = time().set_now("14/04/2023 00:00");

display return_now(clock);

Cette fois-ci, nous sommes capable de décider de ce que now() retourne. La fonction return_now_pure est donc pure.

Maintenant, que pourrait signifier la pureté dans un contexte de paquets à installer ?

Déjà c'est quoi un paquet ?

Très grossièrement, un paquet c'est:

  • un certain nombre de binaires
  • un certain nombre de fichiers de configuration
  • un certain nombre de paquets en dépendances:
    • un certain nombre de binaires
    • un certain nombre de fichiers de configuration
    • un certain nombre de paquets en dépendances:
      • ...

Bon clairement on a une récursion de concepts ^^

Et donc rendre pur un paquet c'est rendre pure cette récursion.

Cela manque de dessins!

Imaginons le paquet A.

Celui-ci est constitué de sources, qui doivent être compilées.

missing alt

Cela produit un binaire.

Nous voulons être convaincus du contenu de ce que l'on manipule.

Pour cela nous allons utiliser une fonction de hachage crytographique.

Une fonction de hachage ou hash a pour particularité de renvoyer la même valeur de retour pour un contenu en entrée donnée.

Nous allons utiliser cette fonction pour venir créer l'empreinte des sources que l'on souhaite compiler.

missing alt

Si le contenu des sources évolue, son empreinte en fait d'autant.

missing alt

Notre paquet A, possède également un fichier qui est nécessaire à son fonctionnement.

missing alt

On étiquette alors le paquet avec l'empreinte calculée précédemment.

missing alt

Le souci c'est que si le contenu du fichier accompagnant le binaire évolue, mais que le binaire reste identique.

missing alt

Alors, l'étiquette restera la même ! Or ça c'est impur. De l'extérieur, nous pensons savoir ce que l'on manipule alors que finalement cette modification peut avoir des répercussions sur le fonctionnement du paquet A.

Pour éviter cela, nous allons également ajouter le contenu du fichier dans le calcul d'empreinte.

missing alt

Ainsi, peu importe les modifications, l'empreinte évoluera. L'utilisateur du paquet A n'est pas "pris en traître"

On créé alors une série de "paquets A", mais étiquetés différemment, ce qui reflète l'état interne du contenu du paquet.

missing alt

Si on en revient à la première chose dont on a parlé, on a dit que le binaire devait être compilé, mais compilé par quoi ?

Et bien par un compilateur.

missing alt

Compilateur lui même possède une version.

missing alt

Et du coup, si la version du compilateur vient à changer, qu'est ce qui garantit que le binaire résultant ne soit pas altéré ?

missing alt

Et bien, rien !

Or, nous voulons un système déterministe.

Ce que nous allons faire, c'est également ajouter dans le calcul de l'empreinte, le compilateur lui-même.

missing alt

Ainsi, compiler les même sources, mais avec un compilateur différent, nous donne une empreinte finale différente.

missing alt

Étant donné que l'on commence à planifier les choses. Nous allons réaliser un plan de construction.

Ce plan de construction se nomme dans le monde de Nix, une dérivation.

Cette dérivation nous explique que pour compiler le paquet A, nous avons besoin:

  • du compilateur à la version v1.0
  • des sources à la version v1.0.1
  • du fichier à la révision rev 2

Si tous les ingrédients sont réunis, et dans cet état précis, la dérivation créera un paquet A étiqueté #e12d.

missing alt

En imaginant que le compilateur v1.0 est dans le paquet compilateur étiqueté #7a84.

missing alt

Alors, tout comme nous utilisons une dérivation pour obtenir, le paquet A, nous allons utiliser la dérivation à l'étiquette #7a84 pour obtenir le paquet compilateur étiqueté #7a84.

On dit que l'on réalise la dérivation.

missing alt

On peut alors mettre à jour notre dérivation du paquet A, qui désormais référence également le dérivation du compilateur #7a84.

missing alt

Pour obtenir notre compilateur, nous avons alors 3 choix:

  • réaliser la dérivation pour obtenir le paquet, récupérer le compilateur
  • télécharger le paquet déjà réalisé
  • utiliser le cache local de la dérivation déjà réalisée

On reparlera du cache plus tard dans la série.

missing alt

Imaginons, maintenant une dérivation paquet B qui demande, une dérivation de paquet A, de paquet C et des sources.

missing alt

Étant donné que toutes les entrées sont fixées, la dérivation garantie que pour une collection d'entrée donnée, le contenu du paquet B sera TOUJOURS identique.

Nous avons un système de création de paquet qui est pur !

D'une manière générale, pour une collection de dérivation d'entrées et de données définie. Nous sommes toujours capable de créer une dérivation qui donnera un paquet donc le contenu est déterministe et donc peut être connu à la simple lecture de son empreinte. 😄

missing alt

Conclusion

Dans cette partie, nous avons un peu joué avec la partie utilisateur du package manager et expliqué comment Nix s'assurait de la pureté, je veux plutôt dire de la reproductibilité des builds.

On verra dans la partie 2 comment créer nos propres packages.

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.