Développer en Haskell avec Nix, en 2023
Voir aussi : video youtube - code source
Dans l’article Développer en Haskell avec ghcup et vscode, en 2022, on a vu que ghcup
permet désormais d’installer facilement un environnement pour développer en Haskell. Dans Comment utiliser Nix pour développer en Haskell, on avait vu que Nix
fournit également un environnement intéressant. Cet environnement a évolué et ce nouvel article en fait une présentation plus à jour.
Rappels sur Nix
Nix est un système de paquets logiciels basé sur une approche déclarative et sans effet de bord. Concrètement, il s’agit à la fois d’un langage de programmation permettant d’écrire des expressions Nix (définir des paquets) et d’un outil permettant d’évaluer ces expressions (construire des paquets…). Basés sur ces deux outils, il existe aussi une logithèque (nixpkgs
), un système d’exploitation (nixos
)…
Nix apporte de nombreux avantages pour développer des logiciels, dans les principaux langages de programmation dont Haskell. Actuellement, il existe deux façons de configurer un projet Haskell avec Nix : Haskell4nix et Nix Flakes.
Haskell4nix
Haskell4nix est le support de Haskell dans l’infrastructure de nixpkgs
. Concrètement, il fournit des compilateurs Haskell (ghc, ghcjs…) , des outils de développement (cabal, ghcid, hls…), des bibliothèques et des outils de packaging spécifiques.
En pratique, il s’agit d’utiliser la logithèque Nix classique. Par exemple, pour connaitre les compilateurs supportés, ou les bibliothèques Haskell disponibles :
$ nix-env -f "<nixpkgs>" -qaP -A haskell.compiler
haskell.compiler.ghc8107 ghc-8.10.7
haskell.compiler.ghc884 ghc-8.8.4
haskell.compiler.ghc902 ghc-9.0.2
...
$ nix-env -f "<nixpkgs>" -qaP -A haskell.packages.ghc925
haskell.packages.ghc925.a50 a50-0.5
haskell.packages.ghc925.AAI AAI-0.2.0.1
haskell.packages.ghc925.aasam aasam-0.2.0.0
...
On peut également lancer un environnement Nix spécifique, par exemple via un fichier shell.nix. Mais généralement, on configure un projet Haskell avec l’outil Cabal, qui est compatible avec Haskell4nix. Il suffit donc d’écrire un fichier .cabal
, classique en Haskell, puis d’écrire le fichier default.nix
suivant pour avoir automatiquement une configuration Nix du projet :
? import <nixpkgs> {} }:
{ pkgs
let
# ghc = pkgs.haskellPackages; # version par défaut du compilateur
= pkgs.haskell.packages.ghc925; # version 9.2.5
ghc
in ghc.developPackage {
= ./.;
root = drv:
modifier .haskell.lib.addBuildTools drv (with ghc; [
pkgs-install
cabal
]); }
Par exemple pour travailler sur le projet, on peut lancer un nix-shell
(pour lancer un environnement avec toutes les dépendances nécessaires, déterminées d’après le fichier .cabal
) puis utiliser les commandes Cabal habituelles :
$ nix-shell
these 2 derivations will be built:
/nix/store/0dlwbm8gll94aw22zb01v85h409bik82-hoogle-with-packages.drv
/nix/store/9yb2mfx3a17hsv81xjnpdl8hl0cghz9j-ghc-9.0.2-with-packages.drv
...
[nix-shell]$ cabal build
Resolving dependencies...
Build profile: -w ghc-9.0.2 -O1
In order, the following will be built (use -v for more details):
- myproject-0.1.0.0 (lib) (first run)
- myproject-0.1.0.0 (exe:myproject) (first run)
...
[nix-shell]$ cabal run
<h1>Hello World!</h1>
On peut également contruire complètement le projet via Nix :
$ nix-build
this derivation will be built:
/nix/store/rimxqr1avc7ycyzgqcd5lqdw0z0wgcqr-myproject-0.1.0.0.drv
...
$ ./result/bin/myproject
<h1>Hello World!</h1>
Installer le projet sur la machine, via le système de paquet :
$ nix-env -if .
installing 'myproject-0.1.0.0'
this derivation will be built:
/nix/store/8xf8mc0m1cvnfb3c0ybpy38rrxsa0gq2-myproject-0.1.0.0.drv
...
$ myproject
<h1>Hello World!</h1>
Ou encore, intégrer le projet dans la logithèque, par exemple via un overlay Nix qui récupère le code du projet depuis un dépôt git :
# ~/.config/nixpkgs/overlays/myproject.nix
self: super:
let
myproject-src = self.fetchFromGitLab {
owner = "nokomprendo";
repo = "nokomprendo.gitlab.io";
rev = "0bb6f2e";
sha256 = "sha256-4F4GXnMVWrE5BzJcewopr6xGmdPySeawwbvN66Cu9b0=";
};
in {
myproject = self.callPackage "${myproject-src}/posts/tuto_093/code/myproject-h4n/default.nix" {};
}
Enfin, on peut également fixer plus précisemment la version de la logithèque à utiliser, modifier des paquets (changer de version, patcher), etc.
Nix Flakes
Les Nix Flakes sont des fonctionnalités, encore expérimentales, disponibles via Nix. Celles-ci permettent de définir des unités de constructions d’un package, d’un déploiement, voire d’un système complet.
Les Flakes visent à rendre le packaging plus reproductible (meilleure gestion des fichiers de configuration, gel de versions…), composable et performant (meilleure mise en cache).
Ici, on s’intéresse uniquement à comment utiliser les Flakes pour configurer simplement un projet en Haskell. Pour cela, on peut écrire le fichier flake.nix
suivant :
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
#flake-utils.lib.eachDefaultSystem (system:
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
with nixpkgs.legacyPackages.${system};
let
t = lib.trivial;
hl = haskell.lib;
name = "myproject";
# ghc = haskellPackages; # version par défaut
ghc = pkgs.haskell.packages.ghc925; # version 9.2.5
project = devTools:
let addBuildTools = (t.flip hl.addBuildTools) devTools;
in ghc.developPackage {
root = lib.sourceFilesBySuffices ./. [ ".cabal" ".hs" ];
name = name;
returnShellEnv = !(devTools == [ ]);
modifier = (t.flip t.pipe) [
addBuildTools
hl.dontHaddock
hl.enableStaticLibrarieshl.justStaticExecutables
hl.disableLibraryProfiling
hl.disableExecutableProfiling
];
};
in {
packages.pkg = project [];
defaultPackage = self.packages.${system}.pkg;
devShell = project (with ghc; [
cabal-install]);
});
}
Grossièrement, ce fichier définit les entrées (ici : nixpkgs
, pour récupérer les outils et bibliothèques Haskell nécessaires, et flake-utils
, pour configurer le flake), et les sorties du flake (ici, un package et un environnement de développement correspondant).
On peut interroger la configuration du flake, par exemple pour connaitre ses sorties :
$ nix flake show --allow-import-from-derivation
path:/home/test/code/code/myproject-flake...
├───defaultPackage
│ └───x86_64-linux: package 'myproject-0.1.0.0'
├───devShell
│ └───x86_64-linux: development environment 'ghc-shell-for-myproject-0.1.0.0'
└───packages
└───x86_64-linux
└───pkg: package 'myproject-0.1.0.0'
On peut alors lancer un environnement de développement :
$ nix develop
$ cabal build
Resolving dependencies...
Build profile: -w ghc-9.2.4 -O1
In order, the following will be built (use -v for more details):
- myproject-0.1.0.0 (lib) (first run)
- myproject-0.1.0.0 (exe:myproject) (first run)
...
$ cabal run
<h1>Hello World!</h1>
Ou lancer le package (si besoin après avoir été recompilé) :
$ nix run
<h1>Hello World!</h1>
Enfin, on peut également configurer le système avec des flakes et y inclure notre projet.
Voir aussi :
- Tweag, Nix Flakes Series
- Serokell, Practical Nix Flakes
- NixOS wiki, Flakes
- Getting started with Nix Flakes and devshell
- Simple nix flake for Haskell development
Conclusion
Pour développer en Haskell, il est généralement plus simple d’utiliser Ghcup. Si le projet est complexe (plusieurs langages de programmation, besoin de builds reproductibles, déploiements complexes…), alors il peut être intéressant d’utiliser Nix : soit l’environnement Haskell4nix (qui est plutôt complet et mature), soit l’environnement Flakes (qui apporte quelques améliorations concernant la reproductibilité, composabilité…).