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 49 (x: lib.length x > 0) 50 50 ]; 51 51 52 + ignoredType = lib.types.mkOptionType { 53 + name = "ignored type"; 54 + description = "ignored values"; 55 + merge = _loc: _defs: null; 56 + check = _: true; 57 + }; 58 + 52 59 aspectSubmodule = lib.types.submodule ( 53 60 { 54 61 name, 55 - aspect, 56 62 config, 57 63 ... 58 64 }: ··· 102 108 visible = false; 103 109 readOnly = true; 104 110 description = "resolved modules from this aspect"; 105 - type = lib.types.attrsOf lib.types.deferredModule; 106 - default = lib.mapAttrs (class: _: aspect.resolve { inherit class; }) aspect; 111 + type = ignoredType; 112 + apply = _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config; 107 113 }; 108 114 options.resolve = lib.mkOption { 109 115 internal = true; 110 116 visible = false; 111 117 readOnly = true; 112 118 description = "function to resolve a module from this aspect"; 113 - type = lib.types.functionTo lib.types.deferredModule; 114 - default = 119 + type = ignoredType; 120 + apply = 121 + _: 115 122 { 116 123 class, 117 124 aspect-chain ? [ ], 118 125 }: 119 - resolve class aspect-chain (aspect { 126 + resolve class aspect-chain (config { 120 127 inherit class aspect-chain; 121 128 }); 122 129 };