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
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix contexts for user aspects to avoid duplicates. (#153)

Prevously we had a `{ OS, HM, host, user}` context applied to both, the
host aspect and the user aspect, that was causing duplicate definitions
since the context was the same and both host and user aspects include
den.default.

To fix this properly we now have separate aspects.

The host aspect is applied to `{ OS, host, user }` for each home-managed
user.
The user aspect is applied to `{ HM, host, user }` for each home-managed
user.

> ![NOTICE] Update your context-aware aspects if they use `{ OS, HM,
host, user}`
> As mentioned above this context is now split in two according if it
applies to a host or to a user aspect.

Also refactored the code to make it more idiomatic and easy to read.
Added tests based on the bogus repos from
https://github.com/vic/den/discussions/147#discussioncomment-15741563
and #150.

Closes #150

authored by

Victor Borja and committed by
GitHub
79dfb50d 1c8fc94e

+262 -222
+62
modules/aspects/provides/flake-parts/inputs.nix
··· 1 + { den, withSystem, ... }: 2 + let 3 + inherit (den.lib) parametric; 4 + inherit (den.lib.take) unused; 5 + 6 + description = '' 7 + Provides the `flake-parts` `inputs'` (the flake's `inputs` with system pre-selected) 8 + as a top-level module argument. 9 + 10 + This allows modules to access per-system flake outputs without needing 11 + `pkgs.stdenv.hostPlatform.system`. 12 + 13 + ## Usage 14 + 15 + **Global (Recommended):** 16 + Apply to all hosts, users, and homes. 17 + 18 + den.default.includes = [ den._.inputs' ]; 19 + 20 + **Specific:** 21 + Apply only to a specific host, user, or home aspect. 22 + 23 + den.aspects.my-laptop.includes = [ den._.inputs' ]; 24 + den.aspects.alice.includes = [ den._.inputs' ]; 25 + 26 + **Note:** This aspect is contextual. When included in a `host` aspect, it 27 + configures `inputs'` for the host's OS. When included in a `user` or `home` 28 + aspect, it configures `inputs'` for the corresponding Home Manager configuration. 29 + ''; 30 + 31 + mkAspect = 32 + class: system: 33 + withSystem system ( 34 + { inputs', ... }: 35 + { 36 + ${class}._module.args.inputs' = inputs'; 37 + } 38 + ); 39 + 40 + osAspect = { OS, host }: unused OS (mkAspect host.class host.system); 41 + 42 + userAspect = 43 + { 44 + HM, 45 + user, 46 + host, 47 + }: 48 + unused HM (mkAspect user.class host.system); 49 + 50 + hmAspect = { HM, home }: unused HM (mkAspect home.class home.system); 51 + 52 + in 53 + { 54 + den.provides.inputs' = parametric.exactly { 55 + inherit description; 56 + includes = [ 57 + osAspect 58 + userAspect 59 + hmAspect 60 + ]; 61 + }; 62 + }
+61
modules/aspects/provides/flake-parts/self.nix
··· 1 + { den, withSystem, ... }: 2 + let 3 + inherit (den.lib.take) unused; 4 + inherit (den.lib) parametric; 5 + 6 + description = '' 7 + Provides the `flake-parts` `self'` (the flake's `self` with system pre-selected) 8 + as a top-level module argument. 9 + 10 + This allows modules to access per-system flake outputs without needing 11 + `pkgs.stdenv.hostPlatform.system`. 12 + 13 + ## Usage 14 + 15 + **Global (Recommended):** 16 + Apply to all hosts, users, and homes. 17 + 18 + den.default.includes = [ den._.self' ]; 19 + 20 + **Specific:** 21 + Apply only to a specific host, user, or home aspect. 22 + 23 + den.aspects.my-laptop.includes = [ den._.self' ]; 24 + den.aspects.alice.includes = [ den._.self' ]; 25 + 26 + **Note:** This aspect is contextual. When included in a `host` aspect, it 27 + configures `self'` for the host's OS. When included in a `user` or `home` 28 + aspect, it configures `self'` for the corresponding Home Manager configuration. 29 + ''; 30 + 31 + mkAspect = 32 + class: system: 33 + withSystem system ( 34 + { self', ... }: 35 + { 36 + ${class}._module.args.self' = self'; 37 + } 38 + ); 39 + 40 + osAspect = { OS, host }: unused OS (mkAspect host.class host.system); 41 + 42 + userAspect = 43 + { 44 + HM, 45 + user, 46 + host, 47 + }: 48 + unused HM (mkAspect user.class host.system); 49 + 50 + homeAspect = { HM, home }: unused HM (mkAspect home.class home.system); 51 + in 52 + { 53 + den.provides.self' = parametric.exactly { 54 + inherit description; 55 + includes = [ 56 + osAspect 57 + userAspect 58 + homeAspect 59 + ]; 60 + }; 61 + }
+7 -2
modules/aspects/provides/home-manager/hm-dependencies.nix
··· 18 18 # from OS home-managed integration. 19 19 hmUserDependencies = 20 20 { HM-OS-USER }: 21 + let 22 + inherit (HM-OS-USER) OS HM; 23 + hostCtx = { inherit (HM-OS-USER) OS host user; }; 24 + userCtx = { inherit (HM-OS-USER) HM host user; }; 25 + in 21 26 { 22 27 includes = [ 23 28 (owned den.default) 24 29 (statics den.default) 25 - (parametric.fixedTo HM-OS-USER HM-OS-USER.OS) 26 - (parametric.fixedTo HM-OS-USER HM-OS-USER.HM) 30 + (parametric.fixedTo hostCtx OS) 31 + (parametric.fixedTo userCtx HM) 27 32 ]; 28 33 }; 29 34
-82
modules/aspects/provides/inputs.nix
··· 1 - { den, withSystem, ... }: 2 - let 3 - inherit (den.lib) 4 - parametric 5 - take 6 - ; 7 - in 8 - { 9 - den.provides.inputs' = parametric.exactly { 10 - description = '' 11 - Provides the `flake-parts` `inputs'` (the flake's `inputs` with system pre-selected) 12 - as a top-level module argument. 13 - 14 - This allows modules to access per-system flake outputs without needing 15 - `pkgs.stdenv.hostPlatform.system`. 16 - 17 - ## Usage 18 - 19 - **Global (Recommended):** 20 - Apply to all hosts, users, and homes. 21 - 22 - den.default.includes = [ den._.inputs' ]; 23 - 24 - **Specific:** 25 - Apply only to a specific host, user, or home aspect. 26 - 27 - den.aspects.my-laptop.includes = [ den._.inputs' ]; 28 - den.aspects.alice.includes = [ den._.inputs' ]; 29 - 30 - **Note:** This aspect is contextual. When included in a `host` aspect, it 31 - configures `inputs'` for the host's OS. When included in a `user` or `home` 32 - aspect, it configures `inputs'` for the corresponding Home Manager configuration. 33 - ''; 34 - 35 - includes = [ 36 - ( 37 - { OS, host }: 38 - let 39 - unused = take.unused OS; 40 - in 41 - withSystem host.system ( 42 - { inputs', ... }: 43 - unused { 44 - ${host.class}._module.args.inputs' = inputs'; 45 - } 46 - ) 47 - ) 48 - ( 49 - { 50 - OS, 51 - HM, 52 - user, 53 - host, 54 - }: 55 - let 56 - unused = take.unused [ 57 - OS 58 - HM 59 - ]; 60 - in 61 - withSystem host.system ( 62 - { inputs', ... }: 63 - unused { 64 - ${user.class}._module.args.inputs' = inputs'; 65 - } 66 - ) 67 - ) 68 - ( 69 - { HM, home }: 70 - let 71 - unused = take.unused HM; 72 - in 73 - withSystem home.system ( 74 - { inputs', ... }: 75 - unused { 76 - ${home.class}._module.args.inputs' = inputs'; 77 - } 78 - ) 79 - ) 80 - ]; 81 - }; 82 - }
-76
modules/aspects/provides/self.nix
··· 1 - { den, withSystem, ... }: 2 - { 3 - den.provides.self' = den.lib.parametric.exactly { 4 - description = '' 5 - Provides the `flake-parts` `self'` (the flake's `self` with system pre-selected) 6 - as a top-level module argument. 7 - 8 - This allows modules to access per-system flake outputs without needing 9 - `pkgs.stdenv.hostPlatform.system`. 10 - 11 - ## Usage 12 - 13 - **Global (Recommended):** 14 - Apply to all hosts, users, and homes. 15 - 16 - den.default.includes = [ den._.self' ]; 17 - 18 - **Specific:** 19 - Apply only to a specific host, user, or home aspect. 20 - 21 - den.aspects.my-laptop.includes = [ den._.self' ]; 22 - den.aspects.alice.includes = [ den._.self' ]; 23 - 24 - **Note:** This aspect is contextual. When included in a `host` aspect, it 25 - configures `self'` for the host's OS. When included in a `user` or `home` 26 - aspect, it configures `self'` for the corresponding Home Manager configuration. 27 - ''; 28 - 29 - includes = [ 30 - ( 31 - { OS, host }: 32 - let 33 - unused = den.lib.take.unused OS; 34 - in 35 - withSystem host.system ( 36 - { self', ... }: 37 - { 38 - ${host.class}._module.args.self' = unused self'; 39 - } 40 - ) 41 - ) 42 - ( 43 - { 44 - OS, 45 - HM, 46 - user, 47 - host, 48 - }: 49 - let 50 - unused = den.lib.take.unused [ 51 - OS 52 - HM 53 - ]; 54 - in 55 - withSystem host.system ( 56 - { self', ... }: 57 - { 58 - ${user.class}._module.args.self' = unused self'; 59 - } 60 - ) 61 - ) 62 - ( 63 - { HM, home }: 64 - let 65 - unused = den.lib.take.unused HM; 66 - in 67 - withSystem home.system ( 68 - { self', ... }: 69 - { 70 - ${home.class}._module.args.self' = unused self'; 71 - } 72 - ) 73 - ) 74 - ]; 75 - }; 76 - }
+45 -53
modules/aspects/provides/unfree/unfree-predicate-builder.nix
··· 14 14 15 15 ''; 16 16 17 - unfreeComposableModule.options.unfree = { 17 + unfreeOption.options.unfree = { 18 18 packages = lib.mkOption { 19 19 type = lib.types.listOf lib.types.str; 20 20 default = [ ]; 21 21 }; 22 22 }; 23 23 24 - nixosAspect = 25 - { config, ... }: 24 + unfreeModule = 25 + { config, ... }@args: 26 + let 27 + # nixpkgs.config must not be set when useGlobalPkgs is true. 28 + globalPkgs = args.osConfig.home-manager.useGlobalPkgs or false; 29 + in 26 30 { 27 - nixpkgs.config.allowUnfreePredicate = (pkg: builtins.elem (lib.getName pkg) config.unfree.packages); 31 + nixpkgs = lib.mkIf (!globalPkgs) { 32 + config.allowUnfreePredicate = (pkg: builtins.elem (lib.getName pkg) config.unfree.packages); 33 + }; 28 34 }; 29 35 30 - homeManagerAspect = 31 - { config, osConfig, ... }: 36 + osAspect = 37 + { OS, host }: 38 + take.unused OS { 39 + ${host.class}.imports = [ 40 + unfreeOption 41 + unfreeModule 42 + ]; 43 + }; 44 + 45 + userAspect = 32 46 { 33 - nixpkgs = lib.mkIf (!osConfig.home-manager.useGlobalPkgs) { 34 - config.allowUnfreePredicate = (pkg: builtins.elem (lib.getName pkg) config.unfree.packages); 47 + HM, 48 + user, 49 + host, 50 + }: 51 + take.unused 52 + [ 53 + HM 54 + host 55 + ] 56 + { 57 + ${user.class}.imports = [ 58 + unfreeOption 59 + unfreeModule 60 + ]; 35 61 }; 62 + 63 + homeAspect = 64 + { HM, home }: 65 + take.unused HM { 66 + ${home.class}.imports = [ 67 + unfreeOption 68 + unfreeModule 69 + ]; 36 70 }; 37 71 38 72 aspect = parametric.exactly { 39 73 inherit description; 40 74 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 - ) 75 + osAspect 76 + userAspect 77 + homeAspect 86 78 ]; 87 79 }; 88 80 in
+9 -4
modules/aspects/provides/unfree/unfree.nix
··· 1 1 { den, ... }: 2 - { 3 - den.provides.unfree.description = '' 2 + let 3 + description = '' 4 4 A class generic aspect that enables unfree packages by name. 5 5 6 6 Works for any class (nixos/darwin/homeManager,etc) on any host/user/home context. 7 7 8 8 ## Usage 9 9 10 - den.aspects.my-laptop.includes = [ (den._.unfree [ "code" ]) ]; 10 + den.aspects.my-laptop.includes = [ (den._.unfree [ "example-unfree-package" ]) ]; 11 11 12 12 It will dynamically provide a module for each class when accessed. 13 13 ''; 14 14 15 - den.provides.unfree.__functor = 15 + __functor = 16 16 _self: allowed-names: 17 17 { class, aspect-chain }: 18 18 den.lib.take.unused aspect-chain { 19 19 ${class}.unfree.packages = allowed-names; 20 20 }; 21 + in 22 + { 23 + den.provides.unfree = { 24 + inherit description __functor; 25 + }; 21 26 }
+4 -5
templates/ci/modules/hm-enabled-host.nix
··· 4 4 # a HM supported OS and at least one user with homeManager class. 5 5 den.aspects.hm-global-pkgs = 6 6 { HM-OS-HOST }: 7 - den.lib.take.unused [ HM-OS-HOST.host ] # access host from context if needed 8 - { 9 - nixos.home-manager.useGlobalPkgs = true; 10 - }; 7 + { 8 + nixos.home-manager.useGlobalPkgs = HM-OS-HOST.host.hostName == "rockhopper"; 9 + }; 11 10 12 - den.default.includes = [ den.aspects.hm-global-pkgs ]; 11 + den.aspects.rockhopper.includes = [ den.aspects.hm-global-pkgs ]; 13 12 14 13 den.hosts.x86_64-linux.no-homes = { }; 15 14
+74
templates/ci/modules/unfree-and-inputs-nodups.nix
··· 1 + { 2 + den, 3 + lib, 4 + withSystem, 5 + inputs, 6 + ... 7 + }: 8 + { 9 + # we use another unfree-host and unfree-user because 10 + # unlike rockhopper, we need useGlobalPkgs=false for hm test. 11 + den.hosts.x86_64-linux.unfree-host.users.unfree-user = { }; 12 + # for testing home-manager nixpkgs.config: 13 + den.aspects.unfree-host.nixos.home-manager.useGlobalPkgs = false; 14 + 15 + den.aspects.testingInputs'.homeManager = 16 + { inputs', ... }: 17 + { 18 + home.packages = [ inputs'.nixpkgs.legacyPackages.cowsay ]; 19 + }; 20 + 21 + den.aspects.testingSelf'.homeManager = 22 + { self', ... }: 23 + { 24 + home.packages = [ self'.packages.bye ]; 25 + }; 26 + 27 + den.aspects.testingUnfree.includes = [ 28 + (den._.unfree [ "example-unfree-package" ]) 29 + { 30 + homeManager = 31 + { pkgs, ... }: 32 + { 33 + home.packages = [ pkgs.hello-unfree ]; 34 + }; 35 + } 36 + ]; 37 + 38 + # including on user was causing duplicate definitions. 39 + den.aspects.unfree-user.includes = [ 40 + den.aspects.testingInputs' 41 + den.aspects.testingSelf' 42 + den.aspects.testingUnfree 43 + ]; 44 + 45 + flake.checks.x86_64-linux = withSystem "x86_64-linux" ( 46 + { 47 + pkgs, 48 + checkCond, 49 + ... 50 + }: 51 + let 52 + host = inputs.self.nixosConfigurations.unfree-host.config; 53 + user = host.home-manager.users.unfree-user; 54 + 55 + names = map lib.getName user.home.packages; 56 + inputsCheck = builtins.elem "cowsay" names; 57 + selfCheck = builtins.elem "hello" names; 58 + 59 + useGlobalPkgs = host.home-manager.useGlobalPkgs; 60 + unfreeCheck = user.nixpkgs.config.allowUnfreePredicate pkgs.hello-unfree; 61 + in 62 + { 63 + unfree-user-has-self-bye = checkCond "self bye" selfCheck; 64 + unfree-user-has-inputs-cowsay = checkCond "inputs cowsay" inputsCheck; 65 + unfree-user-hm-unfree = checkCond "hm unfree" ((!useGlobalPkgs) && unfreeCheck); 66 + } 67 + ); 68 + 69 + perSystem = 70 + { pkgs, ... }: 71 + { 72 + packages.bye = pkgs.hello; 73 + }; 74 + }