{ lib, stdenv, mkShell, glibcLocales, pkgconfig, ghcForComponent, makeConfigFiles, hsPkgs, hoogleLocal, haskellLib, buildPackages, 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 , 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); # 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 = removeSelectedInputs (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. # # Version of `lib.unique` that should be fast if the name attributes are unique uniqueWithName = list: lib.concatMap lib.unique ( builtins.attrValues ( builtins.groupBy (x: if __typeOf x == "set" then x.name or "noname" else "notset") list)); allSystemInputs = lib.concatMap (c: c.buildInputs ++ c.propagatedBuildInputs) selectedComponents; systemInputs = removeSelectedInputs (uniqueWithName allSystemInputs); nativeBuildInputs = removeSelectedInputs (lib.concatMap (c: c.executableToolDepends) selectedComponents); # Set up a "dummy" component to use with ghcForComponent. component = { depends = packageInputs; libs = []; pkgconfig = []; frameworks = []; 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 = lib.const p.haddockDir; }; in hoogleLocal ({ packages = map docPackage (haskellLib.flatLibDepends component); # Need to add hoogle to hsPkgs. # inherit (hsPkgs) hoogle; } // ( lib.optionalAttrs (args ? tools && args.tools ? hoogle) { hoogle = buildPackages.haskell-nix.hackage-tool ( 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 ] ++ nativeBuildInputs ++ mkDrvArgs.nativeBuildInputs or [] ++ lib.attrValues (buildPackages.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 != "") ( buildPackages.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 + "/" + configFiles.libDir; passthru = (mkDrvArgs.passthru or {}) // { ghc = ghcEnv; inherit configFiles; }; } // lib.optionalAttrs exactDeps { CABAL_CONFIG = "${configFiles}/cabal.config"; })