From 3adba2be9597f776c3bb0e7e10ce30a70452a889 Mon Sep 17 00:00:00 2001
From: Allan Blanchard <allan.blanchard@cea.fr>
Date: Thu, 17 Feb 2022 13:59:21 +0100
Subject: [PATCH] [CI] Build Frama-C

---
 .gitignore       |   4 +
 .gitlab-ci.yml   | 412 +++++++++++++++++++++++++----------------------
 nix/frama-c.nix  |  80 +++++++++
 nix/pkgs.nix     |  29 ++++
 nix/shell.nix    |  13 ++
 nix/sources.json |  45 ++++++
 nix/sources.nix  | 174 ++++++++++++++++++++
 nix/why3.nix     |  80 +++++++++
 8 files changed, 640 insertions(+), 197 deletions(-)
 create mode 100644 nix/frama-c.nix
 create mode 100644 nix/pkgs.nix
 create mode 100644 nix/shell.nix
 create mode 100644 nix/sources.json
 create mode 100644 nix/sources.nix
 create mode 100644 nix/why3.nix

diff --git a/.gitignore b/.gitignore
index 10dac80f7c2..2a86a3fbc97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -215,6 +215,10 @@ _build
 *.install
 /config.sed
 
+# Nix
+
+/result*
+
 #######################
 # should remain empty #
 #######################
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 07877a1ee98..f724695bef3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,11 +5,17 @@ stages:
  - distrib_and_compatibility
  - make_public
 
-variables:
-    CURRENT: $CI_COMMIT_REF_NAME
-    DEFAULT: "master"
-    FRAMA_CI_OPT: "--override frama-c:$CI_COMMIT_REF_NAME,$CI_COMMIT_SHA"
-    OCAML: "4_08"
+################################################################################
+### VARIABLES (todo)
+
+# variables:
+#     CURRENT: $CI_COMMIT_REF_NAME
+#     DEFAULT: "master"
+#     FRAMA_CI_OPT: "--override frama-c:$CI_COMMIT_REF_NAME,$CI_COMMIT_SHA"
+#     OCAML: "4_08"
+
+################################################################################
+### GIT UPDATE
 
 check-no-old-frama-c:
   stage: git-update
@@ -23,14 +29,17 @@ check-no-old-frama-c:
 git-update:
   stage: git-update
   script:
-   - nix/frama-ci.sh instantiate --eval -A frama-c.src.outPath
+   - nix-instantiate --eval nix/pkgs.nix -A ocamlPackages.frama-c.src.outPath
   tags:
    - nix
 
+################################################################################
+### BUILD
+
 frama-c:
   stage: build
   script:
-   - nix/frama-ci.sh build -A frama-c.main
+   - nix-build nix/pkgs.nix -A ocamlPackages.frama-c
   artifacts:
     when: on_failure
     paths:
@@ -40,196 +49,205 @@ frama-c:
   tags:
    - nix
 
-lint:
-  stage: build
-  script:
-   - nix/frama-ci.sh build -A frama-c.lint
-  coverage: '/lint coverage: \d+\.\d+/'
-  tags:
-   - nix
-
-tests:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.tests
-  tags:
-   - nix
-
-wp-qualif:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.wp-qualif
-  tags:
-   - nix
-  allow_failure: true
-
-aorai-prove:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.aorai-prove
-  tags:
-   - nix
-  allow_failure: true
-
-genassigns:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A genassigns.tests
-  tags:
-   - nix
-
-frama-clang:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-clang.tests
-  tags:
-   - nix
-
-counter-examples:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A counter-examples.tests
-  tags:
-   - nix
-
-acsl-importer:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A acsl-importer.tests
-  tags:
-   - nix
-
-volatile:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A volatile.tests
-  tags:
-   - nix
-
-metacsl:
-  stage: tests
-  script:
-  - nix/frama-ci.sh build -A meta.tests
-  tags:
-  - nix
-
-Security:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A security.tests
-  tags:
-   - nix
-
-CFP:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A context-from-precondition.tests
-  tags:
-   - nix
-
-eva-tests:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.eva-tests
-  tags:
-   - nix
-  allow_failure: true
-
-build-distrib-tarball:
-  stage: build
-  script:
-   - nix/frama-ci.sh build -A frama-c.build-distrib-tarball
-  tags:
-   - nix
-
-build-from-distrib-tarball:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.build-from-distrib-tarball
-  tags:
-   - nix
-
-doc:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.doc
-  tags:
-   - nix
-  allow_failure: true
-
-.build_template: &internal_template
-  stage: distrib_and_compatibility
-  tags:
-   - nix
-
-internal:
-  <<: *internal_template
-  script:
-   - nix/frama-ci.sh build -A frama-c.internal
-  when: manual
-
-
-internal_nightly:
-  <<: *internal_template
-  script:
-   - nix/frama-ci.sh build -A frama-c.internal
-  only:
-  - schedules
-
-
-.build_template: &frama-c-ocaml
-  stage: distrib_and_compatibility
-  script:
-   - nix/frama-ci.sh build -A frama-c.tests
-  tags:
-   - nix
-
-frama-c-ocaml-4.13:
-  variables:
-    OCAML: "4_13"
-  <<: *frama-c-ocaml
-
-caveat-importer:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A caveat-importer.tests
-  tags:
-   - nix
-
-mthread:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A mthread.tests
-  tags:
-   - nix
-
-pathcrawler:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A pathcrawler.tests
-  tags:
-   - nix
-
-e-acsl-tests-dev:
-  stage: tests
-  script:
-   - nix/frama-ci.sh build -A frama-c.e-acsl-tests-dev
-  tags:
-   - nix
-
-ivette:
-  stage: build
-  image: node:lts-gallium
-  cache:
-    paths:
-    - ivette/node_modules/
-  script:
-   - node --version
-   - npm --version
-   - yarn --version
-   - make -C ivette
-  tags:
-    - docker
+################################################################################
+### TESTS
+
+################################################################################
+### DISTRIB
+
+################################################################################
+### PUBLIC
+
+# lint:
+#   stage: build
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.lint
+#   coverage: '/lint coverage: \d+\.\d+/'
+#   tags:
+#    - nix
+
+# tests:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.tests
+#   tags:
+#    - nix
+
+# wp-qualif:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.wp-qualif
+#   tags:
+#    - nix
+#   allow_failure: true
+
+# aorai-prove:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.aorai-prove
+#   tags:
+#    - nix
+#   allow_failure: true
+
+# genassigns:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A genassigns.tests
+#   tags:
+#    - nix
+
+# frama-clang:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-clang.tests
+#   tags:
+#    - nix
+
+# counter-examples:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A counter-examples.tests
+#   tags:
+#    - nix
+
+# acsl-importer:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A acsl-importer.tests
+#   tags:
+#    - nix
+
+# volatile:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A volatile.tests
+#   tags:
+#    - nix
+
+# metacsl:
+#   stage: tests
+#   script:
+#   - nix/frama-ci.sh build -A meta.tests
+#   tags:
+#   - nix
+
+# Security:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A security.tests
+#   tags:
+#    - nix
+
+# CFP:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A context-from-precondition.tests
+#   tags:
+#    - nix
+
+# eva-tests:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.eva-tests
+#   tags:
+#    - nix
+#   allow_failure: true
+
+# build-distrib-tarball:
+#   stage: build
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.build-distrib-tarball
+#   tags:
+#    - nix
+
+# build-from-distrib-tarball:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.build-from-distrib-tarball
+#   tags:
+#    - nix
+
+# doc:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.doc
+#   tags:
+#    - nix
+#   allow_failure: true
+
+# .build_template: &internal_template
+#   stage: distrib_and_compatibility
+#   tags:
+#    - nix
+
+# internal:
+#   <<: *internal_template
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.internal
+#   when: manual
+
+
+# internal_nightly:
+#   <<: *internal_template
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.internal
+#   only:
+#   - schedules
+
+
+# .build_template: &frama-c-ocaml
+#   stage: distrib_and_compatibility
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.tests
+#   tags:
+#    - nix
+
+# frama-c-ocaml-4.13:
+#   variables:
+#     OCAML: "4_13"
+#   <<: *frama-c-ocaml
+
+# caveat-importer:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A caveat-importer.tests
+#   tags:
+#    - nix
+
+# mthread:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A mthread.tests
+#   tags:
+#    - nix
+
+# pathcrawler:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A pathcrawler.tests
+#   tags:
+#    - nix
+
+# e-acsl-tests-dev:
+#   stage: tests
+#   script:
+#    - nix/frama-ci.sh build -A frama-c.e-acsl-tests-dev
+#   tags:
+#    - nix
+
+# ivette:
+#   stage: build
+#   image: node:lts-gallium
+#   cache:
+#     paths:
+#     - ivette/node_modules/
+#   script:
+#    - node --version
+#    - npm --version
+#    - yarn --version
+#    - make -C ivette
+#   tags:
+#     - docker
 
 # make_public stage is used to push the current master branch of Frama-C and
 # associated plugins from the internal frama-c group to the public pub group.
diff --git a/nix/frama-c.nix b/nix/frama-c.nix
new file mode 100644
index 00000000000..69603e1714e
--- /dev/null
+++ b/nix/frama-c.nix
@@ -0,0 +1,80 @@
+# Nix
+{ lib
+, stdenv
+, fetchurl
+, gitignoreSource
+, makeWrapper
+, nix-gitignore
+, wrapGAppsHook
+# Generic
+, autoconf
+, findlib
+# Frama-C build
+, graphviz
+, ltl2ba
+, ocamlPackages
+, which
+, why3
+# Frama-C extra (other targets do not reconfigure)
+, dos2unix
+, doxygen
+, python3
+}:
+
+# We do not use buildDunePackage because Frama-C still uses a Makefile to build
+# some files and prepare some information before starting dune.
+stdenv.mkDerivation rec {
+  pname = "frama-c";
+  version = lib.strings.removeSuffix "\n" (builtins.readFile ../VERSION);
+  slang = lib.strings.removeSuffix "\n" (builtins.readFile ../VERSION_CODENAME);
+
+  src = gitignoreSource ./..;
+
+  nativeBuildInputs = [
+    autoconf
+    which
+    wrapGAppsHook
+  ];
+
+  buildInputs = with ocamlPackages; [
+    apron
+    camlzip
+    dune_2
+    dune-site
+    findlib
+    graphviz
+    lablgtk3
+    lablgtk3-sourceview3
+    ltl2ba
+    menhirLib
+    mlgmpidl
+    ocaml
+    ocamlgraph
+    ppx_deriving
+    ppx_deriving_yojson
+    yojson
+    which
+    why3
+    zarith
+    zmq
+    # For other CI targets
+    dos2unix
+    doxygen
+    python3
+  ];
+
+  preConfigure = ''
+    autoconf
+  '';
+
+  installFlags = [
+    "FRAMAC_INSTALLDIR=$(out)"
+  ];
+
+  meta = {
+    description = "An extensible and collaborative platform dedicated to source-code analysis of C software";
+    homepage = "http://frama-c.com/";
+    license = lib.licenses.lgpl21;
+    platforms = lib.platforms.unix;
+  };
+}
diff --git a/nix/pkgs.nix b/nix/pkgs.nix
new file mode 100644
index 00000000000..c67f7584ebf
--- /dev/null
+++ b/nix/pkgs.nix
@@ -0,0 +1,29 @@
+let
+  sources = import ./sources.nix {};
+  ocamlOverlay = oself: osuper: {
+    # External Packages
+    why3 = oself.callPackage ./why3.nix {};
+    # Builds
+    frama-c = oself.callPackage ./frama-c.nix {};
+    # Tests
+  };
+  overlay = self: super: {
+    niv = (import sources.niv {}).niv;
+    ocaml-ng = super.lib.mapAttrs (
+      name: value:
+        if builtins.hasAttr "overrideScope'" value
+        then value.overrideScope' ocamlOverlay
+        else value
+    ) super.ocaml-ng;
+    inherit (super.callPackage sources."gitignore.nix" {}) gitignoreSource;
+    why3 = throw "don't use pkgs.why3 but ocaml-ng.ocamlPackages_4_XX.why3";
+    framac = throw "don't use pkgs.framac but ocaml-ng.ocamlPackages_4_XX.frama-c";
+    frama-c = throw "don't use pkgs.framac but ocaml-ng.ocamlPackages_4_XX.frama-c";
+  };
+  pkgs = import sources.nixpkgs {
+    # alt-ergo
+    config.allowUnfree = true;
+    overlays = [ overlay ];
+  };
+in
+pkgs
diff --git a/nix/shell.nix b/nix/shell.nix
new file mode 100644
index 00000000000..1eda48797e5
--- /dev/null
+++ b/nix/shell.nix
@@ -0,0 +1,13 @@
+let
+  pkgs = import ./pkgs.nix;
+  ocamlPackages_for_shell = pkgs.ocaml-ng.ocamlPackages_4_12;
+in
+pkgs.mkShell {
+  nativeBuildInputs = with pkgs; [
+    niv
+    ocamlPackages_for_shell.merlin
+    ocamlPackages_for_shell.ocaml-lsp
+    ocamlPackages_for_shell.utop
+  ];
+  inputsFrom = [ ocamlPackages_for_shell.frama-c ];
+}
diff --git a/nix/sources.json b/nix/sources.json
new file mode 100644
index 00000000000..050f945a265
--- /dev/null
+++ b/nix/sources.json
@@ -0,0 +1,45 @@
+{
+    "gitignore.nix": {
+        "branch": "master",
+        "description": "Nix functions for filtering local git sources",
+        "homepage": "",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "5b9e0ff9d3b551234b4f3eb3983744fa354b17f1",
+        "sha256": "01l4phiqgw9xgaxr6jr456qmww6kzghqrnbc7aiiww3h6db5vw53",
+        "type": "tarball",
+        "url": "https://github.com/hercules-ci/gitignore.nix/archive/5b9e0ff9d3b551234b4f3eb3983744fa354b17f1.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "niv": {
+        "branch": "master",
+        "description": "Easy dependency management for Nix projects",
+        "homepage": "https://github.com/nmattia/niv",
+        "owner": "nmattia",
+        "repo": "niv",
+        "rev": "65a61b147f307d24bfd0a5cd56ce7d7b7cc61d2e",
+        "sha256": "17mirpsx5wyw262fpsd6n6m47jcgw8k2bwcp1iwdnrlzy4dhcgqh",
+        "type": "tarball",
+        "url": "https://github.com/nmattia/niv/archive/65a61b147f307d24bfd0a5cd56ce7d7b7cc61d2e.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "nixpkgs": {
+        "branch": "nixos-unstable",
+        "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
+        "homepage": "https://github.com/NixOS/nixpkgs",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "2deb07f3ac4eeb5de1c12c4ba2911a2eb1f6ed61",
+        "sha256": "0036sv1sc4ddf8mv8f8j9ifqzl3fhvsbri4z1kppn0f1zk6jv9yi",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/2deb07f3ac4eeb5de1c12c4ba2911a2eb1f6ed61.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "why3": {
+        "branch": "master",
+        "repo": "https://gitlab.inria.fr/why3/why3.git",
+        "rev": "a4a1446efe0435b01103adfcd2a6d5596bae1be0",
+        "type": "git",
+        "version": "1.4.0"
+    }
+}
diff --git a/nix/sources.nix b/nix/sources.nix
new file mode 100644
index 00000000000..1938409dddb
--- /dev/null
+++ b/nix/sources.nix
@@ -0,0 +1,174 @@
+# This file has been generated by Niv.
+
+let
+
+  #
+  # The fetchers. fetch_<type> fetches specs of type <type>.
+  #
+
+  fetch_file = pkgs: name: spec:
+    let
+      name' = sanitizeName name + "-src";
+    in
+      if spec.builtin or true then
+        builtins_fetchurl { inherit (spec) url sha256; name = name'; }
+      else
+        pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
+
+  fetch_tarball = pkgs: name: spec:
+    let
+      name' = sanitizeName name + "-src";
+    in
+      if spec.builtin or true then
+        builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
+      else
+        pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
+
+  fetch_git = name: spec:
+    let
+      ref =
+        if spec ? ref then spec.ref else
+          if spec ? branch then "refs/heads/${spec.branch}" else
+            if spec ? tag then "refs/tags/${spec.tag}" else
+              abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
+    in
+      builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
+
+  fetch_local = spec: spec.path;
+
+  fetch_builtin-tarball = name: throw
+    ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=tarball -a builtin=true'';
+
+  fetch_builtin-url = name: throw
+    ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=file -a builtin=true'';
+
+  #
+  # Various helpers
+  #
+
+  # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
+  sanitizeName = name:
+    (
+      concatMapStrings (s: if builtins.isList s then "-" else s)
+        (
+          builtins.split "[^[:alnum:]+._?=-]+"
+            ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
+        )
+    );
+
+  # The set of packages used when specs are fetched using non-builtins.
+  mkPkgs = sources: system:
+    let
+      sourcesNixpkgs =
+        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
+      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
+      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
+    in
+      if builtins.hasAttr "nixpkgs" sources
+      then sourcesNixpkgs
+      else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
+        import <nixpkgs> {}
+      else
+        abort
+          ''
+            Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
+            add a package called "nixpkgs" to your sources.json.
+          '';
+
+  # The actual fetching function.
+  fetch = pkgs: name: spec:
+
+    if ! builtins.hasAttr "type" spec then
+      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
+    else if spec.type == "file" then fetch_file pkgs name spec
+    else if spec.type == "tarball" then fetch_tarball pkgs name spec
+    else if spec.type == "git" then fetch_git name spec
+    else if spec.type == "local" then fetch_local spec
+    else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
+    else if spec.type == "builtin-url" then fetch_builtin-url name
+    else
+      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
+
+  # If the environment variable NIV_OVERRIDE_${name} is set, then use
+  # the path directly as opposed to the fetched source.
+  replace = name: drv:
+    let
+      saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
+      ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
+    in
+      if ersatz == "" then drv else
+        # this turns the string into an actual Nix path (for both absolute and
+        # relative paths)
+        if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
+
+  # Ports of functions for older nix versions
+
+  # a Nix version of mapAttrs if the built-in doesn't exist
+  mapAttrs = builtins.mapAttrs or (
+    f: set: with builtins;
+    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
+  );
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
+  range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
+  stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
+  stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
+  concatMapStrings = f: list: concatStrings (map f list);
+  concatStrings = builtins.concatStringsSep "";
+
+  # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
+  optionalAttrs = cond: as: if cond then as else {};
+
+  # fetchTarball version that is compatible between all the versions of Nix
+  builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchTarball;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
+      else
+        fetchTarball attrs;
+
+  # fetchurl version that is compatible between all the versions of Nix
+  builtins_fetchurl = { url, name ? null, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchurl;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
+      else
+        fetchurl attrs;
+
+  # Create the final "sources" from the config
+  mkSources = config:
+    mapAttrs (
+      name: spec:
+        if builtins.hasAttr "outPath" spec
+        then abort
+          "The values in sources.json should not have an 'outPath' attribute"
+        else
+          spec // { outPath = replace name (fetch config.pkgs name spec); }
+    ) config.sources;
+
+  # The "config" used by the fetchers
+  mkConfig =
+    { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
+    , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
+    , system ? builtins.currentSystem
+    , pkgs ? mkPkgs sources system
+    }: rec {
+      # The sources, i.e. the attribute set of spec name to spec
+      inherit sources;
+
+      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
+      inherit pkgs;
+    };
+
+in
+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
diff --git a/nix/why3.nix b/nix/why3.nix
new file mode 100644
index 00000000000..54905320d68
--- /dev/null
+++ b/nix/why3.nix
@@ -0,0 +1,80 @@
+{ callPackage
+, fetchurl
+, fetchpatch
+, lib
+, stdenv
+, ocaml
+, findlib
+, ocamlgraph
+, zarith
+, menhir
+, menhirLib
+, js_of_ocaml
+, js_of_ocaml-ppx
+, ppx_deriving
+, ppx_sexp_conv
+, camlzip
+, camlp5
+, sexplib
+, re
+, num
+, lablgtk3-sourceview3
+, coqPackages
+, rubber
+, hevea
+, emacs
+, autoreconfHook
+}:
+
+stdenv.mkDerivation rec {
+  pname = "why3";
+  version = src.version;
+
+  src = (import ./sources.nix {}).why3;
+  nativeBuildInputs = [
+    autoreconfHook
+  ];
+  buildInputs = [
+    ocaml
+    findlib
+    ocamlgraph
+    zarith
+    menhir
+    menhirLib
+    # Emacs compilation of why3.el
+    emacs
+    # Documentation
+    rubber
+    hevea
+    # GUI
+    lablgtk3-sourceview3
+    # WebIDE
+    js_of_ocaml
+    js_of_ocaml-ppx
+    # S-expression output for why3pp
+    ppx_deriving
+    ppx_sexp_conv
+    # Coq Support
+    coqPackages.coq
+    coqPackages.flocq
+    camlp5
+  ];
+
+  propagatedBuildInputs = [ camlzip num re sexplib ];
+
+  enableParallelBuilding = true;
+
+  configureFlags = [ "--enable-verbose-make" ];
+
+  installTargets = [ "install" "install-lib" ];
+
+  passthru.withProvers = callPackage ./with-provers.nix {};
+
+  meta = with lib; {
+    description = "A platform for deductive program verification";
+    homepage = "http://why3.lri.fr/";
+    license = licenses.lgpl21;
+    platforms = platforms.unix;
+    maintainers = with maintainers; [ thoughtpolice vbgl ];
+  };
+}
-- 
GitLab