Skip to content

QA: "cascade" enabling and flake module exports #52

@shimeoki

Description

@shimeoki

hello. first of all, i am pretty new in nix(os), so my understanding is a bit shaky sometimes. and sorry for the longread in advance.

i found your project while searching for ways to automate the process of importing modules. and your project looks amazing! it's like everything i wanted in my head.

before i found it, i even wanted to start implementing something like this by myself to comply with DRY principle, because modular configuration with options becomes boilerplate'y. and now i see no reason to. thank you.

so, i started to migrate my configuration. right now i have a structure for apps only: no "feature" sets like "cli", "gui", "development", etc..

aside from that, i have a single pattern: every module's enable option is dependent on parent's enable option. if shimeoki is enabled, then all shimeoki.XXX modules are enabled, if shimeoki.nixvim is enabled, then shimeoki.nixvim.plugins or shimeoki.nixvim.languages are enabled and so on.

i planned for my configuration to be used internally and externally. for my own usage, i can write

shimeoki = {
    enable = true;
    waybar.enable = false;
};

to enable everything, except for waybar. and other users can write

shimeoki = {
    waybar.enable = true;
};

to enable waybar only.

i think it's pretty convenient. so, is this pattern possible in denix?

i tried to write

options = { myconfig, ... }: delib.singleEnableOption myconfig.enable;

but it errors, because "an option declaration has type lambda". i think that's because singleEnableOption is a function itself, so it becomes a closure.

of course, a declaration like this works:

options =
    { myconfig, ... }:
    {
        zoxide = with delib; {
            enable = boolOption myconfig.enable;
        };
    };

but it's pretty verbose and not very convenient. something like this

options.zoxide =
    { myconfig, ... }:
    with delib;
    {
        enable = boolOption myconfig.enable;
    };

doesn't work, because the value assigned to options needs to be a lambda, and not to any of the values "below" options.

and besides from the verbosity, you need to write myconfig with full path to the current module. in case of a refactoring, this becomes a nightmare.

i think something like "relative paths", but for the modules would be amazing. my idea is to add a parent variable to the closure of options, which equals to "parent attribute set" of the current module name.

for example, the example above with more nested module would look like this:

options =
    { parent, ... }:
    {
        nixvim.plugins.lualine = with delib; {
            enable = boolOption parent.enable; # parent equals to nixvim.plugins
        };
    };

and to go even further, this could be translated into the wrapper function like singleEnableOption:

options = delib.singleCascadeEnableOption { };

or singleEnableCascadeOption. naming is debatable. it shouldn't have an argument, but as i know all functions in nix should have one argument, and i don't know if this could be a non-function to "hide" the argument.

the issue is, that i don't know is this even implementable (and if so, i would like to help), and is this against your philosophy or not. maybe, this can be done as an extension.

that was the first problem. it was just about providing a convenient interface to functionality that's already available, but i don't know about the second problem in this regard.

as mentioned above, i want to allow other users to import my modules into their configuration to use them. so, i do something like this in my flake:

nixosModules.shimeoki = ./shimeoki/nixos.nix;
nixosModules.default = self.nixosModules.shimeoki;

homeModules.shimeoki = ./shimeoki/hm.nix;
homeModules.default = self.homeModules.shimeoki;

where ./shimeoki/nixos.nix imports all <app>/nixos modules. these modules contain only nixos options. ./shimeoki/hm.nix works the same way, but declares home-manager options. the code can be viewed in the repository.

and, as i think it should work, the user adds my flake to their flake in the inputs, and then writes something like this:

{ inputs, ... }:
{
    imports = [
        inputs.nixconfig.nixosModules.shimeoki
    ];

    shimeoki.enable = true; # enable all nixos options from my modules
}

so, is this possible in denix? as i got it, it uses the "monomodule" approach, where delib.module contains the configuration for all platforms at once, and activates it based on the platform. if the platform is not nixos, then nixos is skipped, and the same applies to darwin and home. so, modules are not available to me, because they are "generated" dynamically based on the platform. is that right?

with this approach, is this possible to achieve similar functionality to mine? that the user can import modules for the selected platform from my flake and use them, i mean.

i haven't looked into the code yet, but if denix.lib.configurations uses "module generation based on platform" logic inside and then passes it to nixpkgs.lib.nixosSystem in modules, then the api can look something like this:

with denix.lib; {
    nixosModules.shimeoki = modules "nixos";
    homeModules.shimeoki = modules "home";
};

the issue with this approach that the nixos module in this case would export the home-manager modules as well (because if the platform is nixos, home platform is not disabled, but config is just declared in a different way), but i personally don't think it's a problem. it's probably weird to just import the nixos module without the home configuration, and many flakes do this as well (import home-manager module is nixos module is imported and home-manager is present).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions