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