diff --git a/pkgs/development/haskell-modules/configuration-common.nix b/pkgs/development/haskell-modules/configuration-common.nix index cdb15fd9f814..5ab9ed11469d 100644 --- a/pkgs/development/haskell-modules/configuration-common.nix +++ b/pkgs/development/haskell-modules/configuration-common.nix @@ -899,4 +899,8 @@ self: super: { sha256 = "1vss7b99zrhw3r29krl1b60r4qk0m2mpwmrz8q8zdxrh33hb8pd7"; }); + # Has extra data files which are referred to from the binary output, + # creating a store reference cycle. Putting data in separate output + # solves the problem. + happy = overrideCabal super.happy (drv: { enableSeparateDataOutput = true; }); } diff --git a/pkgs/development/haskell-modules/generic-builder.nix b/pkgs/development/haskell-modules/generic-builder.nix index 60cce56cca02..7beafe4ce1ff 100644 --- a/pkgs/development/haskell-modules/generic-builder.nix +++ b/pkgs/development/haskell-modules/generic-builder.nix @@ -56,6 +56,10 @@ let isCross = (ghc.cross or null) != null; in , hardeningDisable ? lib.optional (ghc.isHaLVM or false) "all" , enableSeparateDataOutput ? false , enableSeparateDocOutput ? doHaddock +, enableSeparateBinOutput ? isExecutable +, outputsToInstall ? [] +, enableSeparateLibOutput ? true +, enableSeparateEtcOutput ? (stdenv.lib.versionOlder "7.7" ghc.version) } @ args: assert editedCabalFile != null -> revision != null; @@ -79,9 +83,6 @@ let then "package-db" else "package-conf"; - # the target dir for haddock documentation - docdir = docoutput: docoutput + "/share/doc"; - newCabalFileUrl = "http://hackage.haskell.org/package/${pname}-${version}/revision/${revision}.cabal"; newCabalFile = fetchurl { url = newCabalFileUrl; @@ -95,6 +96,13 @@ let ''; hasActiveLibrary = isLibrary && (enableStaticLibraries || enableSharedLibraries || enableLibraryProfiling); + hasLibOutput = enableSeparateLibOutput && hasActiveLibrary; + libDir = if hasLibOutput then "$lib/lib/${ghc.name}" else "$out/lib/${ghc.name}"; + binDir = if enableSeparateBinOutput then "$bin/bin" else "$out/bin"; + libexecDir = if enableSeparateBinOutput then "$libexec/bin" else "$out/libexec"; + etcDir = if enableSeparateEtcOutput then "$etc/etc" else "$out/etc"; + docDir = if enableSeparateDocOutput then "$doc/share/doc" else "$out/share/doc"; + dataDir = if enableSeparateDataOutput then "$data/share/${ghc.name}" else "$out/share/${ghc.name}"; # We cannot enable -j parallelism for libraries because GHC is far more # likely to generate a non-determistic library ID in that case. Further @@ -113,12 +121,20 @@ let stdenv.lib.optionalString isCross (" " + stdenv.lib.concatStringsSep " " crossCabalFlags); defaultConfigureFlags = [ - "--verbose" "--prefix=$out" "--libdir=\\$prefix/lib/\\$compiler" "--libsubdir=\\$pkgid" - (optionalString enableSeparateDataOutput "--datadir=$data/share/${ghc.name}") - (optionalString enableSeparateDocOutput "--docdir=${docdir "$doc"}") + "--verbose" "--prefix=$out" + # Binary directory has to be $bin/bin instead of just $bin: this + # is so that the package is added to the PATH when it's used as a + # build input. Sadly mkDerivation won't add inputs that don't have + # bin subdirectory. + "--bindir=${binDir}" + "--libdir=${libDir}" "--libsubdir=\\$pkgid" + "--libexecdir=${libexecDir}" + (optionalString (enableSeparateEtcOutput) "--sysconfdir=${etcDir}") # Old versions of cabal don't support this flag. + "--datadir=${dataDir}" + "--docdir=${docDir}" "--with-gcc=$CC" # Clang won't work without that extra information. "--package-db=$packageConfDir" - (optionalString (enableSharedExecutables && stdenv.isLinux) "--ghc-option=-optl=-Wl,-rpath=$out/lib/${ghc.name}/${pname}-${version}") + (optionalString (enableSharedExecutables && stdenv.isLinux) "--ghc-option=-optl=-Wl,-rpath=${libDir}/${pname}-${version}") (optionalString (enableSharedExecutables && stdenv.isDarwin) "--ghc-option=-optl=-Wl,-headerpad_max_install_names") (optionalString enableParallelBuilding "--ghc-option=-j$NIX_BUILD_CORES") (optionalString useCpphs "--with-cpphs=${cpphs}/bin/cpphs --ghc-options=-cpp --ghc-options=-pgmP${cpphs}/bin/cpphs --ghc-options=-optP--cpp") @@ -152,7 +168,8 @@ let allPkgconfigDepends = pkgconfigDepends ++ libraryPkgconfigDepends ++ executablePkgconfigDepends ++ optionals doCheck testPkgconfigDepends ++ optionals withBenchmarkDepends benchmarkPkgconfigDepends; - nativeBuildInputs = buildTools ++ libraryToolDepends ++ executableToolDepends ++ [ removeReferencesTo ]; + nativeBuildInputs = map stdenv.lib.getBin + (buildTools ++ libraryToolDepends ++ executableToolDepends ++ [ removeReferencesTo ]); propagatedBuildInputs = buildDepends ++ libraryHaskellDepends ++ executableHaskellDepends; otherBuildInputs = setupHaskellDepends ++ extraLibraries ++ librarySystemDepends ++ executableSystemDepends ++ optionals (allPkgconfigDepends != []) ([pkgconfig] ++ allPkgconfigDepends) ++ @@ -181,7 +198,15 @@ assert allPkgconfigDepends != [] -> pkgconfig != null; stdenv.mkDerivation ({ name = "${pname}-${version}"; - outputs = if (args ? outputs) then args.outputs else ([ "out" ] ++ (optional enableSeparateDataOutput "data") ++ (optional enableSeparateDocOutput "doc")); + outputs = if (args ? outputs) then args.outputs else + ( (optional enableSeparateBinOutput "bin") + ++ (optional enableSeparateBinOutput "libexec") + ++ [ "out" ] + ++ (optional enableSeparateDataOutput "data") + ++ (optional enableSeparateDocOutput "doc") + ++ (optional enableSeparateEtcOutput "etc") + ++ (optional hasLibOutput "lib") + ); setOutputFlags = false; pos = builtins.unsafeGetAttrPos "pname" args; @@ -205,7 +230,7 @@ stdenv.mkDerivation ({ postPatch = optionalString jailbreak '' echo "Run jailbreak-cabal to lift version restrictions on build inputs." - ${jailbreak-cabal}/bin/jailbreak-cabal ${pname}.cabal + ${stdenv.lib.getBin jailbreak-cabal}/bin/jailbreak-cabal ${pname}.cabal '' + postPatch; setupCompilerEnvironmentPhase = '' @@ -213,7 +238,7 @@ stdenv.mkDerivation ({ echo "Build with ${ghc}." export PATH="${ghc}/bin:$PATH" - ${optionalString (hasActiveLibrary && hyperlinkSource) "export PATH=${hscolour}/bin:$PATH"} + ${optionalString (hasActiveLibrary && hyperlinkSource) "export PATH=${hscolour.bin}/bin:$PATH"} packageConfDir="$TMPDIR/package.conf.d" mkdir -p $packageConfDir @@ -240,7 +265,7 @@ stdenv.mkDerivation ({ # # Create a local directory with symlinks of the *.dylib (macOS shared # libraries) from all the dependencies. - local dynamicLinksDir="$out/lib/links" + local dynamicLinksDir="${libDir}/links" mkdir -p $dynamicLinksDir for d in $(grep dynamic-library-dirs "$packageConfDir/"*|awk '{print $2}'); do ln -s "$d/"*.dylib $dynamicLinksDir @@ -312,7 +337,7 @@ stdenv.mkDerivation ({ ${if !hasActiveLibrary then "${setupCommand} install" else '' ${setupCommand} copy - local packageConfDir="$out/lib/${ghc.name}/package.conf.d" + local packageConfDir="${libDir}/package.conf.d" local packageConfFile="$packageConfDir/${pname}-${version}.conf" mkdir -p "$packageConfDir" ${setupCommand} register --gen-pkg-config=$packageConfFile @@ -320,7 +345,7 @@ stdenv.mkDerivation ({ mv $packageConfFile $packageConfDir/$pkgId.conf ''} ${optionalString isGhcjs '' - for exeDir in "$out/bin/"*.jsexe; do + for exeDir in "${binDir}/"*.jsexe; do exe="''${exeDir%.jsexe}" printWords '#!${nodejs}/bin/node' > "$exe" cat "$exeDir/all.js" >> "$exe" @@ -329,18 +354,68 @@ stdenv.mkDerivation ({ ''} ${optionalString doCoverage "mkdir -p $out/share && cp -r dist/hpc $out/share"} ${optionalString (enableSharedExecutables && isExecutable && !isGhcjs && stdenv.isDarwin && stdenv.lib.versionOlder ghc.version "7.10") '' - for exe in "$out/bin/"* ; do - install_name_tool -add_rpath "$out/lib/ghc-${ghc.version}/${pname}-${version}" "$exe" + for exe in "${binDir}/"* ; do + install_name_tool -add_rpath "${libDir}/${pname}-${version}" "$exe" done ''} ${optionalString enableSeparateDocOutput '' - for x in ${docdir "$doc"}/html/src/*.html; do - remove-references-to -t $out $x + # Remove references back to $out but also back to $lib if we have + # docs. $lib is needed as it stores path to haddock interfaces in the + # conf file which creates a cycle if docs refer back to library + # path. + mkdir -p ${docDir} + + for x in ${docDir}/html/src/*.html; do + remove-references-to -t $out -t ${libDir} -t ${binDir} ${optionalString enableSeparateDataOutput "-t $data"} $x done - mkdir -p $doc ''} - ${optionalString enableSeparateDataOutput "mkdir -p $data"} + + ${optionalString hasLibOutput '' + # Even if we don't have binary output for the package, things like + # Paths files will embed paths to bin/libexec directories in themselves + # which results in .lib <-> $out cyclic store reference. We + # therefore patch out the paths from separate library if we don't have + # separate bin output too. + # + # If we _do_ have separate bin and lib outputs, we may still be in + # trouble in case of shared executables: executable contains path to + # .lib, .lib contains path (through Paths) to .bin and we have a + # cycle. + # + # Lastly we have to deal with references from .lib back into + # $out/share if we're not splitting out data directory. + # + # It may happen that we have hasLibOutput set but the library + # directory was not created: this happens in the case that library + # section is not exposing any modules. See "fail" package for an + # example where no modules are exposed for GHC >= 8.0. + if [ -d ${libDir} ]; then + find ${libDir} -type f -exec \ + remove-references-to -t ${binDir} -t ${libexecDir} "{}" \; + fi + ''} + + ${optionalString (hasLibOutput && ! enableSeparateDocOutput) '' + # If we don't have separate docs, we have to patch out the ref to + # docs in package conf. This will likely break Haddock + # cross-package links but is necessary to break store cycle… + find ${libDir}/ -type f -name '*.conf' -exec \ + remove-references-to -t ${docDir} "{}" \; + ''} + + ${optionalString (hasLibOutput && ! enableSeparateDataOutput) '' + # Just like for doc output path in $out potentially landing in + # *.conf, we have to also remove the data directory so that it + # doesn't appear under data-dir field creating a cycle. + find ${libDir}/ -type f -exec echo Removing ${dataDir} refs from "{}" \; + find ${libDir}/ -type f -exec \ + remove-references-to -t ${dataDir} "{}" \; + ''} + + ${optionalString enableSeparateDataOutput "mkdir -p ${dataDir}"} + ${optionalString enableSeparateBinOutput "mkdir -p ${binDir} ${libexecDir}"} + ${optionalString enableSeparateEtcOutput "mkdir -p ${etcDir}"} runHook postInstall ''; @@ -386,6 +461,7 @@ stdenv.mkDerivation ({ // optionalAttrs (description != "") { inherit description; } // optionalAttrs (maintainers != []) { inherit maintainers; } // optionalAttrs (hydraPlatforms != platforms) { inherit hydraPlatforms; } + // optionalAttrs (outputsToInstall != []) { inherit outputsToInstall; } ; } diff --git a/pkgs/development/haskell-modules/lib.nix b/pkgs/development/haskell-modules/lib.nix index 48110cffabf8..518c3c82e6cd 100644 --- a/pkgs/development/haskell-modules/lib.nix +++ b/pkgs/development/haskell-modules/lib.nix @@ -142,4 +142,6 @@ rec { overrideSrc = drv: { src, version ? drv.version }: overrideCabal drv (_: { inherit src version; editedCabalFile = null; }); + installOutputs = drv: outputs: overrideCabal drv + (drv: { outputsToInstall = outputs; }); }