diff --git a/pkgs/development/haskell-modules/generic-builder.nix b/pkgs/development/haskell-modules/generic-builder.nix index 018979cf7ae..e2ac61a1416 100644 --- a/pkgs/development/haskell-modules/generic-builder.nix +++ b/pkgs/development/haskell-modules/generic-builder.nix @@ -1,5 +1,6 @@ { stdenv, buildPackages, buildHaskellPackages, ghc -, jailbreak-cabal, hscolour, cpphs, nodejs, shellFor +, jailbreak-cabal, hscolour, cpphs, nodejs +, ghcWithHoogle, ghcWithPackages }: let @@ -205,21 +206,28 @@ let optionals doCheck testPkgconfigDepends ++ optionals doBenchmark benchmarkPkgconfigDepends; depsBuildBuild = [ nativeGhc ]; - nativeBuildInputs = [ ghc removeReferencesTo ] ++ optional (allPkgconfigDepends != []) pkgconfig ++ - setupHaskellDepends ++ - buildTools ++ libraryToolDepends ++ executableToolDepends ++ - optionals doCheck testToolDepends ++ - optionals doBenchmark benchmarkToolDepends; + collectedToolDepends = + buildTools ++ libraryToolDepends ++ executableToolDepends ++ + optionals doCheck testToolDepends ++ + optionals doBenchmark benchmarkToolDepends; + nativeBuildInputs = + [ ghc removeReferencesTo ] ++ optional (allPkgconfigDepends != []) pkgconfig ++ + setupHaskellDepends ++ collectedToolDepends; propagatedBuildInputs = buildDepends ++ libraryHaskellDepends ++ executableHaskellDepends ++ libraryFrameworkDepends; - otherBuildInputs = extraLibraries ++ librarySystemDepends ++ executableSystemDepends ++ executableFrameworkDepends ++ - allPkgconfigDepends ++ - optionals doCheck (testDepends ++ testHaskellDepends ++ testSystemDepends ++ testFrameworkDepends) ++ - optionals doBenchmark (benchmarkDepends ++ benchmarkHaskellDepends ++ benchmarkSystemDepends ++ benchmarkFrameworkDepends); - - - allBuildInputs = propagatedBuildInputs ++ otherBuildInputs ++ depsBuildBuild ++ nativeBuildInputs; - isHaskellPartition = - stdenv.lib.partition isHaskellPkg allBuildInputs; + 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"; @@ -460,17 +468,61 @@ stdenv.mkDerivation ({ runHook postInstall ''; - passthru = passthru // { + 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 + pkgconfigDepends + setupHaskellDepends + ; + } // stdenv.lib.optionalAttrs doCheck { + inherit + testDepends + testFrameworkDepends + testHaskellDepends + testPkgconfigDepends + testSystemDepends + testToolDepends + ; + } // stdenv.lib.optionalAttrs doBenchmark { + inherit + benchmarkDepends + benchmarkFrameworkDepends + benchmarkHaskellDepends + benchmarkPkgconfigDepends + benchmarkSystemDepends + benchmarkToolDepends + ; + }; - getBuildInputs = { + # 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 = stdenv.lib.partition + isHaskellPkg + (propagatedBuildInputs ++ otherBuildInputs ++ depsBuildBuild ++ nativeBuildInputs); }; isHaskellLibrary = isLibrary; @@ -483,10 +535,64 @@ stdenv.mkDerivation ({ # TODO: fetch the self from the fixpoint instead haddockDir = self: if doHaddock then "${docdir self.doc}/html" else null; - env = shellFor { - packages = p: [ drv ]; - inherit shellHook; - }; + # 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 {}); \ + # > 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 ++ + stdenv.lib.optionals (!isCross) setupHaskellDepends); + + ghcCommandCaps = stdenv.lib.toUpper ghcCommand'; + in stdenv.mkDerivation ({ + inherit name shellHook; + + depsBuildBuild = stdenv.lib.optional isCross ghcEnvForBuild; + nativeBuildInputs = + [ ghcEnv ] ++ optional (allPkgconfigDepends != []) pkgconfig ++ + collectedToolDepends; + buildInputs = + otherBuildInputsSystem; + phases = ["installPhase"]; + installPhase = "echo $nativeBuildInputs $buildInputs > $out"; + LANG = "en_US.UTF-8"; + LOCALE_ARCHIVE = stdenv.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}/lib/${ghcCommand}-${ghc.version}"; + }); + + env = envFunc { }; }; diff --git a/pkgs/development/haskell-modules/make-package-set.nix b/pkgs/development/haskell-modules/make-package-set.nix index a4c04067348..d23df0df317 100644 --- a/pkgs/development/haskell-modules/make-package-set.nix +++ b/pkgs/development/haskell-modules/make-package-set.nix @@ -38,12 +38,12 @@ let inherit (stdenv) buildPlatform hostPlatform; inherit (stdenv.lib) fix' extends makeOverridable; - inherit (haskellLib) overrideCabal getBuildInputs; + inherit (haskellLib) overrideCabal; mkDerivationImpl = pkgs.callPackage ./generic-builder.nix { inherit stdenv; nodejs = buildPackages.nodejs-slim; - inherit (self) buildHaskellPackages ghc shellFor; + inherit (self) buildHaskellPackages ghc ghcWithHoogle ghcWithPackages; inherit (self.buildHaskellPackages) jailbreak-cabal; hscolour = overrideCabal self.buildHaskellPackages.hscolour (drv: { isLibrary = false; @@ -255,6 +255,8 @@ in package-set { inherit pkgs stdenv callPackage; } self // { # packages themselves. Using nix-shell on this derivation will # give you an environment suitable for developing the listed # packages with an incremental tool like cabal-install. + # In addition to the "packages" arg and "withHoogle" arg, anything that + # can be passed into stdenv.mkDerivation can be included in the input attrset # # # default.nix # with import {}; @@ -265,9 +267,11 @@ in package-set { inherit pkgs stdenv callPackage; } self // { # }) # # # shell.nix + # let pkgs = import {} in # (import ./.).shellFor { # packages = p: [p.frontend p.backend p.common]; # withHoogle = true; + # buildInputs = [ pkgs.python ]; # } # # -- cabal.project @@ -277,49 +281,41 @@ in package-set { inherit pkgs stdenv callPackage; } self // { # common/ # # bash$ nix-shell --run "cabal new-build all" + # bash$ nix-shell --run "python" shellFor = { packages, withHoogle ? false, ... } @ args: let - selected = packages self; + combinedPackageFor = packages: + let + selected = packages self; - packageInputs = map getBuildInputs selected; + pname = if pkgs.lib.length selected == 1 + then (pkgs.lib.head selected).name + else "packages"; - name = if pkgs.lib.length selected == 1 - then "ghc-shell-for-${(pkgs.lib.head selected).name}" - else "ghc-shell-for-packages"; + # If `packages = [ a b ]` and `a` depends on `b`, don't build `b`, + # because cabal will end up ignoring that built version, assuming + # new-style commands. + combinedPackages = pkgs.lib.filter + (input: pkgs.lib.all (p: input.outPath or null != p.outPath) selected); - # If `packages = [ a b ]` and `a` depends on `b`, don't build `b`, - # because cabal will end up ignoring that built version, assuming - # new-style commands. - haskellInputs = pkgs.lib.filter - (input: pkgs.lib.all (p: input.outPath != p.outPath) selected) - (pkgs.lib.concatMap (p: p.haskellBuildInputs) packageInputs); - systemInputs = pkgs.lib.concatMap (p: p.systemBuildInputs) packageInputs; + # Returns an attrset containing a combined list packages' inputs for each + # stage of the build process + packageInputs = pkgs.lib.zipAttrsWith + (_: pkgs.lib.concatMap combinedPackages) + (map (p: p.getCabalDeps) selected); - withPackages = if withHoogle then self.ghcWithHoogle else self.ghcWithPackages; - ghcEnv = withPackages (p: haskellInputs); - nativeBuildInputs = pkgs.lib.concatMap (p: p.nativeBuildInputs) selected; + genericBuilderArgs = { + inherit pname; + version = "0"; + license = null; + } // packageInputs; - ghcCommand' = if ghc.isGhcjs or false then "ghcjs" else "ghc"; - ghcCommand = "${ghc.targetPrefix}${ghcCommand'}"; - ghcCommandCaps= pkgs.lib.toUpper ghcCommand'; + in self.mkDerivation genericBuilderArgs; - mkDrvArgs = builtins.removeAttrs args ["packages" "withHoogle"]; - in pkgs.stdenv.mkDerivation (mkDrvArgs // { - name = mkDrvArgs.name or name; - - buildInputs = systemInputs ++ mkDrvArgs.buildInputs or []; - nativeBuildInputs = [ ghcEnv ] ++ nativeBuildInputs ++ mkDrvArgs.nativeBuildInputs or []; - phases = ["installPhase"]; - installPhase = "echo $nativeBuildInputs $buildInputs > $out"; - LANG = "en_US.UTF-8"; - LOCALE_ARCHIVE = pkgs.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}/lib/${ghcCommand}-${ghc.version}"; + envFuncArgs = builtins.removeAttrs args [ "packages" ]; + in (combinedPackageFor packages).env.overrideAttrs (old: envFuncArgs // { + nativeBuildInputs = old.nativeBuildInputs ++ envFuncArgs.nativeBuildInputs or []; + buildInputs = old.buildInputs ++ envFuncArgs.buildInputs or []; }); ghc = ghc // { diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 793229f8bbb..da7e8a816cd 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -22,6 +22,8 @@ with pkgs; cc-wrapper-libcxx-7 = callPackage ./cc-wrapper { stdenv = llvmPackages_7.libcxxStdenv; }; stdenv-inputs = callPackage ./stdenv-inputs { }; + haskell-shellFor = callPackage ./haskell-shellFor { }; + cc-multilib-gcc = callPackage ./cc-wrapper/multilib.nix { stdenv = gccMultiStdenv; }; cc-multilib-clang = callPackage ./cc-wrapper/multilib.nix { stdenv = clangMultiStdenv; }; diff --git a/pkgs/test/haskell-shellFor/default.nix b/pkgs/test/haskell-shellFor/default.nix new file mode 100644 index 00000000000..1b3de999d22 --- /dev/null +++ b/pkgs/test/haskell-shellFor/default.nix @@ -0,0 +1,24 @@ +{ stdenv, haskellPackages, cabal-install }: + +haskellPackages.shellFor { + packages = p: [ p.database-id-class p.constraints-extras ]; + nativeBuildInputs = [ cabal-install ]; + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; + unpackPhase = '' + sourceRoot=$(pwd)/scratch + mkdir -p "$sourceRoot" + cd "$sourceRoot" + tar -xf ${haskellPackages.database-id-class.src} + tar -xf ${haskellPackages.constraints-extras.src} + cp ${builtins.toFile "cabal.project" "packages: database-id-class* constraints-extras*"} cabal.project + ''; + buildPhase = '' + export HOME=$(mktemp -d) + mkdir -p $HOME/.cabal + touch $HOME/.cabal/config + cabal v2-build --offline --verbose database-id-class constraints-extras --ghc-options="-O0 -j$NIX_BUILD_CORES" + ''; + installPhase = '' + touch $out + ''; +}