---
options:
  command_prefix: "pr:"
  strict_front_matter_parsing: false
theme:
  name: tokyonight-storm
  override:
    default:
      # colors:
        # foreground: "4c4f69"
        # background: "ffffff"
    code: 
      alignment: center
    palette:
      colors:
        base00: "24283B"
        base01: "16161E"
        base02: "343A52"
        base03: "444B6A"
        base04: "787C99"
        base05: "A9B1D6"
        base06: "CBCCD1"
        base07: "D5D6DB"
        base08: "C0CAF5"
        base09: "A9B1D6"
        base0A: "0DB9D7"
        base0B: "9ECE6A"
        base0C: "B4F9F8"
        base0D: "2AC3DE"
        base0E: "BB9AF7"
        base0F: "F7768E"
lang: fr
---
presenterm build process with nix
===
```file +line_numbers 
path: packaging.nix
language: nix
```
Hier je me suis dit que j'allais faire un petit repo avec les différents posts de
cette série, histoire de rendre les textes source accessible entre autre pour les
personnes malvoyantes ou juste pour ceux ne souhaitant pas lire des images en 24px * 24px.
Problème, mon cerveau un peu mal fichu, c'est aussi dit "tient, et si tu en profitais
pour faire un flake avec un environnement dev pour qui voudrait preview tes slides
sans avoir à installer presenterm puis "hey mais attend, tu pourrais aussi faire
en sorte de générer un package avec nix qui contient tous les PDF", ce qui inclut
donc de les générer au moment du packaging.  
Et donc après quelques heures de trifouillage, me voici pour vous présenter mes
bêtises.  
Vous connaissez la routine, lancez spotify et c'est parti pour un peu de lecture
# album du jour
## American idiot - Green day
![](./cover.jpg)
<!-- pr:end_slide -->
OK, comment ça marche ?
===
Avant de parler de ce que sont les flakes et de leur intérêt dans l'écosystème
de nix, commençons par la base, qu'est-ce qu'un package en nix ? "Simplement",
une fonction qui décrit un process de build, pour reprendre les exemples des articles
précédents, pour le packaging des assets de mon site avec **Vue**, nous avons une
fonction prenant en entrée les dépendances nécessaires pour le process de build,
puis décrivant ce dernier, puis retournant uniquement les assets builder sous la
la forme d'un path dans le store nix.  Grossièrement, en terme de code cela donnerais
quelque chose du style:  
```nix
# package.nix
{ pkgs }: pkgs.stdenv.mkDerivation {
  pname = "mon super website";
  version = "3.14";
  src = pkgs.fetchgit {
    url = "http://git.shobu.fr/shoblog/front";
    rev = "069d2a5bfa4c4024063c25551d5201aeaf921cb3";
    sha256 = "sha256-MlqJOoMSRuYeG+jl8DFgcNnpEyeRgDCK2JlN9pOqBWA=";
  };
  buildInputs = with pkgs; [
    node
    yarn
  ];
  buildPhase = ''yarn build'';
  installPHase = ''
    mkdir -p $out/dist
    cp dist $out
  '';
}
```
L'exemple est bien sûr quelque peu simplifié, mais dans les grandes lignes :
* `{ pkgs }: {}`: constitue notre fonction, avec en argument *pkgs* qui correspond
à une instance de nixpkgs.  
* `pkgs.stdenv.mkDerivation`: est la fonction la plus basique nous permettant de créer
un package, les autres fonction, comme mkYarnPackage, étendent cette dernière.
Cette fonction prend elle aussi plusieurs arguments qu'elle vas utiliser pour builder
notre package
  * `pname` et `version`: le nom de notre package et sa version
  * `src`: les fichiers qui serons accessible dans notre environnement de build et
  qui constituent la source de notre package.
  * `buildInputs`: les paquets qui serons accessibles dans notre environnement
  de build.
  * `buildPhase` et `installPhase`: Les hook qui décrivent comment builder et installer
  notre paquet. Il existe d'autre hooks pour d'autres usages décris dans la documentation.

Vous aurez remarqué de votre œil attentif que la fonction s'appelle `mkDerivation`,
et non pas `mkPackage` par exemple comme on aurait pu s'y attendre. Pourquoi ça ?
Dans nix, la dérivation est le bloc fondamental du système de build de nix, un
package n'étant qu'une forme de dérivation. Pour rester simple, je resterais sur
l'appellation plus classique de "package".  
Un souci de cette approche, c'est que le type du retour de notre fonction n'est pas
spécialement défini, on pourrait avoir un package comme ici, mais aussi une shell avec
mkShell, ou un host dans le cas d'une configuration nixos. Le seul moyen de le savoir,
c'est d'avoir une sémantique propre sur ses noms de fichiers. De même, les outils autour
de nix vont s'attendre à un certain type de retour, mais vont quand même builder la dérivation
avant de pouvoir déterminer celui-ci. Par exemple, nix-shell vas s'attendre à avoir le résultat
d'un mkShell pour fonctionner, mais n'aura pas de moyen de le savoir avant d'avoir interprété
la fonction fourni.  
C'est ainsi que pour résoudre ce problème d'uniformité (et quelques autres raisons), les **flakes**
on ete mis en place.
<!-- pr:end_slide -->
les flakes c'est quoi ?
===
```nix
{
  description = "";
  inputs = {};
  outputs = inputs: {};
}
```
Simplement, un flake est un dossier dans lequel se trouve un fichier `flake.nix`
contenant un attribut set avec les attributs suivants :
* `description`: la description de ce que fait le flake.
* `inputs`: un attribut set contenant les dépendances du flake, typiquement une instance
de nixpkgs. Classiquement, chaque input est un repo contenant un autre flake, sur github,
gitlab ou en local et peut être pin sur une branche ou un commit specifique.
* `outputs`: une fonction ayant pour parametre le résultat de l'interprétation des inputs
et retournant un attribut set ou chaque attribut est un résultat possible retourné par
notre flake, bien qu'il existe des attributs standard utilisé par les outils de nix
(nix build, nix shell, nix develop), le développeur est libre de retourner ce qu'il veut,
ce qui permet à des outils comme *colmena* de créer leur propres standards.
Pour un package, on vas ainsi retrouver l'attribut `packages.<architecture>.<paquet>` indiquant
que le flake peut fournir un ou plusieurs packages pour une architecture donnée.
Ainsi, le flake suivant
```nix
{
  description = "un flake d'exemple";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
  };
  outputs = inputs: {
    packages."x86_64-linux".default =
      with import inputs.nixpkgs {system = "x86_64-linux";};
      writeShellScriptBin "hello" "echo 'world hello'";
  }
}
```
On donne ici en input la dernière version stable de nixpkgs. Dans notre fonction
d'output, on déclare un package nommé "default" et on fait appel à la fonction
`writeShellScriptBin` pour le créer, cette fonction prend en argument un nom
de fichier et une chaine de caractère et créer un fichier exécutable contenant
le code du deuxième argument, utilisant `sh` pour l'exécution.  
Une fois notre package builder avec `nix build`, on se retrouve avec notre
exécutable rangé dans un `./results/bin/hello`. On peut aussi directement lancer
ce dernier avec `nix run`. Ces nouvelles commandes, différentes des classiques
`nix-shell` et `nix-build` sont intimement liés aux flakes.  
En effet, contrairement aux commandes classiques qui lisent juste l'output et tentent de l'interpréter,
ces dernières vont plutôt chercher si une output spécifique est présente.
Ainsi, un simple `nix build` vas chercher s'il y à une output
`packages.<system>`.default, et le cas échéant, builder cette dernière. `<system>`
étant l'architecture courante du système, et il est bien sur possible de remplacer
**default** par un nom spécifique de paquet dans le cas où la flake expose plusieurs paquets.  
Quand à `nix run`, elle va d'abord chercher au niveau de l'output
`apps.<system>.default`, et si cette dernière n'est pas présente, 
`packages.<system>.default`.  
Il existe tout une variété d'output standard dont il est possible de retrouver
la liste dans le manuel de nix.
![](./flake_schema.png)
<!-- pr:end_slide -->
Et donc dans notre cas ?
===
```file +line_numbers {2-4} 
path: packaging.nix
language: nix
```
Passons rapidement sur les deux premières lignes, on déclare une jolie description
ainsi qu'une input nommée `nixpkgs` dont l'url pointera vers le dernier commit
de la branche unstable de nixpgks. Une syntaxe plus standard aurait été
`github:Nixos/nixpkgs/nixos-unstable`, ici on utilise flakehub qui est un host
centralisé pour toute sorte de flakes.
<!-- pr:end_slide -->
```file +line_numbers {7-14} 
path: packaging.nix
language: nix
```
Tout d'abord, on déclare une devShell, qui est le système d'environnement temporaire
de nix, fournissant presenterm ainsi que la librairie weasyprint permettant
d'exporter les slides au format PDF.  
La fonction `mkShell` peut prendre plusieurs options dont voici les plus courantes:
```nix
pkgs.mkShell {
  # Les packages que vous fournissez à votre environnement
  packages = with pkgs; [ ];

  # Pour set vos variable d'environnement
  env = { };

  # Et ici vous pouvez définir un bout de code qui sera exécuter au lancement
  # de l'environnement.
  shellHook = '''';
};
```
<!-- pr:end_slide -->

```file +line_numbers {17-24} 
path: packaging.nix
language: nix
```
Avant de définir le build process, voyons la configuration de notre paquet.
Si vous avez déjà lu les articles précédents, cela ne devrait pas avoir trop
de secrets !
`inherit (pkgs) system;`: Juste du sucre syntaxique pour ne pas taper
`system = pkgs.system;`, oui bon dans le cas présent on gagne pas des masses de
temps, mais c'est une bonne pratique à avoir >w<.  
`name`, `src`, `buildInputs`: Le nom de notre paquet, les sources à fournir lors
du build, et les paquets que l'ont fournis à ce dernier, rien de neuf !
<!-- pr:end_slide -->
```file +line_numbers {25-39} 
path: packaging.nix
language: nix
```
Bon, ici, j'ai omis quelque lignes histoire de garder l'essentiel.  
Un problème rencontré lors de l'export des slides en PDF est que le process de
build se passe dans un environnement "neutre", créer par nix spécifiquement pour
cette tâche et avec le moins possible de variable provenant de l'extérieur, ce
afin d'assurer la reproductibilité du process. Ainsi, contrairement au fait de
juste lancer la commande `presenterm -e machin.md`, lors de l'exécution de la phase
de build, l'exécutable de presenterm n'a ni accès à la police de caractère
habituellement fournis par l'émulateur de terminal, ainsi qu'à la taille de ce
dernier. Heureusement, presenterm nous permet de fournir une taille statique pour
l'export en PDF via un fichier de configuration, ainsi que de définir une police
de caractère.  
Le premier est assez simple, on hardcode juste les options correspondantes
(pour le moment) dans le fichier de conf que l'on génère avec writeTextFile.  
pour la police, ont créé une variable qui pointera vers notre police dans le store,
puis on interpolera cette dernière à l'option correspondante dans la config.  
Pour finir, notre **buildPhase** consiste à définir une variable d'environnement
pointant vers la config que l'on a générée afin qu'elle puisse être lue par notre
script, puis en l'exécution de ce dernier dont le rôle est d'aller crawler chaque
dossier contenant nos posts et d'appeler la commande `presenterm -e` dessus.
Je n'inclus pas le dit script ici car je compte le refactorer un peu plus tard
pour le rendre plus flexible et donner la possibilité de build un post précis au
lieu de tous. Mais globalement voila la magie ! Sur des projets plus complexe on
peut utiliser ce genre de mécanisme pour pousser nos packages sur un cache binaire
afin d'accélérer les déploiements, ou juste dans le cadre d'une CI. Ici, j'avais
juste envie d'expérimenter un peu sur ce mécanisme.
