mirror of
https://github.com/simplex-chat/haskell.nix.git
synced 2026-06-03 09:17:32 +00:00
228 lines
10 KiB
Nix
228 lines
10 KiB
Nix
{ stdenv, lib, haskellLib, ghc, nonReinstallablePkgs, runCommand, writeText, writeScript }@defaults:
|
|
|
|
{ identifier, component, fullName, flags ? {}, needsProfiling ? false, enableDWARF ? false, chooseDrv ? drv: drv, nonReinstallablePkgs ? defaults.nonReinstallablePkgs }:
|
|
|
|
let
|
|
# Sort and remove duplicates from nonReinstallablePkgs.
|
|
# That way changes to the order of nonReinstallablePkgs does not require rebuilds.
|
|
nonReinstallablePkgs' = __attrNames (lib.genAttrs nonReinstallablePkgs (x: x));
|
|
|
|
ghc = if enableDWARF then defaults.ghc.dwarf else defaults.ghc;
|
|
|
|
flagsAndConfig = field: xs: lib.optionalString (xs != []) ''
|
|
echo ${lib.concatStringsSep " " (map (x: "--${field}=${x}") xs)} >> $out/configure-flags
|
|
${lib.concatStrings (map (x: ''
|
|
echo "${field}: ${x}" >> $out/cabal.config
|
|
'') xs)}
|
|
'';
|
|
|
|
target-pkg = "${ghc.targetPrefix}ghc-pkg";
|
|
|
|
# This is a bit of a hack. So we'll have a slightly longer explanation here:
|
|
|
|
# Every library component built with `comp-builder.nix` includes an `exactDep`
|
|
# and `envDep` directory with precomputed values used here.
|
|
# GHC derivations include `exactDep` and `envDep` directories that have
|
|
# the same information for each of the built in packages.
|
|
|
|
# exactDep will pass --exact-configuration to the `SETUP_HS configure` command.
|
|
# This requires us to pass --dependency={dep name}={pkg id}. The dependency
|
|
# name will usually be the name of the package `p`, which we can locate in the
|
|
# package-db, passed in via `pdbArg`. Thus querying the package-db for the
|
|
# id field for package `p`, will usually provide us with the right value. Sublibs
|
|
# need a bit of special handling:
|
|
#
|
|
# - Sublibs: if the dependency is a sublibrary of a package, we need to use
|
|
# the sublibrary's name for the dep name, and lookup the sublibraries
|
|
# pkg id for z-{pkg name}-z-{sublib name}. As we do not provide the
|
|
# sublib name to exactDep, as we don't have access to it at the call-site,
|
|
# we resort to a bit of globbing, which (as pkg db's should contain only
|
|
# a single package) work.
|
|
|
|
getLibComponent = dep:
|
|
dep.components.library # Regular package dependency
|
|
or dep; # or a sublib
|
|
|
|
# Work our suitable packageCfgDir subdirectory
|
|
isGhcjs = ghc.isGhcjs or false;
|
|
ghcCommand' = if isGhcjs then "ghcjs" else "ghc";
|
|
ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
|
|
ghcCommandCaps = lib.toUpper ghcCommand';
|
|
libDir = "lib/${ghcCommand}-${ghc.version}";
|
|
packageCfgDir = "${libDir}/package.conf.d";
|
|
|
|
# Filters out only library packages that for this GHC target
|
|
# TODO investigate why this is needed
|
|
# TODO find out why p ? configFiles helps (for instance for `R1909.aarch64-unknown-linux-gnu.tests.cabal-22.run.x86_64-linux`)
|
|
libDeps = map chooseDrv (
|
|
(if enableDWARF then (x: map (p: p.dwarf or p) x) else x: x)
|
|
((if needsProfiling then (x: map (p: p.profiled or p) x) else x: x)
|
|
(lib.filter (p: (p ? configFiles) && p.configFiles.targetPrefix == ghc.targetPrefix)
|
|
(map getLibComponent component.depends)))
|
|
);
|
|
cfgFiles =
|
|
let xs = map
|
|
(p: "${p.configFiles}")
|
|
libDeps;
|
|
in lib.concatStringsSep "\" \"" xs;
|
|
libs = lib.concatMapStringsSep "\" \"" (p: "${p}") libDeps;
|
|
drv = runCommand "${ghc.targetPrefix}${fullName}-config" {
|
|
nativeBuildInputs = [ghc];
|
|
passthru = {
|
|
inherit (ghc) targetPrefix;
|
|
inherit ghcCommand ghcCommandCaps libDir packageCfgDir component;
|
|
# Use ''${pkgroot} relative paths so that we can relocate the package database
|
|
# along with referenced packages and still have it work on systems with
|
|
# or without nix installed.
|
|
relocatableConfigFiles = runCommand "${ghc.targetPrefix}${fullName}-relocatable-config" ''
|
|
cp -r ${drv} $out
|
|
chmod -R +w $out
|
|
sed -i 's|/nix/store/|''${pkgroot}/../../../|' $out/${packageCfgDir}/*.conf
|
|
${target-pkg} -v0 --package-db $out/${packageCfgDir} recache
|
|
'';
|
|
};
|
|
} (''
|
|
mkdir -p $out
|
|
|
|
${target-pkg} init $out/${packageCfgDir}
|
|
|
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList flagsAndConfig {
|
|
"extra-lib-dirs" = map (p: "${lib.getLib p}/lib") (lib.flatten component.libs)
|
|
# On windows also include `bin` directories that may contain DLLs
|
|
++ lib.optionals (stdenv.hostPlatform.isWindows)
|
|
(map (p: "${lib.getBin p}/bin")
|
|
(lib.flatten component.libs ++ lib.concatLists component.pkgconfig));
|
|
"extra-include-dirs" = map (p: "${lib.getDev p}/include") (lib.flatten component.libs);
|
|
"extra-framework-dirs" = lib.optionals (stdenv.hostPlatform.isDarwin)
|
|
(map (p: "${p}/Library/Frameworks") component.frameworks);
|
|
})}
|
|
|
|
ghc=${ghc}
|
|
${ # Copy over the nonReinstallablePkgs from the global package db.
|
|
''
|
|
for p in ${lib.concatStringsSep " " nonReinstallablePkgs'}; do
|
|
find $ghc/lib/${ghc.name}/package.conf.d -name $p'*.conf' -exec cp -f {} $out/${packageCfgDir} \;
|
|
done
|
|
''}
|
|
|
|
for l in "${cfgFiles}"; do
|
|
if [ -n "$l" ]; then
|
|
files=("$l/${packageCfgDir}/"*.conf)
|
|
if (( ''${#files[@]} )); then
|
|
cp -f "''${files[@]}" $out/${packageCfgDir}
|
|
else
|
|
echo "$l/${packageCfgDir} didn't contain any *.conf files!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
for l in "${libs}"; do
|
|
if [ -n "$l" ]; then
|
|
files=("$l/package.conf.d/"*.conf)
|
|
if (( ''${#files[@]} )); then
|
|
cp -f "''${files[@]}" $out/${packageCfgDir}
|
|
else
|
|
echo "$l/package.conf.d didn't contain any *.conf files!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
${ # Note: we pass `clear` first to ensure that we never consult the implicit global package db.
|
|
# However in `cabal.config` `cabal` requires `global` to be first.
|
|
flagsAndConfig "package-db" ["clear"]
|
|
}
|
|
echo "package-db: global" >> $out/cabal.config
|
|
${ flagsAndConfig "package-db" ["$out/${packageCfgDir}"] }
|
|
|
|
echo ${lib.concatStringsSep " " (lib.mapAttrsToList (fname: val: "--flags=${lib.optionalString (!val) "-" + fname}") flags)} >> $out/configure-flags
|
|
|
|
${ # Provide a cabal config without remote package repositories
|
|
''
|
|
echo "write-ghc-environment-files: never" >> $out/cabal.config
|
|
''}
|
|
|
|
${ # Provide a GHC environment file
|
|
''
|
|
cat > $out/ghc-environment <<EOF
|
|
package-db $out/${packageCfgDir}
|
|
EOF
|
|
''}
|
|
|
|
${ lib.optionalString component.doExactConfig ''
|
|
echo "--exact-configuration" >> $out/configure-flags
|
|
echo "allow-newer: ${identifier.name}:*" >> $out/cabal.config
|
|
echo "allow-older: ${identifier.name}:*" >> $out/cabal.config
|
|
''}
|
|
|
|
for p in ${lib.concatStringsSep " " libDeps}; do
|
|
cat $p/envDep >> $out/ghc-environment
|
|
${ lib.optionalString component.doExactConfig ''
|
|
cat $p/exactDep/configure-flags >> $out/configure-flags
|
|
cat $p/exactDep/cabal.config >> $out/cabal.config
|
|
''}
|
|
done
|
|
for p in ${lib.concatStringsSep " " (lib.remove "ghc" nonReinstallablePkgs')}; do
|
|
if [ -e $ghc/envDeps/$p ]; then
|
|
cat $ghc/envDeps/$p >> $out/ghc-environment
|
|
fi
|
|
done
|
|
'' + lib.optionalString component.doExactConfig ''
|
|
for p in ${lib.concatStringsSep " " nonReinstallablePkgs'}; do
|
|
if [ -e $ghc/exactDeps/$p ]; then
|
|
cat $ghc/exactDeps/$p/configure-flags >> $out/configure-flags
|
|
cat $ghc/exactDeps/$p/cabal.config >> $out/cabal.config
|
|
fi
|
|
done
|
|
''
|
|
# This code originates in the `generic-builder.nix` from nixpkgs. However GHC has been fixed
|
|
# to drop unused libraries referenced from libraries; and this patch is usually included in the
|
|
# nixpkgs's GHC builds. This doesn't sadly make this stupid hack unnecessary. It resurfaces in
|
|
# the form of Cabal trying to be smart. Cabal when linking a library figures out that you likely
|
|
# need those `rpath` entries, and passes `-optl-Wl,-rpath,...` for each dynamic library path to
|
|
# GHC, thus subverting the linker and forcing it to insert all those RPATHs weather or not they
|
|
# are needed. We therefore reuse the linker hack here to move all dynamic libraries into a
|
|
# common folder (as links) and thus prevent Cabal from going nuts.
|
|
#
|
|
# TODO: Fix Cabal.
|
|
# TODO: this is only needed if we do dynamic libraries.
|
|
#
|
|
# NOTE [ln -s -f]: we force link, as we may have dependencies that contain shared deps
|
|
# (e.g. libiconv), and thus we don't want to fail, but just link it again.
|
|
#
|
|
# Confusing sed stuff:
|
|
# '/^ ./{H;$!d} ; x' Groups lines that start with a space with the initial
|
|
# line of a block. Needs a blank line added to the file
|
|
# to terminate the last block.
|
|
# 's/ /\n/g ; s/\n\n*/\n/g; s/^\n//;' Puts each field on its own line.
|
|
# 's|/nix/store/|''${pkgroot}/../../../|' Convert store path to pkgroot relative path
|
|
# 's|''${pkgroot}/../../../|/nix/store/|' Convert pkgroot relative path to store path
|
|
|
|
# Work around a limit in the macOS Sierra linker on the number of paths
|
|
# referenced by any one dynamic library:
|
|
#
|
|
# Create a local directory with symlinks of the *.dylib (macOS shared
|
|
# libraries) from all the dependencies.
|
|
+ lib.optionalString stdenv.isDarwin ''
|
|
local dynamicLinksDir="$out/lib/links"
|
|
mkdir -p $dynamicLinksDir
|
|
# Enumerate dynamic-library-dirs with ''${pkgroot} expanded.
|
|
local dirsToLink=$(
|
|
for f in "$out/${packageCfgDir}/"*.conf; do
|
|
(cat $f; echo) | sed -En '/^ ./{H;$!d} ; x ; /^dynamic-library-dirs:/ {s/^dynamic-library-dirs:// ; s/ /\n/g ; s/\n\n*/\n/g; s/^\n//; p}'
|
|
done | sed 's|''${pkgroot}/../../../|/nix/store/|' | sort -u
|
|
)
|
|
for d in $dirsToLink; do
|
|
ln -f -s "$d/"*.{a,dylib,so} $dynamicLinksDir
|
|
done
|
|
# Edit the local package DB to reference the links directory.
|
|
for f in "$out/${packageCfgDir}/"*.conf; do
|
|
chmod +w $f
|
|
echo >> $f
|
|
sed -i -E "/^ ./{H;$!d} ; x ; s,^dynamic-library-dirs:.*,dynamic-library-dirs: $dynamicLinksDir," $f
|
|
done
|
|
'' + ''
|
|
${target-pkg} -v0 --package-db $out/${packageCfgDir} recache
|
|
'');
|
|
in drv
|