Files
nixpkgs/pkgs/development/haskell-modules/microhs-builder.nix
Alex Tunstall e805baacb7 haskell.{compiler,packages}.microhs: init
The package set is built entirely from source using the stdenv and Hugs.

This also replaces the recently added microhs package, which was built
from pre-generated compiler output.

Co-authored-by: sternenseemann <sternenseemann@systemli.org>
2026-04-09 22:33:33 +01:00

421 lines
12 KiB
Nix

{
buildHaskellPackages,
buildPackages,
fetchurl,
ghc,
jailbreak-cabal,
lib,
MicroCabal,
cpphs,
ghc-compat,
pkg-config,
runCommandCC,
stdenv,
wrapMhs,
}:
let
# make-package-set always names the compiler ghc
compiler = ghc;
isCross = stdenv.buildPlatform != stdenv.hostPlatform;
in
{
pname,
version,
revision ? null,
sha256 ? null,
src ? fetchurl {
url = "mirror://hackage/${pname}-${version}.tar.gz";
inherit sha256;
},
# Extra environment variables to set during the build.
# See: `../../../doc/languages-frameworks/haskell.section.md`
env ? { },
buildDepends ? [ ],
setupHaskellDepends ? [ ],
libraryHaskellDepends ? [ ],
executableHaskellDepends ? [ ],
buildTools ? [ ],
libraryToolDepends ? [ ],
executableToolDepends ? [ ],
testToolDepends ? [ ],
benchmarkToolDepends ? [ ],
configureFlags ? [ ],
description ? null,
doCheck ? !isCross,
doBenchmark ? false,
editedCabalFile ? null,
extraLibraries ? [ ],
librarySystemDepends ? [ ],
executableSystemDepends ? [ ],
# On macOS, statically linking against system frameworks is not supported;
# see https://developer.apple.com/library/content/qa/qa1118/_index.html
# They must be propagated to the environment of any executable linking with the library
libraryFrameworkDepends ? [ ],
executableFrameworkDepends ? [ ],
homepage ? "https://hackage.haskell.org/package/${pname}",
platforms ? with lib.platforms; all, # GHC can cross-compile
badPlatforms ? lib.platforms.none,
hydraPlatforms ? null,
isExecutable ? false,
isLibrary ? !isExecutable,
jailbreak ? false,
license,
maintainers ? null,
teams ? null,
changelog ? null,
mainProgram ? null,
passthru ? { },
pkg-configDepends ? [ ],
libraryPkgconfigDepends ? [ ],
executablePkgconfigDepends ? [ ],
testPkgconfigDepends ? [ ],
benchmarkPkgconfigDepends ? [ ],
testDepends ? [ ],
testHaskellDepends ? [ ],
testSystemDepends ? [ ],
testFrameworkDepends ? [ ],
benchmarkDepends ? [ ],
benchmarkHaskellDepends ? [ ],
benchmarkSystemDepends ? [ ],
benchmarkFrameworkDepends ? [ ],
broken ? false,
preCompileBuildDriver ? null,
postCompileBuildDriver ? null,
preUnpack ? null,
postUnpack ? null,
patches ? null,
patchPhase ? null,
prePatch ? "",
postPatch ? "",
preConfigure ? null,
postConfigure ? null,
preBuild ? null,
postBuild ? null,
preHaddock ? null,
postHaddock ? null,
installPhase ? null,
preInstall ? null,
postInstall ? null,
checkPhase ? null,
preCheck ? null,
postCheck ? null,
preFixup ? null,
postFixup ? null,
shellHook ? "",
...
}@args:
assert editedCabalFile != null -> revision != null;
let
allPkgconfigDepends =
pkg-configDepends
++ libraryPkgconfigDepends
++ executablePkgconfigDepends
++ lib.optionals doCheck testPkgconfigDepends
++ lib.optionals doBenchmark benchmarkPkgconfigDepends;
# MicroCabal adds a dependency on ghc-compat unless the package name is one of the following
isCorePkg = pname == "base" || pname == "ghc-compat" || pname == "MicroHs" || pname == "MicroCabal";
buildDepends' = buildDepends ++ lib.optional (!isCorePkg) ghc-compat;
depsBuildBuild = lib.optionals (!stdenv.hasCC) [ buildPackages.stdenv.cc ];
collectedToolDepends =
buildTools
++ libraryToolDepends
++ executableToolDepends
++ lib.optionals doCheck testToolDepends
++ lib.optionals doBenchmark benchmarkToolDepends;
nativeBuildInputs = [
MicroCabal
cpphs
compilerWithPkgs
buildPackages.removeReferencesTo
];
propagatedBuildInputs =
buildDepends' ++ libraryHaskellDepends ++ executableHaskellDepends ++ libraryFrameworkDepends;
otherBuildInputsHaskell =
lib.optionals doCheck (testDepends ++ testHaskellDepends)
++ lib.optionals doBenchmark (benchmarkDepends ++ benchmarkHaskellDepends);
otherBuildInputsSystem =
extraLibraries
++ librarySystemDepends
++ executableSystemDepends
++ executableFrameworkDepends
++ allPkgconfigDepends
++ lib.optionals doCheck (testSystemDepends ++ testFrameworkDepends)
++ lib.optionals doBenchmark (benchmarkSystemDepends ++ benchmarkFrameworkDepends);
otherBuildInputs = otherBuildInputsHaskell ++ otherBuildInputsSystem;
newCabalFileUrl = "mirror://hackage/${pname}-${version}/revision/${revision}.cabal";
newCabalFile = fetchurl {
url = newCabalFileUrl;
sha256 = editedCabalFile;
name = "${pname}-${version}-r${revision}.cabal";
};
hsLibBuildInputs = lib.filter (p: p ? isHaskellLibrary && p.isHaskellLibrary) propagatedBuildInputs;
compilerWithPkgs = wrapMhs {
microhs = compiler;
packages = hsLibBuildInputs;
};
compilerCommand' = "mhs";
compilerCommand = "${compiler.targetPrefix}${compilerCommand'}";
compilerLibdir = "lib/mcabal/${compiler.haskellCompilerName}";
env' = {
CABALDIR = "${placeholder "out"}/lib/mcabal";
}
// env;
in
stdenv.mkDerivation {
inherit pname version;
pos = builtins.unsafeGetAttrPos "pname" args;
prePhases = [ "setupCompilerEnvironmentPhase" ];
preConfigurePhases = [ "compileBuildDriverPhase" ];
preInstallPhases = [ "haddockPhase" ];
inherit src patches;
inherit depsBuildBuild nativeBuildInputs;
buildInputs = lib.optionals (!isLibrary) propagatedBuildInputs;
propagatedBuildInputs = lib.optionals isLibrary propagatedBuildInputs;
env = env';
prePatch =
lib.optionalString (editedCabalFile != null) ''
echo "Replace Cabal file with edited version from ${newCabalFileUrl}."
cp ${newCabalFile} ${pname}.cabal
''
+ prePatch;
postPatch =
lib.optionalString jailbreak ''
echo "Run jailbreak-cabal to lift version restrictions on build inputs."
${jailbreak-cabal}/bin/jailbreak-cabal ${pname}.cabal
''
+ postPatch;
setupCompilerEnvironmentPhase = ''
runHook preSetupCompilerEnvironment
echo "Building with ${compilerWithPkgs}"
mkdir -p "$CABALDIR/${compiler.haskellCompilerName}/packages"
${lib.concatMapStrings (p: ''
ln -s ${p}/${compilerLibdir}/packages/${p.name}.pkg $CABALDIR/${compiler.haskellCompilerName}/packages/${p.name}.pkg
'') (lib.closePropagation hsLibBuildInputs)}
runHook postSetupCompilerEnvironment
'';
compileBuildDriverPhase = ''
runHook preCompileBuildDriver
runHook postCompileBuildDriver
'';
buildPhase = ''
runHook preBuild
echo "Skipping buildPhase because MicroCabal always builds on install"
runHook postBuild
'';
inherit doCheck;
checkPhase = ''
runHook preCheck
echo "Skipping checkPhase because MicroCabal doesn't support test suites"
runHook postCheck
'';
haddockPhase = ''
runHook preHaddock
echo "Skipping haddockPhase because MicroCabal doesn't support Haddock"
runHook postHaddock
'';
installPhase = ''
runHook preInstall
mcabal -v ${lib.concatStringsSep " " configureFlags} install
mkdir -p $out/bin
find "$CABALDIR/bin" -type f -exec ln -s '{}' "$out/bin/" \; || true
find "$CABALDIR/${compiler.haskellCompilerName}/packages" -type l -delete
runHook postInstall
'';
inherit
preCompileBuildDriver
postCompileBuildDriver
preUnpack
postUnpack
preConfigure
postConfigure
preBuild
postBuild
preHaddock
postHaddock
preInstall
postInstall
preCheck
postCheck
preFixup
postFixup
;
passthru = passthru // rec {
inherit pname version;
compiler = ghc;
# All this information is intended just for `shellFor`. It should be
# considered unstable and indeed we knew how to keep it private we would.
getCabalDeps = {
inherit
buildDepends'
buildTools
executableFrameworkDepends
executableHaskellDepends
executablePkgconfigDepends
executableSystemDepends
executableToolDepends
extraLibraries
libraryFrameworkDepends
libraryHaskellDepends
libraryPkgconfigDepends
librarySystemDepends
libraryToolDepends
pkg-configDepends
setupHaskellDepends
;
}
// lib.optionalAttrs doCheck {
inherit
testDepends
testFrameworkDepends
testHaskellDepends
testPkgconfigDepends
testSystemDepends
testToolDepends
;
}
// lib.optionalAttrs doBenchmark {
inherit
benchmarkDepends
benchmarkFrameworkDepends
benchmarkHaskellDepends
benchmarkPkgconfigDepends
benchmarkSystemDepends
benchmarkToolDepends
;
};
# Attributes for the old definition of `shellFor`. Should be removed but
# this predates the warning at the top of `getCabalDeps`.
getBuildInputs = rec {
inherit propagatedBuildInputs otherBuildInputs allPkgconfigDepends;
haskellBuildInputs = isHaskellPartition.right;
systemBuildInputs = isHaskellPartition.wrong;
isHaskellPartition = lib.partition (x: x ? isHaskellLibrary) (
propagatedBuildInputs ++ otherBuildInputs ++ depsBuildBuild ++ nativeBuildInputs
);
};
isHaskellLibrary = isLibrary;
haddockDir = null;
# Creates a derivation containing all of the necessary dependencies for building the
# parent derivation. The attribute set that it takes as input can be viewed as:
#
# { withHoogle }
#
# The derivation that it builds contains no outpaths because it is meant for use
# as an environment
#
# # Example use
# # Creates a shell with all of the dependencies required to build the "hello" package,
# # and with python:
#
# > nix-shell -E 'with (import <nixpkgs> {}); \
# > haskellPackages.hello.envFunc { buildInputs = [ python ]; }'
envFunc =
{
# Unsupported
withHoogle ? false,
}:
let
name = "microhs-shell-for-${pname}";
compilerCommandCaps = lib.toUpper compilerCommand';
in
runCommandCC name {
inherit shellHook;
nativeBuildInputs = [
compilerWithPkgs
]
++ lib.optional (allPkgconfigDepends != [ ]) pkg-config
++ collectedToolDepends;
buildInputs = otherBuildInputsSystem;
env = {
"NIX_${compilerCommandCaps}" = "${compiler}/bin/${compilerCommand}";
# TODO: is this still valid?
"NIX_${compilerCommandCaps}_LIBDIR" = "${compiler}/${compilerLibdir}";
}
// lib.optionalAttrs (stdenv.buildPlatform.libc == "glibc") {
# TODO: Why is this written in terms of `buildPackages`, unlike
# the outer `env`?
#
# According to @sternenseemann [1]:
#
# > The condition is based on `buildPlatform`, so it needs to
# > match. `LOCALE_ARCHIVE` is set to accompany `LANG` which
# > concerns things we execute on the build platform like
# > `haddock`.
# >
# > Arguably the outer non `buildPackages` one is incorrect and
# > probably works by accident in most cases since the locale
# > archive is not platform specific (the trouble is that it
# > may sometimes be impossible to cross-compile). At least
# > that would be my assumption.
#
# [1]: https://github.com/NixOS/nixpkgs/pull/424368#discussion_r2202683378
LOCALE_ARCHIVE = "${buildPackages.glibcLocales}/lib/locale/locale-archive";
}
// env';
} "echo $nativeBuildInputs $buildInputs > $out";
env = envFunc { };
};
meta = {
inherit homepage license platforms;
}
// lib.optionalAttrs (args ? broken) { inherit broken; }
// lib.optionalAttrs (args ? description) { inherit description; }
// lib.optionalAttrs (args ? maintainers) { inherit maintainers; }
// lib.optionalAttrs (args ? teams) { inherit teams; }
// lib.optionalAttrs (args ? hydraPlatforms) { inherit hydraPlatforms; }
// lib.optionalAttrs (args ? badPlatforms) { inherit badPlatforms; }
// lib.optionalAttrs (args ? changelog) { inherit changelog; }
// lib.optionalAttrs (args ? mainProgram) { inherit mainProgram; };
}