1{ lib, stdenv, haskellPackages, symlinkJoin, makeWrapper
2# GHC will have LLVM available if necessary for the respective target,
3# so useLLVM only needs to be changed if -fllvm is to be used for a
4# platform that has NCG support
5, useLLVM ? false
6, withHoogle ? false
7, hoogleWithPackages
8, postBuild ? ""
9, ghcLibdir ? null # only used by ghcjs, when resolving plugins
10}:
11
12# This argument is a function which selects a list of Haskell packages from any
13# passed Haskell package set.
14#
15# Example:
16# (hpkgs: [ hpkgs.mtl hpkgs.lens ])
17selectPackages:
18
19# It's probably a good idea to include the library "ghc-paths" in the
20# compiler environment, because we have a specially patched version of
21# that package in Nix that honors these environment variables
22#
23# NIX_GHC
24# NIX_GHCPKG
25# NIX_GHC_DOCDIR
26# NIX_GHC_LIBDIR
27#
28# instead of hard-coding the paths. The wrapper sets these variables
29# appropriately to configure ghc-paths to point back to the wrapper
30# instead of to the pristine GHC package, which doesn't know any of the
31# additional libraries.
32#
33# A good way to import the environment set by the wrapper below into
34# your shell is to add the following snippet to your ~/.bashrc:
35#
36# if [ -e ~/.nix-profile/bin/ghc ]; then
37# eval $(grep export ~/.nix-profile/bin/ghc)
38# fi
39
40let
41 inherit (haskellPackages) llvmPackages ghc;
42
43 packages = selectPackages haskellPackages
44 ++ lib.optional withHoogle (hoogleWithPackages selectPackages);
45
46 isGhcjs = ghc.isGhcjs or false;
47 isHaLVM = ghc.isHaLVM or false;
48 ghc761OrLater = isGhcjs || isHaLVM || lib.versionOlder "7.6.1" ghc.version;
49 packageDBFlag = if ghc761OrLater then "--global-package-db" else "--global-conf";
50 ghcCommand' = if isGhcjs then "ghcjs" else "ghc";
51 ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
52 ghcCommandCaps= lib.toUpper ghcCommand';
53 libDir = if isHaLVM then "$out/lib/HaLVM-${ghc.version}"
54 else "$out/lib/${ghc.targetPrefix}${ghc.haskellCompilerName}";
55 docDir = "$out/share/doc/ghc/html";
56 packageCfgDir = "${libDir}/package.conf.d";
57 paths = lib.filter (x: x ? isHaskellLibrary) (lib.closePropagation packages);
58 hasLibraries = lib.any (x: x.isHaskellLibrary) paths;
59 # CLang is needed on Darwin for -fllvm to work:
60 # https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/codegens.html#llvm-code-generator-fllvm
61 llvm = lib.makeBinPath
62 ([ llvmPackages.llvm ]
63 ++ lib.optional stdenv.targetPlatform.isDarwin llvmPackages.clang);
64in
65
66assert ghcLibdir != null -> (ghc.isGhcjs or false);
67
68if paths == [] && !useLLVM then ghc else
69symlinkJoin {
70 # this makes computing paths from the name attribute impossible;
71 # if such a feature is needed, the real compiler name should be saved
72 # as a dedicated drv attribute, like `compiler-name`
73 name = ghc.name + "-with-packages";
74 paths = paths ++ [ghc];
75 nativeBuildInputs = [ makeWrapper ];
76 postBuild = ''
77 # wrap compiler executables with correct env variables
78
79 for prg in ${ghcCommand} ${ghcCommand}i ${ghcCommand}-${ghc.version} ${ghcCommand}i-${ghc.version}; do
80 if [[ -x "${ghc}/bin/$prg" ]]; then
81 rm -f $out/bin/$prg
82 makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
83 --add-flags '"-B$NIX_${ghcCommandCaps}_LIBDIR"' \
84 --set "NIX_${ghcCommandCaps}" "$out/bin/${ghcCommand}" \
85 --set "NIX_${ghcCommandCaps}PKG" "$out/bin/${ghcCommand}-pkg" \
86 --set "NIX_${ghcCommandCaps}_DOCDIR" "${docDir}" \
87 --set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}" \
88 ${lib.optionalString (ghc.isGhcjs or false)
89 ''--set NODE_PATH "${ghc.socket-io}/lib/node_modules"''
90 } \
91 ${lib.optionalString useLLVM ''--prefix "PATH" ":" "${llvm}"''}
92 fi
93 done
94
95 for prg in runghc runhaskell; do
96 if [[ -x "${ghc}/bin/$prg" ]]; then
97 rm -f $out/bin/$prg
98 makeWrapper ${ghc}/bin/$prg $out/bin/$prg \
99 --add-flags "-f $out/bin/${ghcCommand}" \
100 --set "NIX_${ghcCommandCaps}" "$out/bin/${ghcCommand}" \
101 --set "NIX_${ghcCommandCaps}PKG" "$out/bin/${ghcCommand}-pkg" \
102 --set "NIX_${ghcCommandCaps}_DOCDIR" "${docDir}" \
103 --set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}"
104 fi
105 done
106
107 for prg in ${ghcCommand}-pkg ${ghcCommand}-pkg-${ghc.version}; do
108 if [[ -x "${ghc}/bin/$prg" ]]; then
109 rm -f $out/bin/$prg
110 makeWrapper ${ghc}/bin/$prg $out/bin/$prg --add-flags "${packageDBFlag}=${packageCfgDir}"
111 fi
112 done
113
114 # haddock was referring to the base ghc, https://github.com/NixOS/nixpkgs/issues/36976
115 if [[ -x "${ghc}/bin/haddock" ]]; then
116 rm -f $out/bin/haddock
117 makeWrapper ${ghc}/bin/haddock $out/bin/haddock \
118 --add-flags '"-B$NIX_${ghcCommandCaps}_LIBDIR"' \
119 --set "NIX_${ghcCommandCaps}_LIBDIR" "${libDir}"
120 fi
121
122 '' + (lib.optionalString (stdenv.targetPlatform.isDarwin && !isGhcjs && !stdenv.targetPlatform.isiOS) ''
123 # Work around a linker limit in macOS Sierra (see generic-builder.nix):
124 local packageConfDir="${packageCfgDir}";
125 local dynamicLinksDir="$out/lib/links"
126 mkdir -p $dynamicLinksDir
127 # Clean up the old links that may have been (transitively) included by
128 # symlinkJoin:
129 rm -f $dynamicLinksDir/*
130 for d in $(grep -Poz "dynamic-library-dirs:\s*\K .+\n" $packageConfDir/*|awk '{print $2}'|sort -u); do
131 ln -s $d/*.dylib $dynamicLinksDir
132 done
133 for f in $packageConfDir/*.conf; do
134 # Initially, $f is a symlink to a read-only file in one of the inputs
135 # (as a result of this symlinkJoin derivation).
136 # Replace it with a copy whose dynamic-library-dirs points to
137 # $dynamicLinksDir
138 cp $f $f-tmp
139 rm $f
140 sed "N;s,dynamic-library-dirs:\s*.*,dynamic-library-dirs: $dynamicLinksDir," $f-tmp > $f
141 rm $f-tmp
142 done
143 '') + ''
144 ${lib.optionalString hasLibraries ''
145 # GHC 8.10 changes.
146 # Instead of replacing package.cache[.lock] with the new file,
147 # ghc-pkg is now trying to open the file. These file are symlink
148 # to another nix derivation, so they are not writable. Removing
149 # them allow the correct behavior of ghc-pkg recache
150 # See: https://github.com/NixOS/nixpkgs/issues/79441
151 rm ${packageCfgDir}/package.cache.lock
152 rm ${packageCfgDir}/package.cache
153
154 $out/bin/${ghcCommand}-pkg recache
155 ''}
156 ${# ghcjs will read the ghc_libdir file when resolving plugins.
157 lib.optionalString (isGhcjs && ghcLibdir != null) ''
158 mkdir -p "${libDir}"
159 rm -f "${libDir}/ghc_libdir"
160 printf '%s' '${ghcLibdir}' > "${libDir}/ghc_libdir"
161 ''}
162 $out/bin/${ghcCommand}-pkg check
163 '' + postBuild;
164 passthru = {
165 preferLocalBuild = true;
166 inherit (ghc) version meta;
167
168 # Inform users about backwards incompatibilities with <= 21.05
169 override = _: throw ''
170 The ghc.withPackages wrapper itself can now be overridden, but no longer
171 the result of calling it (as before). Consequently overrides need to be
172 adjusted: Instead of
173
174 (ghc.withPackages (p: [ p.my-package ])).override { withLLLVM = true; }
175
176 use
177
178 (ghc.withPackages.override { useLLVM = true; }) (p: [ p.my-package ])
179
180 Also note that withLLVM has been renamed to useLLVM for consistency with
181 the GHC Nix expressions.'';
182 };
183}