{ lib, stdenv, buildPackages, buildHaskellPackages, ghc
, jailbreak-cabal, hscolour, cpphs
, ghcWithHoogle, ghcWithPackages
, nodejs
}:

let
  isCross = stdenv.buildPlatform != stdenv.hostPlatform;

  # Pass the "wrong" C compiler rather than none at all so packages that just
  # use the C preproccessor still work, see
  # https://github.com/haskell/cabal/issues/6466 for details.
  cc =
    if stdenv.hasCC then "$CC"
    else if stdenv.hostPlatform.isGhcjs then "${emscripten}/bin/emcc"
    else "$CC_FOR_BUILD";

  inherit (buildPackages)
    fetchurl removeReferencesTo
    pkg-config coreutils gnugrep glibcLocales
    emscripten;
in

{ pname
# Note that ghc.isGhcjs != stdenv.hostPlatform.isGhcjs.
# ghc.isGhcjs implies that we are using ghcjs, a project separate from GHC.
# (mere) stdenv.hostPlatform.isGhcjs means that we are using GHC's JavaScript
# backend. The latter is a normal cross compilation backend and needs little
# special accommodation.
, dontStrip ? (ghc.isGhcjs or false || stdenv.hostPlatform.isGhcjs)
, version, revision ? null
, sha256 ? null
, src ? fetchurl { url = "mirror://hackage/${pname}-${version}.tar.gz"; inherit sha256; }
, buildDepends ? [], setupHaskellDepends ? [], libraryHaskellDepends ? [], executableHaskellDepends ? []
, buildTarget ? ""
, buildTools ? [], libraryToolDepends ? [], executableToolDepends ? [], testToolDepends ? [], benchmarkToolDepends ? []
, configureFlags ? []
, buildFlags ? []
, haddockFlags ? []
, description ? null
, doCheck ? !isCross
, doBenchmark ? false
, doHoogle ? true
, doHaddockQuickjump ? doHoogle
, doInstallIntermediates ? false
, editedCabalFile ? null
, enableLibraryProfiling ? !(ghc.isGhcjs or false)
, enableExecutableProfiling ? false
, profilingDetail ? "exported-functions"
# TODO enable shared libs for cross-compiling
, enableSharedExecutables ? false
, enableSharedLibraries ? !stdenv.hostPlatform.isStatic && (ghc.enableShared or false)
, enableDeadCodeElimination ? (!stdenv.isDarwin)  # TODO: use -dead_strip for darwin
# Disabling this for ghcjs prevents this crash: https://gitlab.haskell.org/ghc/ghc/-/issues/23235
, enableStaticLibraries ? !(stdenv.hostPlatform.isWindows || stdenv.hostPlatform.isWasm || stdenv.hostPlatform.isGhcjs)
, enableHsc2hsViaAsm ? stdenv.hostPlatform.isWindows
, 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
, hyperlinkSource ? true
, isExecutable ? false, isLibrary ? !isExecutable
, jailbreak ? false
, license
, enableParallelBuilding ? true
, maintainers ? null
, changelog ? null
, mainProgram ? null
, doCoverage ? false
, doHaddock ? !(ghc.isHaLVM or false) && (ghc.hasHaddock or true)
, doHaddockInterfaces ? doHaddock && lib.versionAtLeast ghc.version "9.0.1"
, passthru ? {}
, pkg-configDepends ? [], libraryPkgconfigDepends ? [], executablePkgconfigDepends ? [], testPkgconfigDepends ? [], benchmarkPkgconfigDepends ? []
, testDepends ? [], testHaskellDepends ? [], testSystemDepends ? [], testFrameworkDepends ? []
, benchmarkDepends ? [], benchmarkHaskellDepends ? [], benchmarkSystemDepends ? [], benchmarkFrameworkDepends ? []
, testTarget ? "", testFlags ? []
, 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 ? ""
, coreSetup ? false # Use only core packages to build Setup.hs.
, useCpphs ? false
, hardeningDisable ? null
, enableSeparateBinOutput ? false
, enableSeparateDataOutput ? false
, enableSeparateDocOutput ? doHaddock
, enableSeparateIntermediatesOutput ? false
, # Don't fail at configure time if there are multiple versions of the
  # same package in the (recursive) dependencies of the package being
  # built. Will delay failures, if any, to compile time.
  allowInconsistentDependencies ? false
, maxBuildCores ? 16 # more cores usually don't improve performance: https://ghc.haskell.org/trac/ghc/ticket/9221
, # If set to true, this builds a pre-linked .o file for this Haskell library.
  # This can make it slightly faster to load this library into GHCi, but takes
  # extra disk space and compile time.
  enableLibraryForGhci ? false
  # Set this to a previous build of this same package to reuse the intermediate
  # build products from that prior build as a starting point for accelerating
  # this build
, previousIntermediates ? null
  # References to these store paths are forbidden in the produced output.
, disallowedRequisites ? []
  # Whether to allow the produced output to refer to `ghc`.
  #
  # This is used by `haskell.lib.justStaticExecutables` to help prevent static
  # Haskell binaries from having erroneous dependencies on GHC.
  #
  # See https://nixos.org/manual/nixpkgs/unstable/#haskell-packaging-helpers
  # or its source doc/languages-frameworks/haskell.section.md
, disallowGhcReference ? false
, # Cabal 3.8 which is shipped by default for GHC >= 9.3 always calls
  # `pkg-config --libs --static` as part of the configure step. This requires
  # Requires.private dependencies of pkg-config dependencies to be present in
  # PKG_CONFIG_PATH which is normally not the case in nixpkgs (except in pkgsStatic).
  # Since there is no patch or upstream patch yet, we replicate the automatic
  # propagation of dependencies in pkgsStatic for allPkgConfigDepends for
  # GHC >= 9.3 by default. This option allows overriding this behavior manually
  # if mismatching Cabal and GHC versions are used.
  # See also <https://github.com/haskell/cabal/issues/8455>.
  __propagatePkgConfigDepends ? lib.versionAtLeast ghc.version "9.3"
, # Propagation can easily lead to the argv limit being exceeded in linker or C
  # compiler invocations. To work around this we can only propagate derivations
  # that are known to provide pkg-config modules, as indicated by the presence
  # of `meta.pkgConfigModules`. This option defaults to false for now, since
  # this metadata is far from complete in nixpkgs.
  __onlyPropagateKnownPkgConfigModules ? false
} @ args:

assert editedCabalFile != null -> revision != null;

# --enable-static does not work on windows. This is a bug in GHC.
# --enable-static will pass -staticlib to ghc, which only works for mach-o and elf.
assert stdenv.hostPlatform.isWindows -> enableStaticLibraries == false;
assert stdenv.hostPlatform.isWasm -> enableStaticLibraries == false;

let

  inherit (lib) optional optionals optionalString versionAtLeast
                       concatStringsSep enableFeature optionalAttrs;

  isGhcjs = ghc.isGhcjs or false;
  isHaLVM = ghc.isHaLVM or false;

  # GHC used for building Setup.hs
  #
  # Same as our GHC, unless we're cross, in which case it is native GHC with the
  # same version, or ghcjs, in which case its the ghc used to build ghcjs.
  nativeGhc = buildHaskellPackages.ghc;

  # the target dir for haddock documentation
  docdir = docoutput: docoutput + "/share/doc/" + pname + "-" + version;

  binDir = if enableSeparateBinOutput then "$bin/bin" else "$out/bin";

  newCabalFileUrl = "mirror://hackage/${pname}-${version}/revision/${revision}.cabal";
  newCabalFile = fetchurl {
    url = newCabalFileUrl;
    sha256 = editedCabalFile;
    name = "${pname}-${version}-r${revision}.cabal";
  };

  defaultSetupHs = builtins.toFile "Setup.hs" ''
                     import Distribution.Simple
                     main = defaultMain
                   '';

  # This awk expression transforms a package conf file like
  #
  #   author:               John Doe <john-doe@example.com>
  #   description:
  #       The purpose of this library is to do
  #       foo and bar among other things
  #
  # into a more easily processeable form:
  #
  #   author: John Doe <john-doe@example.com>
  #   description: The purpose of this library is to do foo and bar among other things
  unprettyConf = builtins.toFile "unpretty-cabal-conf.awk" ''
    /^[^ ]+:/ {
      # When the line starts with a new field, terminate the previous one with a newline
      if (started == 1) print ""
      # to strip leading spaces
      $1=$1
      printf "%s", $0
      started=1
    }

    /^ +/ {
      # to strip leading spaces
      $1=$1
      printf " %s", $0
    }

    # Terminate the final field with a newline
    END { print "" }
  '';

  crossCabalFlags = [
    "--with-ghc=${ghcCommand}"
    "--with-ghc-pkg=${ghc.targetPrefix}ghc-pkg"
    "--with-gcc=${cc}"
  ] ++ optionals stdenv.hasCC [
    "--with-ld=${stdenv.cc.bintools.targetPrefix}ld"
    "--with-ar=${stdenv.cc.bintools.targetPrefix}ar"
    # use the one that comes with the cross compiler.
    "--with-hsc2hs=${ghc.targetPrefix}hsc2hs"
    "--with-strip=${stdenv.cc.bintools.targetPrefix}strip"
  ] ++ optionals (!isHaLVM) [
    "--hsc2hs-option=--cross-compile"
    (optionalString enableHsc2hsViaAsm "--hsc2hs-option=--via-asm")
  ] ++ optional (allPkgconfigDepends != [])
    "--with-pkg-config=${pkg-config.targetPrefix}pkg-config";

  parallelBuildingFlags = "-j$NIX_BUILD_CORES" + optionalString stdenv.isLinux " +RTS -A64M -RTS";

  buildFlagsString = optionalString (buildFlags != []) (" " + concatStringsSep " " buildFlags);

  defaultConfigureFlags = [
    "--verbose"
    "--prefix=$out"
    # Note: This must be kept in sync manually with mkGhcLibdir
    ("--libdir=\\$prefix/lib/\\$compiler" + lib.optionalString (ghc ? hadrian) "/lib")
    "--libsubdir=\\$abi/\\$libname"
    (optionalString enableSeparateDataOutput "--datadir=$data/share/${ghcNameWithPrefix}")
    (optionalString enableSeparateDocOutput "--docdir=${docdir "$doc"}")
  ] ++ optionals stdenv.hasCC [
    "--with-gcc=$CC" # Clang won't work without that extra information.
  ] ++ [
    "--package-db=$packageConfDir"
    (optionalString (enableSharedExecutables && stdenv.isLinux) "--ghc-option=-optl=-Wl,-rpath=$out/${ghcLibdir}/${pname}-${version}")
    (optionalString (enableSharedExecutables && stdenv.isDarwin) "--ghc-option=-optl=-Wl,-headerpad_max_install_names")
    (optionalString enableParallelBuilding "--ghc-options=${parallelBuildingFlags}")
    (optionalString useCpphs "--with-cpphs=${cpphs}/bin/cpphs --ghc-options=-cpp --ghc-options=-pgmP${cpphs}/bin/cpphs --ghc-options=-optP--cpp")
    (enableFeature enableLibraryProfiling "library-profiling")
    (optionalString (enableExecutableProfiling || enableLibraryProfiling) "--profiling-detail=${profilingDetail}")
    (enableFeature enableExecutableProfiling "profiling")
    (enableFeature enableSharedLibraries "shared")
    (enableFeature doCoverage "coverage")
    (enableFeature enableStaticLibraries "static")
    (enableFeature enableSharedExecutables "executable-dynamic")
    (enableFeature doCheck "tests")
    (enableFeature doBenchmark "benchmarks")
    "--enable-library-vanilla"  # TODO: Should this be configurable?
    (enableFeature enableLibraryForGhci "library-for-ghci")
    (enableFeature enableDeadCodeElimination "split-sections")
    (enableFeature (!dontStrip) "library-stripping")
    (enableFeature (!dontStrip) "executable-stripping")
  ] ++ optionals isGhcjs [
    "--ghcjs"
  ] ++ optionals isCross ([
    "--configure-option=--host=${stdenv.hostPlatform.config}"
  ] ++ crossCabalFlags
  ) ++ optionals enableSeparateBinOutput [
    "--bindir=${binDir}"
  ] ++ optionals (doHaddockInterfaces && isLibrary) [
    "--ghc-options=-haddock"
  ];

  postPhases = optional doInstallIntermediates "installIntermediatesPhase";

  setupCompileFlags = [
    (optionalString (!coreSetup) "-package-db=$setupPackageConfDir")
    (optionalString enableParallelBuilding parallelBuildingFlags)
    "-threaded"       # https://github.com/haskell/cabal/issues/2398
    "-rtsopts"        # allow us to pass RTS flags to the generated Setup executable
  ];

  isHaskellPkg = x: x ? isHaskellLibrary;

  # Work around a Cabal bug requiring pkg-config --static --libs to work even
  # when linking dynamically, affecting Cabal 3.8 and 3.9.
  # https://github.com/haskell/cabal/issues/8455
  #
  # For this, we treat the runtime system/pkg-config dependencies of a Haskell
  # derivation as if they were propagated from their dependencies which allows
  # pkg-config --static to work in most cases.
  allPkgconfigDepends =
    let
      # If __onlyPropagateKnownPkgConfigModules is set, packages without
      # meta.pkgConfigModules will be filtered out, otherwise all packages in
      # buildInputs and propagatePlainBuildInputs are propagated.
      propagateValue = drv:
        lib.isDerivation drv
        && (__onlyPropagateKnownPkgConfigModules -> drv ? meta.pkgConfigModules);

      # Take list of derivations and return list of the transitive dependency
      # closure, only taking into account buildInputs. Loosely based on
      # closePropagationFast.
      propagatePlainBuildInputs = drvs:
        builtins.map (i: i.val) (
          builtins.genericClosure {
            startSet = builtins.map (drv:
              { key = drv.outPath; val = drv; }
            ) (builtins.filter propagateValue drvs);
            operator = { val, ... }:
              builtins.concatMap (drv:
                if propagateValue drv
                then [ { key = drv.outPath; val = drv; } ]
                else [ ]
              ) (val.buildInputs or [ ] ++ val.propagatedBuildInputs or [ ]);
          }
        );
    in

    if __propagatePkgConfigDepends
    then propagatePlainBuildInputs allPkgconfigDepends'
    else allPkgconfigDepends';
  allPkgconfigDepends' =
    pkg-configDepends ++ libraryPkgconfigDepends ++ executablePkgconfigDepends ++
    optionals doCheck testPkgconfigDepends ++ optionals doBenchmark benchmarkPkgconfigDepends;

  depsBuildBuild = [ nativeGhc ]
    # CC_FOR_BUILD may be necessary if we have no C preprocessor for the host
    # platform. See crossCabalFlags above for more details.
    ++ lib.optionals (!stdenv.hasCC) [ buildPackages.stdenv.cc ];
  collectedToolDepends =
    buildTools ++ libraryToolDepends ++ executableToolDepends ++
    optionals doCheck testToolDepends ++
    optionals doBenchmark benchmarkToolDepends;
  nativeBuildInputs =
    [ ghc removeReferencesTo ] ++ optional (allPkgconfigDepends != []) (assert pkg-config != null; pkg-config) ++
    setupHaskellDepends ++ collectedToolDepends ++ optional stdenv.hostPlatform.isGhcjs nodejs;
  propagatedBuildInputs = buildDepends ++ libraryHaskellDepends ++ executableHaskellDepends ++ libraryFrameworkDepends;
  otherBuildInputsHaskell =
    optionals doCheck (testDepends ++ testHaskellDepends) ++
    optionals doBenchmark (benchmarkDepends ++ benchmarkHaskellDepends);
  otherBuildInputsSystem =
    extraLibraries ++ librarySystemDepends ++ executableSystemDepends ++ executableFrameworkDepends ++
    allPkgconfigDepends ++
    optionals doCheck (testSystemDepends ++ testFrameworkDepends) ++
    optionals doBenchmark (benchmarkSystemDepends ++ benchmarkFrameworkDepends);
  # TODO next rebuild just define as `otherBuildInputsHaskell ++ otherBuildInputsSystem`
  otherBuildInputs =
    extraLibraries ++ librarySystemDepends ++ executableSystemDepends ++ executableFrameworkDepends ++
    allPkgconfigDepends ++
    optionals doCheck (testDepends ++ testHaskellDepends ++ testSystemDepends ++ testFrameworkDepends) ++
    optionals doBenchmark (benchmarkDepends ++ benchmarkHaskellDepends ++ benchmarkSystemDepends ++ benchmarkFrameworkDepends);

  setupCommand = "./Setup";

  ghcCommand' = if isGhcjs then "ghcjs" else "ghc";
  ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";

  ghcNameWithPrefix = "${ghc.targetPrefix}${ghc.haskellCompilerName}";
  mkGhcLibdir = ghc: "lib/${ghc.targetPrefix}${ghc.haskellCompilerName}"
    + lib.optionalString (ghc ? hadrian) "/lib";
  ghcLibdir = mkGhcLibdir ghc;

  nativeGhcCommand = "${nativeGhc.targetPrefix}ghc";

  buildPkgDb = thisGhc: packageConfDir: ''
    # If this dependency has a package database, then copy the contents of it,
    # unless it is one of our GHCs. These can appear in our dependencies when
    # we are doing native builds, and they have package databases in them, but
    # we do not want to copy them over.
    #
    # We don't need to, since those packages will be provided by the GHC when
    # we compile with it, and doing so can result in having multiple copies of
    # e.g. Cabal in the database with the same name and version, which is
    # ambiguous.
    if [ -d "$p/${mkGhcLibdir thisGhc}/package.conf.d" ] && [ "$p" != "${ghc}" ] && [ "$p" != "${nativeGhc}" ]; then
      cp -f "$p/${mkGhcLibdir thisGhc}/package.conf.d/"*.conf ${packageConfDir}/
      continue
    fi
  '';

  intermediatesDir = "share/haskell/${ghc.version}/${pname}-${version}/dist";

  # This is a script suitable for --test-wrapper of Setup.hs' test command
  # (https://cabal.readthedocs.io/en/3.12/setup-commands.html#cmdoption-runhaskell-Setup.hs-test-test-wrapper).
  # We use it to set some environment variables that the test suite may need,
  # e.g. GHC_PACKAGE_PATH to invoke GHC(i) at runtime with build dependencies
  # available. See the comment accompanying checkPhase below on how to customize
  # this behavior. We need to use a wrapper script since Cabal forbids setting
  # certain environment variables since they can alter GHC's behavior (e.g.
  # GHC_PACKAGE_PATH) and cause failures. While building, Cabal will set
  # GHC_ENVIRONMENT to make the packages picked at configure time available to
  # GHC, but unfortunately not at test time. The test wrapper script will be
  # executed after such environment checks, so we can take some liberties which
  # is unproblematic since we know our synthetic package db matches what Cabal
  # will see at configure time exactly. See also
  # <https://github.com/haskell/cabal/issues/7792>.
  testWrapperScript = buildPackages.writeShellScript
    "haskell-generic-builder-test-wrapper.sh"
    ''
      set -eu

      # We expect this to be either empty or set by checkPhase
      if [[ -n "''${NIX_GHC_PACKAGE_PATH_FOR_TEST}" ]]; then
        export GHC_PACKAGE_PATH="''${NIX_GHC_PACKAGE_PATH_FOR_TEST}"
      fi

      exec "$@"
    '';

in lib.fix (drv:

stdenv.mkDerivation ({
  inherit pname version;

  outputs = [ "out" ]
         ++ (optional enableSeparateDataOutput "data")
         ++ (optional enableSeparateDocOutput "doc")
         ++ (optional enableSeparateBinOutput "bin")
         ++ (optional enableSeparateIntermediatesOutput "intermediates");

  setOutputFlags = false;

  pos = builtins.unsafeGetAttrPos "pname" args;

  prePhases = ["setupCompilerEnvironmentPhase"];
  preConfigurePhases = ["compileBuildDriverPhase"];
  preInstallPhases = ["haddockPhase"];

  inherit src;

  inherit depsBuildBuild nativeBuildInputs;
  buildInputs = otherBuildInputs ++ optionals (!isLibrary) propagatedBuildInputs
    # For patchShebangsAuto in fixupPhase
    ++ optionals stdenv.hostPlatform.isGhcjs [ nodejs ];
  propagatedBuildInputs = optionals isLibrary propagatedBuildInputs;

  LANG = "en_US.UTF-8";         # GHC needs the locale configured during the Haddock phase.

  prePatch = optionalString (editedCabalFile != null) ''
    echo "Replace Cabal file with edited version from ${newCabalFileUrl}."
    cp ${newCabalFile} ${pname}.cabal
  '' + prePatch;

  postPatch = optionalString jailbreak ''
    echo "Run jailbreak-cabal to lift version restrictions on build inputs."
    ${jailbreak-cabal}/bin/jailbreak-cabal ${pname}.cabal
  '' + postPatch;

  setupCompilerEnvironmentPhase = ''
    NIX_BUILD_CORES=$(( NIX_BUILD_CORES < ${toString maxBuildCores} ? NIX_BUILD_CORES : ${toString maxBuildCores} ))
    runHook preSetupCompilerEnvironment

    echo "Build with ${ghc}."
    ${optionalString (isLibrary && hyperlinkSource) "export PATH=${hscolour}/bin:$PATH"}

    builddir="$(mktemp -d)"
    setupPackageConfDir="$builddir/setup-package.conf.d"
    mkdir -p $setupPackageConfDir
    packageConfDir="$builddir/package.conf.d"
    mkdir -p $packageConfDir

    setupCompileFlags="${concatStringsSep " " setupCompileFlags}"
    configureFlags="${concatStringsSep " " defaultConfigureFlags} $configureFlags"
  ''
  # We build the Setup.hs on the *build* machine, and as such should only add
  # dependencies for the build machine.
  #
  # pkgs* arrays defined in stdenv/setup.hs
  + ''
    for p in "''${pkgsBuildBuild[@]}" "''${pkgsBuildHost[@]}" "''${pkgsBuildTarget[@]}"; do
      ${buildPkgDb nativeGhc "$setupPackageConfDir"}
    done
    ${nativeGhcCommand}-pkg --package-db="$setupPackageConfDir" recache
  ''
  # For normal components
  + ''
    for p in "''${pkgsHostHost[@]}" "''${pkgsHostTarget[@]}"; do
      ${buildPkgDb ghc "$packageConfDir"}
      if [ -d "$p/include" ]; then
        configureFlags+=" --extra-include-dirs=$p/include"
      fi
      if [ -d "$p/lib" ]; then
        configureFlags+=" --extra-lib-dirs=$p/lib"
      fi
      if [[ -d "$p/Library/Frameworks" ]]; then
        configureFlags+=" --extra-framework-dirs=$p/Library/Frameworks"
      fi
  '' + ''
    done
  ''
  + (optionalString stdenv.hostPlatform.isGhcjs ''
    export EM_CACHE="$(realpath "$(mktemp -d emcache.XXXXXXXXXX)")"
    cp -Lr ${emscripten}/share/emscripten/cache/* "$EM_CACHE/"
    chmod u+rwX -R "$EM_CACHE"
  '')
  # only use the links hack if we're actually building dylibs. otherwise, the
  # "dynamic-library-dirs" point to nonexistent paths, and the ln command becomes
  # "ln -s $out/lib/links", which tries to recreate the links dir and fails
  #
  # Note: We need to disable this work-around when using intermediate build
  # products from a prior build because otherwise Nix will change permissions on
  # the `$out/lib/links` directory to read-only when the build is done after the
  # dist directory has already been exported, which triggers an unnecessary
  # rebuild of modules included in the exported dist directory.
  + (optionalString (stdenv.isDarwin && (enableSharedLibraries || enableSharedExecutables) && !enableSeparateIntermediatesOutput) ''
    # 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.
    local dynamicLinksDir="$out/lib/links"
    mkdir -p $dynamicLinksDir

    # Unprettify all package conf files before reading/writing them
    for d in "$packageConfDir/"*; do
      # gawk -i inplace seems to strip the last newline
      gawk -f ${unprettyConf} "$d" > tmp
      mv tmp "$d"
    done

    for d in $(grep '^dynamic-library-dirs:' "$packageConfDir"/* | cut -d' ' -f2- | tr ' ' '\n' | sort -u); do
      for lib in "$d/"*.{dylib,so}; do
        # Allow overwriting because C libs can be pulled in multiple times.
        ln -sf "$lib" "$dynamicLinksDir"
      done
    done
    # Edit the local package DB to reference the links directory.
    for f in "$packageConfDir/"*.conf; do
      sed -i "s,dynamic-library-dirs: .*,dynamic-library-dirs: $dynamicLinksDir," "$f"
    done
  '') + ''
    ${ghcCommand}-pkg --package-db="$packageConfDir" recache

    runHook postSetupCompilerEnvironment
  '';

  compileBuildDriverPhase = ''
    runHook preCompileBuildDriver

    for i in Setup.hs Setup.lhs ${defaultSetupHs}; do
      test -f $i && break
    done

    echo setupCompileFlags: $setupCompileFlags
    ${nativeGhcCommand} $setupCompileFlags --make -o Setup -odir $builddir -hidir $builddir $i

    runHook postCompileBuildDriver
  '';

  # Cabal takes flags like `--configure-option=--host=...` instead
  configurePlatforms = [];
  inherit configureFlags;

  # Note: the options here must be always added, regardless of whether the
  # package specifies `hardeningDisable`.
  hardeningDisable = lib.optionals (args ? hardeningDisable) hardeningDisable
    ++ lib.optional (ghc.isHaLVM or false) "all"
    # Static libraries (ie. all of pkgsStatic.haskellPackages) fail to build
    # because by default Nix adds `-pie` to the linker flags: this
    # conflicts with the `-r` and `-no-pie` flags added by GHC (see
    # https://gitlab.haskell.org/ghc/ghc/-/issues/19580). hardeningDisable
    # changes the default Nix behavior regarding adding "hardening" flags.
    ++ lib.optional enableStaticLibraries "pie";

  configurePhase = ''
    runHook preConfigure

    echo configureFlags: $configureFlags
    ${setupCommand} configure $configureFlags 2>&1 | ${coreutils}/bin/tee "$NIX_BUILD_TOP/cabal-configure.log"
    ${lib.optionalString (!allowInconsistentDependencies) ''
      if ${gnugrep}/bin/egrep -q -z 'Warning:.*depends on multiple versions' "$NIX_BUILD_TOP/cabal-configure.log"; then
        echo >&2 "*** abort because of serious configure-time warning from Cabal"
        exit 1
      fi
    ''}

    runHook postConfigure
  '';

  buildPhase =
      ''
      runHook preBuild
      ''
    + lib.optionalString (previousIntermediates != null)
        ''
        mkdir -p dist;
        rm -r dist/build
        cp -r ${previousIntermediates}/${intermediatesDir}/build dist/build
        find dist/build -exec chmod u+w {} +
        find dist/build -exec touch -d '1970-01-01T00:00:00Z' {} +
        ''
    + ''
      ${setupCommand} build ${buildTarget}${buildFlagsString}
      runHook postBuild
      '';

  inherit doCheck;

  # Run test suite(s) and pass `checkFlags` as well as `checkFlagsArray`.
  # `testFlags` are added to `checkFlagsArray` each prefixed with
  # `--test-option`, so Cabal passes it to the underlying test suite binary.
  #
  # We also take care of setting GHC_PACKAGE_PATH during test suite execution,
  # so it can run GHC(i) with build dependencies available:
  # - If NIX_GHC_PACKAGE_PATH_FOR_TEST is set, it become the value of GHC_PACKAGE_PATH
  #   while the test suite is executed.
  # - If it is empty, it'll be unset during test suite execution.
  # - Otherwise GHC_PACKAGE_PATH will have the package db used for configuring
  #   plus GHC's core packages.
  checkPhase = ''
    runHook preCheck
    checkFlagsArray+=(
      "--show-details=streaming"
      "--test-wrapper=${testWrapperScript}"
      ${lib.escapeShellArgs (builtins.map (opt: "--test-option=${opt}") testFlags)}
    )
    export NIX_GHC_PACKAGE_PATH_FOR_TEST="''${NIX_GHC_PACKAGE_PATH_FOR_TEST:-$packageConfDir:}"
    ${setupCommand} test ${testTarget} $checkFlags ''${checkFlagsArray:+"''${checkFlagsArray[@]}"}
    runHook postCheck
  '';

  haddockPhase = ''
    runHook preHaddock
    ${optionalString (doHaddock && isLibrary) ''
      ${setupCommand} haddock --html \
        ${optionalString doHoogle "--hoogle"} \
        ${optionalString doHaddockQuickjump "--quickjump"} \
        ${optionalString (isLibrary && hyperlinkSource) "--hyperlink-source"} \
        ${lib.concatStringsSep " " haddockFlags}
    ''}
    runHook postHaddock
  '';

  installPhase = ''
    runHook preInstall

    ${if !isLibrary && buildTarget == "" then "${setupCommand} install"
      # ^^ if the project is not a library, and no build target is specified, we can just use "install".
      else if !isLibrary then "${setupCommand} copy ${buildTarget}"
      # ^^ if the project is not a library, and we have a build target, then use "copy" to install
      # just the target specified; "install" will error here, since not all targets have been built.
    else ''
      ${setupCommand} copy ${buildTarget}
      local packageConfDir="$out/${ghcLibdir}/package.conf.d"
      local packageConfFile="$packageConfDir/${pname}-${version}.conf"
      mkdir -p "$packageConfDir"
      ${setupCommand} register --gen-pkg-config=$packageConfFile
      if [ -d "$packageConfFile" ]; then
        mv "$packageConfFile/"* "$packageConfDir"
        rmdir "$packageConfFile"
      fi
      for packageConfFile in "$packageConfDir/"*; do
        local pkgId=$(gawk -f ${unprettyConf} "$packageConfFile" \
          | grep '^id:' | cut -d' ' -f2)
        mv "$packageConfFile" "$packageConfDir/$pkgId.conf"
      done

      # delete confdir if there are no libraries
      find $packageConfDir -maxdepth 0 -empty -delete;
    ''}
    ${optionalString isGhcjs ''
      for exeDir in "${binDir}/"*.jsexe; do
        exe="''${exeDir%.jsexe}"
        printWords '#!${nodejs}/bin/node' > "$exe"
        echo >> "$exe"
        cat "$exeDir/all.js" >> "$exe"
        chmod +x "$exe"
      done
    ''}
    ${optionalString doCoverage "mkdir -p $out/share && cp -r dist/hpc $out/share"}

    ${optionalString enableSeparateDocOutput ''
    for x in ${docdir "$doc"}"/html/src/"*.html; do
      remove-references-to -t $out $x
    done
    mkdir -p $doc
    ''}
    ${optionalString enableSeparateDataOutput "mkdir -p $data"}

    runHook postInstall
  '';

  ${if doInstallIntermediates then "installIntermediatesPhase" else null} = ''
    runHook preInstallIntermediates
    intermediatesOutput=${if enableSeparateIntermediatesOutput then "$intermediates" else "$out"}
    installIntermediatesDir="$intermediatesOutput/${intermediatesDir}"
    mkdir -p "$installIntermediatesDir"
    cp -r dist/build "$installIntermediatesDir"
    runHook postInstallIntermediates
  '';

  passthru = passthru // rec {

    inherit pname version disallowGhcReference;

    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
        isHaskellPkg
        (propagatedBuildInputs ++ otherBuildInputs ++ depsBuildBuild ++ nativeBuildInputs);
    };

    isHaskellLibrary = isLibrary;

    # TODO: ask why the split outputs are configurable at all?
    # TODO: include tests for split if possible
    # Given the haskell package, returns
    # the directory containing the haddock documentation.
    # `null' if no haddock documentation was built.
    # TODO: fetch the self from the fixpoint instead
    haddockDir = self: if doHaddock then "${docdir self.doc}/html" else 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> {}); \
    #   >    haskell.packages.ghc865.hello.envFunc { buildInputs = [ python ]; }'
    envFunc = { withHoogle ? false }:
      let
        name = "ghc-shell-for-${drv.name}";

        withPackages = if withHoogle then ghcWithHoogle else ghcWithPackages;

        # We use the `ghcWithPackages` function from `buildHaskellPackages` if we
        # want a shell for the sake of cross compiling a package. In the native case
        # we don't use this at all, and instead put the setupDepends in the main
        # `ghcWithPackages`. This way we don't have two wrapper scripts called `ghc`
        # shadowing each other on the PATH.
        ghcEnvForBuild =
          assert isCross;
          buildHaskellPackages.ghcWithPackages (_: setupHaskellDepends);

        ghcEnv = withPackages (_:
          otherBuildInputsHaskell ++
          propagatedBuildInputs ++
          lib.optionals (!isCross) setupHaskellDepends);

        ghcCommandCaps = lib.toUpper ghcCommand';
      in stdenv.mkDerivation {
        inherit name shellHook;

        depsBuildBuild = lib.optional isCross ghcEnvForBuild;
        nativeBuildInputs =
          [ ghcEnv ] ++ optional (allPkgconfigDepends != []) pkg-config ++
          collectedToolDepends;
        buildInputs =
          otherBuildInputsSystem;
        phases = ["installPhase"];
        installPhase = "echo $nativeBuildInputs $buildInputs > $out";
        LANG = "en_US.UTF-8";
        LOCALE_ARCHIVE = lib.optionalString (stdenv.hostPlatform.libc == "glibc") "${buildPackages.glibcLocales}/lib/locale/locale-archive";
        "NIX_${ghcCommandCaps}" = "${ghcEnv}/bin/${ghcCommand}";
        "NIX_${ghcCommandCaps}PKG" = "${ghcEnv}/bin/${ghcCommand}-pkg";
        # TODO: is this still valid?
        "NIX_${ghcCommandCaps}_DOCDIR" = "${ghcEnv}/share/doc/ghc/html";
        "NIX_${ghcCommandCaps}_LIBDIR" = if ghc.isHaLVM or false
          then "${ghcEnv}/lib/HaLVM-${ghc.version}"
          else "${ghcEnv}/${ghcLibdir}";
      };

    env = envFunc { };

  };

  meta = { inherit homepage license platforms; }
         // optionalAttrs (args ? broken)         { inherit broken; }
         // optionalAttrs (args ? description)    { inherit description; }
         // optionalAttrs (args ? maintainers)    { inherit maintainers; }
         // optionalAttrs (args ? hydraPlatforms) { inherit hydraPlatforms; }
         // optionalAttrs (args ? badPlatforms)   { inherit badPlatforms; }
         // optionalAttrs (args ? changelog)      { inherit changelog; }
         // optionalAttrs (args ? mainProgram)    { inherit mainProgram; }
         ;

}
// optionalAttrs (args ? preCompileBuildDriver)  { inherit preCompileBuildDriver; }
// optionalAttrs (args ? postCompileBuildDriver) { inherit postCompileBuildDriver; }
// optionalAttrs (args ? preUnpack)              { inherit preUnpack; }
// optionalAttrs (args ? postUnpack)             { inherit postUnpack; }
// optionalAttrs (args ? patches)                { inherit patches; }
// optionalAttrs (args ? patchPhase)             { inherit patchPhase; }
// optionalAttrs (args ? preConfigure)           { inherit preConfigure; }
// optionalAttrs (args ? postConfigure)          { inherit postConfigure; }
// optionalAttrs (args ? preBuild)               { inherit preBuild; }
// optionalAttrs (args ? postBuild)              { inherit postBuild; }
// optionalAttrs (args ? doBenchmark)            { inherit doBenchmark; }
// optionalAttrs (args ? checkPhase)             { inherit checkPhase; }
// optionalAttrs (args ? preCheck)               { inherit preCheck; }
// optionalAttrs (args ? postCheck)              { inherit postCheck; }
// optionalAttrs (args ? preHaddock)             { inherit preHaddock; }
// optionalAttrs (args ? postHaddock)            { inherit postHaddock; }
// optionalAttrs (args ? preInstall)             { inherit preInstall; }
// optionalAttrs (args ? installPhase)           { inherit installPhase; }
// optionalAttrs (args ? postInstall)            { inherit postInstall; }
// optionalAttrs (args ? preFixup)               { inherit preFixup; }
// optionalAttrs (args ? postFixup)              { inherit postFixup; }
// optionalAttrs (args ? dontStrip)              { inherit dontStrip; }
// optionalAttrs (postPhases != [])              { inherit postPhases; }
// optionalAttrs (stdenv.buildPlatform.libc == "glibc"){ LOCALE_ARCHIVE = "${glibcLocales}/lib/locale/locale-archive"; }
// optionalAttrs (disallowedRequisites != [] || disallowGhcReference) {
  disallowedRequisites =
    disallowedRequisites
    ++ (
      if disallowGhcReference
      then [ghc]
      else []
    );
}

# Implicit pointer to integer conversions are errors by default since clang 15.
# Works around https://gitlab.haskell.org/ghc/ghc/-/issues/23456.
// optionalAttrs (stdenv.hasCC && stdenv.cc.isClang) {
  NIX_CFLAGS_COMPILE = "-Wno-error=int-conversion";
}
)
)
