Premier Paquet pour nixos

Un petit tuto de création de paquet pour nixos…

Nixos c’est sympa mais la courbe d’apprentissage est abominable. Aujourd’hui un petit tuto de survie pour créer un premier paquet.

Que choisir pour un premier paquet ? On va prendre quelque chose de facile: arc welder : un petit outil pour optimiser le gcode pour les imprimantes 3d.

Ça va être “facile” car:

  • il y a peu de dépendances
  • ça compile avec cmake
  • c’est du c++

Test manuel

Première étape, on va compiler à la main.

On commence par récupérer le code :

cd /tmp
git clone https://github.com/FormerLurker/ArcWelderLib.git
cd ArcWelderLib

On va utiliser nix-shell avec l’option pure pour se placer dans un environnement de compilation “pur” c’est à dire ne disposant de quasiment aucune commande par défaut. Ça va nous forcer à spécifier manuellement tous les outils nécessaires pour compiler arcwelder.

Comme on sait que l’on aura besoin de cmake on le précise directement avec l’option -p cmake.

nix-shell --pure -p cmake

On crée un répertoire de build et on lance cmake :

[nix-shell:/tmp/ArcWelderLib]$ mkdir build

[nix-shell:/tmp/ArcWelderLib]$ cd build/

[nix-shell:/tmp/ArcWelderLib/build]$ cmake ..
-- The C compiler identification is GNU 10.3.0
-- The CXX compiler identification is GNU 10.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /nix/store/f9592ghi61h9q590f6hav7vw1nykl5h8-gcc-wrapper-10.3.0/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /nix/store/f9592ghi61h9q590f6hav7vw1nykl5h8-gcc-wrapper-10.3.0/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Error at /nix/store/h240v616x2b9j3sbsiw7m58df47q91vn-cmake-3.19.7/share/cmake-3.19/Modules/FindPackageHandleStandardArgs.cmake:218 (message):
  Could NOT find PythonLibs (missing: PYTHON_LIBRARIES PYTHON_INCLUDE_DIRS)
Call Stack (most recent call first):
  /nix/store/h240v616x2b9j3sbsiw7m58df47q91vn-cmake-3.19.7/share/cmake-3.19/Modules/FindPackageHandleStandardArgs.cmake:582 (_FPHSA_FAILURE_MESSAGE)
  /nix/store/h240v616x2b9j3sbsiw7m58df47q91vn-cmake-3.19.7/share/cmake-3.19/Modules/FindPythonLibs.cmake:310 (FIND_PACKAGE_HANDLE_STANDARD_ARGS)
  PyArcWelder/CMakeLists.txt:4 (find_package)


-- Configuring incomplete, errors occurred!
See also "/tmp/ArcWelderLib/build/CMakeFiles/CMakeOutput.log".

On peut voir qu’il nous manque python pour continuer. On sort donc du shell et on recommence en ajoutant un second paquet: python3

╭─wagnerf@hopi /tmp/ArcWelderLib  ‹master*›
╰─➤  nix-shell --pure -p cmake -p python3

[nix-shell:/tmp/ArcWelderLib]$ rm -rf build/

[nix-shell:/tmp/ArcWelderLib]$ mkdir build

[nix-shell:/tmp/ArcWelderLib]$ cd build/

[nix-shell:/tmp/ArcWelderLib/build]$ cmake ..
-- The C compiler identification is GNU 10.3.0
-- The CXX compiler identification is GNU 10.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /nix/store/f9592ghi61h9q590f6hav7vw1nykl5h8-gcc-wrapper-10.3.0/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /nix/store/f9592ghi61h9q590f6hav7vw1nykl5h8-gcc-wrapper-10.3.0/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonLibs: /nix/store/5f36vg11fdfl6x6sva70cb0jqf722qqn-python3-3.8.11/lib/libpython3.8.so (found version "3.8.11")
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/ArcWelderLib/build

make

Cette fois ci tout compile et on récupère bien la commande ArcWelder dans le répertoire ArcWelderConsole.

[nix-shell:/tmp/ArcWelderLib/build]$ cd ArcWelderConsole/

[nix-shell:/tmp/ArcWelderLib/build/ArcWelderConsole]$ ./ArcWelder
PARSE ERROR:
             Required argument missing: source

Brief USAGE:
   ./ArcWelder  [-l=<NOSET|VERBOSE|DEBUG|INFO|WARNING|ERROR|CRITICAL>]
                [-p=<NONE|SIMPLE|FULL>] [-g] [-c=<int>] [-v=<double>]
                [-e=<unsigned int>] [-x=<unsigned int>] [-d] [-y] [-z]
                [-s=<float>] [-a=<int>] [-m=<float>] [-t=<float>]
                [-r=<float>] [--] [--version] [-h] <path to source gcode
                file> <path to target gcode file>

For complete USAGE and HELP type:
   ./ArcWelder --help

[nix-shell:/tmp/ArcWelderLib/build/ArcWelderConsole]$ ldd ./ArcWelder
	linux-vdso.so.1 (0x00007fffc5784000)
	libstdc++.so.6 => /nix/store/9w7ra0b2nqnbly4v64vwayml255lb9bp-gcc-10.3.0-lib/lib/libstdc++.so.6 (0x00007f9bd68ae000)
	libm.so.6 => /nix/store/jsp3h3wpzc842j0rz61m5ly71ak6qgdn-glibc-2.32-54/lib/libm.so.6 (0x00007f9bd676b000)
	libgcc_s.so.1 => /nix/store/jsp3h3wpzc842j0rz61m5ly71ak6qgdn-glibc-2.32-54/lib/libgcc_s.so.1 (0x00007f9bd6751000)
	libc.so.6 => /nix/store/jsp3h3wpzc842j0rz61m5ly71ak6qgdn-glibc-2.32-54/lib/libc.so.6 (0x00007f9bd6590000)
	/nix/store/jsp3h3wpzc842j0rz61m5ly71ak6qgdn-glibc-2.32-54/lib/ld-linux-x86-64.so.2 => /nix/store/jsp3h3wpzc842j0rz61m5ly71ak6qgdn-glibc-2.32-54/lib64/ld-linux-x86-64.so.2 (0x00007f9bd6a85000)

Excellent, bon, on peut attaquer la création du paquet.

Mise en place

Première étape, on va récupérer nixpkgs. On peut prendre la version correspondant à la distribution courante (pour que le paquet soit installable facilement en local) ou la dernière version du git pour faire une pull-request et que le paquet soit intégré à nixos.

cd ~
git clone https://github.com/NixOS/nixpkgs.git

Il y a plein de trucs là dedans. Les paquets sont contenus dans le répertoires pkgs.

On va commencer par chercher un paquet qui nous ressemble. Ça nous fera une bonne base pour la suite.

Pour ce faire, on dégaine grep. Je vais chercher les paquets en lien avec cmake et github.

╭─wagnerf@hopi ~/nixpkgs/pkgs  ‹master*›
╰─➤  grep -r cmake | grep -i github

La liste est longue:

development/python-modules/pc-ble-driver-py/default.nix:{ lib, fetchFromGitHub, cmake, git, swig, boost, udev, pc-ble-driver, pythonOlder
development/python-modules/pyosmium/default.nix:{ lib, buildPythonPackage, fetchFromGitHub, cmake, python
development/python-modules/pyside/tools.nix:{ lib, buildPythonPackage, fetchFromGitHub, cmake, qt4, pyside, pysideShiboken }:
development/python-modules/pythonocc-core/default.nix:{ lib, stdenv, python, fetchFromGitHub, cmake, swig, ninja
development/python-modules/uranium/default.nix:{ lib, buildPythonPackage, fetchFromGitHub, python, cmake
...
tools/compression/lzham/default.nix:{ lib, stdenv, fetchFromGitHub, cmake } :
...

On voit ici pour différents paquets la liste des entrées nécessaires à sa compilation. fetchFromGitHub est utilisé pour récupérer du code source depuis github, cmake pour la compilation. Je prend un paquet qui ne nécessite pas grand chose de plus, ici : lzham.

Jetons un coup d’oeil :

{ lib, stdenv, fetchFromGitHub, cmake } :

stdenv.mkDerivation {
  name = "lzham-1.0";

  src = fetchFromGitHub {
    owner = "richgel999";
    repo = "lzham_codec";
    rev = "v1_0_release";
    sha256 = "14c1zvzmp1ylp4pgayfdfk1kqjb23xj4f7ll1ra7b18wjxc9ja1v";
  };

  nativeBuildInputs = [ cmake ];

  installPhase = ''
    mkdir -p $out/bin
    cp ../bin_linux/lzhamtest $out/bin
  '';

  meta = with lib; {
    description = "Lossless data compression codec with LZMA-like ratios but 1.5x-8x faster decompression speed";
    homepage = "https://github.com/richgel999/lzham_codec";
    license = with licenses; [ mit ];
    platforms = platforms.linux;
  };
}

Je ne vais pas rentrer ici dans les détails de la syntaxe de nix (le language). Il faut savoir que l’install (automatique ou manuelle) d’un programme ou d’une bibliothèque se décompose en plusieurs étapes.

Généralement c’est quelque chose comme :

  • récupérer le code
  • le patcher éventuellement pour que ça marche sur une distro exotique
  • configurer (un script ./configure par exemple
  • compiler
  • installer les fichiers au bon endroit

Ça sera exactement la même chose pour nous. La création du paquet est décomposée en phases qui reprennent ces différentes étapes. La plupart du temps, les procédures sont les mêmes. Par exemple pour tous les projets utilisant cmake on va commencer par créer un répertoire build pour lancer cmake à partir d’un fichier CMakeLists.txt. Pas besoin pour nous de réinventer la roue. Pour toutes les phases qui suivent une procédure standard, nix va tout gérer automatiquement. Seules les phases nécessitant des opérations particulières ont besoin d’être explicitées.

Regardons notre paquet lzham de plus près. On distingue quatre parties différentes :

  • src qui nous dit comment récupérer le code ;
  • nativeBuildInputs qui nous dit ce qui est nécessaire à la compilation ;
  • installPhase qui est la phase d’installation et s’occupe de copier les bons fichiers aux bons endroits ;
  • meta qui contient les infos du paquet.

Construction du paquet

Pour créer notre nouveau paquet on va commencer par se choisir un répertoire et y copier le fichier de lzham.

cd ~/nixpkgs/pkgs/applications/misc
mkdir arcwelder
cd arcwelder
cp ../../../tools/compression/lzham/default.nix .

On va tout suite enregistrer notre nouveau paquet dans la liste globale en éditant le fichier pkgs/top-level/all-packages.nix. Je rajoute arcwelder en dessous de antsimulator.

  antsimulator = callPackage ../games/antsimulator { };

  arcwelder = callPackage ../applications/misc/arcwelder { };

Tout est maintenant en place, plus qu’à modifier notre fichier default.nix.

On commence par changer le nom du paquet, les descriptions, les dépendances et la source :

{ lib, stdenv, fetchFromGitHub, cmake, python3 }:

stdenv.mkDerivation rec {
  pname = "arcwelder";
  version = "1.2.0";

  src = fetchFromGitHub {
    owner = "FormerLurker";
    repo = "ArcWelderLib";
    rev = "${version}";
    sha256 = "14c1zvzmp1ylp4pgayfdfk1kqjb23xj4f7ll1ra7b18wjxc9ja1v";
  };

  nativeBuildInputs = [ cmake python3 ];

  installPhase = ''
    mkdir -p $out/bin
  '';

  meta = with lib; {
    description = "post-process gcode paths for better 3d printing quality";
    homepage = "https://github.com/FormerLurker/ArcWelderLib";
    maintainers = [ ];
    license = [ licenses.agpl3Plus ];
    platforms = platforms.unix;
  };
}

À noter que j’ai également ajouté python3 à la liste des trucs dont j’ai besoin en entrée. La license correspond à celle du site web d’arcwelder et j’ai trouvé quoi utiliser à l’aide de grep qui m’a orienté vers le fichier nixpkgs/libs/licenses.nix.

Bon, jusqu’ici rien de très très dur mais voilà une première difficulté : il nous faut trouver la somme de contrôle à utiliser.

Je procède comme ça :

  • je rentre une fausse somme ;
  • j’installe le paquet ;
  • nix crie au scandale et m’indique la somme qui était attendue.

Attention il est nécessaire de rentrer une fausse somme et pas de reprendre celle de l’autre paquet. Dans mon exemple je remplace la somme par :

sha256 = "04c1zvzmp1ylp4pgayfdfk1kqjb23xj4f7ll1ra7b18wjxc9ja1v";

(je transforme le premier 1 en 0)

Pour installer le paquet on fait :

╭─wagnerf@hopi /tmp
╰─➤  nix-env -f ~/nixpkgs -iA arcwelder
installing 'arcwelder-1.2.0'
these derivations will be built:
  /nix/store/7dvdf68piirjwrzhh31276ayf9l9s7z8-source.drv
  /nix/store/m5knyxgc12iyfcamng0mzfq7zal47mnw-arcwelder-1.2.0.drv
building '/nix/store/7dvdf68piirjwrzhh31276ayf9l9s7z8-source.drv'...

trying https://github.com/FormerLurker/ArcWelderLib/archive/1.2.0.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   132  100   132    0     0    640      0 --:--:-- --:--:-- --:--:--   640
100  155k    0  155k    0     0   334k      0 --:--:-- --:--:-- --:--:--  334k
unpacking source archive /build/1.2.0.tar.gz
hash mismatch in fixed-output derivation '/nix/store/a3j9g9wjaq8mpbb6grpvdqnp9hhahgrg-source':
  wanted: sha256:04c1zvzmp1ylp4pgayfdfk1kqjb23xj4f7ll1ra7b18wjxc9ja1v
  got:    sha256:0hs2xnki8294drks81md1mvq8kga9b5acidjmqwl4c87ma33jz8m
cannot build derivation '/nix/store/m5knyxgc12iyfcamng0mzfq7zal47mnw-arcwelder-1.2.0.drv': 1 dependencies couldn't be built
error: build of '/nix/store/m5knyxgc12iyfcamng0mzfq7zal47mnw-arcwelder-1.2.0.drv' failed

Il ne nous reste plus qu’à remplacer par la bonne somme.

Dernière étape, changer la phase d’install. Rien de compliquer ici, je copie juste à la main le fichier obtenu dans le bon répertoire. À noter qu’il s’agit ici de commandes bash et que la variable $out contient le répertoire destination.

  installPhase = ''
    mkdir -p $out/bin
    cp ArcWelderConsole/ArcWelder $out/bin
  '';

Je réinstalle alors le paquet à l’aide de nix-env. arcwelder s’installe alors dans /nix/store et un lien apparait dans le nix-profile.

╭─wagnerf@hopi ~
╰─➤  which ArcWelder
/home/wagnerf/.nix-profile/bin/ArcWelder

À ce stade c’est terminé mais on peut également ajouter le paquet dans le fichier configuration.nix plutôt que de vouloir une install manuelle.

On copie default.nix dans /etc/nixos/arcwelder.nix et on ajoute dans la liste des paquets de configuration.nix le résultat d’un appel à callPackage.

      environment.systemPackages = with pkgs; [
        (pkgs.callPackage arcwelder.nix {})

Voilà, je vous laisse avec le fichier final.