portfolio_hugo/content/post/nix_flake/index.md
2024-10-05 19:30:50 +02:00

12 KiB

title description slug date categories tags weight
Découverte de Nix et de Flake Installation de pmbootstrap et compilation d'un paquet Postmarket OS nix-flake 2024-10-04 00:00:00+0000
Nix
Nix
Flake
1

Introduction

Nix est un gestionnaire de paquets basé sur le langage Nix. Il permet de gérer facilement une configuration système, d'un projet ou même d'un package (logiciel) et surtout de s'assurer de la reproductibilité de la configuration. Cependant, par défaut, Nix ne permet pas de "lock" la version de la configuration sur l'ensemble des machines. C'est pour cela que les développeurs développent Flake.

Flake est une feature experimentale qui permet de lock les dépendances sur une version très précise. Un peu comme le Gemfile.lock ou même le package-lock.json.

Grâce au coté déclaratif de Nix, on peut par exemple facilement tester un outils sans l'installer de manière permanente sur sa machine avec :

nix run nixpkgs#cowsay Salut # Lance directement la commande cowsay avec l'argument Salut

nix shell nixpkgs#cowsay # Prépare un shell avec la commande cowsay dedans
cowsay Salut # La commande est disponible
exit # On revient sur notre shell de base

nix-collect-garbage # On nettoie les dépendances inutiles (Supprime cowsay)

Ou même configurer son shell existant pour pouvoir développer sur un logiciel

nix develop nixpkgs#cowsay # Prépare le shell actuel avec toutes les dépendances nécessaires pour compiler et exécuter cowsay

Attention: Ne clone pas les sources

Lexique

  • attribute set => Fonctionne un peu comme un Hash en Ruby ou un object en JS
  • package => Représente un logiciel, binaire, script, librairies installable sur le système.
  • derivation => Représentation d'un package en Nix. Les fonctions mkShell, writeScriptBin, buildGoModule, buildNpmPackage, buildRustPackage, ... sont juste une surcouche pour faciliter la construction d'une dérivation. Derrière, ils appelent tous la fonctions mkDerivation. (Ex pour buildGoPackage https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/go/module.nix )
  • nix => Gestionnaire de package et le language de programmation
  • flake => Configuration reproductible localisé sur un repo git (projet).

Attention: Le flake.nix ou flake.lock doit-être ajouté dans git avec un git add flake.nix flake.lock car sinon vous ne pourrait pas utiliser le Flake.

Structure d'un Flake

Introduction

On va ce baser sur cette exemple ci-dessous pour expliquer chaque partie du Flake.

{
  description = "Ma description";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        devShells = rec {
          default = pkgs.mkShell {
            inputsFrom = with pkgs; [openssl];
            packages = with pkgs; [redis minio];
            shellHook = ''
              echo "Votre shell est configuré"
            '';
          };
        };
      });
}

Partie inputs

Dans ce flake, on peut trouver tout d'abord la partie inputs, elle correspond aux dépendances de notre flake.

La dépendance (inputs) nixpkgs correspond au repo github nixpkgs. En général, on l'utilise pour récupérer des packages ou utiliser les outils mis à disposition par les contributeurs de NixOS.

On peut retrouver dans le repo.

  • /nixos Contiens la configuration pour nixos (La distribution basée sur Nix)
  • /pkgs Contiens tous les packages (ex: ruby_3_3)

Les nouveaux packages doivent être mis dans le sous-dossier by-name. C'est un nouveau standard du projet. Les packages déjà existants migrent dessus petit à petit. Si un package manque, n'hésitez surtout pas à faire des PR sur github.

  • lib Contiens plein d'outils pratiques comme makeLibraryPath ou makeIncludePath
  • doc Contiens la doc ^^
  • maintainers Contiens la liste de tous les mainteneurs des packages.

La dépendance (inputs) flake-utils contient des helpers pour faciliter de la configuration pour un ensemble de systèmes (x86_64-linux, x86_64-darwin, ...).

darwin => Mac OS

Partie outputs

Outputs prend une fonction qui prend en paramètre un attribute set avec pour attributs self et les inputs déclarés dans la partie inputs.

Dans le cas ou l'on a les inputs suivants:

inputs = {
  nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
  flake-utils.url = "github:numtide/flake-utils";
  mon-input-custom.url = "github:mongithub/monprojet";
};

On aura les attributs suivants dans outputs:

outputs = { self, nixpkgs, flake-utils, mon-input-custom }:
{
  ...
}

Elle correspond aux configurations disponibles pour un flake pour chaque système.

outputs = { self, nixpkgs }:
{
  devShells.x86_64-linux.default = {...};
  packages.x86_64-linux.default = {...};
  nixosConfigurations.x86_64-linux.default = {...};
};

Rappel Nix

add = a: b: a + b

Créer une fonction avec le nom add qui prend en paramètre a et b et qui renvoie l'addition des deux. On déclare un paramètre avec la syntaxe [nom du paramètre]:

add 1 2 renvoie 3

On peut également faire des variantes.

add3N = add 3

add3N 10 renvoie 13

Car add 3 renvoie une fonction avec le premier paramètre a = 3

Les attributs que l'on peut retrouver dans outputs sont :

  • devShells Des environnements de développement.
  • nixosConfigurations Des configurations de NixOS (La distribution Linux)
  • darwinConfigurations Des configurations pour Mac OS En savoir plus
  • packages Des packages (Docker, ruby, go, rust, ...)
  • [...]

Comment mettre à jour le flake.lock

Le flake.nix vient avec un fichier flake.lock qui permet de vérrouiller la version de chaque input. Si on souhaite mettre à jour nos packages pour notre projet. Il est nécessaire de le mettre à jour.

On retrouve deux commandes:

  • nix flake update Mets à jour tous les inputs
  • nix flake lock --update-input <input> Mets à jour uniquement un input en particulier

Comment débugger sa configuration avec la console nix

Il existe une console depuis la version expérimentale de nix nix-command. On peut y accéder avec la commande nix repl.

Normalement, on doit avoir un shell comme ci-dessous

$ nix repl
Welcome to Nix 2.18.2. Type :? for help.
nix-repl>

Dedans, on peut écrire n'importe quoi en nix mais aussi lui demander de charger notre configuration flake avec l'aide de la commande :lf [path racine ou ce trouve le flake]

lf est l'abréviation de Load Flake

$ nix repl
Welcome to Nix 2.18.2. Type :? for help.
nix-repl> :lf .
Added 13 variables.

Une fois chargé, on peut accéder aux outputs avec outputs.<type de conf>.<system>.<nom>

Tips : Vous pouvez appuyer deux fois sur Tab pour avoir de l'aide.

Vous pouvez aussi facilement vérifier votre configuration nix avec la commande nix flake check .

Fonctionnement des environnements de développement

Comment utiliser un devShell

Les devShells permettent de configurer des environnements par défaut. On peut y accéder avec l'aide de la commande nix develop [path du flake]#[nom de l'environnement].

Ex: nix develop .#default

Le path par défaut est déjà le répertoire courant et la configuration utilisée par défaut est également default Donc la commande nix develop suffit dans notre cas, mais si on configure par exemple outputs.devShells.<system>.monshell. Dans ce cas, il faudra utiliser nix develop .#monshell ou nix develop /home/user/monflake#monshell

On peut accéder aux environnements utilisés pour développer n'importe quel package nix. Par exemple, si l'on souhaite aider au développement de ruby. On peut lancer une console de développement avec la commande nix develop nixpkgs#ruby ou même voir la configuration d'un package avec nix edit nixpkgs#ruby

On remarquera qu'ici j'utilise nixpkgs et pas le path du flake. Ça permet d'utiliser la configuration d'un package depuis nixpkgs directement.

Attention Pour des logiciels compilés, en général, il faut d'abord configurer le compilateur via les phases du logiciel. ex: configurePhase et ensuite compiler le logiciel ex: buildPhase . Les phases peuvent varier en fonction du logiciel.

Comment configurer un devShell

outputs = { self, nixpkgs, flake-utils }:
    // Génère une configuration avec l'ensemble des systèmes par défaut
    // aarch64-linux => ARM sur Linux
    // aarch64-darwin => M1, M2, M3, ... sur MacOS
    // x86_64-linux => AMD et Intel sur Linux
    // x86_64-darwin => Intel sur MacOS
    //
    // Pour configurer avec une liste précise de systèmes, vous pouvez utiliser cette fonction
    // flake-utils.lib.eachSystem ["aarch64-linux" " x86_64-darwin"] (system: [...])
    // Voir plus https://github.com/numtide/flake-utils
    //
    // Les éléments d'un tableau ne sont pas séparés par une virgule ex: [ 12 14 ]
    flake-utils.lib.eachDefaultSystem (system:
      let
        // Importe les packages pour le système.
        // Le mot clef inherit équivaut à import nixpkgs { system = system };
        // On peut aussi inherit depuis autre chose
        // { inherit (pkgs.ruby) pname; } renverra { pname = "ruby" }
        pkgs = import nixpkgs { inherit system; };
      in
      {
        // rec Permet d'utiliser les variables dans le même attributs set
        // Exemple:
        //         { a = 12; b = a + 2; } => error: undefined variable 'a'
        //     rec { a = 12; b = a + 2; } => { a = 12; b = 14; }
        //
        // Attention, car parfois, on peut faire des infinites recurse par accident.
        // Exemple:
        //     rec { a = b + 2; b = a + 2; } => error: infinite recursion encountered
        // a dépend de b pour fonctionner, mais b dépend également de a
        devShells = rec {
          default = pkgs.mkShell {
            // with pkgs permet d'éviter de faire [ pkgs.redis pkgs.minio ]
            inputsFrom = with pkgs; [ openssl ]; // Rajoute des dépendances pour la compilation comme les librairies
            packages = with pkgs; [ redis minio ]; // Rajoute des binaires ou des librairies pour l'execution

            MY_CUSTOM_ENV_VAR = "test";

            // Parfois nécessaire pour lancer des projets avec des librairies comme Ruby par exemple.
            // *_LIBRARY_PATH permet de dire à l'OS ou chercher les librairies.
            // Il faut le rajouter par exemple si on rencontre le soucis `Could not open library 'libsodium.so.23'`
            // LD_LIBRARY_PATH => Linux
            // DYLD_LIBRARY_PATH => MacOS
            LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath(with pkgs; [libsodium]);
            DYLD_LIBRARY_PATH = pkgs.lib.makeLibraryPath(with pkgs; [libsodium]);

            // Parfois nécessaire pour configurer des projets comme Ruby avec bundle
            // Certaines gems Ruby génèrent un fichier .c avec du code en dure et surtout un `#include <malib.h>` dedans.
            // Sauf qu'ils utilisent pas les outils de configuration du compilateur fournis par les OS. Donc, il faut créer des variables d'environnements pour dire au compilateur ou chercher les fichiers requis.
            // C_INCLUDE_PATH => Pour le language C
            // CPLUS_INCLUDE_PATH => Pour le language C++
            C_INCLUDE_PATH = pkgs.lib.makeIncludePath(with pkgs; [libsodium]);
            CPLUS_INCLUDE_PATH = pkgs.lib.makeIncludePath(with pkgs; [libsodium]);

            // Le shellHook est un script lancé au lancement du shell
            shellHook = ''
              echo "Votre shell est configuré avec MY_CUSTOM_ENV_VAR = $MY_CUSTOM_ENV_VAR"
            '';
          };
        };
      });

docs

Liens utiles

Pour apprendre nix

Pour trouver des packages

Repository officiel de nixpkgs

Pour connaître l'état d'une Pull Request