Nixpkgs overlays/overrides are way too hard to get right. Here's a seemingly innocent example:
self: super: {
python2Packages = super.python2Packages.override (old: {
overrides = pself: psuper: {
myPythonPkg = pself.callPackage ./pkg.nix {};
};
});
}
But it has problems:
pkgs.pythonPackages and pkgs.python2.pkgs won't contain the package, even though it should point to pkgs.python2PackagesHere's a Haskell example:
self: super: {
haskellPackages = super.haskellPackages.extend (hself: hsuper: {
myHaskellPkg = hself.callPackage ./pkg.nix {};
});
}
But it has problems:
.extend doesn't properly work with .override, which other overlays might use, see https://github.com/NixOS/nixpkgs/issues/26561pkgs.haskell.packages.ghc883 won't contain the new package.extend is quadratic. Every new .extend access evaluates all previous overlays again.And this is just two package sets. There's also:
pkgs.linuxPackages set, which uses lib.makeExtensible for allowing overrides with .extend, but not with .overridelib.makeScope mechanism, which creates a new scope that can be extended with .overrideScope', which works very much the same as .extend. Don't use .overrideScope though, that has its arguments flipped around.pkgs.appendOverlays which works very much the same as pkgs.extend, except for multiple overlayspkgs.perlPackages doesn't support overriding with scoped overlays at all. You need to use super.perlPackages.<pkg> to refer to a previous perl package, or self.perlPackages.<pkg> for final ones.override, .overrideAttrs and .overrideDerivation, and it's hard to know which one is right.overrideAttrs (old: { version = "1.2"; }) and wonder why it doesn't override the versionself and super is very tricky. And sometimes very intuitive. In fact, using self to avoid infinite recursion can sometimes even introduce inconsistencies.It's just a big mess!
This issue should serve as a place to track this problem and how it can be improved. I myself have thought about this a bunch, and will soon elaborate my idea to fix all these problems in a subsequent comment.
For an ideal overlay interface, a change like https://github.com/NixOS/nix/issues/4090 would be very beneficial
It's just a big mess!
I totally agree, there's way to many things to get wrong and little corners. When I first started using nix there were overlays and I had a pretty confused time.
I will also add that on nixUnstable there isn't self: super: anymore.
It's final: prev:, which I think makes it a bit more obvious with the proper documentation.
Might I add there was no helpful message in nixUnstable for overlays that used self: super:...
Those are just arbitrary arguments btw, they can even be glitter: sparkles:! The super/self is just a convention really
Those are just arbitrary arguments btw, they can even be
glitter: sparkles:! Thesuper/selfis just a convention really
Yep, they can be arbitrary arguments. But in nixUnstable I notice that they're not anymore because of the check https://github.com/NixOS/nix/blob/b721877b85bbf9f78fd2221d8eb540373ee1e889/src/nix/flake.cc#L260
Ahh that's only for flakes though. But neat, didn't know that.
Anyways, I should start explaining how I think this can be solved:
One of the main insights I had while thinking about this was that currently the override author is responsible for getting overrides correct. This is just how it is for how overlays currently work, you need to know how to modify the original super to include what you need.
However it doesn't have to be this way: By redesigning overlays slightly, we can shift this responsibility to the package set author. This means that whoever defines the package set will be specifying in Nix code how it should be overridden, such that the people that actually write overrides won't have to know about it.
How can this be implemented though? You see, currently overlays are just applied by essentially using super // overlay. This means that any attributes in super are just overridden completely. However super actually contains the original package set definition, the one specified by the package set author. If we want to allow them to control how overrides are applied, we need to allow them to specify how the // operation should work for their attribute!
So what we do is to change the // for overlays to instead do something like this:
left: right:
left // builtins.mapAttrs (name: rval:
# If the left attribute defines a custom merging strategy
if left ? ${name}.overlayMerge
# use that to merge the two values together
then left.${name}.overlayMerge left.${name} rval
# Otherwise override completely (old behavior)
else rval
) right
Now with this, you can have an initial overlay that defines a package set:
self: super: {
set = {
foo = 10;
# Multiple definitions of this set should be merged together
overlayMerge = lval: rval: lval // rval;
};
}
And another one that extends this set how you'd expect it:
self: super: {
# Just like that!
set.bar = 20;
}
Which with this new merging strategy will not give you { bar = 20; } like before but
{
bar = 20;
foo = 10;
overlayMerge = <LAMBDA>;
}
instead! So just by changing the // for overlays, we're able to have a much better user interface. Previously to achieve the same you'd have to do
self: super: {
set = super.set // {
bar = 20;
};
}
Now this is just a single insight I had. This allows us to solve the problem of overlays being very hard to get right, given that the package set author provides a good enough overrideMerge function. However there are still problems with this, which I'll get to in future comments.
@Infinisil I think you have a typo in your examples - or I'm really not following how you ended up with bar = 20 and foo = 10, since those values don't show up anywhere else. :)
@chreekat Oh yes, fixed now, thanks!