Generate flake.nix from module options. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/
dendrix.oeiuwq.com/Dendritic.html
dendritic
nix
inputs
1{
2 lib,
3 config,
4 inputs,
5 ...
6}:
7let
8 inherit (import ./../dev/modules/_lib lib) inputsExpr isNonEmptyString;
9
10 flake-file = config.flake-file;
11
12 template = ''
13 {
14 <description>
15 <outputs>
16 <nixConfig>
17 <inputs>
18 }
19 '';
20
21 unformatted = lib.pipe template [
22 (lib.replaceString "<description>" description)
23 (lib.replaceString "<outputs>" outputs)
24 (lib.replaceString "<nixConfig>" nixConfig)
25 (lib.replaceString "<inputs>" flakeInputs)
26 addHeader
27 ];
28
29 nixAttr =
30 name: value:
31 let
32 childIsAttr = builtins.isAttrs value;
33 childIsOne = builtins.length (builtins.attrNames value) == 1;
34 nested = lib.head (lib.mapAttrsToList nixAttr value);
35 in
36 if childIsAttr && childIsOne then
37 {
38 name = "${name}.${nested.name}";
39 value = nested.value;
40 }
41 else
42 {
43 inherit name;
44 value = value;
45 };
46
47 # expr to code
48 nixCode =
49 x:
50 if lib.isStringLike x then
51 lib.strings.escapeNixString x
52 else if lib.isAttrs x then
53 lib.pipe x [
54 (lib.mapAttrsToList nixAttr)
55 (map ({ name, value }: ''${name} = ${nixCode value}; ''))
56 (values: ''{ ${lib.concatStringsSep " " values} }'')
57 ]
58 else if lib.isList x then
59 lib.pipe x [
60 (lib.map nixCode)
61 (values: ''[ ${lib.concatStringsSep " " values} ]'')
62 ]
63 else if x == true then
64 "true"
65 else if x == false then
66 "false"
67 else
68 toString x;
69
70 description =
71 if isNonEmptyString flake-file.description then
72 ''
73 description = ${nixCode flake-file.description};
74 ''
75 else
76 "";
77
78 outputs = ''
79 outputs = ${flake-file.outputs};
80 '';
81
82 nixConfig =
83 if flake-file.nixConfig != { } then
84 ''
85 nixConfig = ${nixCode flake-file.nixConfig};
86 ''
87 else
88 "";
89
90 flakeInputs = ''
91 inputs = ${nixCode (inputsExpr flake-file.inputs)};
92 '';
93
94 addHeader =
95 code: if isNonEmptyString flake-file.do-not-edit then flake-file.do-not-edit + code else code;
96
97 formatted =
98 pkgs:
99 pkgs.stdenvNoCC.mkDerivation {
100 name = "flake-formatted";
101 passAsFile = [ "unformatted" ];
102 inherit unformatted;
103 phases = [ "format" ];
104 format = ''
105 cp $unformattedPath flake.nix
106 ${pkgs.lib.getExe (flake-file.formatter pkgs)} flake.nix
107 cp flake.nix $out
108 '';
109 };
110
111in
112{
113 config.perSystem =
114 { pkgs, ... }:
115 {
116 packages.write-flake =
117 let
118 hooks = lib.pipe config.flake-file.write-hooks [
119 (lib.sortOn (i: i.index))
120 (map (i: pkgs.lib.getExe (i.program pkgs)))
121 (lib.concatStringsSep "\n")
122 ];
123 in
124 pkgs.writeShellApplication {
125 name = "write-flake";
126 text = ''
127 cp ${formatted pkgs} flake.nix
128 ${hooks}
129 '';
130 };
131
132 checks.check-flake-file =
133 let
134 hooks = lib.pipe config.flake-file.check-hooks [
135 (lib.sortOn (i: i.index))
136 (map (i: pkgs.lib.getExe (i.program pkgs)))
137 (map (p: "${p} ${inputs.self}"))
138 (lib.concatStringsSep "\n")
139 ];
140 in
141 pkgs.runCommand "check-flake-file"
142 {
143 nativeBuildInputs = [ pkgs.diffutils ];
144 }
145 ''
146 set -e
147 diff -u ${inputs.self}/flake.nix ${formatted pkgs}
148 ${hooks}
149 touch $out
150 '';
151
152 };
153
154}