all-hies/default.nix
2019-05-22 23:02:06 +02:00

196 lines
7.7 KiB
Nix

# nixpkgs used only for library functions, not for dependencies
{ pkgs ? import ./nixpkgs.nix
, lib ? pkgs.lib
}:
let
# A la lib.composeExtensions, but with any number of extensions
composeMultiple = extensions:
if extensions == [] then self: super: {}
else lib.composeExtensions
(lib.head extensions)
(composeMultiple (lib.tail extensions));
# Speeds up Haskell builds
speedierBuilds = self: super: {
mkDerivation = args: super.mkDerivation (args // {
enableLibraryProfiling = false;
});
};
ghcSpecific = buildName: ghcVersion: rec {
# Reformats the ghc version into the format "8.6.4"
versionString = lib.concatStringsSep "."
(builtins.match "ghc(.)(.)(.)" ghcVersion);
# The more recent the version, the higher the priority
# But higher priorities are lower on the number scale (WHY?), so we need the -
versionPriority = - lib.toInt (lib.head (builtins.match "ghc(.*)" ghcVersion));
# Evaluates to a nixpkgs that has the given ghc version in it
pkgs = let
rev = builtins.readFile (./nixpkgsForGhc + "/${ghcVersion}");
sha256 = builtins.readFile (./generated + "/${buildName}/nixpkgsHashes/${rev}");
in import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/tarball/${rev}";
inherit sha256;
}) { config = {}; overlays = []; };
# Returns a Haskell overlay that sets all ghc base libraries to null
# (minus a select few)
baseLibraryNuller = self: super: let
libs = lib.splitString " "
(builtins.readFile (./generated + "/${buildName}/ghcBaseLibraries/${ghcVersion}"));
libNames = map (lib: (builtins.parseDrvName lib).name) libs;
# It seems that some versions require Cabal and some don't
filtered = lib.filter (name: ! lib.elem name [ "ghc" "Cabal" ]) libNames;
in lib.genAttrs filtered (name: null);
# Custom overrides for specific ghc versions, declared in ./overrides
customOverrides =
let path = ./overrides + "/${ghcVersion}.nix";
in if builtins.pathExists path then import path
else self: super: {};
};
# Build for a specific GHC version
hieBuild = buildName: ghcVersion: let
forGhc = ghcSpecific buildName ghcVersion;
hlib = forGhc.pkgs.haskell.lib;
revision = builtins.readFile (./generated + "/${buildName}/stack2nix/revision");
hieOverride = self: super: {
haskell-ide-engine = (hlib.overrideCabal super.haskell-ide-engine (old: {
# Embed the ghc version into the name
pname = "${old.pname}-${ghcVersion}";
version = revision;
# Link Haskell libraries dynamically, improves startup time for projects
# using TH by a lot (40x faster in one of my tests), but also Increases
# closure size by about 50% (=~ 1.2GB) per HIE version
# Can be disabled again for GHC versions that have a fix for
# https://gitlab.haskell.org/ghc/ghc/issues/15524
enableSharedExecutables = true;
isLibrary = false;
doHaddock = false;
})).overrideAttrs (old: {
nativeBuildInputs = old.nativeBuildInputs or [] ++ [ pkgs.makeWrapper ];
# Make sure hie-x.x.x binary exists
# And make sure hie-wrapper finds this version
postInstall = old.postInstall or "" + ''
ln -s hie $out/bin/hie-${forGhc.versionString}
wrapProgram $out/bin/hie-wrapper \
--suffix PATH : $out/bin
'';
# Assign a priority for allowing multiple versions to be installed at once
meta = old.meta // {
priority = forGhc.versionPriority;
};
});
};
overrideFun = old: {
overrides = composeMultiple [
(old.overrides or (self: super: {}))
speedierBuilds
hieOverride
forGhc.baseLibraryNuller
forGhc.customOverrides
];
};
expr = import (./generated + "/${buildName}/stack2nix/${ghcVersion}.nix") {
pkgs = forGhc.pkgs;
};
build = (expr.override overrideFun).haskell-ide-engine;
in build;
# A set of all ghc versions for all hie versions, like
# { stable = { ghc864 = <derivation ..>; ... }; unstable = ...; }
# Each of which contain binaries hie and hie-$major.$minor.$patch for their
# GHC version, along with a hie-wrapper binary that knows about this version
allVersions = lib.mapAttrs (buildName: _:
let ghcVersionFiles = lib.filterAttrs (file: _: file != "revision")
(builtins.readDir (./generated + "/${buildName}/stack2nix"));
in lib.mapAttrs' (ghcVersionFile: _:
let ghcVersion = lib.removeSuffix ".nix" ghcVersionFile;
in lib.nameValuePair ghcVersion (hieBuild buildName ghcVersion)
) ghcVersionFiles) (builtins.readDir ./generated);
# Combine a set of HIE versions (given as { <ghcVersion> = <derivation>; })
# into a single derivation with the following binaries:
# - hie-*.*.*: The GHC specific HIE versions, such as ghc-8.6.4
# - hie-wrapper: A HIE version that selects the appropriate version
# automatically out of the given ones
# - hie: Same as hie-wrapper, provided for easy editor integration
combined = versions: pkgs.buildEnv {
name = "haskell-ide-engine-combined";
paths = lib.attrValues versions;
buildInputs = [ pkgs.makeWrapper ];
pathsToLink = [ "/bin" ];
extraPrefix = "/libexec";
postBuild = ''
# Remove hie/hie-wrapper in /libexec/bin because those are both just PATH
# wrapped versions. Move the actual hie-wrapper to $out/bin
rm $out/libexec/bin/{hie,hie-wrapper}
mkdir -p $out/bin
mv $out/libexec/bin/.hie-wrapper-wrapped $out/bin/hie-wrapper
# Now /libexec/bin only contains binaries hie-*.*.*. Link all of them to
# $out/bin such that users installing this directly can access these
# specific versions too in $PATH
for bin in $out/libexec/bin/*; do
ln -s ../libexec/bin/$(basename $bin) $out/bin/$(basename $bin)
done
# Because we don't want hie-wrapper to fall back to hie binaries later in
# PATH (because if this derivation is installed, a later hie might be
# hie-wrapper itself, leading to infinite process recursion), we provide
# our own hie binary instead, which will only be called if it couldn't
# find any appropriate hie-*.*.* binary, in which case the user needs to
# adjust their all-hies installation to make that one available.
cat > $out/libexec/bin/hie << EOF
#!${pkgs.runtimeShell}
echo "hie-wrapper couldn't find a HIE binary with a matching GHC" \
"version in your all-hies installation" >&2
exit 1
EOF
chmod +x $out/libexec/bin/hie
# Wrap hie-wrapper with PATH prefixed with /libexec/bin, such
# that it can find all those binaries. Not --suffix because
# hie-wrapper needs to find our hie binary first and foremost as per
# above comment, also makes it more reproducible. Not --set because hie
# needs to find binaries for cabal/ghc and such.
wrapProgram $out/bin/hie-wrapper \
--prefix PATH : $out/libexec/bin
# Make hie-wrapper available as hie as well, in order to minimize the need
# for customizing editors, and to override a potential hie binary from
# another derivation in the same environment.
ln -s hie-wrapper $out/bin/hie
'';
};
# Generates a set with the utility functions from a set of versions
mkSet = versions: {
inherit combined versions;
selection = { selector }: combined (selector versions);
latest = lib.last (lib.attrValues versions);
};
in mkSet allVersions.stable
// lib.mapAttrs (_: mkSet) (builtins.removeAttrs allVersions ["stable"])
// {
# Stable, but fall back to unstable if stable doesn't have a certain GHC
# version
unstableFallback = mkSet (allVersions.unstable // allVersions.stable);
}