fake.modules transposition for aspect-oriented Dendritic Nix. with cross-aspect dependencies. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic nix aspect oriented

Merging (#25)

* test merging aspects

* fix: use custom merge functions for modules and resolve options (#23)

When merging aspect configs across scopes (e.g. bar = config.foo), the
modules and resolve options were incorrectly merging pre-computed values
from individual scopes instead of recomputing from the merged config.

The fix uses custom type merge functions that always recompute from the
final merged config, ensuring resolved modules reflect all merged aspect
definitions.

* dont merge modules and resolve

---------

Co-authored-by: Albert O'Shea <albertoshea2@gmail.com>

authored by oeiuwq.com

Albert O'Shea and committed by
GitHub
d0a226c8 d5065812

+79 -6
+66
checkmate/modules/tests/aspect_assignment.nix
···
··· 1 + { lib, targetLib, ... }: 2 + { 3 + # This test verifies that we can define aspects inside 4 + # an scope and then merge them in another scope. 5 + # 6 + # This is important for Den social aspects, since people will 7 + # try to merge aspects from different sources, local, and remote flakes. 8 + flake.tests."test-assign-aspects-on-scopes" = 9 + let 10 + flake-aspects-lib = import targetLib lib; 11 + 12 + first = lib.evalModules { 13 + modules = [ 14 + # each scope creates a new <name>.aspects tree. 15 + (flake-aspects-lib.new-scope "foo") 16 + (flake-aspects-lib.new-scope "bar") 17 + (flake-aspects-lib.new-scope "baz") 18 + # create a._.b._.c aspect on each namespace 19 + # we will be trying to merge them for this test. 20 + { 21 + foo.aspects.a._.b._.c.nixos.x = [ "foo" ]; 22 + } 23 + { 24 + bar.aspects.a._.b._.c.nixos.x = [ "bar" ]; 25 + } 26 + { 27 + baz.aspects.a._.b._.c.nixos.x = [ "baz" ]; 28 + } 29 + ( 30 + { config, ... }: 31 + { 32 + bar = config.foo; # bar merges all of foo 33 + } 34 + ) 35 + ( 36 + { config, ... }: 37 + { 38 + baz = config.bar; # baz merges all of baz 39 + } 40 + ) 41 + ]; 42 + }; 43 + 44 + second = lib.evalModules { 45 + modules = [ 46 + # We evaluate the abc nixos module from baz 47 + first.config.baz.aspects.a._.b._.c.modules.nixos 48 + # create the options to merge all different values 49 + { options.x = lib.mkOption { type = lib.types.listOf lib.types.str; }; } 50 + ]; 51 + }; 52 + 53 + # Sort and dedupe for deterministic comparison - merged modules may 54 + # produce duplicates and order is not guaranteed across merges. 55 + expr = lib.sort (a: b: a < b) (lib.unique second.config.x); 56 + expected = [ 57 + "bar" 58 + "baz" 59 + "foo" 60 + ]; 61 + in 62 + { 63 + inherit expected expr; 64 + }; 65 + 66 + }
+13 -6
nix/types.nix
··· 49 (x: lib.length x > 0) 50 ]; 51 52 aspectSubmodule = lib.types.submodule ( 53 { 54 name, 55 - aspect, 56 config, 57 ... 58 }: ··· 102 visible = false; 103 readOnly = true; 104 description = "resolved modules from this aspect"; 105 - type = lib.types.attrsOf lib.types.deferredModule; 106 - default = lib.mapAttrs (class: _: aspect.resolve { inherit class; }) aspect; 107 }; 108 options.resolve = lib.mkOption { 109 internal = true; 110 visible = false; 111 readOnly = true; 112 description = "function to resolve a module from this aspect"; 113 - type = lib.types.functionTo lib.types.deferredModule; 114 - default = 115 { 116 class, 117 aspect-chain ? [ ], 118 }: 119 - resolve class aspect-chain (aspect { 120 inherit class aspect-chain; 121 }); 122 };
··· 49 (x: lib.length x > 0) 50 ]; 51 52 + ignoredType = lib.types.mkOptionType { 53 + name = "ignored type"; 54 + description = "ignored values"; 55 + merge = _loc: _defs: null; 56 + check = _: true; 57 + }; 58 + 59 aspectSubmodule = lib.types.submodule ( 60 { 61 name, 62 config, 63 ... 64 }: ··· 108 visible = false; 109 readOnly = true; 110 description = "resolved modules from this aspect"; 111 + type = ignoredType; 112 + apply = _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config; 113 }; 114 options.resolve = lib.mkOption { 115 internal = true; 116 visible = false; 117 readOnly = true; 118 description = "function to resolve a module from this aspect"; 119 + type = ignoredType; 120 + apply = 121 + _: 122 { 123 class, 124 aspect-chain ? [ ], 125 }: 126 + resolve class aspect-chain (config { 127 inherit class aspect-chain; 128 }); 129 };