-
Notifications
You must be signed in to change notification settings - Fork 244
/
Copy pathshell-for.nix
203 lines (180 loc) · 9.39 KB
/
shell-for.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
{ lib, stdenv, mkShell, glibcLocales, ghcForComponent, makeConfigFiles, hsPkgs, hoogleLocal, haskellLib, pkgsBuildBuild, evalPackages, compiler }:
{ # `packages` function selects packages that will be worked on in the shell itself.
# These packages will not be built by `shellFor`, but their
# dependencies will be included in the shell's `ghc-pkg list`.
packages ? ps: builtins.attrValues (haskellLib.selectLocalPackages ps)
# `components` function selects components that will be worked on in the shell itself.
# By default `shellFor` will include the dependencies of all of the components
# in the selected packages. If only as subset of the components will be
# worked on in the shell then we can pass a different `components` function
# to select those.
, components ? ps: lib.concatMap haskellLib.getAllComponents (packages ps)
# Additional packages to be added unconditionally
, additional ? _: []
, withHoogle ? true
, withHaddock ? withHoogle
, exactDeps ? false
, allToolDeps ? !exactDeps
, tools ? {}
, packageSetupDeps ? true
, enableDWARF ? false
, ... } @ args:
let
# TODO find out why hoogle index creation can be made to work for cross compilers
withHoogle' = withHoogle && !haskellLib.isCrossHost;
selectedPackages = packages hsPkgs;
additionalPackages = additional hsPkgs;
directlySelectedComponents = components hsPkgs;
# Given `directlySelectedComponents = [ a b ]`, we construct a shell that includes all of their dependencies
#
# But we want to exclude `a` if it is a transitive dependecy of `b`
# because:
# - cabal will end up ignoring that built version;
# - The user has indicated that's what they're working on, so they probably don't want to have to build
# it first (and it will change often).
# Generally we never want to include any of `directlySelectedComponents` as dependencies.
#
# We do this by defining a set of `selectedComponents`, where `x` is in `selectedComponents` if and only if:
# - `x` is in `directlySelectedComponents`
# - `x` is a transitive dependency of something in `directlySelectedComponents`
# and `x` transitively depends on something in `directlySelectedComponents`
# We use the dependencies of `selectedComponents` filtering out members of `selectedComponents`
#
# Furthermore, if `a` depends on `b`, `a` will include the library component of `b` in its `buildInputs`
# (to make `propagatedBuildInputs` of `pkgconfig-depends` work). So we also need to filter those
# (the pkgconfig depends of `b` will still be included in the
# system shell's `buildInputs` via `b`'s own `buildInputs`).
# all packages that are indirectly depended on by `directlySelectedComponents`
# including `directlySelectedComponents`
transitiveDependenciesComponents =
builtins.listToAttrs
(builtins.map (x: lib.nameValuePair (x.name) x)
(haskellLib.flatLibDepends {depends = directlySelectedComponents;}));
isSelectedComponent =
comp: selectedComponentsBitmap."${((haskellLib.dependToLib comp).name or null)}" or false;
selectedComponentsBitmap =
lib.mapAttrs
(_: x: (builtins.any isSelectedComponent x.config.depends))
transitiveDependenciesComponents
// builtins.listToAttrs (map (x: lib.nameValuePair x.name true) directlySelectedComponents); # base case
selectedComponents =
lib.filter isSelectedComponent (lib.attrValues transitiveDependenciesComponents);
allHsPkgsComponents = lib.concatMap haskellLib.getAllComponents
(lib.filter (x: !(x.isRedirect or false)) (builtins.attrValues hsPkgs));
# Given a list of `depends`, removes those which are selected components
removeSelectedInputs =
lib.filter (input: !(isSelectedComponent input));
# The configs of all the selected components.
# This excludes the `setup` dependencies of `Simple` packages, because
# `cabal-install` does not build a `Setup` executable for `Simple` packages.
selectedConfigs = map (c: c.config) selectedComponents
++ lib.optionals packageSetupDeps (map (p: p.setup.config)
(lib.filter (p: p.buildType != "Simple") selectedPackages));
identifierName = if lib.length selectedPackages == 1
then "ghc-shell-for-${(lib.head selectedPackages).identifier.name}"
else "ghc-shell-for-packages";
name = if (mkDrvArgs.name or null) == null then identifierName else mkDrvArgs.name;
# We need to remove any dependencies which would bring in selected components (see above).
packageInputs = haskellLib.uniqueWithName
(removeSelectedInputs (haskellLib.uniqueWithName (lib.concatMap (cfg: cfg.depends) selectedConfigs))
++ additionalPackages);
# Add the system libraries and build tools of the selected haskell packages to the shell.
# We need to remove any inputs which are selected components (see above).
# `buildInputs`, `propagatedBuildInputs`, and `executableToolDepends` contain component
# derivations, not packages, so we use `removeSelectedInputs`).
#
# Also, we take care to keep duplicates out of the list, otherwise we may see
# "Argument list too long" errors from bash when entering a shell.
allSystemInputs = lib.concatMap (c: c.buildInputs ++ c.propagatedBuildInputs) selectedComponents;
systemInputs = removeSelectedInputs (haskellLib.uniqueWithName allSystemInputs);
nativeBuildInputs = removeSelectedInputs
(haskellLib.uniqueWithName (lib.concatMap (c: c.executableToolDepends)
# When not using `exactDeps` cabal may try to build arbitrary dependencies
# so in this case we need to provide the build tools for all of `hsPkgs`.
# In some cases those tools may be unwanted or broken so the `allToolDeps`
# flag can be set to `false` to disable this (stack projects default `allToolDeps`
# to `false` as `hsPkgs` for them includes all of stackage):
(if exactDeps || !allToolDeps then selectedComponents else allHsPkgsComponents)));
# Set up a "dummy" component to use with ghcForComponent.
component = {
depends = packageInputs;
pre-existing = lib.concatMap (x: (haskellLib.dependToLib x).config.pre-existing or []) packageInputs;
libs = lib.concatMap (x: (haskellLib.dependToLib x).config.libs or []) packageInputs;
pkgconfig = lib.concatMap (x: (haskellLib.dependToLib x).config.pkgconfig or []) packageInputs;
frameworks = lib.concatMap (x: (haskellLib.dependToLib x).config.frameworks or []) packageInputs;
doExactConfig = false;
};
configFiles = makeConfigFiles {
fullName = name;
identifier.name = identifierName;
inherit component enableDWARF;
chooseDrv = p: if withHaddock && p ? haddock then p.haddock else p;
};
ghcEnv = ghcForComponent {
inherit configFiles;
componentName = identifierName;
postInstall = lib.optionalString withHoogle' ''
ln -s ${hoogleIndex}/bin/hoogle $out/bin
'';
inherit enableDWARF;
plugins = [];
};
hoogleIndex = let
# Get the doc package for a component, and add attributes that
# hoogle.nix expects.
docPackage = p: lib.getOutput "doc" p // {
pname = p.identifier.name;
haddockDir = p.haddockDir;
};
in hoogleLocal {
packages = map docPackage (haskellLib.flatLibDepends component);
hoogle = pkgsBuildBuild.haskell-nix.hackage-tool (
lib.optionals (args ? tools && args.tools ? hoogle) (haskellLib.versionOrModToMods args.tools.hoogle)
++ [{
name = "hoogle";
compiler-nix-name = compiler.nix-name;
inherit evalPackages;
}]);
};
mkDrvArgs = builtins.removeAttrs args ["packages" "components" "additional" "withHoogle" "tools"];
in
mkShell (mkDrvArgs // {
inherit name;
buildInputs = systemInputs
++ mkDrvArgs.buildInputs or [];
nativeBuildInputs = [ ghcEnv.drv ]
++ nativeBuildInputs
++ mkDrvArgs.nativeBuildInputs or []
++ lib.attrValues (pkgsBuildBuild.haskell-nix.tools' evalPackages compiler.nix-name tools)
# If this shell is a cross compilation shell include
# wrapper script for running cabal build with appropriate args.
# Includes `--with-compiler` in case the `cabal.project` file has `with-compiler:` in it.
++ lib.optional (ghcEnv.targetPrefix != "") (
pkgsBuildBuild.writeShellScriptBin "${ghcEnv.targetPrefix}cabal" ''
exec cabal \
--with-ghc=${ghcEnv.targetPrefix}ghc \
--with-compiler=${ghcEnv.targetPrefix}ghc \
--with-ghc-pkg=${ghcEnv.targetPrefix}ghc-pkg \
--with-hsc2hs=${ghcEnv.targetPrefix}hsc2hs \
${lib.optionalString (ghcEnv.targetPrefix == "js-unknown-ghcjs-") ''
--with-ghcjs=${ghcEnv.targetPrefix}ghc \
--with-ghcjs-pkg=${ghcEnv.targetPrefix}ghc-pkg \
--ghcjs \
''} $(builtin type -P "${ghcEnv.targetPrefix}pkg-config" &> /dev/null && echo "--with-pkg-config=${ghcEnv.targetPrefix}pkg-config") \
"$@"
'');
phases = ["installPhase"];
installPhase = ''
echo "${"Shell for " + toString (builtins.map (p : p.identifier.name) selectedPackages)}"
echo $nativeBuildInputs $buildInputs > $out
'';
# This helps tools like `ghcide` (that use the ghc api) to find
# the correct global package DB.
NIX_GHC_LIBDIR = ghcEnv.drv + "/" + configFiles.libDir;
passthru = (mkDrvArgs.passthru or {}) // {
ghc = ghcEnv.drv;
configFiles = configFiles.drv;
};
} // lib.optionalAttrs exactDeps {
CABAL_CONFIG = "${ghcEnv.drv}/configFiles/cabal.config";
})