Partie 1 : Premier pas avec Nix
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
|
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.
)
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.
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
Selon votre shell, vous devriez avoir une indication de changement de contexte.
Maintenant, si on retente le
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
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.
Après un certain temps, vu que l'on est en train de compiler les sources de python, vous entrerez dans un shell.
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é.
Créons un projet rust.
()
)
)
)
)
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
.
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.
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.
Si le contenu des sources évolue, son empreinte en fait d'autant.
Notre paquet A, possède également un fichier qui est nécessaire à son fonctionnement.
On étiquette alors le paquet avec l'empreinte calculée précédemment.
Le souci c'est que si le contenu du fichier accompagnant le binaire évolue, mais que le binaire reste identique.
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.
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.
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.
Compilateur lui même possède une version.
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é ?
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.
Ainsi, compiler les même sources, mais avec un compilateur différent, nous donne une empreinte finale différente.
É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
.
En imaginant que le compilateur v1.0
est dans le paquet compilateur étiqueté #7a84
.
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.
On peut alors mettre à jour notre dérivation du paquet A, qui désormais référence également le dérivation du compilateur #7a84
.
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.
Imaginons, maintenant une dérivation paquet B qui demande, une dérivation de paquet A, de paquet C et des sources.
É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. 😄
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 ❤️
Ce travail est sous licence CC BY-NC-SA 4.0.