diff --git a/nix/default-config-tests.nix b/nix/default-config-tests.nix
index 04f0b1bdb3dd8fc6492d23338b6dc90b1d706ba3..9fcdfbf151e870b5e40d28b9b3b729c2f627e307 100644
--- a/nix/default-config-tests.nix
+++ b/nix/default-config-tests.nix
@@ -1,7 +1,10 @@
-{ mk_tests } :
+{ mk_tests, frama-c-nocover } :
 
-mk_tests {
-  tests-name = "default-config-tests";
+let mk_tests_distrib = mk_tests.override {
+  frama-c = frama-c-nocover ;
+}; in
+mk_tests_distrib {
+  tests-name = "src-distrib-tests";
   tests-command = ''
     dune exec -- frama-c-ptests -never-disabled tests src/plugins/*/tests
     dune build -j1 --display short @ptests_config
diff --git a/nix/external-plugin-ci.sh b/nix/external-plugin-ci.sh
index e63d692dd76cb206d1174379c38c2645e7927815..fc1bc18392745742b4e9b3ff94e8955d9de7aa75 100755
--- a/nix/external-plugin-ci.sh
+++ b/nix/external-plugin-ci.sh
@@ -45,6 +45,11 @@ fi
 # Normalize version for Nix
 OCAML=${OCAML/./_}
 
+OUTOPT="--no-out-link"
+if [ ! -z ${OUT+x} ]; then
+  OUTOPT="-o $(pwd)/$OUT"
+fi
+
 DEFAULT=${DEFAULT:-master}
 
 # prints
@@ -118,7 +123,7 @@ if [[ -f "./nix/dependencies" ]]; then
 fi
 
 # run the build
-nix-build --no-out-link "./nix/pkgs.nix" $OPTS -A ocaml-ng.ocamlPackages_$OCAML."$plugin"
+nix-build $OUTOPT "./nix/pkgs.nix" $OPTS -A ocaml-ng.ocamlPackages_$OCAML."$plugin"
 
 cd "$fc_dir"
 
diff --git a/nix/frama-c.nix b/nix/frama-c.nix
index 4be82fb950a17b6a28b4a09c0c8834462f96c25d..5dcbb81b27da66e5e7a9cffa82e4f123e409acfd 100644
--- a/nix/frama-c.nix
+++ b/nix/frama-c.nix
@@ -11,6 +11,7 @@
 , findlib
 # Frama-C build
 , apron
+, bisect_ppx
 , camlzip
 , camomile
 , dune_3
@@ -46,6 +47,7 @@
 , python3
 , python3Packages
 , yq
+, cover ? true
 , release_mode ? false
 }:
 
@@ -71,6 +73,7 @@ stdenvNoCC.mkDerivation rec {
 
   buildInputs = [
     apron
+    bisect_ppx
     camlzip
     camomile
     dune_3
@@ -105,8 +108,6 @@ stdenvNoCC.mkDerivation rec {
     dos2unix
     doxygen
     python3
-    python3Packages.pyaml
-    yq
   ];
 
   outputs = [ "out" "build_dir" ];
@@ -121,11 +122,15 @@ stdenvNoCC.mkDerivation rec {
   enableParallelBuilding = false;
   dune_opt = if release_mode then "--release" else "" ;
 
-  buildPhase = ''
-    dune build -j2 --display short --error-reporting=twice $release_mode @install
-    make tools/ptests/ptests.exe
-    make tools/ptests/wtests.exe
-  '';
+  buildPhase = (if cover then ''
+      export DUNE_WORKSPACE="dev/dune-workspace.cover"
+    '' else "") +
+    ''
+      dune build -j2 --display short --error-reporting=twice $dune_opt @install
+
+      make tools/ptests/ptests.exe
+      make tools/ptests/wtests.exe
+    '';
 
   installFlags = [
     "PREFIX=$(out)"
diff --git a/nix/mk_tests.nix b/nix/mk_tests.nix
index d56dc7155dc3a698acb0d594c8197a887c152761..458ef9320aca2cd4eed0c6ba985817966bd0aaba 100644
--- a/nix/mk_tests.nix
+++ b/nix/mk_tests.nix
@@ -33,6 +33,7 @@
 { tests-name
 , tests-command
 , has-wp-proofs ? false
+, cover ? true
 } :
 
 stdenvNoCC.mkDerivation {
@@ -68,14 +69,28 @@ stdenvNoCC.mkDerivation {
     else "" ;
 
   preBuild =
-    if has-wp-proofs
+    (if has-wp-proofs
+     then ''
+         mkdir home
+         HOME=$(pwd)/home
+         why3 config detect
+         export FRAMAC_WP_CACHE=offline
+         export FRAMAC_WP_CACHEDIR=$wp_cache
+     ''
+     else "") +
+    (if cover
+     then ''
+         mkdir coverage
+         export DUNE_WORKSPACE="dev/dune-workspace.cover"
+         export BISECT_FILE="$(pwd)/coverage/bisect-"
+     ''
+     else "");
+
+  postBuild =
+    if cover
     then ''
-        mkdir home
-        HOME=$(pwd)/home
-        why3 config detect
-        export FRAMAC_WP_CACHE=offline
-        export FRAMAC_WP_CACHEDIR=$wp_cache
-      ''
+      bisect-ppx-report cobertura --coverage-path=coverage coverage.xml
+    ''
     else "" ;
 
   buildPhase = ''
@@ -87,6 +102,7 @@ stdenvNoCC.mkDerivation {
 
   # No installation required
   installPhase = ''
-    touch $out
+    mkdir $out
+    cp -r coverage.xml $out
   '';
 }
diff --git a/nix/pkgs.nix b/nix/pkgs.nix
index 8f08f15e2ed2c3ad9c9f176c69dd830e48dae2a3..a386bb99cb86898847fde54961f9f61a1b349ec1 100644
--- a/nix/pkgs.nix
+++ b/nix/pkgs.nix
@@ -23,11 +23,16 @@ let
 
     # Builds
     frama-c = oself.callPackage ./frama-c.nix {};
+    frama-c-no-cover = oself.callPackage ./frama-c.nix { cover = false ; };
     frama-c-hdrck = oself.callPackage ./frama-c-hdrck.nix {};
     frama-c-lint = oself.callPackage ./frama-c-lint.nix {};
 
     # Tests
-    default-config-tests = oself.callPackage ./default-config-tests.nix {};
+    default-config-tests = oself.callPackage ./default-config-tests.nix {
+      frama-c-nocover = oself.frama-c.override {
+        cover = false ;
+      } ;
+    };
     e-acsl-tests = oself.callPackage ./e-acsl-tests.nix { config = ""; };
     e-acsl-dev-tests = oself.callPackage ./e-acsl-tests.nix { config = "dev"; };
     eva-default-tests = oself.callPackage ./eva-tests.nix { config = ""; };
@@ -52,7 +57,10 @@ let
     api-doc = oself.callPackage ./api-doc.nix {};
     manuals = oself.callPackage ./manuals.nix {};
     src-distrib-tests = oself.callPackage ./src-distrib-tests.nix {
-      frama-c-release = oself.frama-c.override { release_mode = true ; } ;
+      frama-c-release = oself.frama-c.override {
+        release_mode = true ;
+        cover = false ;
+      } ;
     };
   };
   overlay = self: super: {
diff --git a/nix/ts-api.nix b/nix/ts-api.nix
index cda9f980c8d6ef7811f8976bf361654e0af80dc8..064a4207d0955408b9ff899bbf767dafd8af8d38 100644
--- a/nix/ts-api.nix
+++ b/nix/ts-api.nix
@@ -28,11 +28,15 @@ stdenv.mkDerivation rec {
   '';
 
   buildPhase = ''
+    mkdir coverage
+    export BISECT_FILE="$(pwd)/coverage/bisect-"
     make -C ivette check-api
+    bisect-ppx-report cobertura --coverage-path=coverage coverage.xml
   '';
 
   # No installation required
   installPhase = ''
-    touch $out
+    mkdir $out
+    cp -r coverage.xml $out
   '';
 }