11lib :
22let
3+ # Import the resolve function which handles aspect resolution and dependency injection
34 resolve = import ./resolve.nix lib ;
45
6+ # Top-level aspects container type
7+ # This is the entry point for defining all aspects in a flake
8+ # Structure: aspects.<aspectName> = { ... }
9+ # Makes the entire aspects config available as 'aspects' in module args
10+ # allowing cross-referencing between aspects
511 aspectsType = lib . types . submodule (
612 { config , ... } :
713 {
8- freeformType = lib . types . attrsOf ( lib . types . either aspectSubmoduleAttrs providerType ) ;
14+ # Allow arbitrary aspect definitions as attributes
15+ # Each aspect can be either:
16+ # - An aspect submodule (aspectSubmoduleAttrs)
17+ # - A provider function (providerType)
18+ freeformType = lib . types . lazyAttrsOf ( lib . types . either aspectSubmoduleAttrs providerType ) ;
19+ # Inject the aspects config into _module.args for cross-referencing
920 config . _module . args . aspects = config ;
1021 }
1122 ) ;
1223
13- # checks the argument names to be those of a provider function:
24+ # Type checker for provider functions with specific argument patterns
25+ # Valid provider function signatures:
26+ # 1. { class } => aspect-object
27+ # 2. { aspect-chain } => aspect-object
28+ # 3. { class, aspect-chain } => aspect-object
29+ # 4. { class, ... } => aspect-object (with other ignored args)
30+ # 5. { aspect-chain, ... } => aspect-object (with other ignored args)
1431 #
15- # { class, aspect-chain } => aspect-object
16- # { class, ... } => aspect-object
17- # { aspect-chain, ... } => aspect-object
32+ # 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 ) ;
3146
47+ # Provider functions can be:
48+ # 1. A function taking { class, aspect-chain } and returning an aspect (functionToAspect)
49+ # 2. A function taking parameters and returning another provider (curried)
50+ # This allows for parametric aspects and lazy evaluation
3251 functionProviderType = lib . types . either functionToAspect ( lib . types . functionTo providerType ) ;
52+
53+ # Provider type allows three forms:
54+ # 1. A function provider (functionProviderType)
55+ # 2. An aspect configuration (aspectSubmodule)
56+ # This enables both immediate aspect definitions and deferred/parametric ones
3357 providerType = lib . types . either functionProviderType aspectSubmodule ;
3458
59+ # Additional validation for aspect submodules to ensure they're not mistyped functions
60+ # An aspectSubmoduleAttrs is either:
61+ # - Not a function at all (plain attribute set)
62+ # - A function with submodule-style arguments (lib, config, options, aspect)
63+ # This prevents accidentally treating provider functions as aspect configs
3564 aspectSubmoduleAttrs = lib . types . addCheck aspectSubmodule (
3665 m : ( ! builtins . isFunction m ) || ( isAspectSubmoduleFn m )
3766 ) ;
67+
68+ # Helper to identify if a function is a submodule-style function
69+ # Submodule functions take args like { lib, config, options, aspect, ... }
70+ # 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 ] ;
5184
85+ # Special type that accepts any value but always merges to null
86+ # Used for internal computed values that shouldn't be serialized
87+ # This prevents type errors when values don't have proper types
5288 ignoredType = lib . types . mkOptionType {
5389 name = "ignored type" ;
5490 description = "ignored values" ;
5591 merge = _loc : _defs : null ;
5692 check = _ : true ;
5793 } ;
5894
95+ # Core aspect definition type
96+ # Each aspect represents a reusable configuration module that can:
97+ # - Define configuration for multiple "classes" (e.g., nixos, home-manager, darwin)
98+ # - Include other aspects as dependencies
99+ # - Provide sub-aspects for selective composition
100+ # - Be parametrized via __functor
59101 aspectSubmodule = lib . types . submodule (
60102 {
61103 name ,
62104 config ,
63105 ...
64106 } :
65107 {
108+ # Allow arbitrary class configurations (e.g., nixos, home-manager, etc.)
109+ # Each class maps to a deferred module that will be resolved later
66110 freeformType = lib . types . attrsOf lib . types . deferredModule ;
111+
112+ # Make the aspect config available as 'aspect' in module args
113+ # This allows modules within the aspect to reference their own aspect
67114 config . _module . args . aspect = config ;
115+
116+ # Create "_" as a shorthand alias for "provides"
117+ # Allows writing: aspect._.foo instead of aspect.provides.foo
118+ # This improves ergonomics for the common case of defining sub-aspects
68119 imports = [ ( lib . mkAliasOptionModule [ "_" ] [ "provides" ] ) ] ;
120+
121+ # Human-readable aspect name, defaults to the attribute name
122+ # 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 } ;
128+
129+ # Optional description for documentation purposes
130+ # 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 } ;
136+
137+ # Dependencies: list of other providers this aspect includes
138+ # During resolution, included aspects are merged with this aspect
139+ # Includes can be:
140+ # - Direct aspect references: aspects.otherAspect
141+ # - Parametrized providers: aspects.other.provides.foo "param"
142+ # - Functorized aspects: aspects.otherAspect { param = value; }
143+ # 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 } ;
149+
150+ # Sub-aspects that can be selectively included by other aspects
151+ # This allows aspects to expose multiple named variants or components
152+ # Creates a fixpoint where provides can reference the aspects in their scope
153+ # 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 {
160+ # Allow arbitrary sub-aspect definitions
90161 freeformType = lib . types . attrsOf providerType ;
162+ # Make the provides scope available as 'aspects' for fixpoint references
163+ # This enables provides.foo to reference provides.bar via aspects.bar
91164 config . _module . args . aspects = config ;
92165 }
93166 ) ;
94167 } ;
168+
169+ # Functor enables aspects to be callable like functions
170+ # When defined, calling aspect { param = value; } invokes the functor
171+ # The functor receives:
172+ # 1. The aspect config itself
173+ # 2. The parameters passed by the caller (which must include class and aspect-chain)
174+ # This allows aspects to be parametrized and context-aware
175+ #
176+ # The default functor:
177+ # - Takes the aspect config
178+ # - Takes { class, aspect-chain } parameters
179+ # - Returns the aspect unchanged (identity function with parameter access)
180+ # - The weird `if true || (class aspect-chain) then` is to silence nixf-diagnose
181+ # about unused variables while ensuring they're in scope
95182 options . __functor = lib . mkOption {
96183 internal = true ;
97184 visible = false ;
@@ -100,17 +187,40 @@ let
100187 default =
101188 aspect :
102189 { class , aspect-chain } :
103- # silence nixf-diagnose :/
190+ # silence nixf-diagnose about unused variables
104191 if true || ( class aspect-chain ) then aspect else aspect ;
105192 } ;
193+
194+ # Convenience accessor: aspect.modules.<class> automatically resolves
195+ # This is equivalent to calling aspect.resolve { class = "<class>"; }
196+ # Returns a map of all classes with their resolved modules
197+ #
198+ # For example: aspect.modules.nixos == aspect.resolve { class = "nixos"; }
199+ #
200+ # 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 ;
207+ # For each class in the aspect, resolve it with empty aspect-chain
112208 apply = _ : lib . mapAttrs ( class : _ : config . resolve { inherit class ; } ) config ;
113209 } ;
210+
211+ # Main resolution function that converts an aspect into a nixpkgs module
212+ # Takes { class, aspect-chain } and returns a resolved module
213+ # - class: The target configuration class (e.g., "nixos", "home-manager")
214+ # - aspect-chain: List of aspects traversed so far (for tracking dependencies)
215+ #
216+ # The resolution process:
217+ # 1. Invokes the aspect config with class and aspect-chain parameters
218+ # This triggers the __functor if defined, allowing parametrization
219+ # 2. Calls resolve.nix to recursively resolve includes
220+ # 3. Returns a module with imports from the aspect and its dependencies
221+ #
222+ # The aspect-chain parameter allows aspects to introspect their dependency tree
223+ # 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 } :
236+ # Invoke config (the aspect) with class and aspect-chain parameters
237+ # This works because config is wrapped with __functor via the submodule system
238+ # 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
136- aspectsType
137- aspectSubmodule
138- providerType
249+ aspectsType # Main entry point for flake.aspects
250+ aspectSubmodule # Individual aspect definition type
251+ providerType # Type for provider expressions
139252 ;
140253}
0 commit comments