1{ lib, silicon }:
2with lib;
3with silicon;
4rec {
5 getFlake =
6 args@{ src, ... }:
7 let
8 yesFlake = false; # builtins ? getFlake;
9 flake =
10 if yesFlake then
11 (builtins.getFlake (toString src))
12 else
13 (
14 let
15 nodes =
16 if (builtins.pathExists ./flake.lock) then
17 (builtins.fromJSON (builtins.readFile ./flake.lock))
18 else
19 {
20 root.inputs.flake-compat = "flake-compat";
21 flake-compat.locked.rev = "master";
22 };
23 inherit (nodes.${nodes.root.inputs.flake-compat}) locked;
24 url = locked.url or "https://github.com/edolstra/flake-compat/archive/${locked.rev}.tar.gz";
25 flake-compat =
26 if (args ? flake-compat) then
27 (import args.flake-compat)
28 else if yesFlake then
29 (builtins.getFlake url)
30 else
31 (builtins.import (fetchTarball {
32 inherit url;
33 ${if (locked ? narHash) then "sha256" else null} = locked.narHash;
34 }));
35
36 # builtins.unsafeDiscardStringContext
37 in
38 flake-compat { inherit src; }
39 ).defaultNix;
40 in
41 formatOutputs {
42 inherit flake;
43 getFlake = true;
44 };
45
46 isLambda = f: (!(isAttrs f)) && (isFunction f);
47 isFunctor = f: (isAttrs f) && (isFunction f);
48 isFunctionTo = f: (f ? __functionArgs) && (f ? __functor) && ((length (attrNames f)) == 2);
49 isFunctionToLambda = f: (isLambda f) || (isFunctionTo f);
50
51 hasAnyAttrs = attrs: any (flip hasAttr attrs);
52 hasAllAttrs = attrs: all (flip hasAttr attrs);
53
54 mk =
55 let
56 defaulter =
57 config: options: attr:
58 if (config.${attr} != options.${attr}.default) then
59 config.${attr}
60 else if (hasAttr attr args) then
61 args.${attr}
62 else
63 config.${attr};
64 in
65 {
66 # Adapted From: https://github.com/hercules-ci/flake-parts/blob/4524271976b625a4a605beefd893f270620fd751/lib.nix#L72C5-L125C11
67 __functor = self: mk.simple;
68
69 simple =
70 module:
71 let
72 defaultSystems = lib.systems.flakeExposed;
73 systemType = types.enum defaultSystems;
74
75 # TODO: Which option types need to be replaced with `update'?
76 updateType = mkOptionType {
77 name = "update";
78 description = "update";
79 check = isAttrs;
80 merge = loc: recursiveFold;
81 emptyValue.value = { };
82 };
83
84 modules = {
85 lib =
86 { config, ... }:
87 let
88
89 # TODO: Test thoroughly. Can this use a `mks' function instead of `composeManyMergedExtensions'?
90 libType = mkOptionType {
91 name = "lib";
92 description = "library";
93 check = isAttrs;
94 merge =
95 loc: defs:
96 let
97 predicate = def: isFunction (def.extend or null);
98 values = map (getAttr "value") defs;
99 in
100 recursiveFold (
101 [
102 (
103 let
104 libs = filter predicate values;
105 in
106 if (libs == [ ]) then { } else ((head libs).extend (composeManyMergedExtensions (tail libs)))
107 )
108 ]
109 ++ (filter (def: !(predicate def)) values)
110 );
111 emptyValue.value = { };
112 };
113
114 in
115 {
116 options = {
117
118 # TODO: If not already, make libs passed to this extensible using a variation of `mks.extend'.
119 # TODO: Extend the lib manually, facilitating the use of `libType' in `libs' below.
120 lib = mkOption {
121 type = libType;
122 default = lib;
123 apply = lib: if (config.libs == { }) then lib else (mks.libs lib config.inheritances config.libs);
124 };
125
126 # TODO: Merge this and `libs' into a module following the format:
127 # { syvl = { lib = ...; inheritance = { ... }; }; };
128 # Modify the `mks.libs' call accordingly.
129 inheritances = mkOption {
130 type = types.attrsOf types.attrs;
131 default = { };
132 };
133
134 # TODO: Use `libType' here somehow. Also make these sublibs extensible if not already.
135 libs = mkOption {
136 type =
137 with types;
138 attrsOf (oneOf [
139 path
140 attrs
141 list
142 (functionTo attrs)
143 ]);
144 default = { };
145 };
146
147 };
148 };
149 # TODO: Maybe add a `specialArgs' for this as well?
150 nixosConfigurations =
151 { config, options, ... }:
152 {
153 options = {
154 hosts =
155 let
156 hostOptions = {
157 system = mkOption {
158 type = types.nullOr systemType;
159 default = null;
160 };
161 modules = mkOption {
162 type = options.modules.type.nestedTypes.finalType;
163 };
164 };
165 hostOptionNames = attrNames hostOptions;
166 hostModule =
167 { ... }:
168 {
169 options = hostOptions;
170 config.modules = config.modules;
171 };
172 in
173 mkOption {
174 type =
175 with types;
176 coercedTo (either str (listOf str)) (compose [
177 toList
178 (flip genAttrs (host: { }))
179 ]) attrs;
180 default = { };
181 apply = mapAttrs (
182 n: v:
183 recursiveUpdateAll
184 (evalModules {
185 class = "host";
186 modules = [
187 hostModule
188 (filterAttrs (n: v: elem n hostOptionNames) v)
189 ];
190 }).config
191 (filterAttrs (n: v: !(elem n hostOptionNames)) v)
192 );
193 };
194 modules = mkOption {
195 type =
196 with types;
197 let
198 moduleType = oneOf [
199 path
200 attrs
201 (functionTo attrs)
202 ];
203 in
204 coercedTo moduleType toList (listOf moduleType);
205 default = [ ];
206 };
207 nixosConfigurations = mkOption {
208 type =
209 with types;
210 let
211 configType = either attrs (functionTo attrs);
212 in
213 coercedTo configType toList (listOf configType);
214 default = [ ];
215 };
216 __nixosHosts = mkOption {
217 type = types.attrs;
218 internal = true;
219 };
220 };
221 config = mkIf (config.hosts != { }) {
222 __nixosHosts = mkMerge (
223 map (
224 nc:
225 mapAttrs (
226 hostName: host:
227 if (isFunctionToLambda nc) then
228 (recursiveUpdateAll (nc (
229 let
230 inherit (host) system;
231 in
232 {
233 inherit hostName system;
234 nixosArgs =
235 let
236 settings = mk.settings config system;
237 in
238 {
239 inherit hostName system settings;
240 inherit (config) inputs;
241 inherit (settings) overlays;
242 };
243 }
244 // config
245 )) host)
246 else
247 nc
248 ) config.hosts
249 ) config.nixosConfigurations
250 );
251 outputs.nixosConfigurations = mapAttrs (n: config.nixpkgs.lib.nixosSystem) config.__nixosHosts;
252 };
253 };
254 };
255 parent =
256 args@{ config, options, ... }:
257 let
258 makerType = optionTypes.functionArgsTo 2 types.attrs;
259 in
260 {
261 _file = ./flake.nix;
262 imports = attrValues modules;
263 # TODO: Organize these into subsets.
264 options = {
265 src = mkOption {
266 type = types.path;
267 default = args.src or (dirOf (unsafeGetAttrPos (head (attrNames module)) module).file);
268 apply = toString;
269 };
270 sources = mkOption {
271 type =
272 with types;
273 coercedTo
274 (oneOf [
275 str
276 path
277 (listOf str)
278 ])
279 (compose [
280 toList
281 (map (src: config.src + "/" + src))
282 (filter pathExists)
283 ])
284 (listOf path);
285 };
286 inputs = mkOption {
287 type = types.attrs;
288
289 # TODO: This might slow things down.
290 apply = mapAttrs (n: v: if (isAttrsOnly v) then (formatOutputs { flake = v; }) else v);
291
292 };
293 self = mkOption {
294 type = types.attrs;
295 default = config.inputs.self;
296 };
297 makers = mkOption { type = types.attrsOf makerType; };
298 maker = mkOption {
299 type =
300 with types;
301 coercedTo (enum (builtins.attrNames config.makers)) (flip getAttr config.makers) makerType;
302 default = "default";
303 };
304
305 # NOTE: Remember, `anything' matches first, so everything will be converted:
306 # https://github.com/NixOS/nixpkgs/blob/eba10d3bc41567ef00c0f7afd6ff4a763d578c1b/lib/types.nix#L1532-L1537
307 outputs = mkOption {
308 type =
309 with types;
310 coercedTo anything (compose [
311 (outputs: if (path.check outputs) then (builtins.import outputs) else outputs)
312 (config.maker config)
313 ]) attrs;
314 default = { };
315 };
316 passthru = {
317 system = mkOption {
318 type = types.nullOr systemType;
319 default = null;
320 };
321 outputs = mkOption {
322 type = types.attrs;
323 default = { };
324 };
325 };
326
327 systems =
328 let
329 default = defaultSystems;
330 in
331 mkOption {
332 inherit default;
333 type =
334 with types;
335 let
336 systemType = enum default;
337 in
338 coercedTo (either systemType (listOf systemType)) (compose [
339 toList
340 (flip genAttrs (system: { }))
341 ]) attrs;
342 };
343 nixpkgs = mkOption {
344 type = with types; nullOr (either attrs (functionTo attrs));
345 default =
346 let
347 nixpkgs = attrValues (filterAttrs (n: v: hasPrefix "nixpkgs" n) config.inputs);
348 in
349 if (nixpkgs == [ ]) then null else (head nixpkgs);
350 };
351 settings = mkOption {
352 type =
353 with types;
354 submodule (
355 { ... }:
356 {
357 options = {
358 config = mkOption {
359 type = attrs;
360 default = { };
361 };
362 overlays = mkOption {
363 type = coercedTo (either str (listOf str)) (
364 pkgs:
365 genAttrs (toList pkgs) (
366 pkg:
367 config.inputs.${pkg}.overlays.default or (final: prev: {
368 ${pkg} = config.inputs.${pkg}.packages.${final.stdenv.targetPlatform.system}.default;
369 })
370 )
371 ) (attrsOf ((optionTypes.functionArgsTo 2 attrs)));
372 default = { };
373 };
374 };
375 }
376 );
377 };
378 specialArgs = mkOption {
379 type = types.attrs;
380 default = { };
381 };
382 };
383 config = {
384 sources = [
385 "nix/sources.nix"
386 "npins"
387 ];
388 inputs = mkMerge (map builtins.import config.sources);
389 makers = mapAttrs (n: mkDefault) {
390 default =
391 config: outputs:
392 let
393 evalModule =
394 specialArgs:
395 let
396 system = specialArgs.system or null;
397 module =
398 (lib.evalModules {
399 class = "outputs";
400 modules = [
401 child
402
403 # Adapted From: https://github.com/NixOS/nixpkgs/blob/df3de70468b743b371a7191ea61d1040fe3bf399/lib/modules.nix#L567-L570
404 (setDefaultModuleLocation "${config.src}/flake.nix" outputs)
405 ];
406 specialArgs =
407 config
408 // {
409 inherit (config) lib;
410 parent = { inherit config options; };
411 }
412 // specialArgs
413 // config.specialArgs;
414 }).config.outputs;
415 in
416 mkMerge [
417 (mapAttrs
418 (
419 n: v:
420 if (!(specialArgs ? system)) then
421 module.${n}
422 else
423 {
424 ${system} = v;
425
426 # TODO: Modularize this.
427 }
428 )
429 (
430 removeAttrs module [
431 "passthru"
432 "nixosConfigurations"
433 ]
434 )
435 )
436 (mkIf ((module.passthru.system or null) == system) module.passthru.outputs)
437 (mkIf (module ? nixosConfigurations) {
438 inherit (module) nixosConfigurations;
439 })
440
441 ];
442 in
443 if (isAttrs outputs) then
444 outputs
445 else if
446 (hasAnyAttrs (builtins.functionArgs outputs) [
447 "system"
448 "pkgs"
449 ])
450 then
451 (mkMerge (
452 map (compose [
453 (mk.system config)
454 evalModule
455 ]) (attrNames config.systems)
456 ))
457 else
458 (evalModule { });
459 flake-parts =
460 config: outputs:
461 config.inputs.flake-parts.lib.mkFlake {
462 inherit (config) inputs;
463 } (if (isFunction outputs) then outputs else ({ systems = attrNames config.systems; } // outputs));
464 };
465 outputs =
466 let
467 srcDir = builtins.readDir config.src;
468 in
469 mkMerge [
470
471 (
472 let
473 outputs = config.src + "/outputs.nix";
474 in
475 mkIf ((srcDir."outputs.nix" or "directory") != "directory") outputs
476 )
477
478 (
479 let
480 outputs = config.src + "/outputs";
481 in
482 mkIf (
483 ((srcDir.outputs or "file") == "directory") && ((builtins.readDir outputs) ? "default.nix")
484 ) outputs
485 )
486
487 config.passthru.outputs
488
489 ];
490 };
491 };
492
493 # TODO: Revise what the child needs, but shouldn't modify.
494 child =
495 args@{
496 parent,
497 config,
498 options,
499 ...
500 }:
501 {
502 _file = ./flake.nix;
503 imports = attrValues modules;
504 options = {
505 inherit (parent.options)
506 maker
507 makers
508 specialArgs
509 settings
510 outputs
511 passthru
512 ;
513 src = mkOption {
514 inherit (parent.options.src) type;
515 readOnly = true;
516 };
517 sources = mkOption {
518 type = parent.options.sources.nestedTypes.finalType;
519 readOnly = true;
520 };
521 inputs = mkOption {
522 inherit (parent.options.inputs) type;
523 readOnly = true;
524 };
525 self = mkOption {
526 inherit (parent.options.self) type;
527 readOnly = true;
528 };
529 systems = mkOption {
530 inherit (parent.options.systems) type;
531 readOnly = true;
532 };
533 nixpkgs = mkOption {
534 inherit (parent.options.nixpkgs) type;
535 default =
536 let
537 nixpkgs = attrValues (filterAttrs (n: v: hasPrefix "nixpkgs" n) config.inputs);
538 in
539 if (args.nixpkgs != null) then
540 args.nixpkgs
541 else if (nixpkgs == [ ]) then
542 null
543 else
544 (head nixpkgs);
545 };
546 };
547 config =
548 let
549 parentOpts = attrNames parent.options;
550 childOpts = attrNames options;
551 sameOpts = parentOpts == childOpts;
552 missingOpts = {
553 parent = filter (opt: !(elem opt childOpts)) parentOpts;
554 child = filter (opt: !(elem opt parentOpts)) childOpts;
555 };
556 in
557 if sameOpts then
558 {
559 inherit (args)
560 src
561 sources
562 inputs
563 self
564 maker
565 makers
566 specialArgs
567 settings
568 systems
569 lib
570 modules
571
572 # TODO: Won't these duplicate lists when provided an attribute set, for example?
573 # And what about the options above?
574 hosts
575 nixosConfigurations
576 ;
577 outputs = mkIf ((args.system or null) == config.passthru.system) {
578 inherit (config) passthru;
579 };
580 }
581 else
582 (
583 let
584 childMessage = "Child options [${concatStringsSep " " missingOpts.child}] are missing from the parent module.";
585 parentMessage = "Parent options [${concatStringsSep " " missingOpts.parent}] are missing from the child module.";
586 in
587 throw (
588 if (missingOpts.parent == [ ]) then
589 childMessage
590 else if (missingOpts.child == [ ]) then
591 parentMessage
592 else
593 ''
594 ${parentMessage}
595 ${childMessage}
596 ''
597 )
598 );
599 };
600 in
601 formatOutputs {
602 flake =
603 (lib.evalModules {
604 class = "flake";
605 modules = [
606 parent
607
608 # Adapted From: https://github.com/NixOS/nixpkgs/blob/df3de70468b743b371a7191ea61d1040fe3bf399/lib/modules.nix#L567-L570
609 (
610 { config, ... }:
611 {
612 imports =
613 let
614 moduleLocation = "${config.src}/flake.nix";
615 in
616 [
617 {
618 _file = moduleLocation;
619 _module.args = {
620 inherit moduleLocation;
621 }
622 // config.specialArgs;
623 imports = [ module ];
624 }
625 ];
626 config.outputs.simpleflake = config;
627 }
628 )
629 ];
630 }).config.outputs;
631 };
632
633 settings =
634 config: system:
635 config.settings
636 // {
637 inherit system;
638 hostPlatform = system;
639 overlays = attrValues config.settings.overlays;
640 };
641
642 system =
643 config: system:
644 let
645 settings = mk.settings config system;
646 in
647 {
648 inherit system;
649 }
650 // (optionalAttrs (config.nixpkgs != null) {
651 pkgs = import config.nixpkgs settings;
652 });
653 };
654
655 optionTypes.functionArgsTo = argNumber: compose (replicate argNumber types.functionTo);
656
657 formatOutputs =
658 args@{
659 getFlake ? false,
660 ...
661 }:
662 let
663 _flake = args.flake;
664 systemsList = builtins.attrNames (
665 _flake.simpleflake.systems or _flake.checks or _flake.packages or _flake.apps or _flake.formatter
666 or _flake.devShells or _flake.legacyPackages or (genAttrs lib.systems.flakeExposed id)
667 );
668 flake = {
669 inherit lib;
670 }
671 // _flake;
672 systems =
673 let
674 allSystems = lib.genAttrs systemsList (system: lib.mapAttrs (n: v: v.${system} or v) flake);
675 in
676 allSystems
677 // {
678 currentSystem = allSystems.${builtins.currentSystem};
679 };
680 in
681 (
682 optionalAttrs (!getFlake) { inherit systems; }
683 // (lib.mapAttrs (
684 n: v:
685 if ((isAttrsOnly v) && (builtins.hasAttr (head systemsList) v)) then
686 (v // { currentSystem = systems.currentSystem.${n}; })
687 else
688 v
689 ) flake)
690 )
691 // {
692 inputs =
693 flake.simpleflake.inputs
694 or (mapAttrs (n: v: if (isAttrsOnly v) then (formatOutputs { flake = v; }) else v) (
695 flake.inputs or { }
696 ));
697 };
698
699}