From 17eee57642f0cd010176767a873c42d34167cde4 Mon Sep 17 00:00:00 2001 From: "(cdep)illabout" Date: Tue, 3 Nov 2020 16:45:15 +0900 Subject: [PATCH] haskellPackages.shellFor: improve documentation --- .../haskell-modules/make-package-set.nix | 156 +++++++++++++++--- 1 file changed, 131 insertions(+), 25 deletions(-) diff --git a/pkgs/development/haskell-modules/make-package-set.nix b/pkgs/development/haskell-modules/make-package-set.nix index 1418cfef0574..c5604aa41962 100644 --- a/pkgs/development/haskell-modules/make-package-set.nix +++ b/pkgs/development/haskell-modules/make-package-set.nix @@ -258,6 +258,7 @@ 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 # @@ -274,7 +275,7 @@ in package-set { inherit pkgs stdenv callPackage; } self // { # (import ./.).shellFor { # packages = p: [p.frontend p.backend p.common]; # withHoogle = true; - # buildInputs = [ pkgs.python ]; + # buildInputs = [ pkgs.python pkgs.cabal-install ]; # } # # -- cabal.project @@ -285,38 +286,143 @@ in package-set { inherit pkgs stdenv callPackage; } self // { # # bash$ nix-shell --run "cabal new-build all" # bash$ nix-shell --run "python" - shellFor = { packages, withHoogle ? false, ... } @ args: + shellFor = + { # Packages to create this development shell for. These are usually + # your local packages. + packages + , # Whether or not to generated a Hoogle database for all the + # dependencies. + withHoogle ? false + , ... + } @ args: let - combinedPackageFor = packages: - let - selected = packages self; + # A list of the packages we want to build a development shell for. + # + # This is a list of Haskell package derivations. + selected = packages self; - pname = if pkgs.lib.length selected == 1 - then (pkgs.lib.head selected).name - else "packages"; + # This is a list of attribute sets, where the each attribute set + # corresponds to the build inputs one of the packages input to shellFor. + # + # Each attribute has keys like buildDepends, executableHaskellDepends, + # testPkgconfigDepends, etc. The values for the keys of the attribute + # set are lists of dependencies. + # + # Example: + # cabalDepsForSelected + # => [ + # # This may be the attribute set corresponding to the `backend` + # # package in the example above. + # { buildDepends = [ gcc ... ]; + # libraryHaskellDepends = [ lens conduit ... ]; + # ... + # } + # # This may be the attribute set corresponding to the `common` + # # package in the example above. + # { testHaskellDepends = [ tasty hspec ... ]; + # libraryHaskellDepends = [ lens aeson ]; + # benchmarkHaskellDepends = [ criterion ... ]; + # ... + # } + # ... + # ] + cabalDepsForSelected = map (p: p.getCabalDeps) 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. - combinedPackages = pkgs.lib.filter - (input: pkgs.lib.all (p: input.outPath or null != p.outPath) selected); + # A predicate that takes a derivation as input, and tests whether it is + # the same as any of the `selected` packages. + # + # Returns true if the input derivation is not in the list of `selected` + # packages. + # + # isNotSelected :: Derivation -> Bool + # + # Example: + # + # isNotSelected common [ frontend backend common ] + # => false + # + # isNotSelected lens [ frontend backend common ] + # => true + isNotSelected = 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); + # A function that takes a list of list of derivations, filters out all + # the `selected` packages from each list, and concats the results. + # + # zipperCombinedPkgs :: [[Derivation]] -> [Derivation] + # + # Example: + # zipperCombinedPkgs [ [ lens conduit ] [ aeson frontend ] ] + # => [ lens conduit aeson ] + # + # Note: The reason this isn't just the function `pkgs.lib.concat` is + # that we need to be careful to remove dependencies that are in the + # `selected` packages. + # + # For instance, in the above example, if `common` is a dependency of + # `backend`, then zipperCombinedPkgs needs to be careful to filter out + # `common`, because cabal will end up ignoring that built version, + # assuming new-style commands. + zipperCombinedPkgs = vals: + pkgs.lib.concatMap + (drvList: pkgs.lib.filter isNotSelected drvList) + vals; - genericBuilderArgs = { - inherit pname; - version = "0"; - license = null; - } // packageInputs; + # Zip `cabalDepsForSelected` into a single attribute list, combining + # the derivations in all the individual attributes. + # + # Example: + # packageInputs + # => # Assuming the value of cabalDepsForSelected is the same as + # # the example in cabalDepsForSelected: + # { buildDepends = [ gcc ... ]; + # libraryHaskellDepends = [ lens conduit aeson ... ]; + # testHaskellDepends = [ tasty hspec ... ]; + # benchmarkHaskellDepends = [ criterion ... ]; + # ... + # } + # + # See the Note in `zipperCombinedPkgs` for what gets filtered out from + # each of these dependency lists. + packageInputs = + pkgs.lib.zipAttrsWith (_name: zipperCombinedPkgs) cabalDepsForSelected; - in self.mkDerivation genericBuilderArgs; + # A attribute set to pass to `haskellPackages.mkDerivation`. + # + # The important thing to note here is that all the fields from + # packageInputs are set correctly. + genericBuilderArgs = { + pname = + if pkgs.lib.length selected == 1 + then (pkgs.lib.head selected).name + else "packages"; + version = "0"; + license = null; + } + // packageInputs; + + # This is a pseudo Haskell package derivation that contains all the + # dependencies for the packages in `selected`. + # + # This is a derivation created with `haskellPackages.mkDerivation`. + # + # pkgWithCombinedDeps :: HaskellDerivation + pkgWithCombinedDeps = self.mkDerivation genericBuilderArgs; + + # The derivation returned from `envFunc` for `pkgWithCombinedDeps`. + # + # This is a derivation that can be run with `nix-shell`. It provides a + # GHC with a package database with all the dependencies of our + # `selected` packages. + # + # This is a derivation created with `stdenv.mkDerivation` (not + # `haskellPackages.mkDerivation`). + # + # pkgWithCombinedDepsDevDrv :: Derivation + pkgWithCombinedDepsDevDrv = pkgWithCombinedDeps.envFunc { inherit withHoogle; }; mkDerivationArgs = builtins.removeAttrs args [ "packages" "withHoogle" ]; - in ((combinedPackageFor packages).envFunc { inherit withHoogle; }).overrideAttrs (old: mkDerivationArgs // { + + in pkgWithCombinedDepsDevDrv.overrideAttrs (old: mkDerivationArgs // { nativeBuildInputs = old.nativeBuildInputs ++ mkDerivationArgs.nativeBuildInputs or []; buildInputs = old.buildInputs ++ mkDerivationArgs.buildInputs or []; });