1# This expression takes a file like `hackage-packages.nix` and constructs
2# a full package set out of that.
3
4{ # package-set used for build tools (all of nixpkgs)
5 buildPackages
6
7, # A haskell package set for Setup.hs, compiler plugins, and similar
8 # build-time uses.
9 buildHaskellPackages
10
11, # package-set used for non-haskell dependencies (all of nixpkgs)
12 pkgs
13
14, # stdenv to use for building haskell packages
15 stdenv
16
17, haskellLib
18
19, # hashes for downloading Hackage packages
20 all-cabal-hashes
21
22, # compiler to use
23 ghc
24
25, # A function that takes `{ pkgs, stdenv, callPackage }` as the first arg and
26 # `self` as second, and returns a set of haskell packages
27 package-set
28
29, # The final, fully overriden package set usable with the nixpkgs fixpoint
30 # overriding functionality
31 extensible-self
32}:
33
34# return value: a function from self to the package set
35self:
36
37let
38 inherit (stdenv) buildPlatform hostPlatform;
39
40 inherit (stdenv.lib) fix' extends makeOverridable;
41 inherit (haskellLib) overrideCabal getBuildInputs;
42
43 mkDerivationImpl = pkgs.callPackage ./generic-builder.nix {
44 inherit stdenv;
45 nodejs = buildPackages.nodejs-slim;
46 inherit buildHaskellPackages;
47 inherit (self) ghc;
48 inherit (buildHaskellPackages) jailbreak-cabal;
49 hscolour = overrideCabal buildHaskellPackages.hscolour (drv: {
50 isLibrary = false;
51 doHaddock = false;
52 hyperlinkSource = false; # Avoid depending on hscolour for this build.
53 postFixup = "rm -rf $out/lib $out/share $out/nix-support";
54 });
55 cpphs = overrideCabal (self.cpphs.overrideScope (self: super: {
56 mkDerivation = drv: super.mkDerivation (drv // {
57 enableSharedExecutables = false;
58 enableSharedLibraries = false;
59 doHaddock = false;
60 useCpphs = false;
61 });
62 })) (drv: {
63 isLibrary = false;
64 postFixup = "rm -rf $out/lib $out/share $out/nix-support";
65 });
66 };
67
68 mkDerivation = makeOverridable mkDerivationImpl;
69
70 # manualArgs are the arguments that were explictly passed to `callPackage`, like:
71 #
72 # callPackage foo { bar = null; };
73 #
74 # here `bar` is a manual argument.
75 callPackageWithScope = scope: fn: manualArgs:
76 let
77 # this code is copied from callPackage in lib/customisation.nix
78 #
79 # we cannot use `callPackage` here because we want to call `makeOverridable`
80 # on `drvScope` (we cannot add `overrideScope` after calling `callPackage` because then it is
81 # lost on `.override`) but determine the auto-args based on `drv` (the problem here
82 # is that nix has no way to "passthrough" args while preserving the reflection
83 # info that callPackage uses to determine the arguments).
84 drv = if stdenv.lib.isFunction fn then fn else import fn;
85 auto = builtins.intersectAttrs (stdenv.lib.functionArgs drv) scope;
86
87 # this wraps the `drv` function to add a `overrideScope` function to the result.
88 drvScope = allArgs: drv allArgs // {
89 overrideScope = f:
90 let newScope = mkScope (fix' (extends f scope.__unfix__));
91 # note that we have to be careful here: `allArgs` includes the auto-arguments that
92 # weren't manually specified. If we would just pass `allArgs` to the recursive call here,
93 # then we wouldn't look up any packages in the scope in the next interation, because it
94 # appears as if all arguments were already manually passed, so the scope change would do
95 # nothing.
96 in callPackageWithScope newScope drv manualArgs;
97 };
98 in stdenv.lib.makeOverridable drvScope (auto // manualArgs);
99
100 mkScope = scope: pkgs // pkgs.xorg // pkgs.gnome2 // { inherit stdenv; } // scope;
101 defaultScope = mkScope self;
102 callPackage = drv: args: callPackageWithScope defaultScope drv args;
103
104 withPackages = packages: buildPackages.callPackage ./with-packages-wrapper.nix {
105 inherit (self) ghc llvmPackages;
106 inherit packages;
107 };
108
109 haskellSrc2nix = { name, src, sha256 ? null, extraCabal2nixOptions ? "" }:
110 let
111 sha256Arg = if isNull sha256 then "--sha256=" else ''--sha256="${sha256}"'';
112 in pkgs.buildPackages.stdenv.mkDerivation {
113 name = "cabal2nix-${name}";
114 nativeBuildInputs = [ pkgs.buildPackages.cabal2nix ];
115 preferLocalBuild = true;
116 phases = ["installPhase"];
117 LANG = "en_US.UTF-8";
118 LOCALE_ARCHIVE = pkgs.lib.optionalString buildPlatform.isLinux "${buildPackages.glibcLocales}/lib/locale/locale-archive";
119 installPhase = ''
120 export HOME="$TMP"
121 mkdir -p "$out"
122 cabal2nix --compiler=${self.ghc.haskellCompilerName} --system=${hostPlatform.system} ${sha256Arg} "${src}" ${extraCabal2nixOptions} > "$out/default.nix"
123 '';
124 };
125
126 all-cabal-hashes-component = name: version: pkgs.runCommand "all-cabal-hashes-component-${name}-${version}" {} ''
127 tar --wildcards -xzvf ${all-cabal-hashes} \*/${name}/${version}/${name}.{json,cabal}
128 mkdir -p $out
129 mv */${name}/${version}/${name}.{json,cabal} $out
130 '';
131
132 hackage2nix = name: version: let component = all-cabal-hashes-component name version; in self.haskellSrc2nix {
133 name = "${name}-${version}";
134 sha256 = ''$(sed -e 's/.*"SHA256":"//' -e 's/".*$//' "${component}/${name}.json")'';
135 src = "${component}/${name}.cabal";
136 };
137
138 # Adds a nix file as an input to the haskell derivation it
139 # produces. This is useful for callHackage / callCabal2nix to
140 # prevent the generated default.nix from being garbage collected
141 # (requiring it to be frequently rebuilt), which can be an
142 # annoyance.
143 callPackageKeepDeriver = src: args:
144 overrideCabal (self.callPackage src args) (orig: {
145 preConfigure = ''
146 # Generated from ${src}
147 ${orig.preConfigure or ""}
148 '';
149 });
150
151in package-set { inherit pkgs stdenv callPackage; } self // {
152
153 inherit mkDerivation callPackage haskellSrc2nix hackage2nix;
154
155 inherit (haskellLib) packageSourceOverrides;
156
157 callHackage = name: version: callPackageKeepDeriver (self.hackage2nix name version);
158
159 # Creates a Haskell package from a source package by calling cabal2nix on the source.
160 callCabal2nix = name: src: args: let
161 filter = path: type:
162 pkgs.lib.hasSuffix "${name}.cabal" path ||
163 baseNameOf path == "package.yaml";
164 expr = self.haskellSrc2nix {
165 inherit name;
166 src = if pkgs.lib.canCleanSource src
167 then pkgs.lib.cleanSourceWith { inherit src filter; }
168 else src;
169 };
170 in overrideCabal (callPackageKeepDeriver expr args) (orig: {
171 inherit src;
172 });
173
174 # : { root : Path
175 # , source-overrides : Defaulted (Either Path VersionNumber)
176 # , overrides : Defaulted (HaskellPackageOverrideSet)
177 # , modifier : Defaulted
178 # } -> NixShellAwareDerivation
179 # Given a path to a haskell package directory whose cabal file is
180 # named the same as the directory name, an optional set of
181 # source overrides as appropriate for the 'packageSourceOverrides'
182 # function, an optional set of arbitrary overrides, and an optional
183 # haskell package modifier, return a derivation appropriate
184 # for nix-build or nix-shell to build that package.
185 developPackage = { root, source-overrides ? {}, overrides ? self: super: {}, modifier ? drv: drv }:
186 let name = builtins.baseNameOf root;
187 drv =
188 (extensible-self.extend (pkgs.lib.composeExtensions (self.packageSourceOverrides source-overrides) overrides)).callCabal2nix name root {};
189 in if pkgs.lib.inNixShell then (modifier drv).env else modifier drv;
190
191 ghcWithPackages = selectFrom: withPackages (selectFrom self);
192
193 ghcWithHoogle = selectFrom:
194 let
195 packages = selectFrom self;
196 hoogle = callPackage ./hoogle.nix {
197 inherit packages;
198 };
199 in withPackages (packages ++ [ hoogle ]);
200
201 # Returns a derivation whose environment contains a GHC with only
202 # the dependencies of packages listed in `packages`, not the
203 # packages themselves. Using nix-shell on this derivation will
204 # give you an environment suitable for developing the listed
205 # packages with an incremental tool like cabal-install.
206 #
207 # # default.nix
208 # with import <nixpkgs> {};
209 # haskellPackages.extend (haskell.lib.packageSourceOverrides {
210 # frontend = ./frontend;
211 # backend = ./backend;
212 # common = ./common;
213 # })
214 #
215 # # shell.nix
216 # (import ./.).shellFor {
217 # packages = p: [p.frontend p.backend p.common];
218 # withHoogle = true;
219 # }
220 #
221 # -- cabal.project
222 # packages:
223 # frontend/
224 # backend/
225 # common/
226 #
227 # bash$ nix-shell --run "cabal new-build all"
228 shellFor = { packages, withHoogle ? false, ... } @ args:
229 let
230 selected = packages self;
231 packageInputs = builtins.map getBuildInputs selected;
232 haskellInputs =
233 builtins.filter
234 (input: pkgs.lib.all (p: input.outPath != p.outPath) selected)
235 (pkgs.lib.concatMap (p: p.haskellBuildInputs) packageInputs);
236 systemInputs = pkgs.lib.concatMap (p: p.systemBuildInputs) packageInputs;
237 withPackages = if withHoogle then self.ghcWithHoogle else self.ghcWithPackages;
238 mkDrvArgs = builtins.removeAttrs args ["packages" "withHoogle"];
239 in pkgs.stdenv.mkDerivation (mkDrvArgs // {
240 name = "ghc-shell-for-packages";
241 nativeBuildInputs = [(withPackages (_: haskellInputs))] ++ mkDrvArgs.nativeBuildInputs or [];
242 buildInputs = systemInputs ++ mkDrvArgs.buildInputs or [];
243 phases = ["installPhase"];
244 installPhase = "echo $nativeBuildInputs $buildInputs > $out";
245 });
246
247 ghc = ghc // {
248 withPackages = self.ghcWithPackages;
249 withHoogle = self.ghcWithHoogle;
250 };
251
252 }