on e94de40 help sign in
haskell.nix/builder/shell-for.nix
146 lines 6.4 kB history raw
1{ lib, stdenv, glibcLocales, pkgconfig, ghcForComponent, makeConfigFiles, hsPkgs, hoogleLocal, haskellLib, buildPackages, compiler }:
3{ # `packages` function selects packages that will be worked on in the shell itself.
4 # These packages will not be built by `shellFor`, but their
5 # dependencies will be included in the shell's `ghc-pkg list`.
6 packages ? ps:
7 let
8 selected = haskellLib.selectLocalPackages ps;
9 in
10 builtins.trace ("Shell for " + toString (builtins.attrNames selected))
11 (builtins.attrValues selected)
12 # `components` function selects components that will be worked on in the shell itself.
13 # By default `shellFor` will include the dependencies of all of the components
14 # in the selected packages. If only as subset of the components will be
15 # worked on in the shell then we can pass a different `components` function
16 # to select those.
17, components ? ps: lib.concatMap haskellLib.getAllComponents (packages hsPkgs)
18 # Additional packages to be added unconditionally
19, additional ? _: []
20, withHoogle ? true
21, exactDeps ? false
22, tools ? {}
23, ... } @ args:
25let
26 # TODO find out why hoogle index creation can be made to work for cross compilers
27 withHoogle' = withHoogle && !haskellLib.isCrossHost;
29 selectedPackages = packages hsPkgs;
30 additionalPackages = additional hsPkgs;
31 selectedComponents = components hsPkgs;
33 # The configs of all the selected components
34 selectedConfigs = map (c: c.config) selectedComponents ++ map (p: p.setup.config) selectedPackages;
36 name = if lib.length selectedPackages == 1
37 then "ghc-shell-for-${(lib.head selectedPackages).identifier.name}"
38 else "ghc-shell-for-packages";
40 # Given a `packages = [ a b ]` we construct a shell with the dependencies of `a` and `b`.
41 #
42 # But if `a` depends on `b`, we don't want to include `b`, because
43 # - cabal will end up ignoring that built version;
44 # - The user has indicated that's what they're working on, so they probably don't want to have to build
45 # it first (and it will change often).
46 # Generally we never want to include any of (the components of) the selected packages as dependencies.
47 #
48 # Furthermore, if `a` depends on `b`, `a` will include the library component of `b` in its `buildInputs`
49 # (to make `propagatedBuildInputs` of `pkgconfig-depends` work). So we also need to filter those
50 # (the pkgconfig depends of `b` will still be included in the
51 # system shell's `buildInputs` via `b`'s own `buildInputs`).
52 # We have to do this slightly differently because we will be looking at the actual components rather
53 # than the packages.
55 # Given a list of packages, removes those which were selected as part of the shell.
56 # We do this on the basis of their identifiers being the same, not direct equality (why?).
57 removeSelectedPackages =
58 # All the identifiers of the selected packages
59 let selectedPackageIds = map (p: p.identifier) selectedPackages;
60 in lib.filter (input: !(builtins.elem (input.identifier or null) selectedPackageIds));
62 # Given a list of derivations, removes those which are components of packages which were selected as part of the shell.
63 removeSelectedInputs =
64 # All the components of the selected packages: we shouldn't add any of these as dependencies
65 let selectedPackageComponents = lib.concatMap haskellLib.getAllComponents selectedPackages;
66 in lib.filter (input: !(builtins.elem input selectedPackageComponents));
68 # We need to remove any dependencies which are selected packages (see above).
69 # `depends` contains packages so we use 'removeSelectedPackages`.
70 packageInputs = removeSelectedPackages (lib.concatMap (cfg: cfg.depends) selectedConfigs) ++ additionalPackages;
72 # Add the system libraries and build tools of the selected haskell packages to the shell.
73 # We need to remove any inputs which are selected components (see above).
74 # `buildInputs`, `propagatedBuildInputs`, and `executableToolDepends` contain component
75 # derivations, not packages, so we use `removeSelectedInputs`).
76 systemInputs = removeSelectedInputs (lib.concatMap
77 (c: c.buildInputs ++ c.propagatedBuildInputs) selectedComponents);
78 nativeBuildInputs = removeSelectedInputs
79 (lib.concatMap (c: c.executableToolDepends) selectedComponents);
81 # Set up a "dummy" component to use with ghcForComponent.
82 component = {
83 depends = packageInputs;
84 libs = [];
85 frameworks = [];
86 doExactConfig = false;
87 };
88 configFiles = makeConfigFiles {
89 fullName = args.name or name;
90 identifier.name = name;
91 inherit component;
92 };
93 ghcEnv = ghcForComponent {
94 inherit configFiles;
95 componentName = name;
96 postInstall = lib.optionalString withHoogle' ''
97 ln -s ${hoogleIndex}/bin/hoogle $out/bin
98 '';
99 };
101 hoogleIndex = let
102 # Get the doc package for a component, and add attributes that
103 # hoogle.nix expects.
104 docPackage = p: lib.getOutput "doc" p // {
105 pname = p.identifier.name;
106 haddockDir = lib.const p.haddockDir;
107 };
108 in hoogleLocal ({
109 packages = map docPackage (haskellLib.flatLibDepends component);
111 # Need to add hoogle to hsPkgs.
112 # inherit (hsPkgs) hoogle;
113 } // (
114 lib.optionalAttrs (args ? tools && args.tools ? hoogle) {
115 hoogle = buildPackages.haskell-nix.tool compiler.nix-name "hoogle" args.tools.hoogle;
116 }
117 ));
119 mkDrvArgs = builtins.removeAttrs args ["packages" "additional" "withHoogle" "tools"];
120in
121 stdenv.mkDerivation (mkDrvArgs // {
122 name = mkDrvArgs.name or name;
124 buildInputs = systemInputs
125 ++ mkDrvArgs.buildInputs or []
126 ++ lib.optional withHoogle' hoogleIndex;
127 nativeBuildInputs = [ ghcEnv ]
128 ++ nativeBuildInputs
129 ++ mkDrvArgs.nativeBuildInputs or []
130 ++ lib.attrValues (buildPackages.haskell-nix.tools compiler.nix-name tools);
131 phases = ["installPhase"];
132 installPhase = "echo $nativeBuildInputs $buildInputs > $out";
133 LANG = "en_US.UTF-8";
134 LOCALE_ARCHIVE = lib.optionalString (stdenv.hostPlatform.libc == "glibc") "${glibcLocales}/lib/locale/locale-archive";
136 # This helps tools like `ghcide` (that use the ghc api) to find
137 # the correct global package DB.
138 NIX_GHC_LIBDIR = ghcEnv + "/" + configFiles.libDir;
140 passthru = (mkDrvArgs.passthru or {}) // {
141 ghc = ghcEnv;
142 inherit configFiles;
143 };
144 } // lib.optionalAttrs exactDeps {
145 CABAL_CONFIG = "${configFiles}/cabal.config";
146 })