Replacing Haskell transitive dependency versions

Hi! I’m using Nixpkgs 21.11.337967 and in a side project I thought I would try to use the optics library. The most recent version is optics 0.4.2 which depends on optics-th 0.4.1 and optics-core 0.4.1. This version of Nixpkgs has optics pointing at an older version but optics_0_4 pointing to a newer version. I’m trying to override each of these dependencies in a way that their dependees find them correctly. I’m trying to write targeted overrides rather than just copying and replacing the whole derivations. So far I haven’t gotten it to work.

Here’s a simplified default.nix that shows more-or-less what I’m stuck on:

{ pkgs ? import <nixpkgs> {}, compiler ? "ghc921" }:
let
  hPackages = pkgs.haskell.packages.${compiler}.override (old: {
    overrides = pkgs.lib.composeExtensions old.overrides (self: super: rec {
      optics-th = (pkgs.haskell.lib.overrideCabal super.optics-th_0_4 (old: {
        version = "0.4.1";
        sha256 = "05zxljfqmhr5if7l8gld5s864nql6kqjfizsf1z7r3ydknvmff6p";
      })); #.overrideScope (_: _: {});
      optics-core = (pkgs.haskell.lib.overrideCabal super.optics-core_0_4 (old: {
        version = "0.4.1";
        sha256 = "16ll8829g8v5g6gqdcfj77k6g4sqpmpxbda9jhm4h68pycay4r6a";
      }));
    });
});
  in
hPackages.optics-th

This file does not build:

Configuring optics-th-0.4.1...

Setup: Encountered missing or private dependencies:
optics-core >=0.4.1 && <0.5

So, clearly my override of the src for optics-th is working fine, but its references in libraryHaskellDependencies are not referring to optics-core. But… shouldn’t it? The Haskell package set uses a fixed-point combinator to create a package set that is mutually recursive, and I guessed/hoped that those references would refer to the final “self” set after all the overrides. It seems like here, that isn’t happening.

In the end, I found that I could get it to pick up my modified versions of dependencies by slapping .overrideScope (_: _: {}) at the end of each overridden derivation. But I don’t really understand why.

I tried to debug a little further but wasn’t really able to make headway. Looking at the definition of optics-th in hackage-packages:

  "optics-th_0_4" = callPackage
    ({ mkDerivation, base, containers, mtl, optics-core, tagged
     , template-haskell, th-abstraction, transformers
     }:
     mkDerivation {
       pname = "optics-th";
       version = "0.4";
       sha256 = "1qddzwhzlhhp1902irswjj18aa5ziavn4klhy7najxjwndilb55y";
       libraryHaskellDepends = [
         base containers mtl optics-core template-haskell th-abstraction
         transformers
       ];
       testHaskellDepends = [ base optics-core tagged ];
       description = "Optics construction using TemplateHaskell";
       license = lib.licenses.bsd3;
       hydraPlatforms = lib.platforms.none;
     }) {};

I tried debugging this a little further using something like this:

        libraryHaskellDepends = pkgs.lib.debug.traceSeq (builtins.elemAt old.libraryHaskellDepends 4) old.libraryHaskellDepends;

… in the overridden definition of optics-th. Here we see that 4 should correspond to the template-haskell dependency. When I try to build this, I see the trace message trace: null. If I change the 4 to 3 and add .name after the parentheses, I see the trace message trace: optics-core-0.4. Hang on a second – optics-th depends on optics-core – shouldn’t this be optics-core-0.3.0.1? (It is if I change the call to overrideCabal to refer to super.optics-th instead of super.optics-th_0_4.) Also, if I add template-haskell = super.template-haskell_2_18_0_0; to my overridden set, then the 3rd dependency stops being null and starts being template-haskell-2.18.0.0. Why? It seems like the mutual recursion is doing something since it picks up the new version of template-haskell, but I’m clearly lost as to why it isn’t picking up the version I want of optics-core.

Any thoughts on this topic, or more generally how to debug this kind of thing, would be greatly appreciated! Thanks!

To answer one of your questions:

template-haskell is a boot library, so we null it out in the normal package sets (since the boot libraries are distributed with ghc):

$ nix repl '<nixpkgs>'
nix-repl> haskellPackages.template-haskell
null

And:

So if you explicitly override it, it is no longer null.


It feels like you have another question about how to correctly specify overrides for the Haskell package set, but I wasn’t sure exactly what you’re asking. Is it just how to write overrides?

There is an example of how to specify overrides in the documentation, but I guess something like this is not working for you, and that’s what you’re asking about?

https://haskell4nix.readthedocs.io/frequently-asked-questions.html#how-to-override-package-versions-in-a-compiler-specific-package-set

Do you happen to have your code up on GitHub where someone could easily clone it and play around with it?

Thanks for your response! I guessed something was special about template-haskell because it was linked to the compiler somehow, but I didn’t have the term “boot library”, so thanks for that.

Here is a repo on Github: https://github.com/glasserc/test-nix-haskell. I’m not on the same computer that I had the original problem, so it’s possible that this is different but it does seem to fail in a way that I didn’t expect. My root question is, how do I write the derivation for this, assuming I am held to this version of nixpkgs (pinned in the derivations), and without rewriting the entire derivation for all the dependencies? From a software engineering perspective, I feel like re-using what is present in the existing derivations is good, so I’m trying to just update src. I’m assuming that updating my version of NixOS/Nixpkgs will make my life easier, but I feel like this is an interesting opportunity to learn something about Nix.

Trying to build this, the build of optics-th fails to find optics-core. I think this is what gave me the idea that there was a problem with a dependency-of-a-dependency. Because of that, I tried to find a minimal reproducible example, which is what the optics-th/optics-core example is about. (I’ve included this as a different .nix file too.) But I’m open to being corrected about any of this.

Cloning https://github.com/glasserc/test-nix-haskell at Make sure I'm describing the right problem · glasserc/test-nix-haskell@28002f9 · GitHub, I was able to get nix-build optics.nix working with the following change:

diff --git a/optics.nix b/optics.nix
index 8289552..05b5037 100644
--- a/optics.nix
+++ b/optics.nix
@@ -16,7 +16,7 @@ let
       optics-th = (pkgs.haskell.lib.overrideCabal super.optics-th_0_4 (old: {
         version = "0.4.1";
         sha256 = "05zxljfqmhr5if7l8gld5s864nql6kqjfizsf1z7r3ydknvmff6p";
-      })); #.overrideScope (_: _: {});
+      })).override { optics-core = self.optics-core; };
       optics-core = (pkgs.haskell.lib.overrideCabal super.optics-core_0_4 (old: {
         version = "0.4.1";
         sha256 = "16ll8829g8v5g6gqdcfj77k6g4sqpmpxbda9jhm4h68pycay4r6a";

My guess is that the override of optics-core wasn’t being picked up in the optics-th derivation the way you were originally writing this, so I forced the override here. I was pretty surprised that the override wasn’t of optics-core wasn’t being picked up in optics-th the way you had originally this, but I guess that is just how it works?

Although, if I was personally writing this, I would just use callHackageDirect to download the package version I need. While overriding the version and sha256 might work in your original code (since optics-0.4 and optics-0.4.1 are so similar), it doesn’t work in the general case. In general, it is safer to use something like callHackageDirect, callHackage, callCabal2nix, etc:

diff --git a/optics.nix b/optics.nix
index 8289552..0a96720 100644
--- a/optics.nix
+++ b/optics.nix
@@ -13,14 +13,22 @@ in
 let
   hPackages = pkgs.haskell.packages.${compiler}.override (old: {
     overrides = pkgs.lib.composeExtensions old.overrides (self: super: rec {
-      optics-th = (pkgs.haskell.lib.overrideCabal super.optics-th_0_4 (old: {
-        version = "0.4.1";
-        sha256 = "05zxljfqmhr5if7l8gld5s864nql6kqjfizsf1z7r3ydknvmff6p";
-      })); #.overrideScope (_: _: {});
-      optics-core = (pkgs.haskell.lib.overrideCabal super.optics-core_0_4 (old: {
-        version = "0.4.1";
-        sha256 = "16ll8829g8v5g6gqdcfj77k6g4sqpmpxbda9jhm4h68pycay4r6a";
-      }));
+      optics-th =
+        self.callHackageDirect
+        {
+          pkg = "optics-th";
+          ver = "0.4.1";
+          sha256 = "sha256-LswYTbNXkJja3vERy6ioTAYtfyUBTEspML5fr3YDWo4=";
+        }
+        {};
+      optics-core =
+        self.callHackageDirect
+        {
+          pkg = "optics-core";
+          ver = "0.4.1";
+          sha256 = "sha256-JnSrzSZW1dRkKBbTamCc705eRXZzfpmqru4jOTe2dX4=";
+        }
+        {};
     });
 });
   in

1 Like

Thanks – yes, this was my guess too. Like I wrote in the original message, overrideScope also works, and I also don’t really know why. If anyone else knows why, I’d love to hear it!

Oh, OK, thanks, this does seem easier. It even seems to resolve dependencies correctly, including drawing from the Haskell package set with my overrides. I have pushed a branch called all-hackage-direct to show this working. Thanks!

1 Like