Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented

Refactor unfree aspect to use a module (#128)

This allows the aspect to be included in other aspects and compose all
unfree packages that are defined.

:warning: I just have tested the code for nixos and home-manager (as
module) definitions.


### Context:
I want to use `<den/unfree>` in multiple aspects and have their defined
packages to combined to one large `allowUnfreePredicate`.

That allows to define the unfree packages in the aspects near their
usage and not on the respective class definition (e.G. the host).

Here an example on my tryout repo where i am currently exploring how to
build my configs with den.

This defines an unfree predicate for vscode and the respective addons:


https://github.com/Shawn8901/dend_test/blob/main/modules/aspects/cfg/desktop/coding.nix

This defines my printer


https://github.com/Shawn8901/dend_test/blob/main/modules/aspects/cfg/printer.nix#L6

Both are later on then used in the host pointalpha together, whilst the
printer aspect is not used on my zenbook.

### Possible workarounds

* Gather unfree packages by hand and define them once on a class
* use `nixpkgs.config.allowUnfree = true`

### Other resources


Discussion on zulip
https://oeiuwq.zulipchat.com/#narrow/channel/548534-den/topic/feat-idea.20composable.20unfree.20batteries/with/564892231


Actual "root issue" why predicates are not combine/composable by default
https://github.com/NixOS/nixpkgs/issues/197325

### Possible Todos

~One issue i found is, that it does not play well with setting~
```nix
nixos.home-manager.useGlobalPkgs = true;
```
~as its setting a `nixpkgs.config.allowUnfreePredicate` even tho its not
allowed when using `useGlobalPkgs` in homeManager class.~
~This should just be done if `useGlobalPkgs` is set to false, tho i am
not yet fully confident how to set that condition correctly with den.~
Might have found a reasonable solution for that, see
https://github.com/vic/den/pull/128#discussion_r2656546800

authored by

Shawn8901 and committed by
GitHub
d56667df 7271da18

+134 -18
+16 -10
modules/aspects/provides/inputs.nix
··· 1 1 { den, withSystem, ... }: 2 + let 3 + inherit (den.lib) 4 + parametric 5 + take 6 + ; 7 + in 2 8 { 3 - den.provides.inputs' = den.lib.parametric.exactly { 9 + den.provides.inputs' = parametric.exactly { 4 10 description = '' 5 11 Provides the `flake-parts` `inputs'` (the flake's `inputs` with system pre-selected) 6 12 as a top-level module argument. ··· 30 36 ( 31 37 { OS, host }: 32 38 let 33 - unused = den.lib.take.unused OS; 39 + unused = take.unused OS; 34 40 in 35 41 withSystem host.system ( 36 42 { inputs', ... }: 37 - { 38 - ${host.class}._module.args.inputs' = unused inputs'; 43 + unused { 44 + ${host.class}._module.args.inputs' = inputs'; 39 45 } 40 46 ) 41 47 ) ··· 47 53 host, 48 54 }: 49 55 let 50 - unused = den.lib.take.unused [ 56 + unused = take.unused [ 51 57 OS 52 58 HM 53 59 ]; 54 60 in 55 61 withSystem host.system ( 56 62 { inputs', ... }: 57 - { 58 - ${user.class}._module.args.inputs' = unused inputs'; 63 + unused { 64 + ${user.class}._module.args.inputs' = inputs'; 59 65 } 60 66 ) 61 67 ) 62 68 ( 63 69 { HM, home }: 64 70 let 65 - unused = den.lib.take.unused HM; 71 + unused = take.unused HM; 66 72 in 67 73 withSystem home.system ( 68 74 { inputs', ... }: 69 - { 70 - ${home.class}._module.args.inputs' = unused inputs'; 75 + unused { 76 + ${home.class}._module.args.inputs' = inputs'; 71 77 } 72 78 ) 73 79 )
+2 -2
modules/aspects/provides/unfree.nix modules/aspects/provides/unfree/unfree.nix
··· 1 - { lib, den, ... }: 1 + { den, ... }: 2 2 { 3 3 den.provides.unfree.description = '' 4 4 A class generic aspect that enables unfree packages by name. ··· 16 16 _self: allowed-names: 17 17 { class, aspect-chain }: 18 18 den.lib.take.unused aspect-chain { 19 - ${class}.nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) allowed-names; 19 + ${class}.unfree.packages = allowed-names; 20 20 }; 21 21 }
+91
modules/aspects/provides/unfree/unfree-predicate-builder.nix
··· 1 + { den, lib, ... }: 2 + let 3 + inherit (den.lib) 4 + parametric 5 + take 6 + ; 7 + 8 + description = '' 9 + This is a private aspect always included in den.default. 10 + 11 + It adds a module option that gathers all packages defined 12 + in den._.unfree usages and declares a 13 + nixpkgs.config.allowUnfreePredicate for each class. 14 + 15 + ''; 16 + 17 + unfreeComposableModule.options.unfree = { 18 + packages = lib.mkOption { 19 + type = lib.types.listOf lib.types.str; 20 + default = [ ]; 21 + }; 22 + }; 23 + 24 + nixosAspect = 25 + { config, ... }: 26 + { 27 + nixpkgs.config.allowUnfreePredicate = (pkg: builtins.elem (lib.getName pkg) config.unfree.packages); 28 + }; 29 + 30 + homeManagerAspect = 31 + { config, osConfig, ... }: 32 + { 33 + nixpkgs = lib.mkIf (!osConfig.home-manager.useGlobalPkgs) { 34 + config.allowUnfreePredicate = (pkg: builtins.elem (lib.getName pkg) config.unfree.packages); 35 + }; 36 + }; 37 + 38 + aspect = parametric.exactly { 39 + inherit description; 40 + includes = [ 41 + ( 42 + { OS, host }: 43 + let 44 + unused = take.unused OS; 45 + in 46 + { 47 + ${host.class}.imports = unused [ 48 + unfreeComposableModule 49 + nixosAspect 50 + ]; 51 + } 52 + ) 53 + ( 54 + { 55 + OS, 56 + HM, 57 + user, 58 + host, 59 + }: 60 + let 61 + unused = take.unused [ 62 + OS 63 + HM 64 + host 65 + ]; 66 + in 67 + { 68 + ${user.class}.imports = unused [ 69 + unfreeComposableModule 70 + homeManagerAspect 71 + ]; 72 + } 73 + ) 74 + ( 75 + { HM, home }: 76 + let 77 + unused = take.unused HM; 78 + in 79 + { 80 + ${home.class}.imports = unused [ 81 + unfreeComposableModule 82 + nixosAspect 83 + ]; 84 + } 85 + ) 86 + ]; 87 + }; 88 + in 89 + { 90 + den.default.includes = [ aspect ]; 91 + }
+3 -3
templates/examples/flake.lock
··· 22 22 }, 23 23 "den": { 24 24 "locked": { 25 - "lastModified": 1763707606, 26 - "narHash": "sha256-l9v3NNdKj3GJvV5LhzsWDs4Sl2bg0tuKNFFkMeFvUWo=", 25 + "lastModified": 1767645236, 26 + "narHash": "sha256-VMrWett3fWRb0hQh9K1JUC3zV2OlV4zOajFZHGr1GPw=", 27 27 "owner": "vic", 28 28 "repo": "den", 29 - "rev": "8164e0d89c59839d67757bc9a1fb61770dc6e8b7", 29 + "rev": "e7837daa0ea0f03fba62a2653ce16314a7d9d1c8", 30 30 "type": "github" 31 31 }, 32 32 "original": {
+22 -3
templates/examples/modules/_example/ci/unfree.nix
··· 1 1 { den, ... }: 2 + 3 + let 4 + codeAspect = { 5 + includes = [ (den._.unfree [ "vscode" ]) ]; 6 + homeManager.programs.vscode.enable = true; 7 + }; 8 + discordAspect = { 9 + includes = [ 10 + (den._.unfree [ "discord" ]) 11 + ]; 12 + homeManager = 13 + { pkgs, ... }: 14 + { 15 + home.packages = [ pkgs.discord ]; 16 + }; 17 + }; 18 + in 2 19 { 3 - # cam uses unfree vscode. 4 - den.aspects.cam.homeManager.programs.vscode.enable = true; 5 - den.aspects.cam.includes = [ (den._.unfree [ "vscode" ]) ]; 20 + # cam uses unfree vscode and discord loaded from different aspects. 21 + den.aspects.cam.includes = [ 22 + codeAspect 23 + discordAspect 24 + ]; 6 25 }