···11lib:
22let
33+ # Import the resolve function which handles aspect resolution and dependency injection
34 resolve = import ./resolve.nix lib;
4566+ # Top-level aspects container type
77+ # This is the entry point for defining all aspects in a flake
88+ # Structure: aspects.<aspectName> = { ... }
99+ # Makes the entire aspects config available as 'aspects' in module args
1010+ # allowing cross-referencing between aspects
511 aspectsType = lib.types.submodule (
612 { config, ... }:
713 {
88- freeformType = lib.types.attrsOf (lib.types.either aspectSubmoduleAttrs providerType);
1414+ # Allow arbitrary aspect definitions as attributes
1515+ # Each aspect can be either:
1616+ # - An aspect submodule (aspectSubmoduleAttrs)
1717+ # - A provider function (providerType)
1818+ freeformType = lib.types.lazyAttrsOf (lib.types.either aspectSubmoduleAttrs providerType);
1919+ # Inject the aspects config into _module.args for cross-referencing
920 config._module.args.aspects = config;
1021 }
1122 );
12231313- # checks the argument names to be those of a provider function:
2424+ # Type checker for provider functions with specific argument patterns
2525+ # Valid provider function signatures:
2626+ # 1. { class } => aspect-object
2727+ # 2. { aspect-chain } => aspect-object
2828+ # 3. { class, aspect-chain } => aspect-object
2929+ # 4. { class, ... } => aspect-object (with other ignored args)
3030+ # 5. { aspect-chain, ... } => aspect-object (with other ignored args)
1431 #
1515- # { class, aspect-chain } => aspect-object
1616- # { class, ... } => aspect-object
1717- # { aspect-chain, ... } => aspect-object
3232+ # This ensures that provider functions receive the proper context when invoked
1833 functionToAspect = lib.types.addCheck (lib.types.functionTo aspectSubmodule) (
1934 f:
2035 let
···2944 classOnly || chainOnly || both
3045 );
31464747+ # Provider functions can be:
4848+ # 1. A function taking { class, aspect-chain } and returning an aspect (functionToAspect)
4949+ # 2. A function taking parameters and returning another provider (curried)
5050+ # This allows for parametric aspects and lazy evaluation
3251 functionProviderType = lib.types.either functionToAspect (lib.types.functionTo providerType);
5252+5353+ # Provider type allows three forms:
5454+ # 1. A function provider (functionProviderType)
5555+ # 2. An aspect configuration (aspectSubmodule)
5656+ # This enables both immediate aspect definitions and deferred/parametric ones
3357 providerType = lib.types.either functionProviderType aspectSubmodule;
34585959+ # Additional validation for aspect submodules to ensure they're not mistyped functions
6060+ # An aspectSubmoduleAttrs is either:
6161+ # - Not a function at all (plain attribute set)
6262+ # - A function with submodule-style arguments (lib, config, options, aspect)
6363+ # This prevents accidentally treating provider functions as aspect configs
3564 aspectSubmoduleAttrs = lib.types.addCheck aspectSubmodule (
3665 m: (!builtins.isFunction m) || (isAspectSubmoduleFn m)
3766 );
6767+6868+ # Helper to identify if a function is a submodule-style function
6969+ # Submodule functions take args like { lib, config, options, aspect, ... }
7070+ # Returns true if the function accepts at least one of these special args
3871 isAspectSubmoduleFn =
3972 m:
4073 lib.pipe m [
···4982 (x: lib.length x > 0)
5083 ];
51848585+ # Special type that accepts any value but always merges to null
8686+ # Used for internal computed values that shouldn't be serialized
8787+ # This prevents type errors when values don't have proper types
5288 ignoredType = lib.types.mkOptionType {
5389 name = "ignored type";
5490 description = "ignored values";
···5692 check = _: true;
5793 };
58949595+ # Core aspect definition type
9696+ # Each aspect represents a reusable configuration module that can:
9797+ # - Define configuration for multiple "classes" (e.g., nixos, home-manager, darwin)
9898+ # - Include other aspects as dependencies
9999+ # - Provide sub-aspects for selective composition
100100+ # - Be parametrized via __functor
59101 aspectSubmodule = lib.types.submodule (
60102 {
61103 name,
···63105 ...
64106 }:
65107 {
108108+ # Allow arbitrary class configurations (e.g., nixos, home-manager, etc.)
109109+ # Each class maps to a deferred module that will be resolved later
66110 freeformType = lib.types.attrsOf lib.types.deferredModule;
111111+112112+ # Make the aspect config available as 'aspect' in module args
113113+ # This allows modules within the aspect to reference their own aspect
67114 config._module.args.aspect = config;
115115+116116+ # Create "_" as a shorthand alias for "provides"
117117+ # Allows writing: aspect._.foo instead of aspect.provides.foo
118118+ # This improves ergonomics for the common case of defining sub-aspects
68119 imports = [ (lib.mkAliasOptionModule [ "_" ] [ "provides" ]) ];
120120+121121+ # Human-readable aspect name, defaults to the attribute name
122122+ # Used in aspect-chain tracking and for display purposes
69123 options.name = lib.mkOption {
70124 description = "Aspect name";
71125 default = name;
72126 type = lib.types.str;
73127 };
128128+129129+ # Optional description for documentation purposes
130130+ # Defaults to a generic description using the aspect name
74131 options.description = lib.mkOption {
75132 description = "Aspect description";
76133 default = "Aspect ${name}";
77134 type = lib.types.str;
78135 };
136136+137137+ # Dependencies: list of other providers this aspect includes
138138+ # During resolution, included aspects are merged with this aspect
139139+ # Includes can be:
140140+ # - Direct aspect references: aspects.otherAspect
141141+ # - Parametrized providers: aspects.other.provides.foo "param"
142142+ # - Functorized aspects: aspects.otherAspect { param = value; }
143143+ # The resolution order matters for module merging semantics
79144 options.includes = lib.mkOption {
80145 description = "Providers to ask aspects from";
81146 type = lib.types.listOf providerType;
82147 default = [ ];
83148 };
149149+150150+ # Sub-aspects that can be selectively included by other aspects
151151+ # This allows aspects to expose multiple named variants or components
152152+ # Creates a fixpoint where provides can reference the aspects in their scope
153153+ # The provides scope gets its own 'aspects' arg for internal cross-referencing
84154 options.provides = lib.mkOption {
85155 description = "Providers of aspect for other aspects";
86156 default = { };
87157 type = lib.types.submodule (
88158 { config, ... }:
89159 {
160160+ # Allow arbitrary sub-aspect definitions
90161 freeformType = lib.types.attrsOf providerType;
162162+ # Make the provides scope available as 'aspects' for fixpoint references
163163+ # This enables provides.foo to reference provides.bar via aspects.bar
91164 config._module.args.aspects = config;
92165 }
93166 );
94167 };
168168+169169+ # Functor enables aspects to be callable like functions
170170+ # When defined, calling aspect { param = value; } invokes the functor
171171+ # The functor receives:
172172+ # 1. The aspect config itself
173173+ # 2. The parameters passed by the caller (which must include class and aspect-chain)
174174+ # This allows aspects to be parametrized and context-aware
175175+ #
176176+ # The default functor:
177177+ # - Takes the aspect config
178178+ # - Takes { class, aspect-chain } parameters
179179+ # - Returns the aspect unchanged (identity function with parameter access)
180180+ # - The weird `if true || (class aspect-chain) then` is to silence nixf-diagnose
181181+ # about unused variables while ensuring they're in scope
95182 options.__functor = lib.mkOption {
96183 internal = true;
97184 visible = false;
···100187 default =
101188 aspect:
102189 { class, aspect-chain }:
103103- # silence nixf-diagnose :/
190190+ # silence nixf-diagnose about unused variables
104191 if true || (class aspect-chain) then aspect else aspect;
105192 };
193193+194194+ # Convenience accessor: aspect.modules.<class> automatically resolves
195195+ # This is equivalent to calling aspect.resolve { class = "<class>"; }
196196+ # Returns a map of all classes with their resolved modules
197197+ #
198198+ # For example: aspect.modules.nixos == aspect.resolve { class = "nixos"; }
199199+ #
200200+ # This is computed lazily and uses ignoredType to avoid serialization issues
106201 options.modules = lib.mkOption {
107202 internal = true;
108203 visible = false;
109204 readOnly = true;
110205 description = "resolved modules from this aspect";
111206 type = ignoredType;
207207+ # For each class in the aspect, resolve it with empty aspect-chain
112208 apply = _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config;
113209 };
210210+211211+ # Main resolution function that converts an aspect into a nixpkgs module
212212+ # Takes { class, aspect-chain } and returns a resolved module
213213+ # - class: The target configuration class (e.g., "nixos", "home-manager")
214214+ # - aspect-chain: List of aspects traversed so far (for tracking dependencies)
215215+ #
216216+ # The resolution process:
217217+ # 1. Invokes the aspect config with class and aspect-chain parameters
218218+ # This triggers the __functor if defined, allowing parametrization
219219+ # 2. Calls resolve.nix to recursively resolve includes
220220+ # 3. Returns a module with imports from the aspect and its dependencies
221221+ #
222222+ # The aspect-chain parameter allows aspects to introspect their dependency tree
223223+ # This is useful for debugging and for aspects that need to know their context
114224 options.resolve = lib.mkOption {
115225 internal = true;
116226 visible = false;
···123233 class,
124234 aspect-chain ? [ ],
125235 }:
236236+ # Invoke config (the aspect) with class and aspect-chain parameters
237237+ # This works because config is wrapped with __functor via the submodule system
238238+ # Then pass the result to resolve for dependency resolution
126239 resolve class aspect-chain (config {
127240 inherit class aspect-chain;
128241 });
···133246in
134247{
135248 inherit
136136- aspectsType
137137- aspectSubmodule
138138- providerType
249249+ aspectsType # Main entry point for flake.aspects
250250+ aspectSubmodule # Individual aspect definition type
251251+ providerType # Type for provider expressions
139252 ;
140253}