diff --git a/flake.nix b/flake.nix index a04f9f7..b99a962 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "An empty flake template that you can adapt to your own environment"; + description = "An flake to provide dev and build environnement for presenterm post production"; # Flake inputs inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; diff --git a/src/configs/presenterm.yaml b/src/configs/presenterm.yaml index 77d73b3..b813d31 100644 --- a/src/configs/presenterm.yaml +++ b/src/configs/presenterm.yaml @@ -1,3 +1,5 @@ +options: + strict_front_matter_parsing: false export: dimensions: columns: 108 diff --git a/src/posts/general_nix/post_packaging/cover.jpg b/src/posts/general_nix/post_packaging/cover.jpg new file mode 100644 index 0000000..2b1ca25 Binary files /dev/null and b/src/posts/general_nix/post_packaging/cover.jpg differ diff --git a/src/posts/general_nix/post_packaging/flake_schema.png b/src/posts/general_nix/post_packaging/flake_schema.png new file mode 100644 index 0000000..f729caa Binary files /dev/null and b/src/posts/general_nix/post_packaging/flake_schema.png differ diff --git a/src/posts/general_nix/post_packaging/packaging.nix b/src/posts/general_nix/post_packaging/packaging.nix new file mode 100644 index 0000000..a83eecc --- /dev/null +++ b/src/posts/general_nix/post_packaging/packaging.nix @@ -0,0 +1,42 @@ +{ + description = "An flake to provide dev and build environnement for presenterm post production"; + + inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; + + outputs = inputs: { + devShells = forEachSupportedSystem ( { pkgs }: { + default = pkgs.mkShell { + packages = with pkgs; [ + presenterm + python313Packages.weasyprint + ]; + }; + } ); + + packages = forEachSupportedSystem ( { pkgs }: { + default = pkgs.stdenv.mkDerivation { + inherit (pkgs) system; + name = "linkedin-shoblog-posts"; + src = ./src; + buildInputs = with pkgs; [ + presenterm + python313Packages.weasyprint + ]; + buildPhase = + let + font_dir = pkgs.nerd-fonts.jetbrains-mono; + config = pkgs.writeTextFile { + name = "presenterm.conf"; + text = '' + # presenterm configuration + ''; + }; + in + '' + export PRES_CONFIG=${config} + python scripts/build.py + ''; + }; + } ); + }; +} diff --git a/src/posts/general_nix/post_packaging/post_packaging.md b/src/posts/general_nix/post_packaging/post_packaging.md new file mode 100644 index 0000000..867079f --- /dev/null +++ b/src/posts/general_nix/post_packaging/post_packaging.md @@ -0,0 +1,251 @@ +--- +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) + +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. + +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..` 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.`.default, et le cas échéant, builder cette dernière. `` +é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..default`, et si cette dernière n'est pas présente, +`packages..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) + +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. + +```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 = ''''; +}; +``` + + +```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 ! + +```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. diff --git a/src/posts/general_nix/post_packaging/presenterm.yml b/src/posts/general_nix/post_packaging/presenterm.yml new file mode 100644 index 0000000..063af7c --- /dev/null +++ b/src/posts/general_nix/post_packaging/presenterm.yml @@ -0,0 +1,2 @@ +options: + strict_front_matter_parsing: false