How to define the default package for a NixOS module in a Nix flake?

I have a Nix flake with a package and a NixOS module.

In the module, I have a package option, and I want to set the default value to a package that’s defined in the flake. But, I haven’t been able to figure out how to do this.

Similar to pkgs, (which is Nixpkgs), I can access self within the module, but I can’t access system; Without system I can’t reference the package.

How is this typically handled?

1 Like

You take it from pkgs which is passed to your module (cf. _module.args.pkgs) by whoever imports it. It’ll be an instance of nixpkgs with a concrete system already selected. Also cf. lib.mkPackageOption. Seeing that you mention a self variable, you’re probably using flake-parts, so take care to not confuse flake-parts’ fixpoint and NixOS fixpoint. You might also want to re-consider whether you really need to waste your time on flakes

The package is in the flake along with the module, not in Nixpkgs. What I would need is the equivalent of pkgs, but for the flake. The closest thing I know of is self, but that doesn’t have system resolved. I’m not using flake-parts.

@emmanuelrosa can you post your flake here so we can see what you tried?

Just use self.packages.${pkgs.stdenv.hostPlatform.system}.package. I’m pretty sure that works.

4 Likes

The flake is completely irrelevant here, a NixOS module is lambda that receives, among other things such as config, the pkgs argument. The NixOS module does not need to “know” that the flake exists, or that the flake exports it as an attribute. It does not need to pick a system on its own, it does not need to instantiate pkgs, it does not need to extract anything from the flake. All it needs is ask for the caller (the caller, in this case, is in lib.evalModules) to provide pkgs as an argument:

outputs = { ... }: {
  nixosModules.default = { config, pkgs, lib, ... }: { # these `config` and `pkgs` are not coming from the flake!
    options.hello = lib.mkPackageOption pkgs "hello"
  };
}

This is also the reason nixosModules are not prefixed with .${system}.
I just checked and it’s all relatively discoverable in the official documentation if one Ctrl-F:s e.g. for “module”: #sec-writing-modules, nix.dev: Module System. Navigating via search engines and chatbots is, unfortunately, a bit harder at the moment…

The default package comes from the flake’s outputs AFAIK. the flake is not “irrelevant”, it is the source for the package used in the module.

Ah, while at it, please do not that this is a widely known anti-pattern: you do not want to refer to packages using flake outputs (unless in the CLI), but rather you want to create a normal Nixpkgs-style entrypoint: a callPackage-able package.nix, possibly also using lib.makeScope and pkgs.newScope if you have multiple inter-dependent packages. Then in your NixOS module you’d simply pkgs.callPackage ./foo/package.nix { }. Cf. Flakes aren't real and cannot hurt you: a guide to using Nix flakes the non-flake way - jade's www site

1 Like

Then referring to packages from other flake inputs is hard. Unless you use overlays, which are kind of horrible IMO (I use flake-parts and dendritic pattern, and applying overlays there is hard, which is why I don’t).

Yes, that is one of the reasons flakes are frozen and unsupported, their design is specifically very bad for composability, and you need other flake authors to take care to maintain similar composable entrypoints that you can callPackage :upside_down_face:

This is not a very good use-case for overlays, which are for modifying deep dependencies and inherently a rather dangerous surgical tool…

Apologies for being annoying, but note that this is somewhat of a tautology and that “dendritic pattern” is not really a thing. It’s literally just flake-parts, which in turn is just lib.evalModules, which in turn is just an AOP system, and which we are now struggling very hard to break apart into smaller subsystems in NixOS since we’re suffering increasingly intolerable eval times, struggling to perform static analysis or extract metadata due to lack of boundaries (AOP being explicitly about breaking boundaries or making logic that operators across them).

3 Likes

That is one approach, but you cannot control the version of nixpkgs used to build the package in that scenario, potentially leading to package build failures.

I don’t think it’s incorrect to use the flake’s packages directly.

One of the reasons the flake interface is designed as it is is to permit package builds independent of a “host” nixpkgs version. follows exists to permit collapsing inputs. Overlays aren’t incorrect either, it’s just different designs, which you choose depends on whether you want to prioritize nixpkgs integration at the cost of maintainability.

Personally I’d typically follow @bitbloxhub 's advice, less maintenance effort is almost always my priority.

1 Like

Picking the right pkgs to use callPackage from is the responsibility of the user or the dependency injection layer. Flakes should have been that, the broken “follows” mechanism is the symptom of Flakes needing to be a that, but per their author they explicitly are not: flake: add Nixpkgs default arguments as input by roberth · Pull Request #160061 · NixOS/nixpkgs · GitHub, ELSEWHERE

To be clear, I don’t disagree that the flakes design needs some serious rethought.

I also believe the “dendritic” approach is silly and only makes sense in one incredibly niche scenario which honestly nobody realistically should have, and could be solved better other ways even if they did. Don’t use flake-parts for NixOS configuration, hell, don’t even use flakes for that.

But I disagree that if someone wants to maintain a nix package + module outside the nixpkgs monorepo they should incur an unpinned dependency on nixpkgs, in fact regardless of whether they use flakes or not.

That is one position to take, but I think it’s also totally valid to take the opposite position, and say that it is the maintainer’s responsibility to choose - or at least default to - a version of nixpkgs that can build and run their software.

You’re offering the very-married-to-nixpkgs solution, which is one option, but the tradeoff is that you’re going to run into breakage more often, and users will be exposed to that with unhelpful error messages. It also forces more boilerplate upon users to use your project. Yes, the flake design could have offered better solutions to this, but that doesn’t mean you’re forced to take this tradeoff.

Honestly if you’re going that route I think you should just upstream your project into nixpkgs directly.

@bitbloxhub suggests the alternative, which mostly comes at… Actually, honestly, what is the cost in this case? Just disk space and eval performance, right?

I understand the 1000-nixpkgs-instance problem, but at least you’re giving users the explicit choice to either instantiate another nixpkgs or risk breakage, with a reasonable default of not risking breakage for users who don’t understand what’s going on (and therefore are most likely a leaf project where the impact is small).

3 Likes

There is no “incurring an unpinned dependency” here. The pinning is just to be done elsewhere. Namely in your DI layer, whatever that is.

We haven’t seen any other yet, nor is what you’re suggesting any different, because whatever piece of code you have that interprets the…

…constraint is essentially a DI system. Just that there are bad DI systems, and there are worse.

Yes, because evalModules and scopes are the only DI systems we’ve so far designed for Nixlang and stuff :person_shrugging: You can hold them right (expose a callPackage-able entrypoint explicitly and handle the pinning in a different layer), or you can hold them wrong (e.g. through flakes’ outputs.packages), but you are holding them in any case.

Great. Is your code meant for out-of-tree consumers? Then you probably need an entrypoint, and you need to make things composable. That’s what these Nixpkgs’ tools are for, and what flakes do not do…

You’re the one applying the “right” and “wrong” labels without justification here. Flakes can serve as a - poorly designed - DI layer, and that’s what the thread is asking for.

I don’t think there’s much point in continuing this discussion, we clearly disagree on the finer details about what workarounds are acceptable for now, but agree that there currently is no “good” way to do what @emmanuelrosa wants to do in the ecosystem.

________But there is:)

As we’ve run into this problem too - yes we do try to upstream whenever possible, but exposing an overlay is our only tenable alternative. Possible build failures are unfortunate, but throwing another nixpkgs instance that the user can’t get rid of is disrespectful of their resources at best - the last thing we want to hear is “my eval time doubled when I used your project”. And cross-compilation machinery and even just compilation machinery are all in nixpkgs, so having some nixpkgs instance seems unavoidable.

Hence it seems to us that the best compromise is exposing an overlay.

(Ironically, this thought process has absolutely nothing to do with flakes :rofl:)

1 Like

this conversation really needs to be split up… poor @emmanuelrosa :laughing:

7 Likes

4 posts were split to a new topic: The dendritic pattern (in response to comments in ‘How to define the default package for a NixOS module in a Nix flake?’)