Skip to content

pkgs/top-level: make package sets composable (reapply)#376988

Merged
wolfgangwalther merged 3 commits intoNixOS:masterfrom
wolfgangwalther:compose-pkgs-sets-reapply
Feb 2, 2025
Merged

pkgs/top-level: make package sets composable (reapply)#376988
wolfgangwalther merged 3 commits intoNixOS:masterfrom
wolfgangwalther:compose-pkgs-sets-reapply

Conversation

@wolfgangwalther
Copy link
Contributor

This was merged in #303849 and reverted in #376899, because of #303849 (comment).

Turns out the nixos/nixpkgs module still wasn't happy with what I tried in eec2100. Thus, I removed that commit out of the reapplication and am trying again:

  • The first commit prepares the nixos/nixpkgs module for composability of nixpkgs.
  • The second commit reapplies pkgs/top-level: make package sets composable #303849 minus the above mentioned commit.
  • The third commit adds some assertions to guard against the underlying problem, described below.

The composability of nixpkgs depends on not passing elaborated systems into localSystem and crossSystem. Once you pass in an elaborated system, you can't use any of the package sets like pkgsStatic anymore. This is due to how lib.systems.elaborate works - it can't handle arbitrary fields properly when overriding and will return garbage, i.e. certain fields out of sync. For example, you can quickly end up with libc == musl, but isMusl == false etc.

My first attempt on the nixos side was to store the original arguments for localSystem/crossSystem etc. and pass those on. After investigating more, however, I think a cleaner approach is to just not elaborate the systems via apply for the nixos module anymore. The module had self-contradictory statements in this regard:

# Make sure that the final value has all fields for sake of other modules
# referring to this. TODO make `lib.systems` itself use the module system.
apply = lib.systems.elaborate;

and

throw ''
Neither ${opt.system} nor any other option in nixpkgs.* is meant
to be read by modules and configurations.
Use pkgs.stdenv.hostPlatform instead.

In the first commit here, I am getting rid of the elaboration, making it easier to pass those values on to new incarnations of nixos without accidentally passing on elaborated systems.

I would appreciate a review of the nixos/nixpkgs module side. Also touching the nixpkgs/read-only module, which @roberth introduced.

Things done

Couldn't figure out how to run e.g. all tests from nixos/release-small locally.

How do I do that?

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@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/` 6.topic: nixos-container Imperative and declarative systemd-nspawn containers labels Jan 26, 2025
@nix-owners nix-owners bot requested a review from Ericson2314 January 26, 2025 14:36
@wolfgangwalther wolfgangwalther force-pushed the compose-pkgs-sets-reapply branch from d87d7e7 to d3716ed Compare January 26, 2025 14:37
@github-actions github-actions bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. labels Jan 26, 2025
@philiptaron philiptaron added the 1.severity: significant Novel ideas, large API changes, notable refactorings, issues with RFC potential, etc. label Jan 27, 2025
…form,hostPlatform} write only

The description for options.nixpkgs.system already hints at this:

  Neither ${opt.system} nor any other option in nixpkgs.* is meant
  to be read by modules and configurations.
  Use pkgs.stdenv.hostPlatform instead.

We can support this goal by not elaborating the systems anymore, forcing
users to go via pkgs.stdenv.

This will prevent problems when making the top-level package sets
composable in the next commit. For this to work, you should pass a fully
elaborated system to nixpkgs' localSystem or crossSystem options.
This reverts commit 7c251e2.

Left out eec2100, which changed
nixos/nixpkgs, doing it differently this time.
The commit to make package sets composable depends on the fact that
localSystem and crossSystem can be passed forward to lower package sets
for composition. Once we pass a fully elaborated system, this will break
down - getters like "isStatic", "isMusl" etc. will not change with the
package set anymore, but be stuck on the value passed in via those
options.

This is a limitation of lib.systems.elaborate primarily, because it
can't deal with arbitrary overrides properly.

freshBootstrapTools on darwin used to do this, although in this case it
didn't do any harm: There shouldn't be any package sets composed during
bootstrap anyway. Refactor it, to avoid throwing the assert.
@wolfgangwalther
Copy link
Contributor Author

I don't really want to delay this PR much more, because the merge conflicts last time, after a new package set had been added, were annoying. Since I don't seem to get any feedback on this, I'll need to handle this to the best of my knowledge.

I'm sure I fixed the problem with failing tests, that lead to the revert. I'm fairly sure my changes to the nixos/nixpkgs module make sense.

My best course of action right now seems to be to merge and hope it doesn't need to be reverted again.

I rebased this on latest master again, to confirm that eval still passes. I intend to merge this in the next couple of days, either tomorrow or Monday.

@wolfgangwalther wolfgangwalther merged commit c1793a3 into NixOS:master Feb 2, 2025
25 of 27 checks passed
@wolfgangwalther wolfgangwalther deleted the compose-pkgs-sets-reapply branch February 2, 2025 10:41
Comment on lines -202 to -204
# Make sure that the final value has all fields for sake of other modules
# referring to this. TODO make `lib.systems` itself use the module system.
apply = lib.systems.elaborate;
Copy link
Member

Choose a reason for hiding this comment

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

Please add this back (or something like it), so that the type of config.nixpkgs.hostPlatform is always a platform attrset.

Copy link
Contributor Author

@wolfgangwalther wolfgangwalther Feb 2, 2025

Choose a reason for hiding this comment

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

So, if we do that, then we can't do any of this PR, essentially.

Citing from the commit message:

The description for options.nixpkgs.system already hints at this:

Neither ${opt.system} nor any other option in nixpkgs.* is meant
to be read by modules and configurations.
Use pkgs.stdenv.hostPlatform instead.

We can support this goal by not elaborating the systems anymore, forcing
users to go via pkgs.stdenv.

It seems the intent of the apply = elaborate was, that consumers could look at config.nixpkgs.hostPlatform and use it for downstream logic... but the comment cited above already says otherwise.

Also, almost every consumer already depended on stdenv.hostPlatform instead - except for two tests. I think this makes more sense.

What's your reasoning to not do it like this / what am I missing?

if lib.systems.equals elaborated cfg.hostPlatform then
cfg.hostPlatform # make identical, so that `==` equality works; see https://github.com/NixOS/nixpkgs/issues/278001
else
elaborated;
Copy link
Member

Choose a reason for hiding this comment

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

This one as well.

}
{
assertion =
(opt.hostPlatform.isDefined -> builtins.isAttrs cfg.buildPlatform -> !(cfg.buildPlatform ? parsed))
Copy link
Member

Choose a reason for hiding this comment

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

cfg.buildPlatform must always be attrs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, nixos/modules/misc/nixpkgs/test.nix says otherwise - it sets nixpkgs.buildPlatform to different strings, too.

Copy link
Contributor

Choose a reason for hiding this comment

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

cfg.buildPlatform must always be attrs.

it sets nixpkgs.buildPlatform to different strings, too.

It could be defined as a string, but previously the apply function would always coerce the final value into attrs.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess what we really need here is a lib.systems.partiallyElaborate, so that the apply function can be restored without violating the purpose of this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The important part is, that it must be possible to pass the inputs to the module (localSystem, crossSystem, buildPlatform, hostPlatform) onwards unchanged.

No matter what you do as apply, it will always break this premise, as soon as a second layer is involved. I tried adding rawValue in eec2100 to avoid touching apply, but that didn't work out.

But there is really no need to be able to access config.nixpkgs.buildPlatform etc. at all - you can get that information from pkgs.stdenv.buildPlatform etc, right?

Copy link
Contributor

@MattSturgeon MattSturgeon Feb 4, 2025

Choose a reason for hiding this comment

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

But there is really no need to be able to access config.nixpkgs.buildPlatform etc. at all - you can get that information from pkgs.stdenv.buildPlatform etc, right?

It seems that up until now, it was intended for users to be able to read those options. But I agree, accessing pkgs.stdenv is better (and is what I do in my configs).

I'm trying to think about whether there's a sane way to enforce that though...

Obviously, the option descriptions should be updated with a note explaining they are write-only, and pkgs.stdenv should be read instead.

Perhaps an apply function could warn or throw when evaluated (similar to mkRemovedOptionModule), but internal usage could bypass the apply by merging the option definitions manually?

# Bypass `apply` function by merging `foo` manually:
(
  lib.modules.mergeDefinitions
    options.foo.loc
    options.foo.type
    options.foo.definitionsWithLocations
).mergedValue

@roberth
Copy link
Member

roberth commented Feb 2, 2025

Oh, you already merged this??

@wolfgangwalther
Copy link
Contributor Author

Oh, you already merged this??

Yeah, sorry, didn't know you were going to review.

@nikstur
Copy link
Contributor

nikstur commented Feb 2, 2025

This broke the userborn-* tests. They set nixpkgs.hostPlatform = config.nixpkgs.hostPlatform which doesn't work anymore. Setting it to pkgs.stdenv.hostPlatform also doesn't work, however.

@wolfgangwalther
Copy link
Contributor Author

This broke the userborn-* tests. They set nixpkgs.hostPlatform = config.nixpkgs.hostPlatform which doesn't work anymore. Setting it to pkgs.stdenv.hostPlatform also doesn't work, however.

Ah. I only grepped for hostPlatform = ... patterns to fix, but this uses inherit (config.nixpkgs) hostPlatform. Will look at it.

@wolfgangwalther
Copy link
Contributor Author

This broke the userborn-* tests. They set nixpkgs.hostPlatform = config.nixpkgs.hostPlatform which doesn't work anymore. Setting it to pkgs.stdenv.hostPlatform also doesn't work, however.

This seems to work for me:

          nixpkgs = {
            hostPlatform = {
              inherit (pkgs.stdenv.hostPlatform) system;
            };
          };

I can open a PR tomorrow, if you don't beat me to it, @nikstur.

@wooseopkim
Copy link

@wolfgangwalther Hi, this line seems to be problematic, causing a build failure, when run(not sure if that's a right word as a non-native and not knowing much about Nix) by darwin-rebuild. If I comment it out or checkout into the previous commit, the issue goes away. I don't really know much about Nix and since this is my first time trying nix-darwin, I'm not sure if it's my skill issue or not. When I get back home from work and if you don't mind, I'd like to try building a minimal repro repository using GitHub Actions.

@wolfgangwalther
Copy link
Contributor Author

Hi, this line seems to be problematic, causing a build failure, when run(not sure if that's a right word as a non-native and not knowing much about Nix) by darwin-rebuild. If I comment it out or checkout into the previous commit, the issue goes away.

It could very well be that out-of-nixpkgs projects need adjustments, too. I don't specifically know about nix-darwin. @emilazy could you have a look?

@nyabinary
Copy link
Contributor

To chime in I rather this be iterated on than reverted again if my opinion matters at all :p

keithschulze added a commit to keithschulze/nix-config that referenced this pull request Feb 4, 2025
- Pin nixpkgs to work around NixOS/nixpkgs#376988
- Remove custom aerospace HM module since it's now in core
MattSturgeon added a commit to MattSturgeon/nixvim that referenced this pull request Feb 4, 2025
@yuyuyureka
Copy link
Contributor

Hi, this broke some downstream consumers which access config.nixpkgs.hostPlatform.system, which now no longer exists because the apply = elaborate is gone so the config.nixpkgs.hostPlatform is now a string.

@yuyuyureka
Copy link
Contributor

If the interface change is intended, it should get a release-notes entry. Clearly, this isn't easy to fix, so I would like to see this reverted again and properly thought over.

@wolfgangwalther
Copy link
Contributor Author

Clearly, this isn't easy to fix

It is:

Hi, this broke some downstream consumers which access config.nixpkgs.hostPlatform.system

You should be able to replace that with config.nixpkgs.pkgs.stdenv.hostPlatform.system.

@yuyuyureka
Copy link
Contributor

yuyuyureka commented Feb 5, 2025

People make a lot of effort to not break the interface, since it causes trouble for all users and downstream developers when their configs simply error out on evaluation.
But even when it's a good idea to change the interface, a lot of the time we do deprecation periods, release notes entry, etc.

I appreciate the goal here, but this could have been accomplished without breaking the interface, by leaving config.nixpkgs.hostPlatform be an elaborated system config but simply adding a second option next to it, which inherits the definitions from options.nixpkgs.hostPlatform without an "apply", and use that to instantiate the pkgs.
This would even make the change a lot more simple, because all the consumers of this interface within nixpkgs wouldn't have to be adapted.
I think you are underestimating the amount of work it causes for everyone involved.

@PedroHLC
Copy link
Member

PedroHLC commented Feb 5, 2025

Yep, this PR should never have been merged without a release note. No matter how trivial, if it requires manual interference, then it should have notes.

My flake broke in 3 different lines, and I would have to use GitHub 💩-search to know why if I wasn't pinged. The assertion isn't enough, I saw people in random groups asking where did that error come from (since the backtrace points to a random place). Searching the assertion message in GH points to this PR right now, but in the future it will get lost between the high number of issues+PRs within this repo.

GaetanLepage pushed a commit to nix-community/nixvim that referenced this pull request Feb 5, 2025
@roberth roberth mentioned this pull request Feb 5, 2025
13 tasks
@roberth
Copy link
Member

roberth commented Feb 5, 2025

I have prepared a revert to limit the disruption

As said, that's not a value judgement; just balancing the interests of users.
I think some of the ideas here are valid, and can be implemented with minimal disruption.

To summarize our findings and requirements so far:

  • current implementation of the change is too disruptive
  • breaking changes should come with release notes
  • breaking changes in core components should be handled with deprecation warnings
  • nixpkgs.hostPlatform = "x86_64-linux"; should be accepted
  • nixpkgs.hostPlatform = lib.elaborate "x86_64-linux"; should be accepted with a deprecation warning
  • config.nixpkgs.hostPlatform (the option value, not the definitions) should be in a canonical form, as much as possible
  • either lib.elaborate should be idempotent, or we could use a similar function that canonicalizes a platform in a better way

These are competing requirements, but not mutually exclusive.
Doing this correctly, respecting users, will take a bit of effort, hence the revert.

@emilazy
Copy link
Member

emilazy commented Feb 5, 2025

Currently taking a short break from Nixpkgs, but since we were affected by this in nix-darwin: I’m afraid I do think a revert is the best option here. I’m generally not all that fussed about backwards compatibility, but in this case we’ve broken basically every third‐party user of the module system that instantiates Nixpkgs without any warning period or even a helpful error message. Over 50 people were confused by their nix-darwin systems suddenly breaking with an obscure error message over the past couple days, and I believe Home Manager is still broken. Breaking changes are often necessary and we’re happy to embrace them on the nix-darwin end, but this one wasn’t very graceful.

I do agree that the whole system elaboration procedure conflates inputs and outputs in a problematic way. I would like to see a world where we have a clear delineation between stuff the Nixpkgs user supplies to specify the desired platform and derived properties that are inferred from that specification. I’m not sure if this quite solves that problem, though; I’m worried about the idea that we should be reducing platforms to their double string when reading and adjusting them, which seems like it might get in the way of respecting platforms with the same system double string but more obscure settings changed and even reduce compositionality as a result?

You should be able to replace that with config.nixpkgs.pkgs.stdenv.hostPlatform.system.

I’m also a bit concerned that this incorrect replacement is being suggested:

nix-repl> darwin.linux-builder.nixosConfig.nixpkgs.pkgs.stdenv.hostPlatform.system
error:
       … while evaluating the attribute 'value'
         at /nix/store/6zgxj37rf9nsf5r0pd4pvwdazpbpnqi4-source/lib/modules.nix:846:9:
          845|     in warnDeprecation opt //
          846|       { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
             |         ^
          847|         inherit (res.defsFinal') highestPrio;

       … while evaluating the option `nixpkgs.pkgs':

       … while evaluating the attribute 'mergedValue'
         at /nix/store/6zgxj37rf9nsf5r0pd4pvwdazpbpnqi4-source/lib/modules.nix:881:5:
          880|     # Type-check the remaining definitions, and merge them. Or throw if no definitions.
          881|     mergedValue =
             |     ^
          882|       if isDefined then

       (stack trace truncated; use '--show-trace' to show the full, detailed trace)

       error: The option `nixpkgs.pkgs' was accessed but has no value defined. Try setting the option.

I think the correct construction is ‹NixOS system›.pkgs.stdenv.hostPlatform.system. In the case of linux-builder, it only exposes the configuration, so I think the correct expression is actually (darwin.linux-builder.nixosConfig._module.specialArgs.pkgs or darwin.linux-builder.nixosConfig._module.args.pkgs).stdenv.hostPlatform.system, which is (a) a mouthful (b) pretty counterintuitive (I seriously doubt people will rederive this accurately across the ecosystem) and (c) a really indirect‐feeling way to access this basic property. darwin.linux-builder should probably re‐export its package set, but in general I feel like either nixpkgs.pkgs should be guaranteed to always be set or there should be another option to access the full elaborated system.

@wolfgangwalther
Copy link
Contributor Author

First of all, I'd like to apologize to everyone who was affected by this PR.

I hope to learn from this for the future.

Many things went wrong, but the biggest thing for me was, that I didn't know about those downstream copies of nixos/nixpkgs in e.g. nix-darwin or nixvim (I still don't know why they exist, this seems to indicate some architectural problem to me - you shouldn't need to copy code mostly 1:1 from nixpkgs, right?). If I had known about those, I'm sure I wouldn't have merged already. And from what I understand that affected most of the users.

Most other things that went wrong.. I should have already known better.

I have prepared a revert to limit the disruption

@roberth thank you for making the call. In the last couple of days, I wasn't able to convince, and was still torn on it, myself. I still believe the change itself is right and it's essentially impossible to avoid breakage, but I do accept that the way this has happened was not OK.

  • breaking changes should come with release notes

Agreed. I somehow had the impression that release notes would become mostly relevant for.. the next release, so I had assumed there to be time to write them - but clearly people are reading release notes while tracking unstable already.

  • breaking changes in core components should be handled with deprecation warnings

Agreed.

  • nixpkgs.hostPlatform = "x86_64-linux"; should be accepted

I think it is and always was the case, right?

  • nixpkgs.hostPlatform = lib.elaborate "x86_64-linux"; should be accepted with a deprecation warning

That's the right approach, yes.

  • config.nixpkgs.hostPlatform (the option value, not the definitions) should be in a canonical form, as much as possible

  • either lib.elaborate should be idempotent, or we could use a similar function that canonicalizes a platform in a better way

Those will be very hard to achieve. In fact, I already found fixing elaborate way outside what I was able to do back when I started #303849. I still think I am not capable of doing that. Clearly, I am also still lacking the knowledge to work on the NixOS modules side of things - which is not really what I wanted to do anyway. The path to fixing top-level package sets just somehow lead me there accidentally...

My conclusion regarding composability of package sets: I won't pursue this again for now, because the base on the NixOS side is just not there, yet. We can't do it.


@emilazy

I’m worried about the idea that we should be reducing platforms to their double string when reading and adjusting them, which seems like it might get in the way of respecting platforms with the same system double string but more obscure settings changed and even reduce compositionality as a result?

Reducing things to their system double is actually not the idea. I somehow proposed that, because it seems like a good-enough fix for most cases, especially on the NixOS-side: When running NixOS, you are much less likely to run obscure settings compared to when compiling via pkgsCross or other package sets.

The proper way to do it: You'll need to collect the inputs exactly and pass them forward. Not only the system double. That's the very reason why I am so opposed to applying the elaboration - this makes it really hard to get to those original inputs, but it shouldn't be.

Passing on the inputs is also what I did in the reverted main commit of this PR, about the package sets. This worked very well and fixed all the package sets eventually.

One other thing that I proposed was this:

You should be able to replace that with config.nixpkgs.pkgs.stdenv.hostPlatform.system.

This is just wrong. There's two cases we need to look at:

  • Placing conditionals in your code, e.g. depending on isMusl or isDarwin or whatever - those should all go via pkgs.stdenv.
  • Passing on the nixos/nixpkgs options as discussed above. Those need to access the inputs, but there is no easy way to do that, yet. This applies to linux-builder. (I left out constructing the correct piece of code, which would include a few conditionals on xxx.isDefined and so on ...)

My generic suggestion in the comment above was even worse, because it wasn't even clear which of the two cases applied here.

@MattSturgeon
Copy link
Contributor

MattSturgeon commented Feb 5, 2025

You'll need to collect the inputs exactly and pass them forward. Not only the system double. That's the very reason why I am so opposed to applying the elaboration - this makes it really hard to get to those original inputs, but it shouldn't be.

The problem is actually even worse: most of the time when we want the system/platform "inputs", we don't necessarily want the platforms configured via the nixpkgs module. Rather we want the inputs passed to whatever nixpkgs instance happens to be in use by the module configuration.

In the strictest sense, the nixpkgs instance is the module arg pkgs, i.e.:

config._module.specialArgs.pkgs
or config._module.args.pkgs

To clarify: there is no guarantee that the nixpkgs.*Platform options were used to construct that nixpkgs instance, even though most of the time they were.

On that basis, perhaps reading those options should produce a warning, clarifying that users probably want to read pkgs.stdenv.*Platform instead?


Additionally, if we need access to the un-elaborated input platform args, then these should be exposed as attrs on the nixpkgs instance, so that we could do something like:

pkgs.stdenv.hostPlatformInput

Or

pkgs.input.hostPlatform

Or something along those lines.

I'm more familiar with the nixpkgs module and I'm unfamiliar with pkgs/top-level, so I don't know the best approach to implementing something like that.


Finally, the issue of warning when an elaborated platform is assigned to the platform module options and/or asserting that a non-elaborated platform is passed to the top-level nixpkgs constructor.

I wonder if the best approach here is to have lib.systems.elaborate "tag" its output, e.g. with a _type = "elaborated-platform" attr? That way we can have a more reliable check than platform ? parsed.

@emilazy
Copy link
Member

emilazy commented Feb 5, 2025

Many things went wrong, but the biggest thing for me was, that I didn't know about those downstream copies of nixos/nixpkgs in e.g. nix-darwin or nixvim (I still don't know why they exist, this seems to indicate some architectural problem to me - you shouldn't need to copy code mostly 1:1 from nixpkgs, right?). If I had known about those, I'm sure I wouldn't have merged already. And from what I understand that affected most of the users.

I don’t know about NixVim, but you can think of (a subset of) nix-darwin as being a port of (a subset of) NixOS to macOS. A good number of our modules are copied from NixOS, adjusted to work on macOS and with the rest of our modules.

In the case of the Nixpkgs module, we could probably import it directly from NixOS, but in other cases this is often not directly possible (e.g. porting things from systemd to launchd) and even in the case of the Nixpkgs module there are some differences (we never had the deprecated nixpkgs.{localSystem,crossSystem} so we omit them, we have an option for the path to import Nixpkgs from as we lack the luxury of being in the same monorepo, and we track some stuff related to that for complicated legacy reasons), and we change all the docs to refer to nix-darwin rather than NixOS.

In that context, I think the copy‐paste is understandable; although the Nixpkgs module is one of the very few (or the only?) we could potentially reuse from NixOS wholesale, almost every other module that derives from NixOS code requires adjusting to work with a different OS that is not even entirely managed by nix-darwin.

Agreed. I somehow had the impression that release notes would become mostly relevant for.. the next release, so I had assumed there to be time to write them - but clearly people are reading release notes while tracking unstable already.

There is also the Breaking changes announcement for unstable Discourse thread, FWIW.

Those will be very hard to achieve. In fact, I already found fixing elaborate way outside what I was able to do back when I started #303849. I still think I am not capable of doing that. Clearly, I am also still lacking the knowledge to work on the NixOS modules side of things - which is not really what I wanted to do anyway. The path to fixing top-level package sets just somehow lead me there accidentally...

My conclusion regarding composability of package sets: I won't pursue this again for now, because the base on the NixOS side is just not there, yet. We can't do it.

I hope you don’t get discouraged from working to make fundamental improvements. Not hesitating to roll back changes that cause problems shouldn’t lead us into stagnation and conservatism, because it means we have a good backup plan if something goes wrong. In general I think we should be more proactive to revert and reassess when changes cause problems, because otherwise we hold back evolution until we’re overly confident, precisely because we think we won’t quickly revert if it goes wrong. (Though I realize that in this case we’d already been through a cycle of that before it hit the channels and broke things on a wider scale, and that can be demoralizing.)

Reducing things to their system double is actually not the idea. I somehow proposed that, because it seems like a good-enough fix for most cases, especially on the NixOS-side: When running NixOS, you are much less likely to run obscure settings compared to when compiling via pkgsCross or other package sets.

The proper way to do it: You'll need to collect the inputs exactly and pass them forward. Not only the system double. That's the very reason why I am so opposed to applying the elaboration - this makes it really hard to get to those original inputs, but it shouldn't be.

Right. By “the inputs”, do you mean the raw value of nixpkgs.hostPlatform? It seems like that would be difficult to “tweak”: e.g. you have to handle both a string and an attribute set, and there are many non‐canonical forms that mean the same thing. I guess some of the magic to make this work is in your mkCrossPkgs/mkHybridPkgs?

Perhaps we want three layers: a convenience form for input, a “canonical” input form that has a consistent structure but contains values that can be varied freely and are as decoupled as possible, and the output form that is only for consumption rather than modification.

(FWIW, it may be that every time we want to “tweak the platform” we’re actually making a mistake, as @Ericson2314 alluded to; it’s a handy operation but perhaps not always a sensible one. The top‐level package sets do have the feeling of being a bit of a convenience hack to me – nice for quick use of cross‐compilation or alternative toolchains/libraries on the command‐line, but hard to define in a principled way – and they’re costly to instantiate which means we have to discourage their use within Nixpkgs. Unfortunately i don’t know what the right solution to all of this is. I’d really like it if we could use cross‐compilation more freely in Nixpkgs. I feel like for that case you usually want to wipe the slate mostly clean – e.g. it makes no sense to try and preserve the preference of Musl when instantiating a Nixpkgs with a macOS host platform – though you can also point to examples where the intuition points the opposite way.)

One other thing that I proposed was this:

You should be able to replace that with config.nixpkgs.pkgs.stdenv.hostPlatform.system.

This is just wrong. There's two cases we need to look at:

  • Placing conditionals in your code, e.g. depending on isMusl or isDarwin or whatever - those should all go via pkgs.stdenv.
  • Passing on the nixos/nixpkgs options as discussed above. Those need to access the inputs, but there is no easy way to do that, yet. This applies to linux-builder. (I left out constructing the correct piece of code, which would include a few conditionals on xxx.isDefined and so on ...)

My feeling is that it might be nice if we can arrange it so that nixpkgs.pkgs always reflects the actually instantiated package set. e.g., it could be set to config._module.specialArgs.pkgs or config._module.args.pkgs whenever that wouldn’t be circular?

@wolfgangwalther
Copy link
Contributor Author

wolfgangwalther commented Feb 5, 2025

@emilazy

My conclusion regarding composability of package sets: I won't pursue this again for now, because the base on the NixOS side is just not there, yet. We can't do it.

I hope you don’t get discouraged from working to make fundamental improvements. Not hesitating to roll back changes that cause problems shouldn’t lead us into stagnation and conservatism, because it means we have a good backup plan if something goes wrong. In general I think we should be more proactive to revert and reassess when changes cause problems, because otherwise we hold back evolution until we’re overly confident, precisely because we think we won’t quickly revert if it goes wrong. (Though I realize that in this case we’d already been through a cycle of that before it hit the channels and broke things on a wider scale, and that can be demoralizing.)

I am still motivated to fix the package sets, I just didn't see a way to fix the NixOS side to make this work...

... but that might change with my idea based on what @MattSturgeon proposed here:

Additionally, if we need access to the un-elaborated input platform args, then these should be exposed as attrs on the nixpkgs instance

I'm currently thinking about whether we could add those input platform args to the elaborated system itself. This would kind of turn everything we discussed in this PR upside down. If I was able to reconstruct the input args from the elaborated system itself... would all other pieces fall magically into place?

Everything I dealt with for package set composability and everything we discussed here for nixpkgs arguments... was all under the premise that this is about "input to nixpkgs". What if we treat this as "input to elaborate" instead?

Assume:

  • elaborate stores the input arguments in an extra attr, let's call this input.
  • Everyone can freely use pkgs.stdenv.hostPlatform.input to get back whatever was used to elaborate this system.
  • We can even build a lib.systems.extend function or so, which takes an elaborated system and an additional attrset, merges the original inputs and the attrset and returns a newly elaborated system.
  • Edit: lib.systems.equals would need to ignore input for comparison.

The status-quo is, that pkgs/top-level doesn't really want to be passed elaborated systems, but the nixos/nixpkgs module did so anyway. I was dead set on adjusting nixos/nixpkgs to stop doing that. But could the above allow the opposite i.e. make pkgs/top-level work with elaborated input, because the "original input" is preserved inside of that?

Only an idea, so far, but with the test-suite I added for package set composition, this should be easy to verify for correctness, once implemented.


Edit2: One thing that would need to be changed first... all of stdenv.hostPlatform == stdenv.buildPlatform wouldn't work anymore. Those would need to be compared with lib.systems.equals.

@MattSturgeon
Copy link
Contributor

I don’t know about NixVim, [but] In the case of the Nixpkgs module, we could probably import it directly from NixOS

For nixvim, the main reasons we copy-paste the module is to have custom documentation (descriptions, defaults, examples), and to avoid needing a specialArgs.nixosModulesPath to import from.

My feeling is that it might be nice if we can arrange it so that nixpkgs.pkgs always reflects the actually instantiated package set. e.g., it could be set to config._module.specialArgs.pkgs or config._module.args.pkgs whenever that wouldn’t be circular?

I had the same thought earlier.

It could be non-circular with some hacks:

  • Add an apply to the nixpkgs.pkgs option that ignores its parameter and instead returns the final pkgs argument that's actually in use.
  • Manually merge options.nixpkgs.pkgs's definitions for internal usage (or add rawValue as-per an earlier impl)

I'm currently thinking about whether we could add those input platform args to the elaborated system itself.

Makes sense to me.

I was dead set on adjusting nixos/nixpkgs to stop [passing elaborated systems]. But could the above allow the opposite i.e. make pkgs/top-level work with elaborated input, because the "original input" is preserved inside of that?

Interesting 👀

I like this, conceptually.

One thing that would need to be changed first... all of stdenv.hostPlatform == stdenv.buildPlatform wouldn't work anymore. Those would need to be compared with lib.systems.equals.

From my perspective losing referential equality may be worth it. We could retain it, but at the cost of losing the input values for buildPlatform.

@wolfgangwalther
Copy link
Contributor Author

From my perspective losing referential equality may be worth it. We could retain it, but at the cost of losing the input values for buildPlatform.

Ah, right. But if the systems otherwise are considered equal, would we really lose by setting buildPlatform.input = hostPlatform.input? Maybe in this case one set of original inputs is enough, as long as they end up at the same elaboration.

@emilazy
Copy link
Member

emilazy commented Feb 5, 2025

I think fixing all uses of == on systems turns this from a relatively small‐scope project into a big one. I think the general plan has been in the direction of making systems more amenable to direct comparison rather than less, e.g. by evicting functions.

@MattSturgeon
Copy link
Contributor

MattSturgeon commented Feb 5, 2025

I think fixing all uses of == on systems turns this from a relatively small‐scope project into a big one. I think the general plan has been in the direction of making systems more amenable to direct comparison rather than less

That's reasonable. Would it be acceptable to lose == equality if lib.systems.equals correctly ignored the "input"?

Otherwise I think we'd need to expose/preserve the inputs separately from the elaborated platforms. Which will be more difficult and uglier.

Maybe in this case one set of original inputs is enough, as long as they end up at the same elaboration.

Retaining only one set of inputs when the outputs are identical also sounds reasonable to me, but idk if I'm missing any edge-cases.

If it is sufficient, it'll definitely simplify things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.severity: significant Novel ideas, large API changes, notable refactorings, issues with RFC potential, etc. 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 6.topic: nixos-container Imperative and declarative systemd-nspawn containers 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 This PR causes between 1 and 10 packages to rebuild on Linux.

Projects

None yet

Development

Successfully merging this pull request may close these issues.