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:
-utils.lib.eachSystem [ "x86_64-linux" ] (system:
flake
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.enableStaticLibraries
hl.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é…).