@@ -0,0 +1,131 @@
2+, pkgs ? import <nixpkgs> {}
3+, hackage ? import <hackage>
4+, haskell ? import <haskell> }:
6+{ extraDeps ? hsPkgs: {} }:
9+ # packages that we must never try to reinstall.
10+ nonReinstallablePkgs = [ "rts" "ghc" "ghc-prim" "integer-gmp" "integer-simple" "base"
11+ "array" "deepseq" "pretty" "ghc-boot-th" "template-haskell"];
13+ hackagePkgs = with pkgs.lib;
14+ let shippedPkgs = filterAttrs (n: _: builtins.elem n nonReinstallablePkgs)
15+ (mapAttrs (name: version: { ${version} = null; })
16+ (pkg-def {}).compiler.packages);
17+ in recursiveUpdate hackage.exprs shippedPkgs;
19+ # We may depend on packages shipped with ghc, or need to rebuild them.
20+ ghcPackages = pkgs.lib.mapAttrs (name: version: hackagePkgs.${name}.${version}
21+ ) (pkg-def {}).compiler.packages;
23+ # Thus the final package set in our augmented (extrDeps) lts set is the following:
24+ ltsPkgs = ghcPackages
25+ // (pkg-def hackagePkgs).packages
26+ // extraDeps hackagePkgs;
28+ driver = haskell.compat.driver;
29+ host-map = haskell.compat.host-map;
31+ # compiler this lts set is built against.
32+ compiler = pkgs.haskell.packages.${(pkg-def {}).compiler.nix-name};
34+ # This is a tiny bit better than doJailbreak.
36+ # We essentially *know* the dependencies, and with the
37+ # full cabal file representation, we also know all the
38+ # flags. As such we can sidestep the solver.
41+ # - no need for doJailbreak
42+ # - no need for jailbreak-cabal to be built with
43+ # Cabal2 if the cabal file requires it.
44+ # - no reliance on --allow-newer, which only made
45+ # a very short lived appearance in Cabal.
46+ # (Cabal-2.0.0.2 -- Cabal-2.2.0.0)
49+ # - automatic flag resolution won't happen and will
50+ # have to be hard coded.
52+ # Ideally we'd just inspect the haskell*Depends fields
53+ # we feed the builder. However because we null out the
54+ # lirbaries ghc ships (e.g. base, ghc, ...) this would
55+ # result in an incomplete --dependency=<name>=<name>-<version>
56+ # set and not lead to the desired outcome.
58+ # If we could still have base, etc. not nulled, but
59+ # produce some virtual derivation, that might allow us
60+ # to just use the haskell*Depends fields to extract the
61+ # name and version for each dependency.
63+ # Ref: https://github.com/haskell/cabal/issues/3163#issuecomment-185833150
65+ # ghc-pkg should be ${ghcCommand}-pkg; and --package-db
66+ # should better be --${packageDbFlag}; but we don't have
67+ # those variables in scope.
68+ doExactConfig = pkgs: pkg: let targetPrefix = with pkgs.stdenv; lib.optionalString
69+ (hostPlatform != buildPlatform)
70+ "${hostPlatform.config}-";
72+ in pkgs.haskell.lib.overrideCabal pkg (drv: {
73+ # TODO: need to run `ghc-pkg field <pkg> id` over all `--dependency`
74+ # values. Should we encode the `id` in the nix-pkg as well?
75+ preConfigure = (drv.preConfigure or "") + ''
76+ configureFlags+=" --exact-configuration"
77+ globalPackages=$(${targetPrefix}ghc-pkg list --global --simple-output)
78+ localPackages=$(${targetPrefix}ghc-pkg --package-db="$packageConfDir" list --simple-output)
79+ for pkg in $globalPackages; do
81+ if [ "$pkgName" != "rts" ]; then
82+ if [[ " ${pkgs.lib.concatStringsSep " " nonReinstallablePkgs} " =~ " $pkgName " ]]; then
83+ configureFlags+=" --dependency="''${pkg%-*}=$pkg
87+ for pkg in $localPackages; do
88+ configureFlags+=" --dependency="''${pkg%-*}=$pkg
91+ #echo ''${configureFlags}
92+ configureFlags=$(for flag in ''${configureFlags};do case "X''${flag}" in
94+ pkgId=$(${targetPrefix}ghc-pkg --package-db="$packageConfDir" field ''${flag##*=} id || ${targetPrefix}ghc-pkg --global field ''${flag##*=} id)
95+ echo ''${flag%=*}=$(echo $pkgId | awk -F' ' '{ print $2 }')
100+ #echo ''${configureFlags}
105+ toGenericPackage = stackPkgs: args: name: path:
106+ if path == null then null else
107+ let expr = driver { cabalexpr = import path;
108+ pkgs = pkgs // { haskellPackages = stackPkgs; };
109+ inherit (host-map pkgs.stdenv) os arch;
110+ version = compiler.ghc.version; };
111+ # Use `callPackage` from the `compiler` here, to get the
113+ in compiler.callPackage expr args;
115+in let stackPackages = pkgs: self:
116+ (let p = (pkgs.lib.mapAttrs (toGenericPackage self {}) ltsPkgs);
117+ # for all packages do the `exactConfig` logic. That is, we
118+ # *know* that that our package-db contains only a single valid
119+ # set of proper packages. So we can sidestep cabals solver.
120+ in (pkgs.lib.mapAttrs (_: v: if v == null
122+ else doExactConfig pkgs v) p)
123+ // (with pkgs.haskell.lib;
126+ buildPackages = pkgs.buildPackages.haskellPackages; }));
127+ in compiler.override {
128+ initialPackages = { pkgs, stdenv, callPackage }: self: (stackPackages pkgs self);
129+ configurationCommon = { ... }: self: super: {};
130+ compilerConfig = self: super: {};