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 (self) buildHaskellPackages ghc shellFor;
47 inherit (self.buildHaskellPackages) jailbreak-cabal;
48 hscolour = overrideCabal self.buildHaskellPackages.hscolour (drv: {
49 isLibrary = false;
50 doHaddock = false;
51 hyperlinkSource = false; # Avoid depending on hscolour for this build.
52 postFixup = "rm -rf $out/lib $out/share $out/nix-support";
53 });
54 cpphs = overrideCabal (self.cpphs.overrideScope (self: super: {
55 mkDerivation = drv: super.mkDerivation (drv // {
56 enableSharedExecutables = false;
57 enableSharedLibraries = false;
58 doHaddock = false;
59 useCpphs = false;
60 });
61 })) (drv: {
62 isLibrary = false;
63 postFixup = "rm -rf $out/lib $out/share $out/nix-support";
64 });
65 };
66
67 mkDerivation = makeOverridable mkDerivationImpl;
68
69 # manualArgs are the arguments that were explictly passed to `callPackage`, like:
70 #
71 # callPackage foo { bar = null; };
72 #
73 # here `bar` is a manual argument.
74 callPackageWithScope = scope: fn: manualArgs:
75 let
76 # this code is copied from callPackage in lib/customisation.nix
77 #
78 # we cannot use `callPackage` here because we want to call `makeOverridable`
79 # on `drvScope` (we cannot add `overrideScope` after calling `callPackage` because then it is
80 # lost on `.override`) but determine the auto-args based on `drv` (the problem here
81 # is that nix has no way to "passthrough" args while preserving the reflection
82 # info that callPackage uses to determine the arguments).
83 drv = if stdenv.lib.isFunction fn then fn else import fn;
84 auto = builtins.intersectAttrs (stdenv.lib.functionArgs drv) scope;
85
86 # this wraps the `drv` function to add a `overrideScope` function to the result.
87 drvScope = allArgs: drv allArgs // {
88 overrideScope = f:
89 let newScope = mkScope (fix' (extends f scope.__unfix__));
90 # note that we have to be careful here: `allArgs` includes the auto-arguments that
91 # weren't manually specified. If we would just pass `allArgs` to the recursive call here,
92 # then we wouldn't look up any packages in the scope in the next interation, because it
93 # appears as if all arguments were already manually passed, so the scope change would do
94 # nothing.
95 in callPackageWithScope newScope drv manualArgs;
96 };
97 in stdenv.lib.makeOverridable drvScope (auto // manualArgs);
98
99 mkScope = scope: let
100 ps = pkgs.__splicedPackages;
101 scopeSpliced = pkgs.splicePackages {
102 pkgsBuildBuild = scope.buildHaskellPackages.buildHaskellPackages;
103 pkgsBuildHost = scope.buildHaskellPackages;
104 pkgsBuildTarget = {};
105 pkgsHostHost = {};
106 pkgsHostTarget = scope;
107 pkgsTargetTarget = {};
108 } // {
109 # Don't splice these
110 inherit (scope) ghc buildHaskellPackages;
111 };
112 in ps // ps.xorg // ps.gnome2 // { inherit stdenv; } // scopeSpliced;
113 defaultScope = mkScope self;
114 callPackage = drv: args: callPackageWithScope defaultScope drv args;
115
116 withPackages = packages: buildPackages.callPackage ./with-packages-wrapper.nix {
117 inherit (self) ghc llvmPackages;
118 inherit packages;
119 };
120
121 haskellSrc2nix = { name, src, sha256 ? null, extraCabal2nixOptions ? "" }:
122 let
123 sha256Arg = if isNull sha256 then "--sha256=" else ''--sha256="${sha256}"'';
124 in pkgs.buildPackages.stdenv.mkDerivation {
125 name = "cabal2nix-${name}";
126 nativeBuildInputs = [ pkgs.buildPackages.cabal2nix ];
127 preferLocalBuild = true;
128 allowSubstitutes = false;
129 phases = ["installPhase"];
130 LANG = "en_US.UTF-8";
131 LOCALE_ARCHIVE = pkgs.lib.optionalString (buildPlatform.libc == "glibc") "${buildPackages.glibcLocales}/lib/locale/locale-archive";
132 installPhase = ''
133 export HOME="$TMP"
134 mkdir -p "$out"
135 cabal2nix --compiler=${self.ghc.haskellCompilerName} --system=${hostPlatform.config} ${sha256Arg} "${src}" ${extraCabal2nixOptions} > "$out/default.nix"
136 '';
137 };
138
139 all-cabal-hashes-component = name: version: pkgs.runCommand "all-cabal-hashes-component-${name}-${version}" {} ''
140 tar --wildcards -xzvf ${all-cabal-hashes} \*/${name}/${version}/${name}.{json,cabal}
141 mkdir -p $out
142 mv */${name}/${version}/${name}.{json,cabal} $out
143 '';
144
145 hackage2nix = name: version: let component = all-cabal-hashes-component name version; in self.haskellSrc2nix {
146 name = "${name}-${version}";
147 sha256 = ''$(sed -e 's/.*"SHA256":"//' -e 's/".*$//' "${component}/${name}.json")'';
148 src = "${component}/${name}.cabal";
149 };
150
151 # Adds a nix file as an input to the haskell derivation it
152 # produces. This is useful for callHackage / callCabal2nix to
153 # prevent the generated default.nix from being garbage collected
154 # (requiring it to be frequently rebuilt), which can be an
155 # annoyance.
156 callPackageKeepDeriver = src: args:
157 overrideCabal (self.callPackage src args) (orig: {
158 preConfigure = ''
159 # Generated from ${src}
160 ${orig.preConfigure or ""}
161 '';
162 passthru = orig.passthru or {} // {
163 # When using callCabal2nix or callHackage, it is often useful
164 # to debug a failure by inspecting the Nix expression
165 # generated by cabal2nix. This can be accessed via this
166 # cabal2nixDeriver field.
167 cabal2nixDeriver = src;
168 };
169 });
170
171in package-set { inherit pkgs stdenv callPackage; } self // {
172
173 inherit mkDerivation callPackage haskellSrc2nix hackage2nix buildHaskellPackages;
174
175 inherit (haskellLib) packageSourceOverrides;
176
177 callHackage = name: version: callPackageKeepDeriver (self.hackage2nix name version);
178
179 # This function does not depend on all-cabal-hashes and therefore will work
180 # for any version that has been released on hackage as opposed to only
181 # versions released before whatever version of all-cabal-hashes you happen
182 # to be currently using.
183 callHackageDirect = {pkg, ver, sha256}@args:
184 let pkgver = "${pkg}-${ver}";
185 in self.callCabal2nix pkg (pkgs.fetchzip {
186 url = "http://hackage.haskell.org/package/${pkgver}/${pkgver}.tar.gz";
187 inherit sha256;
188 });
189
190 # Creates a Haskell package from a source package by calling cabal2nix on the source.
191 callCabal2nixWithOptions = name: src: extraCabal2nixOptions: args:
192 let
193 filter = path: type:
194 pkgs.lib.hasSuffix "${name}.cabal" path ||
195 baseNameOf path == "package.yaml";
196 expr = self.haskellSrc2nix {
197 inherit name extraCabal2nixOptions;
198 src = if pkgs.lib.canCleanSource src
199 then pkgs.lib.cleanSourceWith { inherit src filter; }
200 else src;
201 };
202 in overrideCabal (callPackageKeepDeriver expr args) (orig: {
203 inherit src;
204 });
205
206 callCabal2nix = name: src: args: self.callCabal2nixWithOptions name src "" args;
207
208 # : { root : Path
209 # , name : Defaulted String
210 # , source-overrides : Defaulted (Either Path VersionNumber)
211 # , overrides : Defaulted (HaskellPackageOverrideSet)
212 # , modifier : Defaulted
213 # , returnShellEnv : Defaulted
214 # } -> NixShellAwareDerivation
215 # Given a path to a haskell package directory, an optional package name
216 # which defaults to the base name of the path, an optional set of source
217 # overrides as appropriate for the 'packageSourceOverrides' function, an
218 # optional set of arbitrary overrides, and an optional haskell package
219 # modifier, return a derivation appropriate for nix-build or nix-shell to
220 # build that package.
221 developPackage =
222 { root
223 , name ? builtins.baseNameOf root
224 , source-overrides ? {}
225 , overrides ? self: super: {}
226 , modifier ? drv: drv
227 , returnShellEnv ? pkgs.lib.inNixShell }:
228 let drv =
229 (extensible-self.extend
230 (pkgs.lib.composeExtensions
231 (self.packageSourceOverrides source-overrides)
232 overrides))
233 .callCabal2nix name root {};
234 in if returnShellEnv then (modifier drv).env else modifier drv;
235
236 ghcWithPackages = selectFrom: withPackages (selectFrom self);
237
238 ghcWithHoogle = selectFrom:
239 let
240 packages = selectFrom self;
241 hoogle = callPackage ./hoogle.nix {
242 inherit packages;
243 };
244 in withPackages (packages ++ [ hoogle ]);
245
246 # Returns a derivation whose environment contains a GHC with only
247 # the dependencies of packages listed in `packages`, not the
248 # packages themselves. Using nix-shell on this derivation will
249 # give you an environment suitable for developing the listed
250 # packages with an incremental tool like cabal-install.
251 #
252 # # default.nix
253 # with import <nixpkgs> {};
254 # haskellPackages.extend (haskell.lib.packageSourceOverrides {
255 # frontend = ./frontend;
256 # backend = ./backend;
257 # common = ./common;
258 # })
259 #
260 # # shell.nix
261 # (import ./.).shellFor {
262 # packages = p: [p.frontend p.backend p.common];
263 # withHoogle = true;
264 # }
265 #
266 # -- cabal.project
267 # packages:
268 # frontend/
269 # backend/
270 # common/
271 #
272 # bash$ nix-shell --run "cabal new-build all"
273 shellFor = { packages, withHoogle ? false, ... } @ args:
274 let
275 selected = packages self;
276
277 packageInputs = map getBuildInputs selected;
278
279 name = if pkgs.lib.length selected == 1
280 then "ghc-shell-for-${(pkgs.lib.head selected).name}"
281 else "ghc-shell-for-packages";
282
283 # If `packages = [ a b ]` and `a` depends on `b`, don't build `b`,
284 # because cabal will end up ignoring that built version, assuming
285 # new-style commands.
286 haskellInputs = pkgs.lib.filter
287 (input: pkgs.lib.all (p: input.outPath != p.outPath) selected)
288 (pkgs.lib.concatMap (p: p.haskellBuildInputs) packageInputs);
289 systemInputs = pkgs.lib.concatMap (p: p.systemBuildInputs) packageInputs;
290
291 withPackages = if withHoogle then self.ghcWithHoogle else self.ghcWithPackages;
292 ghcEnv = withPackages (p: haskellInputs);
293 nativeBuildInputs = pkgs.lib.concatMap (p: p.nativeBuildInputs) selected;
294
295 ghcCommand' = if ghc.isGhcjs or false then "ghcjs" else "ghc";
296 ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
297 ghcCommandCaps= pkgs.lib.toUpper ghcCommand';
298
299 mkDrvArgs = builtins.removeAttrs args ["packages" "withHoogle"];
300 in pkgs.stdenv.mkDerivation (mkDrvArgs // {
301 name = mkDrvArgs.name or name;
302
303 buildInputs = systemInputs ++ mkDrvArgs.buildInputs or [];
304 nativeBuildInputs = [ ghcEnv ] ++ nativeBuildInputs ++ mkDrvArgs.nativeBuildInputs or [];
305 phases = ["installPhase"];
306 installPhase = "echo $nativeBuildInputs $buildInputs > $out";
307 LANG = "en_US.UTF-8";
308 LOCALE_ARCHIVE = pkgs.lib.optionalString (stdenv.hostPlatform.libc == "glibc") "${buildPackages.glibcLocales}/lib/locale/locale-archive";
309 "NIX_${ghcCommandCaps}" = "${ghcEnv}/bin/${ghcCommand}";
310 "NIX_${ghcCommandCaps}PKG" = "${ghcEnv}/bin/${ghcCommand}-pkg";
311 # TODO: is this still valid?
312 "NIX_${ghcCommandCaps}_DOCDIR" = "${ghcEnv}/share/doc/ghc/html";
313 "NIX_${ghcCommandCaps}_LIBDIR" = if ghc.isHaLVM or false
314 then "${ghcEnv}/lib/HaLVM-${ghc.version}"
315 else "${ghcEnv}/lib/${ghcCommand}-${ghc.version}";
316 });
317
318 ghc = ghc // {
319 withPackages = self.ghcWithPackages;
320 withHoogle = self.ghcWithHoogle;
321 };
322
323 }