1{
2 lib,
3 stdenv,
4 resholve,
5 binlore,
6 writeTextFile,
7}:
8
9rec {
10 /*
11 These functions break up the work of partially validating the
12 'solutions' attrset and massaging it into env/cli args.
13
14 Note: some of the left-most args do not *have* to be passed as
15 deep as they are, but I've done so to provide more error context
16 */
17
18 # for brevity / line length
19 spaces = l: builtins.concatStringsSep " " l;
20 colons = l: builtins.concatStringsSep ":" l;
21 semicolons = l: builtins.concatStringsSep ";" l;
22
23 # Throw a fit with dotted attr path context
24 nope = path: msg: throw "${builtins.concatStringsSep "." path}: ${msg}";
25
26 # Special-case directive value representations by type
27 phraseDirective =
28 solution: env: name: val:
29 if builtins.isInt val then
30 builtins.toString val
31 else if builtins.isString val then
32 name
33 else if true == val then
34 name
35 else if false == val then
36 "" # omit!
37 else if null == val then
38 "" # omit!
39 else if builtins.isList val then
40 "${name}:${semicolons (map lib.escapeShellArg val)}"
41 else
42 nope [ solution env name ] "unexpected type: ${builtins.typeOf val}";
43
44 # Build fake/fix/keep directives from Nix types
45 phraseDirectives =
46 solution: env: val:
47 lib.mapAttrsToList (phraseDirective solution env) val;
48
49 # Custom ~search-path routine to handle relative path strings
50 relSafeBinPath =
51 input:
52 if lib.isDerivation input then
53 ((lib.getOutput "bin" input) + "/bin")
54 else if builtins.isString input then
55 input
56 else
57 throw "unexpected type for input: ${builtins.typeOf input}";
58
59 # Special-case value representation by type/name
60 phraseEnvVal =
61 solution: env: val:
62 if env == "inputs" then
63 (colons (map relSafeBinPath val))
64 else if builtins.isString val then
65 val
66 else if builtins.isList val then
67 spaces val
68 else if builtins.isAttrs val then
69 spaces (phraseDirectives solution env val)
70 else
71 nope [ solution env ] "unexpected type: ${builtins.typeOf val}";
72
73 # Shell-format each env value
74 shellEnv =
75 solution: env: value:
76 lib.escapeShellArg (phraseEnvVal solution env value);
77
78 # Build a single ENV=val pair
79 phraseEnv =
80 solution: env: value:
81 "RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}";
82
83 /*
84 Discard attrs:
85 - claimed by phraseArgs
86 - only needed for binlore.collect
87 */
88 removeUnneededArgs =
89 value:
90 removeAttrs value [
91 "scripts"
92 "flags"
93 "unresholved"
94 ];
95
96 # Verify required arguments are present
97 validateSolution =
98 {
99 scripts,
100 inputs,
101 interpreter,
102 ...
103 }:
104 true;
105
106 # Pull out specific solution keys to build ENV=val pairs
107 phraseEnvs =
108 solution: value: spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value));
109
110 # Pull out specific solution keys to build CLI argstring
111 phraseArgs =
112 {
113 flags ? [ ],
114 scripts,
115 ...
116 }:
117 spaces (flags ++ scripts);
118
119 phraseBinloreArgs =
120 value:
121 let
122 hasUnresholved = builtins.hasAttr "unresholved" value;
123 in
124 {
125 drvs = value.inputs ++ lib.optionals hasUnresholved [ value.unresholved ];
126 strip = if hasUnresholved then [ value.unresholved ] else [ ];
127 };
128
129 # Build a single resholve invocation
130 phraseInvocation =
131 solution: value:
132 if validateSolution value then
133 # we pass resholve a directory
134 "RESHOLVE_LORE=${binlore.collect (phraseBinloreArgs value)} ${phraseEnvs solution value} ${resholve}/bin/resholve --overwrite ${phraseArgs value}"
135 else
136 throw "invalid solution"; # shouldn't trigger for now
137
138 injectUnresholved =
139 solutions: unresholved:
140 (builtins.mapAttrs (name: value: value // { inherit unresholved; }) solutions);
141
142 # Build resholve invocation for each solution.
143 phraseCommands =
144 solutions: unresholved:
145 builtins.concatStringsSep "\n" (
146 lib.mapAttrsToList phraseInvocation (injectUnresholved solutions unresholved)
147 );
148
149 /*
150 subshell/PS4/set -x and : command to output resholve envs
151 and invocation. Extra context makes it clearer what the
152 Nix API is doing, makes nix-shell debugging easier, etc.
153 */
154 phraseContext =
155 {
156 invokable,
157 prep ? ''cd "$out"'',
158 }:
159 ''
160 (
161 ${prep}
162 PS4=$'\x1f'"\033[33m[resholve context]\033[0m "
163 set -x
164 : invoking resholve with PWD=$PWD
165 ${invokable}
166 )
167 '';
168 phraseContextForPWD =
169 invokable:
170 phraseContext {
171 inherit invokable;
172 prep = "";
173 };
174 phraseContextForOut = invokable: phraseContext { inherit invokable; };
175
176 phraseSolution = name: solution: (phraseContextForOut (phraseInvocation name solution));
177 phraseSolutions =
178 solutions: unresholved: phraseContextForOut (phraseCommands solutions unresholved);
179
180 writeScript =
181 name: partialSolution: text:
182 writeTextFile {
183 inherit name text;
184 executable = true;
185 checkPhase =
186 ''
187 ${
188 (phraseContextForPWD (
189 phraseInvocation name (
190 partialSolution
191 // {
192 scripts = [ "${placeholder "out"}" ];
193 }
194 )
195 ))
196 }
197 ''
198 + lib.optionalString (partialSolution.interpreter != "none") ''
199 ${partialSolution.interpreter} -n $out
200 '';
201 };
202 writeScriptBin =
203 name: partialSolution: text:
204 writeTextFile rec {
205 inherit name text;
206 executable = true;
207 destination = "/bin/${name}";
208 checkPhase =
209 ''
210 ${phraseContextForOut (
211 phraseInvocation name (
212 partialSolution
213 // {
214 scripts = [ "bin/${name}" ];
215 }
216 )
217 )}
218 ''
219 + lib.optionalString (partialSolution.interpreter != "none") ''
220 ${partialSolution.interpreter} -n $out/bin/${name}
221 '';
222 };
223 mkDerivation =
224 {
225 pname,
226 src,
227 version,
228 passthru ? { },
229 solutions,
230 ...
231 }@attrs:
232 let
233 inherit stdenv;
234
235 /*
236 Knock out our special solutions arg, but otherwise
237 just build what the caller is giving us. We'll
238 actually resholve it separately below (after we
239 generate binlore for it).
240 */
241 unresholved = (
242 stdenv.mkDerivation (
243 (removeAttrs attrs [ "solutions" ])
244 // {
245 inherit version src;
246 pname = "${pname}-unresholved";
247 }
248 )
249 );
250 in
251 /*
252 resholve in a separate derivation; some concerns:
253 - we aren't keeping many of the user's args, so they
254 can't readily set LOGLEVEL and such...
255 - not sure how this affects multiple outputs
256 */
257 lib.extendDerivation true passthru (
258 stdenv.mkDerivation {
259 src = unresholved;
260 inherit version pname;
261 buildInputs = [ resholve ];
262 disallowedReferences = [ resholve ];
263
264 # retain a reference to the base
265 passthru = unresholved.passthru // {
266 unresholved = unresholved;
267 # fallback attr for update bot to query our src
268 originalSrc = unresholved.src;
269 };
270
271 # do these imply that we should use NoCC or something?
272 dontConfigure = true;
273 dontBuild = true;
274
275 installPhase = ''
276 cp -R $src $out
277 '';
278
279 # enable below for verbose debug info if needed
280 # supports default python.logging levels
281 # LOGLEVEL="INFO";
282 preFixup = phraseSolutions solutions unresholved;
283
284 # don't break the metadata...
285 meta = unresholved.meta;
286 }
287 );
288}