summary refs log tree commit diff
path: root/pkgs/development/haskell-modules
diff options
context:
space:
mode:
authorJacquin Mininger <jacquin.mininger@gmail.com>2019-12-23 15:33:18 -0500
committerJohn Ericson <John.Ericson@Obsidian.Systems>2020-01-17 10:46:29 -0500
commit7d67db39199130aa75bb3315e991830e2a988258 (patch)
tree73b8bff21d46d60e4fbcc97500ad343f2ff47ca3 /pkgs/development/haskell-modules
parentba9066abcaace69b8ed36ef43a8b82d04a6e0e5b (diff)
downloadnixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar.gz
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar.bz2
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar.lz
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar.xz
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.tar.zst
nixpkgs-7d67db39199130aa75bb3315e991830e2a988258.zip
shellFor: Refactor for consistency and cross
This makes it work like work-on-multi from Reflex Platform. In
particular, rather than making `.env` from `shellFor`, we make `.env`
the primitive, and `shellFor` works by combining together the arguments
of all the packages to `generic-builder` and taking the `.env` of the
resulting mashup-package.

There are 2 benefits of this:

1. The dependency logic is deduplicated. generic builder just concatted
   lists, whereas all the envs until now would sieve apart haskell and
   system build inputs. Now, they both decide haskell vs system the same
   way: according to the argument list and without reflection.
   Consistency is good, especially because it mean that if the build
   works, the shell is more likely to work.

2. Cross is handled better. For native builds, because the
   `ghcWithPackages` calls would shadow, we through both the regular
   component (lib, exe, test, bench) haskell deps and Setup.hs haskell
   deps in the same `ghcWithPackages` call. But for cross builds we use
   `buildPackages.ghcWithPackages` to get the setup deps. This ensures
   everything works correctly.
Diffstat (limited to 'pkgs/development/haskell-modules')
-rw-r--r--pkgs/development/haskell-modules/generic-builder.nix148
-rw-r--r--pkgs/development/haskell-modules/make-package-set.nix82
2 files changed, 166 insertions, 64 deletions
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 <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 ++
+          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 <nixpkgs> {};
@@ -265,9 +267,11 @@ in package-set { inherit pkgs stdenv callPackage; } self // {
     #     })
     #
     #     # shell.nix
+    #     let pkgs = import <nixpkgs> {} 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;
-
-        packageInputs = map getBuildInputs selected;
-
-        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.
-        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;
-
-        withPackages = if withHoogle then self.ghcWithHoogle else self.ghcWithPackages;
-        ghcEnv = withPackages (p: haskellInputs);
-        nativeBuildInputs = pkgs.lib.concatMap (p: p.nativeBuildInputs) selected;
-
-        ghcCommand' = if ghc.isGhcjs or false then "ghcjs" else "ghc";
-        ghcCommand = "${ghc.targetPrefix}${ghcCommand'}";
-        ghcCommandCaps= pkgs.lib.toUpper ghcCommand';
-
-        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}";
+        combinedPackageFor = packages:
+          let
+            selected = packages self;
+
+            pname = if pkgs.lib.length selected == 1
+              then (pkgs.lib.head selected).name
+              else "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);
+
+            # 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);
+
+            genericBuilderArgs = {
+              inherit pname;
+              version = "0";
+              license = null;
+            } // packageInputs;
+
+          in self.mkDerivation genericBuilderArgs;
+
+        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 // {