Nix pour les développeurs

Posté par . Édité par Xavier Claude, Benoît Sibaud, tankey, Davy Defaud, ZeroHeure et palm123. Modéré par Xavier Claude. Licence CC by-sa
Tags :
53
6
juil.
2017
Technologie

Nix est un gestionnaire de paquets « fonctionnel » (basé sur des fonctions, sans effet de bord). Cette caractéristique apporte des avantages indéniables, notamment de pouvoir mettre en place des environnements logiciels isolés, reproductibles et composables. Ceci peut être très utile à un administrateur système mais également à un développeur.

On trouve pas mal d’informations sur l’écosystème Nix et son utilisation, ainsi que des retours d’expérience des utilisateurs. En revanche, les documents à destination des développeurs sont moins nombreux et se limitent souvent à l’utilisation ou à la mise en place d’environnements de développement simples.

Cet article a pour objectif d’illustrer l’intérêt de Nix pour un développeur dans des cas simples et « un peu moins simples ». Pour cela, il se base sur un projet d’exemple en C++ et en Python, mais Nix peut également être utilisé pour d’autres langages. Je ne suis pas un expert en Nix, donc n’hésitez pas à proposer vos remarques ou améliorations dans les commentaires ou sur le dépôt GitHub du projet d’exemple.

Sommaire

Introduction

Exemple 1 : créer un environnement de développement de test

Scénario

Vous avez codé un script Python myscript.py et vous voulez le tester dans un environnement vierge.

# myscript.py
import numpy
x = numpy.ndarray(42, int)
x[0::2] = 13
x[1::2] = 37
print(x)

Avec les outils classiques (Python, virtualenv, pip)

virtualenv -p /usr/bin/python3 --no-site-packages ~/myvenv
source ~/myvenv/bin/activate
pip install numpy
python myscript.py
deactivate
rm -rf ~/myvenv

Avec Nix

nix-shell --pure -p python35Packages.numpy --run "python myscript.py"

Exemple 2 : reproduire un environnement de développement

Scénario

Vous développez un logiciel myprog en C++, compilé avec Cmake et utilisant la bibliothèque Boost. Vous avez récupéré votre projet sur une nouvelle machine et voulez le compiler.

Avec les outils classiques (par exemple sous Arch Linux)

sudo pacman -S gcc cmake boost
mkdir build
cd build
cmake ..
make

Avec Nix

Au cours du projet, un fichier default.nix est tenu à jour (il indique notamment les dépendances à Cmake et à Boost). Il suffit alors de lancer la commande :

nix-build

Exemple 3 : empaqueter un projet

Scénario

Le projet myprog de l’exemple précédent vient d’aboutir à une version 0.1 dont le code source est disponible en ligne. Vous voulez l’installer proprement sur votre système.

Avec les outils classiques (par exemple sous Arch Linux)

sudo pacman -S base-devel
mkdir myprog
cd myprog
# Écrire un fichier PKGBUILD (avec l’adresse URL de la publication, les dépendances, les instructions de compilation, etc.).
makepkg
sudo pacman -U myprog-0.1-1-any.pkg.tar.xz

Cette solution fonctionne pour Arch Linux uniquement. Si vous voulez une solution pour Debian ou Fedora, il faut créer les paquets Deb ou RPM correspondants.

Avec Nix

cp default.nix release.nix
# dans le fichier release.nix, changer la valeur de la variable src par l’URL de la publication
nix-env -f release.nix -i myprog

Ici, la solution devrait fonctionner automatiquement pour tout système compatible avec Nix (Arch Linux, Debian, Fedora…).

Exemple 4 : personnaliser des dépendances

Scénario

Vous développez des logiciels de traitement d’images utilisant la bibliothèque OpenCV. Pour cela, vous utilisez le paquet OpenCV fourni par la logithèque système. Un de vos logiciels doit utiliser GTK ; malheureusement, le paquet OpenCV a été compilé avec l’option -DWITH_GTK=OFF.

Avec les outils classiques

Bienvenue en enfer… Quelques « solutions » classiques :

  • recompiler OpenCV à partir du code source ; vous pourrez ensuite faire une installation système (mais cela peut impacter les autres logiciels) ou une installation locale (mais il faudra configurer les chemins vers votre OpenCV personnalisé quand vous en aurez besoin) ;
  • utiliser un système d’environnement virtualisé (chroot, Flatpak, Docker…). Pour cela, vous devrez mettre en place le système, puis récupérer une image d’OpenCV compilé avec les bonnes options ou créer votre propre image.

Avec Nix

Les paquets Nix sont paramétrables. Ainsi pour activer l’option GTK 2 du paquet OpenCV, il suffit (presque) d’ajouter la ligne suivante dans le fichier default.nix. La recompilation et la gestion des différentes versions est automatique.

opencv3gtk = pkgs.opencv3.override { enableGtk2 = true; };

Quelques rappels sur Nix

Présentation

Nix est un gestionnaire de paquets fonctionnel. Le terme « fonctionnel » est à prendre au sens mathématique : une fonction prend des entrées et produit une sortie, sans réaliser d’effets de bord. Ceci permet de créer des environnements logiciels (compilation, installation et configuration) avec les avantages suivants :

  • les environnements sont reproductibles ;
  • ils sont paramétrables et composables ;
  • ils n’ont jamais besoin d’être dupliqués ;
  • ils sont exécutés nativement.

L’écosystème Nix comporte différents éléments :

  • un langage permettant de décrire un environnement logiciel (appelé nix-expression) ;
  • des outils (nix-build, nix-env, nix-shell…) permettant de construire, installer, exécuter, etc., des nix-expressions ;
  • un dépôt officiel (nixpkgs) de nix-expressions…

Il existe une distribution GNU/Linux (NixOS) directement basée sur ces éléments, mais le système Nix peut être installé sur un système d’exploitation quelconque (GNU/Linux, BSD, macOS) pour y servir de logithèque et de système d’environnement virtuel.

Enfin, Nix a inspiré un système concurrent, nommé GNU Guix. Tout comme Nix, Guix peut être utilisé sur un système d’exploitation classique ou via une distribution dédiée, GuixSD. À la différence de Nix, Guix est basé sur un langage existant (Guile Scheme) et accorde une plus grande importance à l’aspect « logiciel libre ».

Quelques commandes Nix

  • voir les paquets installés :
nix-env -q
  • voir les paquets disponibles contenant le motif "firefox" :
nix-env -qa 'firefox'
  • installer le paquet firefox :
nix-env -i firefox
  • désinstaller le paquet firefox :
nix-env -e firefox

Toutes ces commandes sont utilisables avec les droits utilisateurs et dans l’environnement de l’utilisateur. Les paquets sont gérés par un service (nix-daemon) qui les installe dans un répertoire commun /nix/store et les rend disponibles aux différents utilisateurs.

Projet d’exemple (C++/Python)

Pour illustrer l’utilisation de Nix, on considère un projet type (the_checkerboard_project) qui calcule et affiche des images de damier.

checkerboard

Ce projet est composé d’une bibliothèque C++ (checkerboard) et d’une interface Python (pycheckerboard) contenant la liaison proprement dite et un script Python additionnel.

the_checkerboard_project/
├── checkerboard
│   ├── CMakeLists.txt
│   ├── checkerboard.cpp
│   ├── checkerboard.hpp
│   └── test_checkerboard.cpp
└── pycheckerboard
    ├── setup.py
    └── src
        ├── checkerboard
        │   └── binding.cpp
        └── pycheckerboard
            ├── __init__.py
            └── test1.py

Les fonctions pour calculer un damier et pour afficher une image, en utilisant la bibliothèque OpenCV. La compilation est réalisée via Cmake, qui fait le lien avec OpenCV et qui construit la bibliothèque checkerboard et un exécutable de test.

# checkerboard/CMakeLists.txt
cmake_minimum_required( VERSION 3.0 )
project( checkerboard )

# lien avec OpenCV
find_package( PkgConfig REQUIRED )
pkg_check_modules( MYPKG REQUIRED opencv )
include_directories( ${MYPKG_INCLUDE_DIRS} )

# bibliothèque checkerboard
add_library( checkerboard SHARED checkerboard.cpp ) 
target_link_libraries( checkerboard ${MYPKG_LIBRARIES} )
install( TARGETS checkerboard DESTINATION lib )
install( FILES checkerboard.hpp DESTINATION "include" )

# exécutable de test
add_executable( test_checkerboard test_checkerboard.cpp )
target_link_libraries( test_checkerboard checkerboard ${MYPKG_LIBRARIES} ) 
install( TARGETS test_checkerboard DESTINATION bin )

L’interface Python est faite avec Boost Python. La liaison (binding.cpp) expose simplement les deux fonctions de la bibliothèque checkerboard. Un script additionnel (test1.py) fournit une fonction et un programme de test. Le tout est compilé dans un paquet Python en utilisant un script setuptools/pip très classique.

# pycheckerboard/setup.py
from setuptools import setup, Extension

checkerboard_module = Extension('checkerboard_binding',
    sources = ['src/checkerboard/binding.cpp'],
    libraries = ['checkerboard', 'boost_python', 'opencv_core', 'opencv_highgui'])

setup(name = 'pycheckerboard',
    version = '0.1',
    package_dir = {'': 'src'},
    packages = ['pycheckerboard'],
    python_requires = '<3',
    ext_modules = [checkerboard_module])

Configuration Nix basique

Classiquement (sans Nix), on exécuterait les commandes suivantes pour compiler et installer la bibliothèque checkerboard :

mkdir checkerboard/build
cd checkerboard/build
cmake ..
make
sudo make install

Nix permet d’exécuter ces commandes automatiquement. Pour cela, il suffit d’écrire un fichier de configuration default.nix indiquant le nom du paquet, le chemin vers le code source et les dépendances (voir cette dépêche sur l’anatomie d’une dérivation Nix).

# checkerboard/default.nix
with import <nixpkgs> {};
with pkgs; 
stdenv.mkDerivation {
    name = "checkerboard";
    src = ./.;
    buildInputs = [ cmake pkgconfig opencv3 ];
}

Les dépendances spécifiées ici seront installées automatiquement par Nix, si besoin. Ici la dépendance à Cmake implique que la compilation sera réalisée avec Cmake (cf. les commandes précédentes). La compilation et l‘installation de la bibliothèque checkerboard peuvent alors être lancées avec la commande :

nix-env -f . -i checkerboard

La bibliothèque ainsi que l’exécutable de test sont alors disponibles dans les chemins système, ou plus exactement dans les chemins système vus par l’utilisateur. Cependant, il n’est pas obligatoire d’installer le paquet checkerboard. On peut simplement lancer un shell dans un environnement virtuel contenant le paquet :

nix-shell

Ce mécanisme de shell virtuel propose des fonctionnalités très utiles. Par exemple, partir d’un environnement vierge et exécuter juste une commande dans cet environnement :

nix-shell --pure --run test_checkerboard

Configuration Nix modulaire

Au lieu de proposer un paquet complet, on peut découper notre nix-expression (default.nix) en plusieurs modules, appelés dérivations, qui pourront alors être réutilisés ou reparamétrés. Par exemple, pour créer une dérivation opencv3gtk et une dérivation checkerboard :

# checkerboard/default.nix
{ system ? builtins.currentSystem }:
let
    pkgs = import <nixpkgs> { inherit system; };
in
with pkgs; 
stdenv.mkDerivation rec {

    opencv3gtk = import ./opencv3gtk.nix { inherit (pkgs) opencv3; };

    checkerboard = import ./checkerboard.nix { 
        inherit opencv3gtk;
        inherit (pkgs) cmake pkgconfig stdenv;
    };
}

Ces deux dérivations sont implémentées dans des fichiers spécifiques, pour faciliter leur réutilisation. Par exemple, pour checkerboard :

# checkerboard/checkerboard.nix 
{ cmake, opencv3gtk, pkgconfig, stdenv }:
stdenv.mkDerivation {
    name = "checkerboard";
    src = ./.;
    buildInputs = [ cmake opencv3gtk pkgconfig ];
}

Ici, la deuxième ligne indique les paramètres du paquet (c’est‐à‐dire les dépendances à utiliser pour Cmake, pkgconfig, etc.). Ce mécanisme permet de composer les paquets de façon très puissante. Par exemple, on peut reparamétrer le paquet OpenCV en activant la prise en charge de GTK (qui n’est pas activée par défaut) et composer ce nouveau paquet à notre paquet checkerboard, qui disposera alors des fonctionnalités GTK. On peut même modifier finement les options de compilation du paquet OpenCV (par exemple, désactiver les en‐têtes pré‐compilés qui consomment beaucoup de mémoire vive) :

# checkerboard/opencv3gtk.nix 
{ opencv3 }:
let
    opencv3gtk = opencv3.override { enableGtk2 = true; };
in 
opencv3gtk.overrideDerivation (
    attrs: { cmakeFlags = [attrs.cmakeFlags "-DENABLE_PRECOMPILED_HEADERS=OFF"]; }
)

Bien entendu, reparamétrer un paquet nécessite une recompilation si le paquet n’a pas déjà été compilé pour ce jeu de paramètres.

Notez que le nouveau default.nix ne contient pas de dérivation par défaut. Il faut donc préciser la dérivation à utiliser ou à installer :

nix-shell -A checkerboard
nix-env -f . -iA checkerboard

Configuration Nix pour un paquet Python

De nombreux langages proposent leur propre système de gestion de paquets (pip pour Python, gem pour Ruby, npm pour JavaScript, etc.). Nix fournit des fonctionnalités pour créer ce genre de paquets.

Par exemple, pour créer un paquet Python de notre projet, on peut écrire un fichier default.nix, qui va réutiliser les dérivations opencv3gtk et checkerboard précédentes. Nix fournit une fonction buildPythonPackage qui permet de créer simplement un paquet Python en utilisant le script setuptools/pip :

# pycheckerboard/default.nix
{ system ? builtins.currentSystem }:
let
    pkgs = import <nixpkgs> { inherit system; };
    opencv3gtk = import ../checkerboard/opencv3gtk.nix { inherit (pkgs) opencv3; };
    checkerboard = import ../checkerboard/checkerboard.nix { 
        inherit opencv3gtk;
        inherit (pkgs) cmake pkgconfig stdenv; 
    };
in
with pkgs;
pythonPackages.buildPythonPackage {
    name = "pycheckerboard";
    src = ./.;
    buildInputs = [ checkerboard python27Packages.boost opencv3gtk ];
}

Comme pour la bibliothèque, on peut alors installer la dérivation ou la tester interactivement dans un shell virtuel. Les dépendances opencv3gtk et checkerboard correspondent aux dérivations de la section précédente et ne seront pas recompilées ni dupliquées.

$ cd pycheckerboard
$ nix-shell --pure --run python
Obtaining file:///home/nokomprendo/the_checkerboard_project/pycheckerboard
Installing collected packages: pycheckerboard
...
Python 2.7.13 (default, Dec 17 2016, 20:05:07) 
>>> import pycheckerboard.test1 as pt
>>> pt.test1()
running test1.py...

Conclusion

Nix permet de définir des environnements logiciels reproductibles, paramétrables et composables. Il suffit d’écrire quelques fichiers .nix qui viennent compléter les outils de compilation classiques du projet. Les paquets ainsi créés peuvent ensuite être installés ou exécutés, éventuellement dans un environnement isolé. Nix gère automatiquement la compilation, les dépendances et les différentes versions de paquets. Ces fonctionnalités sont intéressantes pour un développeur, car elles permettent non seulement de simplifier le déploiement, mais également d’offrir un environnement de développement multi‐langage léger et reproductible.

  • # nix-shell / shell.nix

    Posté par . Évalué à 9.

    Peut-être ne manque-t-il pas un peu plus d'info sur les fichier shell.nix. Pour moi, à condition d'avoir bien compris le default.nix sert à "builder le rendu / binanire … scripts …" alors que le shell.nix sert à créer un environnement de dev temporaire (anciennement les profiles) qui ressemble plus au virtualenv de python.

    Exemple d'un petit shell.nix pour utiliser maven, docker-compose et ansible :

    with import <nixpkgs> {}; {
      env = stdenv.mkDerivation {
        name = "monprojet";
        buildInputs = [ docker_compose maven python27Packages.ansible ];
      };
    }
    

    Avec ce shell.nix, jai la certitude que le dev/sys… qui voudra utiliser le projet pourra le faire avec un simple "nix-shell". Ca donne le role d'un composer de php ou le npm de node mais pour le system.

    Autre exemple, un shebang & nix : https://gist.github.com/travisbhartwell/f972aab227306edfcfea

    Encore merci pour cette dépêche.

  • # pip, npm ?

    Posté par . Évalué à 3. Dernière modification le 07/07/17 à 11:21.

    Salut,
    Merci pour les exemples ! J'aimerais bcp arriver à me servir de Nix ou Guix pour du dév de projets python/web. Sais-tu si c'est facilement possible à l'heure actuelle ?
    J'aimerais donc:

    • installer les paquets python définis dans les fichiers "requirements.txt", à la bonne version (y a-t-il tous les paquets de pypi dans toutes les bonnes versions dans Nix ? Comment fait-on ?)
    • installer les paquets provenant de npm, encore dans la version spécifiée .
    • installer des paquets système, là ça a l'air d'aller.

    Des articles pour comprendre les enjeux (avec Guix):
    https://blogs.rdoproject.org/7843/guix-tox-a-functional-version-of-tox
    http://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-195/Python-un-environnement-vraiment-isole-avec-GNU-Guix

    peut être peut-on utiliser une méthode hybride, où on spécifie les dépendances systèmes, y compris les dépendances de certains paquets pip et js (C headers,…), et où on installe les autres paquets avec pip et yarn dans un environnement Nix isolé ("conteneurisé") ?

    • [^] # Re: pip, npm ?

      Posté par . Évalué à 4.

      Hello,

      Pour npm et pour pip, même combat: il existe des outils pour générer les dérivations nix à partir du fichier requirements.txt[1]/package.json[2].

      Cela te permettra donc de ne t'occuper que du fichier classique et regénérer de temps en temps le fichier nix listant les dépendances qui ne seraient pas déjà dans nixpkgs (qui se remplit très vite également). Il est aussi fréquent que le dépot nixpkgs soit mis à jour avec des scripts qui aspirent le contenu des dépots de type pipy.

      Dans le manuel de nixpkgs, tu as également des exemples[3] par langages pour expliquer comment adapter à ton cas (Erlang/Elixir, Go, Coq, Haskell, Idris, Java, JS, Lua, Perl, Python, R, Ruby, Rust (via overlay)). Pour te donner une idée des dérivations existantes, il y a la liste sur le site [4].

      [1] https://pypi.python.org/pypi/pip2nix/0.6.0 (distribué uniquement via pip bizarrement)
      [2] https://github.com/NixOS/npm2nix (paquet officiel installable via nix-env)
      [3] https://nixos.org/nixpkgs/manual/#python
      [4] https://nixos.org/nixos/packages.html

      • [^] # Re: pip, npm ?

        Posté par . Évalué à 1.

        Malheureusement, la politique du depot NixPkgs sur github est de ne garder que les versions des derivations les plus recentes:
        https://github.com/NixOS/nixpkgs/issues/9682
        S'il te faut une version precise, il faudra creer ta propre derivation.

        • [^] # Re: pip, npm ?

          Posté par . Évalué à 1.

          Certes dans ce cas, les overlays peuvent être une solution (utilisée par Mozilla concernant rust, afin de proposer les version stables, beta et nightlies très facilement). mais effectivement ça impose d'avoir un ou plusieurs overlays qui étendent ce comportement.

          • [^] # Re: pip, npm ?

            Posté par . Évalué à 1.

            Une autre solution (la dérivation et les overlays sont quand même à privilégier)

            nix-env -f https://github.com/NixOS/nixpkgs-channels/archive/nixos-13.10.tar.gz -qaA nodePackages.npm
            npm-1.3.11
            nix-env -f '<nixpkgs>' -qaA nodePackages.npm
            node-npm-4.4.4
            
  • # Commentaire supprimé

    Posté par . Évalué à 0. Dernière modification le 07/07/17 à 12:56.

    Ce commentaire a été supprimé par l'équipe de modération.

  • # Commentaire supprimé

    Posté par . Évalué à 0. Dernière modification le 19/07/17 à 08:06.

    Ce commentaire a été supprimé par l'équipe de modération.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.