I’ve been thinking about this a bit but thought I’d try to see if this is a relevant/wanted feature before I started hacking stuff together.
Currently if you want to modify defaults in the module system the only option (as far as I’m aware) is to use
mkOverride. Given an option, the module system gathers all the definitions of this option, takes all the ones of lowest priority (which is given by
mkOverride or is set to a high number for option defaults) and merges them. This works well if you want to just completely override the value of some option but the downside is that it completely discards anything of a higher priority.
This can be an inconvenience in some cases:
The first case is one that I’ve come across in my nix code which is where the default for an option is (for example) a list and I want to add an item to it. One way this can be done is by specifying my addition with
mkOptionDefaultbut this feels like a hack to me and not really the intended behaviour. An example of this can be found at https://github.com/rycee/home-manager/blob/8bddc1adab0f7a51476f819fa2197353e8e1d136/modules/services/window-managers/i3.nix#L475 where the description recommends people use
A harder case to deal with is where you want to remove a package from a list of defaults. For example a couple of days ago someone asked in IRC how they would go about removing
environment.systemPackages. Obviously overriding
environment.systemPackagesis a poor idea and I couldn’t see of another way of removing this without either forking nixpkgs or disabling the system path module and putting your own in. Whether or not removing nano from your system is a good idea, I thought the use case was interesting and did not think there was a canonical way to do this in nix.
I propose solving these sort of problems by the addition of a new function that could perhaps be called
mkOverlay. I feel
mkOverride is a better name but this is clearly taken.
mkOverlay would take as input a function. What this would do is that when the module system finds a definition of this type, it would merge all inputs of higher priority and then it would pass these into the function that
mkOverlay wraps. What this returns would then be merged with all items of equal priority (or overridden by items with a lower priority) as normal.
I’ll give an example to demonstrate this: Say as in the second bullet point above we want to remove nano from
environment.systemPackages. Then in our configuration we would put:
with lib; environment.systemPackages = mkForce (mkOverlay (super: filter (x: x != pkgs.nano) super ));
mkForce puts this at a lower priority than all other definitions. Then the module system will gather all definitions with priority higher than 50 (the priority given by
mkForce) and will process these as normal to get the
environment.systemPackages one would have got without adding this. Then this is passed as
super into the function body above and the function returns this with nano filtered out. As there are no other definitions with priority 50, the output of this function becomes the final merged value of
Problems I see with my approach:
- Over use of these could become quite complicated and hard to follow. I would recommend avoiding it’s use in nixpkgs.
- I don’t think the name is good
- It seems counterintuitive to me that you need to use both
mkOverlay, should consider definitions with it’s priority or higher instead of just it’s priority.
Interested to hear if anyone else has any thoughts on this, or if there is an alternative (and neater) solution to these sort of problems