From 9b5bb97eb79232a4daf5bdd60a2e950940a4d29d Mon Sep 17 00:00:00 2001 From: Dennis Gosnell Date: Wed, 5 Mar 2025 18:21:12 +0900 Subject: [PATCH 1/3] Add some passthru values for Haskell derivations created by stacklock2nix --- nix/build-support/stacklock2nix/default.nix | 139 ++++++++++++++++---- test/nixpkgs.nix | 2 + test/test-all-cabal-hashes-is-dir.nix | 39 ++++++ 3 files changed, 156 insertions(+), 24 deletions(-) diff --git a/nix/build-support/stacklock2nix/default.nix b/nix/build-support/stacklock2nix/default.nix index be52e26..c905ce2 100644 --- a/nix/build-support/stacklock2nix/default.nix +++ b/nix/build-support/stacklock2nix/default.nix @@ -299,6 +299,55 @@ let inherit cabal2nixArgsOverrides; }; + # Add some additional pieces of data to passthru on a Haskell derivation + # specifically from stacklock2nix. + # + # addStacklock2nixPassthru :: Args -> HaskellPkgDrv -> HaskellPkgDrv + # + # See the body of the function for what values you can pass to Args. + # + # Example: + # ``` + # > newLensDrv = addStacklock2nixPassthru { is-extra-dep = true; is-hackage-dep = true; } lensDrv + # > newLensDrv.passthru.stacklock2nix + # { + # is-local-pkg = false; + # is-extra-dep = true; + # is-hackage-dep = true; + # is-git-dep = false; + # is-url-dep = false; + # } + # ``` + # + # You can see that this will return `lensDrv`, but with the `is-extra-dep` + # and `is-hackage-dep` keys in `passthru` set to `true`. + addStacklock2nixPassthru = + { # Is this a local package defined in `stack.yaml`? + is-local-pkg ? false + , # Is this an `extra-dep` defined in `stack.yaml`? + is-extra-dep ? false + , # Is this a dep from Hackage? + is-hackage-dep ? false + , # Is this a dep from Git? + is-git-dep ? false + , # Is this a dep from a URL? + is-url-dep ? false + }: + haskell.lib.compose.overrideCabal + (oldAttrs: + let + oldPassthru = oldAttrs.passthru or {}; + oldStacklock2nix = oldPassthru.stacklock2nix or {}; + in + { + passthru = oldPassthru // { + stacklock2nix = oldStacklock2nix // { + inherit is-local-pkg is-extra-dep is-hackage-dep is-git-dep is-url-dep; + }; + }; + } + ); + # Parse a Haskell package Hackage lock into the name, version, and hash info. # # parseHackageStr :: String -> { name :: String, version :: String, cabalFileHash :: String, cabalFileLen :: String } @@ -353,13 +402,20 @@ let # Haskell package derivation. # # pkgHackageInfoToNixHaskPkg - # :: { name :: String, version :: String, cabalFileHash :: String, cabalFileLen :: String } + # :: Bool + # -> { name :: String, version :: String, cabalFileHash :: String, cabalFileLen :: String } # -> HaskellPkgSet # -> HaskellPkgDrv # + # The first `Bool` is `true` if the passed-in information was originally an + # `extra-deps` from `stack.yaml`. Otherwise, `false` if this was not + # specified in `stack.yaml` but instead just from the Stackage + # snapshot/resolver. + # # Example: # ``` # pkgHackageInfoToNixHaskPkg + # false # { name = "cassava"; # version = "0.5.3.0"; # cabalFileHash = "06e6dbc0f3467f3d9823321171adc08d6edc639491cadb0ad33b2dca1e530d29"; @@ -370,22 +426,34 @@ let # # This takes care of replaces the `.cabal` file from Hackage with the correct revision # specified in the Hackage lock info. - pkgHackageInfoToNixHaskPkg = pkgHackageInfo: hfinal: + pkgHackageInfoToNixHaskPkg = isExtraDep: pkgHackageInfo: hfinal: let - additionalArgs = getAdditionalCabal2nixArgs pkgHackageInfo.name pkgHackageInfo.version; + additionalArgs = + getAdditionalCabal2nixArgs pkgHackageInfo.name pkgHackageInfo.version; + baseDrv = + hfinal.callHackage pkgHackageInfo.name pkgHackageInfo.version additionalArgs; + baseDrvWithCorrectRev = + overrideCabalFileRevision + pkgHackageInfo.name + pkgHackageInfo.version + pkgHackageInfo.cabalFileHash + baseDrv; in - overrideCabalFileRevision - pkgHackageInfo.name - pkgHackageInfo.version - pkgHackageInfo.cabalFileHash - (hfinal.callHackage pkgHackageInfo.name pkgHackageInfo.version additionalArgs); + addStacklock2nixPassthru + { + is-extra-dep = isExtraDep; + is-hackage-dep = true; + } + baseDrvWithCorrectRev; # Return a derivation for a Haskell package for the given Haskell package # lock info. # - # extraDepCreateNixHaskPkg :: HaskellPkgSet -> HaskellPkgLock -> HaskellPkgDrv + # extraDepCreateNixHaskPkg :: Bool -> HaskellPkgSet -> HaskellPkgLock -> HaskellPkgDrv # - # where + # The first `Bool` is `true` if the passed-in HaskellPkgLock is an + # `extra-deps` from `stack.yaml`. Otherwise, `false` if the passed-in + # HaskellPkgLock are from the Stackage snapshot/resolver. # # data HaskellPkgLock # = HackageDep { hackage :: String } @@ -400,6 +468,7 @@ let # # ``` # extraDepCreateNixHaskPkg + # false # hfinal # { hackage = "cassava-0.5.3.0@sha256:06e6dbc0f3467f3d9823321171adc08d6edc639491cadb0ad33b2dca1e530d29,6083"; } # ``` @@ -408,6 +477,7 @@ let # # ``` # extraDepCreateNixHaskPkg + # true # hfinal # { name = "servant-client"; # git = "https://github.com/haskell-servant/servant"; @@ -421,6 +491,7 @@ let # # ``` # extraDepCreateNixHaskPkg + # true # hfinal # { name = "pretty-simple"; # url = "https://github.com/cdepillabout/pretty-simple/archive/d8ef1b3c2d913a05515b2d1c4fec0b52d2744434.tar.gz"; @@ -428,7 +499,7 @@ let # sha256 = "aba1659b4c133b00b7a28837bcb413672823d72835bcee0f1594e0ba4e2ea4af"; # } # ``` - extraDepCreateNixHaskPkg = hfinal: haskPkgLock: + extraDepCreateNixHaskPkg = isExtraDep: hfinal: haskPkgLock: let extraHackageDep = let @@ -436,7 +507,7 @@ let pkgHackageInfo = parseHackageStr hackageStr; in { name = pkgHackageInfo.name; - value = pkgHackageInfoToNixHaskPkg pkgHackageInfo hfinal; + value = pkgHackageInfoToNixHaskPkg isExtraDep pkgHackageInfo hfinal; }; extraGitDep = @@ -461,14 +532,21 @@ let "--subpath ${haskPkgLock.subdir}" else ""; - in { - name = haskPkgLock.name; - value = + baseDrv = hfinal.callCabal2nixWithOptions haskPkgLock.name src extraCabal2nixOptions (getAdditionalCabal2nixArgs haskPkgLock.name haskPkgLock.version); + in { + name = haskPkgLock.name; + value = + addStacklock2nixPassthru + { + is-extra-dep = isExtraDep; + is-git-dep = true; + } + baseDrv; }; extraUrlDep = @@ -487,13 +565,20 @@ let tar -xf "${rawSrc}" -C ./raw-input-source --strip-components=1 cp -r "./raw-input-source/${haskPkgLock.subdir or ""}" "$out" ''; - in { - name = haskPkgLock.name; - value = + baseDrv = hfinal.callCabal2nix haskPkgLock.name src (getAdditionalCabal2nixArgs haskPkgLock.name haskPkgLock.version); + in { + name = haskPkgLock.name; + value = + addStacklock2nixPassthru + { + is-extra-dep = isExtraDep; + is-url-dep = true; + } + baseDrv; }; in if haskPkgLock ? "hackage" then @@ -511,7 +596,11 @@ let # set. # # haskPkgLocksToOverlay - # :: [ HaskellPkgLock ] -> HaskellPkgSet -> HaskellPkgSet -> HaskellPkgSet + # :: Bool -> [ HaskellPkgLock ] -> HaskellPkgSet -> HaskellPkgSet -> HaskellPkgSet + # + # The first `Bool` is `true` if the list of passed-in HaskellPkgLocks are + # `extra-deps` from `stack.yaml`. Otherwise, `false` if the list of passed-in + # HaskellPkgLocks are from the Stackage snapshot/resolver. # # See the documentation for extraDepCreateNixHaskPkg for the definition of # HaskellPkgLock. @@ -519,6 +608,7 @@ let # Example: # ``` # haskPkgLocksToOverlay + # false # [ { hackage = "lens-5.0.1@sha256:63ed57e4d54c583ae2873d6892ef690942d90030864d0b772413a1458e98159f,15544"; } # { hackage = "conduit-1.3.4.3@sha256:c32a5f3ee2daff364de2807afeec45996e5633f9a5010c8504472a930ac7153e,5129"; } # ] @@ -532,8 +622,8 @@ let # conduit = callHackage "conduit" "1.3.4" {}; # } # ``` - haskPkgLocksToOverlay = haskPkgLocks: hfinal: hprev: - builtins.listToAttrs (map (extraDepCreateNixHaskPkg hfinal) haskPkgLocks); + haskPkgLocksToOverlay = isExtraDeps: haskPkgLocks: hfinal: hprev: + builtins.listToAttrs (map (extraDepCreateNixHaskPkg isExtraDeps hfinal) haskPkgLocks); # Similar to `haskPkgLocksToOverlay`, but takes in both Hackage locks and Git # locks (so the `HaskellPkgLock` type) from above. @@ -556,7 +646,7 @@ let # hprev # ``` extraDepsToOverlay = extraDepsPkgs: hfinal: hprev: - haskPkgLocksToOverlay (map (pkg: pkg.completed) extraDepsPkgs) hfinal hprev; + haskPkgLocksToOverlay true (map (pkg: pkg.completed) extraDepsPkgs) hfinal hprev; # Figure out the name of a local package from the stack.yaml file and its path. # @@ -725,7 +815,7 @@ let # snapshot / resolver. # # stackYamlResolverOverlay :: HaskellPkgSet -> HaskellPkgSet -> HaskellPkgSet - stackYamlResolverOverlay = haskPkgLocksToOverlay resolverParsed.packages; + stackYamlResolverOverlay = haskPkgLocksToOverlay false resolverParsed.packages; # A Haskell package set overlay that adds all the packages from the `extraDeps` # in your `stack.yaml`. @@ -764,8 +854,9 @@ let value = let additionalArgs = getAdditionalCabal2nixArgs pkgName null; + baseDrv = hfinal.callCabal2nix pkgName pkgPath additionalArgs; in - hfinal.callCabal2nix pkgName pkgPath additionalArgs; + addStacklock2nixPassthru { is-local-pkg = true; } baseDrv; }; in builtins.listToAttrs (map localPkgToOverlayAttr localPkgs); diff --git a/test/nixpkgs.nix b/test/nixpkgs.nix index 05278a7..6f43ade 100644 --- a/test/nixpkgs.nix +++ b/test/nixpkgs.nix @@ -24,6 +24,8 @@ let new-package-set = final.callPackage ./test-new-package-set.nix {}; # This tests that all-cabal-hashes works correctly as a directory (not a tarball). + # + # This also tests that our stacklock2nix-specific passthru values are working. all-cabal-hashes-is-dir = final.callPackage ./test-all-cabal-hashes-is-dir.nix {}; # This test that a stack.yaml with no local packages defined is still diff --git a/test/test-all-cabal-hashes-is-dir.nix b/test/test-all-cabal-hashes-is-dir.nix index 57a6729..77a64d7 100644 --- a/test/test-all-cabal-hashes-is-dir.nix +++ b/test/test-all-cabal-hashes-is-dir.nix @@ -52,4 +52,43 @@ let defaultLocalPkgFilter path type; }; in + +# Check that our additional passthru values are working. We expect that local +# packages have is-local-pkg set to true, and all other values set to false. +assert stacklock.newPkgSet.my-example-haskell-lib.passthru.stacklock2nix.is-local-pkg; +assert !stacklock.newPkgSet.my-example-haskell-lib.passthru.stacklock2nix.is-extra-dep; +assert !stacklock.newPkgSet.my-example-haskell-lib.passthru.stacklock2nix.is-hackage-dep; +assert !stacklock.newPkgSet.my-example-haskell-lib.passthru.stacklock2nix.is-git-dep; +assert !stacklock.newPkgSet.my-example-haskell-lib.passthru.stacklock2nix.is-url-dep; + +# We expect that unagi-streams is Hackage extra-dep. +assert !stacklock.newPkgSet.unagi-streams.passthru.stacklock2nix.is-local-pkg; +assert stacklock.newPkgSet.unagi-streams.passthru.stacklock2nix.is-extra-dep; +assert stacklock.newPkgSet.unagi-streams.passthru.stacklock2nix.is-hackage-dep; +assert !stacklock.newPkgSet.unagi-streams.passthru.stacklock2nix.is-git-dep; +assert !stacklock.newPkgSet.unagi-streams.passthru.stacklock2nix.is-url-dep; + +# We expect that servant-cassava is a Git extra-dep. +assert !stacklock.newPkgSet.servant-cassava.passthru.stacklock2nix.is-local-pkg; +assert stacklock.newPkgSet.servant-cassava.passthru.stacklock2nix.is-extra-dep; +assert !stacklock.newPkgSet.servant-cassava.passthru.stacklock2nix.is-hackage-dep; +assert stacklock.newPkgSet.servant-cassava.passthru.stacklock2nix.is-git-dep; +assert !stacklock.newPkgSet.servant-cassava.passthru.stacklock2nix.is-url-dep; + +# TODO: Add a similar test for a URL extra-dep. + +# We expect that other packages from Stackage are Hackage deps, but not extra deps. +# For example, aeson. +assert !stacklock.newPkgSet.aeson.passthru.stacklock2nix.is-local-pkg; +assert !stacklock.newPkgSet.aeson.passthru.stacklock2nix.is-extra-dep; +assert stacklock.newPkgSet.aeson.passthru.stacklock2nix.is-hackage-dep; +assert !stacklock.newPkgSet.aeson.passthru.stacklock2nix.is-git-dep; +assert !stacklock.newPkgSet.aeson.passthru.stacklock2nix.is-url-dep; + +# We expect that packages not included in Stackage, but from Nixpkgs (from Hackage) don't +# have any of our stacklock2nix passthru values. +# +# For example, this uses vault-tool, a random old package unlikely to be added to stackage. +assert ! stacklock.pkgSet.vault-tool.passthru ? stacklock2nix; + stacklock.newPkgSet.my-example-haskell-app From d19199d5bd24ed3e39318f1445adbf254f5e65d6 Mon Sep 17 00:00:00 2001 From: Dennis Gosnell Date: Fri, 7 Mar 2025 14:41:54 +0900 Subject: [PATCH 2/3] Add dev-shell-pkg-set and shell-for-args passthru args to dev shell --- nix/build-support/stacklock2nix/default.nix | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/nix/build-support/stacklock2nix/default.nix b/nix/build-support/stacklock2nix/default.nix index c905ce2..ce6d7c8 100644 --- a/nix/build-support/stacklock2nix/default.nix +++ b/nix/build-support/stacklock2nix/default.nix @@ -936,13 +936,33 @@ let null else let + # The modified package set that the dev shell + # will be based off of. + # + # This is an attrset where the keys are Haskell package names, + # and the values are Haskell package derivations. pSet = devShellPkgSetModifier packageSet; + + # The arguments to be passed to shellFor. shellForArgs = devShellArgsModifier { packages = localPkgsSelector; nativeBuildInputs = additionalDevShellNativeBuildInputs packageSet; }; + + # The actual dev shell. + shell = pSet.shellFor shellForArgs; in - pSet.shellFor shellForArgs; + # Add a dev-shell-pkg-set and shell-for-args key to passthru.stacklock2nix. + # The end-user should be able to use these to easily access the modified + # package set. + shell.overrideAttrs (oldAttrs: { + passthru = (oldAttrs.passthru or {}) // { + stacklock2nix = ((oldAttrs.passthru or {}).stacklock2nix or {}) // { + dev-shell-pkg-set = pSet; + shell-for-args = shellForArgs; + }; + }; + }); # A development shell created by passing all your local packages (from # `localPkgsSelector`) to `pkgSet.shellFor`. From 2d70b91602cc83f71397d63caba789dadf958d16 Mon Sep 17 00:00:00 2001 From: Dennis Gosnell Date: Wed, 5 Mar 2025 18:30:19 +0900 Subject: [PATCH 3/3] Add CHANGELOG entry --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e81c2..3dde43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,54 @@ Big changes: A few other small changes: +* Expose some additional `passthru` values on Haskell derivations generated + by stacklock2nix. + + All Haskell derivations generated with `stacklock2nix` now have a + `passthru.stacklock2nix` key, which is an attrset filled with the + following keys. All keys have a `Bool` value: + + - `is-local-pkg`: Is this a local package defined in `stack.yaml`? + - `is-extra-dep`: Is this an `extra-dep` defined in `stack.yaml`? + - `is-hackage-dep`: Is this a dep from Hackage? + - `is-git-dep`: Is this a dep from Git? + - `is-url-dep`: Is this a dep from a URL? + + This can be used in your own overlays. For instance, if you want to + apply an overlay to the Haskell package set produced by stacklock2nix + that disables tests ONLY for local Haskell packages, you could write + the overlay like the following: + + ```nix + hfinal: hprev: + lib.mapAttrs + ( name: drv: + if lib.attrByPath [ "passthru" "stacklock2nix" "is-local-pkg" ] false drv then + modifierFunc drv + else + drv + ) + hprev + ``` + + Added in [#63](https://github.com/cdepillabout/stacklock2nix/pull/63) + + See the tests in that PR for some more examples of how you might want to + use this. + +* Expose some additional `passthru` values on the Haskell dev shell generated + by stacklock2nix. + + The dev shells generated with `stacklock2nix` now have a + `passthru.stacklock2nix` key, which is an attrset filled with the + following keys and values: + + - `dev-shell-pkg-set`: The Haskell package set used to generate this dev shell. + This is mainly useful in conjunction with the `devShellPkgSetModifier`, in order + to inspect the final package set produced after applying `devShellPkgSetModifier`. + - `shell-for-args`: The passed to `shellFor` in order to produce the dev shell. + This is an attrset. + * Mark some additional packages as `dontCheck` in `suggestedOverlay`: - cborg