Skip to content
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

nixos/nixpkgs: make nixpkgs.config mergeable #194207

Closed
wants to merge 1 commit into from

Conversation

Ma27
Copy link
Member

@Ma27 Ma27 commented Oct 3, 2022

Description of changes

Closes #194056
Alternative implementation to #80582

nixpkgs.config is now a freeform-type which imports the
<nixpkgs/pkgs/top-level/config.nix>-module. This brings the following
benefits:

  • Proper merging (to a certain degree, more details later) of several
    nixpkgs.config declarations.

  • allowUnfree and friends appear in the NixOS manual.

To be precise, this means that something like this is now possible:

{
  nixpkgs = mkMerge [
    { allowUnfree = false; }
    { allowUnfree = mkForce true; }
  ];
}

Previously this would lead to a bogus attr-set, namely

{ _type = "override"; content = true; priority = 50; }

since no merging for this option-type was implemented.

This however has certain limitations. For instance

nixpkgs.config.allowUnfree = mkForce false;

works fine whereas

nixpkgs.config.virtualbox.enableExtensionPack = mkForce true;

doesn't. This is because we use types.raw inside the config-module for
nixpkgs which doesn't merge undeclared attribute-sets (such as
virtualbox). This is because we don't know if any attribute-set is
actually mergeable, for further details see the previous discussion[1].

A basic type has been implemented for the (actually deprecated)
mechanisms packageOverrides/perlPackageOverrides. This one basically
applies the pkgs argument onto each definition of this function, so
e.g. the following expression

{
  nixpkgs.config = mkMerge [
    { packageOverrides = pkgs: { randomattr = 23; }; }
    { packageOverrides = pkgs: { randomattr = pkgs.randomattr + 19; }; }
  ];
}

adds an attribute randomattr of value 42 to _module.args.pkgs.

[1] #194207 (comment)

Things done
  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 22.11 Release Notes (or backporting 22.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
    • (Release notes changes) Ran nixos/doc/manual/md-to-db.sh to update generated release notes
  • Fits CONTRIBUTING.md.

@Ma27 Ma27 requested review from roberth and infinisil October 3, 2022 10:36
@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: module (update) This PR changes an existing module in `nixos/` labels Oct 3, 2022
@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10 labels Oct 3, 2022
@roberth
Copy link
Member

roberth commented Oct 4, 2022

This kinda looks good, but I think we should address a deeper issue, which is the (worse) duplication of the same logic from Nixpkgs. I think we could give nixpkgs.config a submodule type and imports pkgs/top-level/config.nix in it. That way, packageOverrides like any other option can be represented by nothing more than module system types.

@Ma27
Copy link
Member Author

Ma27 commented Oct 6, 2022

@roberth AFAIU pkgs/top-level/config.nix doesn't have a definition for packageOverrides, so there's (1) no logic duplicated and (2) the merging issue doesn't even exist here. Or am I misunderstanding you? :)

@roberth
Copy link
Member

roberth commented Oct 6, 2022

You are technically correct, but what I'm trying to achieve by suggesting this is to clean up the custom merging code, and move the responsibility for merging from two places to one.

The pkgs/top-level/config.nix module is more developed than the NixOS one, and it's also used for documentation, so I think it makes sense to use that one.

By representing the merging behavior with the module system, it can be understood more easily without having to dig through the custom code. Code of such complexity needs tests and I don't want to ask you to write tests for an unnecessary solution. And it does have at least two subtle problems because types.anything is stricter than it should be, and overlays (called "overrides" here) shouldn't be ordered arbitrarily by the module system.

@Ma27
Copy link
Member Author

Ma27 commented Oct 7, 2022

OK, I played around with it a little bit more: importing pkgs/top-level/config.nix is actually a nice idea because then we also have defined nixpkgs config-options inside the manual.

However:

  • We cannot use lazyAttrsOf raw here: this would break definitions such as virtualbox.enableExtensionPack being defined multiple times (once with false and once with mkForce true for instance).
  • I tried to use lib.types.anything instead to restore the merging-behavior in config.nix, but then we have the same problem with packageOverrides which can only defined once unless the module system "decides" how it will be merged together. Would it be okay to you to implement it (with tests) in pkgs/top-level/config.nix or do you have a nicer idea?

@roberth
Copy link
Member

roberth commented Oct 7, 2022

  • We cannot use lazyAttrsOf raw

I agree raw is a bit excessive, but I'd rather have something unsupported than something that's subtly broken by default. We can't just assume that any attrset can be merged, because we have no idea what it is supposed to mean.
The real solution is to declare more options. That way both merge and the user (docs) know what's supposed to happen.

problem with packageOverrides which can only defined once unless the module system "decides" how it will be merged together. Would it be okay to you to implement it (with tests) in pkgs/top-level/config.nix or do you have a nicer idea?

We should have a type for overlays. That'd be something worth testing.
It could be quite fancy and report ambiguities, but let's start with a simple implementation instead.

@Ma27
Copy link
Member Author

Ma27 commented Oct 9, 2022

I agree raw is a bit excessive, but I'd rather have something unsupported than something that's subtly broken by default. We can't just assume that any attrset can be merged, because we have no idea what it is supposed to mean.

So, your suggestion is to just use raw to have some basic merging capabilities (e.g. for allowUnfree and freeform options that are not an attr-set)?

We should have a type for overlays

Is that related to this change? Overlays are IIRC not passed into config, but directly to <nixpkgs>.

@roberth
Copy link
Member

roberth commented Oct 9, 2022

So, your suggestion is to just use raw to have some basic merging capabilities

Yes. We can use raw where our knowledge of the options ends. When we don't know for what option an attribute in config is, we fall back to raw. This allows multiple definitions of config to each contribute their own "top level" config.<name> attributes, as long as the intersection of those attributes has options declared for them.

We should have a type for overlays

I guess technically packageOverrides isn't an overlay because it lacks the super parameter, but otherwise they're the same.

Overlays are IIRC not passed into config, but directly to .

Correct. The type for nixpkgs.overlays should be improved though.

@Ma27 Ma27 force-pushed the nixpkgs-config-mergable branch from fe288f2 to f45dd29 Compare October 9, 2022 21:11
@Ma27
Copy link
Member Author

Ma27 commented Oct 9, 2022

@roberth I pushed a different implementation which makes nixpkgs.config a freeform-type using pkgs/top-level/config.nix.

Also, I implemented a fairly simple option-type for packageOverrides (a similar approach may be used for nixpkgs.overlays at some point in the future), but I'm not really sure if this approach is actually a good idea, but I thought I'd push it anyways to get some feedback on this approach %)

Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to look good.
After adding a test for the type, I think this will be ready to merge.

pkgs/top-level/config.nix Outdated Show resolved Hide resolved
pkgs/top-level/config.nix Outdated Show resolved Hide resolved
pkgs/top-level/config.nix Outdated Show resolved Hide resolved
nixos/modules/misc/nixpkgs.nix Outdated Show resolved Hide resolved
Closes NixOS#194056
Alternative implementation to NixOS#80582

`nixpkgs.config` is now a freeform-type which imports the
`<nixpkgs/pkgs/top-level/config.nix>`-module. This brings the following
benefits:

* Proper merging (to a certain degree, more details later) of several
  `nixpkgs.config` declarations.

* `allowUnfree` and friends appear in the NixOS manual.

To be precise, this means that something like this is now possible:

    {
      nixpkgs = mkMerge [
        { allowUnfree = false; }
        { allowUnfree = mkForce true; }
      ];
    }

Previously this would lead to a bogus attr-set, namely

    { _type = "override"; content = true; priority = 50; }

since no merging for this option-type was implemented.

This however has certain limitations. For instance

    nixpkgs.config.allowUnfree = mkForce false;

works fine whereas

    nixpkgs.config.virtualbox.enableExtensionPack = mkForce true;

doesn't. This is because we use `types.raw` inside the config-module for
`nixpkgs` which doesn't merge undeclared attribute-sets (such as
`virtualbox`). This is because we don't know if any attribute-set is
actually mergeable, for further details see the previous discussion[1].

A basic type has been implemented for the (actually deprecated)
mechanisms `packageOverrides`/`perlPackageOverrides`. This one basically
applies the `pkgs` argument onto each definition of this function, so
e.g. the following expression

    {
      nixpkgs.config = mkMerge [
        { packageOverrides = pkgs: { randomattr = 23; }; }
        { packageOverrides = pkgs: { randomattr = pkgs.randomattr + 19; }; }
      ];
    }

adds an attribute `randomattr` of value `42` to `_module.args.pkgs`.

[1] NixOS#194207 (comment)
@Ma27 Ma27 force-pushed the nixpkgs-config-mergable branch from ab3174e to 7b2f9a5 Compare October 13, 2022 06:35
@Ma27 Ma27 requested review from edolstra and nbp as code owners October 13, 2022 06:35
@Ma27 Ma27 requested review from infinisil and roberth October 13, 2022 06:36
@bobby285271 bobby285271 added the 12.approvals: 1 This PR was reviewed and approved by one reputable person label Oct 13, 2022
@Ma27
Copy link
Member Author

Ma27 commented Oct 16, 2022

@roberth anything else to get this merged? :)

Comment on lines +902 to +908
merge = const
(foldl'
(final: override: pkgs:
let
final' = final pkgs;
in final' // (override.value (pkgs // final')))
(const {}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the merging behavior from before. Most notably the old merging function didn't propagate changes from earlier overrides to later ones, there's only one pkgs that is reused as the input for all override functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that functionTo attrs then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost. I think not because of the optCall, which allows either functions or attribute sets directly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then it needs a coercedTo.

optCall can be replaced by lib.toFunction, but I guess we'll only need lib.const anyway.

default = const {};
type = types.overrideFnType;
description = lib.mdDoc ''
A function that takes the final set of Perl packages and returns an attribute set of packages to add or change.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, the perlPackageOverrides is super weird, in that it doesn't even take the perl packages as an argument, but rather the main pkgs package set!

❯ nix-instantiate '<nixpkgs>' --arg config '{ perlPackageOverrides = pkgs: builtins.trace (builtins.head (builtins.attrNames pkgs)) {}; }' -A perlPackages.FileFinder
trace: AAAAAASomeThingsFailToEvaluate
trace: AAAAAASomeThingsFailToEvaluate
trace: AAAAAASomeThingsFailToEvaluate
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
/nix/store/sf3bji32b4xw77xjh7w452pj15jq8gh6-perl5.34.1-File-Finder-0.53.drv

(this shows that the first attribute in the passed pkgs is pkgs.AAAAAASomeThingsFailToEvaluate)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes, but it also kinda makes sense, because how else would you access pkgs. Actually doing your own fixpoint might work, but not with features like pkgsCross.

Anyway, thanks @infinisil for catching this and sorry @Ma27 for making a wrong suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries, I also missed that!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm... I obseve the same behavior on my local master (dfae1c1 currently):

❯ ~/Projects/nixpkgs master → nix-instantiate './.' --arg config '{ perlPackageOverrides = pkgs: builtins.trace (builtins.head (builtins.attrNames pkgs)) {}; }' -A perlPackages.FileFinder
trace: AAAAAASomeThingsFailToEvaluate
trace: AAAAAASomeThingsFailToEvaluate
trace: AAAAAASomeThingsFailToEvaluate
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
/nix/store/f9qgccfq4p6n161jlkdf05qikmq4l9jj-perl5.36.0-File-Finder-0.53.drv

Am I missing something? oO

Copy link
Member

@infinisil infinisil Oct 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's as expected. This just demonstrates that perlPackageOverrides gets the pkgs as an argument and not pkgs.perlPackages, since there's only pkgs.AAAAAASomeThingsFailToEvaluate and no pkgs.perlPackages.AAAAAASomeThingsFailToEvaluate. Don't mind this specific attribute name, it's not related to this issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, the perlPackageOverrides is super weird, in that it doesn't even take the perl packages as an argument, but rather the main pkgs package set!

It's funny. In a discussion today the inverse came up. Without a way to reference pkgs, mere two-function overlays are not good enough for distributing additions/changes to language-specific package sets, because those inevitably tend to need some things from pkgs... but which one?
So without a pkgs parameter it's not suitable for distributed use, and without a prev or self, it's not an overlay.
We need both! ... or do we? self is actually available in pkgs.
But yes, we do, because the location may be ambiguous. Is it haskellPackages or haskell.packages.ghc943?

@roberth
Copy link
Member

roberth commented May 12, 2023

We could change the Nixpkgs entrypoint to allow multiple config objects. It is itself backed by the module system, so it can easily merge the definitions on NixOS' behalf. For simplicity we might even pass modules, although that seems like unnecessary power.
type = deferredModule would make that easy on the NixOS side.
If we want to stop short of full modules, we could have a fairly simple custom type that preserves all the definitions as a list. Type-changing types.
The only downside (I think) of these solutions is that they're not readable on the NixOS side, although arguably such code could just read from pkgs.config instead.

@Ma27
Copy link
Member Author

Ma27 commented May 13, 2023

Actually, that's a pretty good idea and way better than effectively doing two rounds of evaluations of config (for nixpkgs), so I'm in favor 👍

The only downside (I think) of these solutions is that they're not readable on the NixOS side, although arguably such code could just read from pkgs.config instead.

It'd be nice to ensure that users get a somewhat usable eval error (or warning at least) when referencing config.nixpkgs.config rather than pkgs.config, though I'm not (yet) sure if that's doable in a reasonable manner (and if so, how).

Will close this PR for now. Adding this suggestion to my todo list, but it'll take a moment until I get to it, so feel free to go ahead if you have the motivation & capacity currently 😄

@Ma27 Ma27 closed this May 13, 2023
@Ma27 Ma27 deleted the nixpkgs-config-mergable branch May 13, 2023 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10 12.approvals: 1 This PR was reviewed and approved by one reputable person
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support merging/overriding for unspecified attr-set options within freeformTypes (for e.g. nixpkgs.config)
4 participants