Files
haskell.nix/builder/shell-for.nix
2022-08-25 10:15:15 -04:00

202 lines
8.7 KiB
Nix

{ 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";
})