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

Fix parametric dups on homeManager (#105)

See https://github.com/vic/den/discussions/99#discussion-9150825


Fixes #99

authored by oeiuwq.com and committed by

GitHub 364b7f14 adcf2519

+94 -41
-2
modules/aspects/dependencies.nix
··· 75 75 (statics den.default) 76 76 (owned HM) 77 77 (statics HM) 78 - (owned OS) 79 - (statics OS) 80 78 (parametric.fixedTo context OS) 81 79 ]; 82 80 };
+2 -1
nix/fn-can-take.nix
··· 3 3 check = 4 4 params: func: 5 5 let 6 + givenFn = builtins.isFunction func || (builtins.isAttrs func && func ? __functor); 6 7 givenArgs = builtins.isAttrs params; 7 8 fargs = lib.functionArgs func; 8 9 provided = builtins.attrNames params; 9 10 args = lib.mapAttrsToList (name: optional: { inherit name optional; }) fargs; 10 11 required = map (x: x.name) (lib.filter (x: !x.optional) args); 11 12 intersection = lib.intersectLists required provided; 12 - satisfied = givenArgs && lib.length required == lib.length intersection; 13 + satisfied = givenFn && givenArgs && lib.length required == lib.length intersection; 13 14 noExtras = lib.length required == lib.length provided; 14 15 exactly = satisfied && noExtras; 15 16 in
+28 -30
nix/lib.nix
··· 17 17 }; 18 18 }; 19 19 20 - isFn = f: (builtins.isFunction f) || (f ? __functor); 20 + isFn = f: (builtins.isFunction f) || (builtins.isAttrs f && f ? __functor); 21 21 canTake = import ./fn-can-take.nix lib; 22 22 23 23 # an aspect producing only owned configs ··· 33 33 // { 34 34 __functor = 35 35 self: 36 - # deadnix: skip 37 - { class, aspect-chain }@ctx: 38 - funk applyStatics self ctx; 36 + { class, aspect-chain }: 37 + { 38 + includes = map (applyStatics { inherit class aspect-chain; }) self.includes; 39 + }; 39 40 }; 40 41 41 42 applyStatics = ··· 51 52 class = ""; 52 53 aspect-chain = [ ]; 53 54 }; 54 - isCtxStatic = (lib.flip canTake.exactly) ( 55 - # deadnix: skip 56 - { class, aspect-chain }: true 57 - ); 55 + isCtxStatic = (lib.flip canTake.exactly) ({ class, aspect-chain }: class aspect-chain); 58 56 59 57 take.unused = _unused: used: used; 60 58 take.exactly = take canTake.exactly; ··· 66 64 parametric.atLeast = funk (lib.flip take.atLeast); 67 65 parametric.exactly = funk (lib.flip take.exactly); 68 66 parametric.expands = 69 - attrs: aspect: ctx: 70 - parametric.fixedTo (ctx // attrs) aspect; 67 + attrs: parametric.withOwn (aspect: ctx: parametric.atLeast aspect (ctx // attrs)); 71 68 parametric.fixedTo = 72 - ctx: aspect: 73 - { class, aspect-chain }: 74 - { 75 - includes = [ 76 - (parametric.atLeast aspect ctx) 77 - (parametricStatics aspect { inherit class aspect-chain; }) 78 - ]; 69 + attrs: aspect: 70 + aspect 71 + // { 72 + __functor = 73 + self: 74 + { class, aspect-chain }: 75 + { 76 + includes = [ 77 + (owned self) 78 + (statics self { inherit class aspect-chain; }) 79 + (parametric.atLeast self attrs) 80 + ]; 81 + }; 79 82 }; 80 83 parametric.withOwn = 81 84 functor: aspect: 82 85 aspect 83 86 // { 84 87 __functor = self: ctx: { 85 - includes = [ 86 - (functor self ctx) 87 - (parametricStatics self ctx) 88 - ]; 88 + includes = 89 + if isCtxStatic ctx then 90 + [ 91 + (owned self) 92 + (statics self ctx) 93 + ] 94 + else 95 + [ (functor self ctx) ]; 89 96 }; 90 97 }; 91 98 parametric.__functor = _: parametric.withOwn parametric.atLeast; 92 - 93 - parametricStatics = self: ctx: { 94 - includes = lib.optionals (isCtxStatic ctx) [ 95 - (owned self) 96 - { 97 - includes = map (applyStatics ctx) self.includes; 98 - } 99 - ]; 100 - }; 101 99 102 100 aspects = inputs.flake-aspects.lib lib; 103 101
+64 -8
templates/examples/modules/_example/ci/parametric-with-owned.nix
··· 1 - { den, lib, ... }: 1 + { 2 + den, 3 + lib, 4 + ... 5 + }: 2 6 let 3 7 # a test module to check context was forwarded 4 - fwdModule.nixos.options.fwd = { 8 + fwdModule.options.fwd = { 5 9 a = strOpt; 6 10 b = strOpt; 7 11 c = strOpt; 8 12 d = strOpt; 9 13 e = strOpt; 10 14 f = strOpt; 11 - # unlike strings, pkgs cannot be duplicated, we use this to 15 + # unlike strings, pkgs cannot be duplicated/merged, we use this to 12 16 # ensure no-dups are created from parametric owned modules. 13 - pkg = lib.mkOption { type = lib.types.package; }; 17 + pkg = pkgOpt; 18 + pkg2 = pkgOpt; 19 + pkg3 = pkgOpt; 14 20 }; 15 21 strOpt = lib.mkOption { type = lib.types.str; }; 22 + pkgOpt = lib.mkOption { type = lib.types.package; }; 16 23 17 24 inherit (den.lib) parametric; 18 25 in 19 26 { 20 - 21 27 den.aspects.rockhopper.includes = [ 22 - fwdModule 28 + { nixos.imports = [ fwdModule ]; } 29 + { homeManager.imports = [ fwdModule ]; } 23 30 den.aspects.fwd._.first 24 31 ]; 25 32 den.aspects.rockhopper.nixos.fwd.c = "host owned C"; 33 + den.aspects.rockhopper.homeManager.fwd.a = "host home-managed A"; 26 34 27 35 # this aspect will take any context and also forward it 28 36 # into any includes function that can take same context. ··· 33 41 fwd.a = "First owned A"; 34 42 fwd.pkg = pkgs.hello; 35 43 }; 44 + homeManager = 45 + { pkgs, ... }: 46 + { 47 + fwd.pkg = builtins.break pkgs.vim; 48 + }; 36 49 includes = [ 37 50 den.aspects.fwd._.second 38 51 { nixos.fwd.d = "First static includes D"; } ··· 46 59 den.aspects.fwd._.second = 47 60 { host, ... }: 48 61 parametric.fixedTo { third = "Impact"; } { 49 - nixos.fwd.b = "Second owned B for ${host.name}"; 50 62 includes = [ den.aspects.fwd._.third ]; 63 + nixos = 64 + { pkgs, ... }: 65 + { 66 + fwd.b = "Second owned B for ${host.name}"; 67 + fwd.pkg2 = pkgs.bat; 68 + }; 69 + homeManager = 70 + { pkgs, ... }: 71 + { 72 + fwd.pkg2 = pkgs.helix; 73 + }; 51 74 }; 52 75 53 76 den.aspects.fwd._.third = ··· 58 81 59 82 den.aspects.fwd._.fourth = parametric.expands { planet = "Earth"; } { 60 83 includes = [ den.aspects.fwd._.fifth ]; 84 + nixos = 85 + { pkgs, ... }: 86 + { 87 + fwd.pkg3 = pkgs.emacs-nox; 88 + }; 89 + homeManager = 90 + { pkgs, ... }: 91 + { 92 + fwd.pkg3 = pkgs.emacs-nox; 93 + }; 61 94 }; 62 95 63 96 den.aspects.fwd._.fifth = ··· 73 106 }; 74 107 75 108 perSystem = 76 - { checkCond, rockhopper, ... }: 109 + { 110 + checkCond, 111 + rockhopper, 112 + alice-at-rockhopper, 113 + ... 114 + }: 77 115 { 78 116 checks.parametric-fwd-a = checkCond "fwd-a" (rockhopper.config.fwd.a == "First owned A"); 79 117 checks.parametric-fwd-b = checkCond "fwd-b" ( ··· 83 121 checks.parametric-fwd-d = checkCond "fwd-d" (rockhopper.config.fwd.d == "First static includes D"); 84 122 checks.parametric-fwd-e = checkCond "fwd-e" (rockhopper.config.fwd.e == "Third Impact"); 85 123 checks.parametric-fwd-f = checkCond "fwd-f" (rockhopper.config.fwd.f == "Fifth Earth rockhopper"); 124 + 86 125 checks.parametric-fwd-pkg = checkCond "fwd-pkg" (lib.getName rockhopper.config.fwd.pkg == "hello"); 126 + checks.parametric-fwd-pkg2 = checkCond "fwd-pkg2" (lib.getName rockhopper.config.fwd.pkg2 == "bat"); 127 + checks.parametric-fwd-pkg3 = checkCond "fwd-pkg3" ( 128 + lib.getName rockhopper.config.fwd.pkg3 == "emacs-nox" 129 + ); 130 + 131 + checks.parametric-fwd-hm-a = checkCond "fwd-hm-a" ( 132 + alice-at-rockhopper.fwd.a == "host home-managed A" 133 + ); 134 + checks.parametric-fwd-hm-pkg = checkCond "fwd-hm-pkg" ( 135 + lib.getName alice-at-rockhopper.fwd.pkg == "vim" 136 + ); 137 + checks.parametric-fwd-hm-pkg2 = checkCond "fwd-hm-pkg2" ( 138 + lib.getName alice-at-rockhopper.fwd.pkg2 == "helix" 139 + ); 140 + checks.parametric-fwd-hm-pkg3 = checkCond "fwd-hm-pkg3" ( 141 + lib.getName alice-at-rockhopper.fwd.pkg3 == "emacs-nox" 142 + ); 87 143 }; 88 144 89 145 }