-
-
Notifications
You must be signed in to change notification settings - Fork 14k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
overlays: (partially) recursive merge #54266
Conversation
One of the problems newcomers face when working with overlays is non-expected behavior of overriding packages in package sets. Let's take, for example, Python. Here's an overlay: ``` self: super: { pythonPackages.my-new-package = super.callPackages ./xxx { }; } ``` Big supprise is that `pythonPackages` set is now empty! ``` $ nix-build -E '(with import ./. { overlays = [ (self: super: { pythonPackages.my-new-package = self.pythonPackages.pip; }) ]; }; pythonPackages.ipython)' error: attribute 'ipython' missing, at (string):5:7 ``` It is not empty, but contains only one package. That's because attribute sets are not merged recursively (like NixOS modules do), but are replaced on each `extends`. Recursive merge is tricky, because it has to force evaluation in lot of places and breaks the knot. But we can do poor-man recursive merge "whitelist". Say, when overlay contains attribute `_merge_pythonPackages = true;`, then we can merge it recursively: ``` $ nix-build -E '(with import ./. { overlays = [ (self: super: { _merge_pythonPackages = true; pythonPackages.my-new-package = self.pythonPackages.pip; }) ]; }; pythonPackages.ipython)' /nix/store/k4wf0244qq8xcml85n45sdwalizy0i6k-python2.7-ipython-5.8.0 ``` We can also support this attribute in left-hand side overlay (for example, `all-packages.nix`): ``` ... _merge_pythonPackages = true; pythonPackages = python.pkgs; _merge_python2Packages = true; python2Packages = python2.pkgs; _merge_python3Packages = true; python3Packages = python3.pkgs; ... ``` So we get merge behavior by default for `pythonPackages`. We can also disable merging in our overlay if desired: ``` $ nix-build -E '(with import ./. { overlays = [ (self: super: { _merge_pythonPackages = false; pythonPackages = self.python3Packages; }) ]; }; pythonPackages.ipython)' /nix/store/0yx0q6v6nisx4fq1vj8x2vc51n7aj5pz-python3.7-ipython-7.1.1 ```
let | ||
merge = name: value: | ||
if builtins.hasAttr name lhs | ||
&& (rhs."_merge_${name}" or lhs."_merge_${name}" or false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we have a function instead similar to recurseIntoAttrs
instead of this top-level attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but then we have to define all sets in one place (in that function), right? In my implementation it is possible to hijack anything from overlay, without the need to extend function.
But maybe you can show an example of what you expect that "merge-approve" function should look like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean something like:
mergeInOverlays = attrs: attrs // { mergeInOverlays = true; };
mergeUpdate = lhs: rhs:
# ...
# this is incorrect because both the value from `lhs` and `rhs` must be checked,
# but the idea should be clear.
if (builtins.isAttrs value && value ? mergeInOverlays)
# ...
python2Packages = mergeInOverlays python2.pkgs;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately it is not possible, because it requires evaluate.of all attributes (we want to know which attributes are sets, and which of those have mergeInOverlays
sub-attribute).
And some of our attributes rely on tying the knot, so we don't want to evaluate them too soon.
In particular, wrapBintoolsWith
evaluation relies on stdenv
, which itself relies on binutils
, which relies on wrapBintoolsWith
... I couldn't make it play.
Obviously, this leads to performance degradation, 1% for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pythonPackages.my-new-package = super.callPackages ./xxx { };
This isn't the documented way to achieve this. Instead, override the interpreter to pass overrides to the package set. Now, these themselves won't compose, and that was a design mistake. If we want to fix this, we should do it properly, and consider #44196.
@FRidh oh, this is another problem, which isn't related to this PR. Probably I made a bad example... You surely have to create python packages correctly, however this change is about to merge namespaces correctly. For example, it allows creating aliases, overrides and new packages conveniently:
Can you show me, in which way this should break? |
@FRidh another toy example, on how deep this can go:
|
I'll copy also example for gst_* package set:
Override of |
@FRidh I found a problem with deep overriding python packages, but this was because python packages don't use
|
OTOH, there is no miracle, and you indeed can't override packages via
Probably it requires some clever alias resolution, but can be done in separate PR. |
Did you see I did something similar in #51213 ? |
@FRidh alright, found one more bug in python packages. Fixed. Now example from #26487 (comment) can be simplified down to:
I think, this is cool now! Never was working with python packages so easy! |
@Ericson2314 yep. Though this is much simpler and deprecates nothing. I couldn't compare though, because I don't understand how to build
will look like
with my approach. This all requires checks, just as I found python infra doesn't use self-fixpoint in some cases (fixed those). |
@@ -7996,8 +7996,11 @@ in | |||
python37Full = python37.override{x11Support=true;}; | |||
|
|||
# pythonPackages further below, but assigned here because they need to be in sync | |||
_merge_pythonPackages = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 this isn't a package, so it shouldn't follow package naming guidelines. This is an attribute lifted out of it's attribute set to prevent evaluations. Maybe pythonPackages_merge_ = true;
would look better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are more often use camelCase for functions then for packages.
Having a magic boolean flag for that looks like a very ugly hack to me, the opposite of a principled solution. |
@Profpatsch think of it as gradual transition towards ideal world described in #44196 (comment) I'll copy an example from that PR:
My proposed "ugly boolean hack" would allow to convert this to
And, just to refresh memory, this is how it's done nowadays:
Think of this "ugly boolean flags" as NixOS options, which guide overlays fixpoint on how to converge to final result. They bring one feature from extensible record proposal - merge of sets - to current nixpkgs. This doesn't remove the need for #44196 (comment), however in this PR I've made it possible actually override python dependencies with simple We also have discusses with @ottidmes in IRC, that flag should be renamed to |
Just a few notes on the merging strategy and the naming aspect.
I still have to think on whether I like this approach or not. |
Extracts some useful parts of NixOS#38698, in particular, it's vision that postgresql plugins should be namespaced. Original approach had several problems: - not gonna happen in forseeable future - did lots of deprecations - was all-in-one solution, which is hard to sell to nixpkgs - even that we have postgresqlPackages now, we can't do arbitrary overrides to postgresql and plugins. Several required functions were not exported Here I've fixed all of those problems: - deprecates nothing (though plugins were moved now into `aliases.nix`) - this doesn't touch NixOS at all, and doesn't break anything - hashes for plugins and PGs are not changed (I hope) - no extra fixes to pg itself - default PG is not changed - plugins and PGs are extensible Last one is the most interesting thing, because it introduces novel way to manage `XXX withPackages` problem. It is novel, but has got lots of inspiration from existing approaches: - python, so we have now `postgresql.pkgs.*`, `postgresql_11.pkgs.*` which all contain plugins compiled with correct PG. - python, so we have now `postgresql.withPackages` as well - in contrast to python, there are no `postgresql11Packages`, only `postgresql_11.pkgs` - NixOS#44196, so plugins are referenced starting at self-fixpoint. This allows override/add plugins with mere `//` in overlay. This works for both `postgresqlPackages` (overrides are applied to all postgresql_xx.pkgs) and `postgresql_xx.pkgs` (overrides are specific to this postgresql) sets - I've made it compatible with proposed mergeable overlays (NixOS#54266) however this PR doesn't depend on it - last, but not least, `postgresql/default.nix` is now an overlay! This replaces previous `callPackages` approach with a modern, extensible concept.
this was a fun idea, but too unpolished
Now I have an idea that each overlay should have an attribute
But I'm not an expert in these internals... |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/makeextensibleasoverlay/7116/3 |
Motivation for this change
One of the problems newcomers face when working with overlays is
non-expected behavior of overriding packages in package sets. Let's take,
for example, Python. Here's an overlay:
Big supprise is that
pythonPackages
set is now empty!It is not empty, but contains only one package. That's because attribute
sets are not merged recursively (like NixOS modules do), but are replaced
on each
extends
. Recursive merge is tricky, because it has to forceevaluation in lot of places and breaks the knot.
But we can do poor-man recursive merge "whitelist". Say, when overlay
contains attribute
_merge_pythonPackages = true;
, then we can merge itrecursively:
We can also support this attribute in left-hand side overlay (for
example,
all-packages.nix
):So we get merge behavior by default for
pythonPackages
. We can alsodisable merging in our overlay if desired:
cc @nbp @Ericson2314
Things done
[x] my system evaluates