zoekt: 3.7.2-2-unstable-2024-10-24 -> 3.7.2-2-unstable-2024-12-09 (#363818)
[NixPkgs.git] / pkgs / development / misc / resholve / resholve-utils.nix
blobf024d04e0c2c7c7f986c29fa1ee69e24029da4e7
2   lib,
3   stdenv,
4   resholve,
5   binlore,
6   writeTextFile,
7 }:
9 rec {
10   /*
11     These functions break up the work of partially validating the
12     'solutions' attrset and massaging it into env/cli args.
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   */
18   # for brevity / line length
19   spaces = l: builtins.concatStringsSep " " l;
20   colons = l: builtins.concatStringsSep ":" l;
21   semicolons = l: builtins.concatStringsSep ";" l;
23   # Throw a fit with dotted attr path context
24   nope = path: msg: throw "${builtins.concatStringsSep "." path}: ${msg}";
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}";
44   # Build fake/fix/keep directives from Nix types
45   phraseDirectives =
46     solution: env: val:
47     lib.mapAttrsToList (phraseDirective solution env) val;
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}";
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}";
73   # Shell-format each env value
74   shellEnv =
75     solution: env: value:
76     lib.escapeShellArg (phraseEnvVal solution env value);
78   # Build a single ENV=val pair
79   phraseEnv =
80     solution: env: value:
81     "RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}";
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     ];
96   # Verify required arguments are present
97   validateSolution =
98     {
99       scripts,
100       inputs,
101       interpreter,
102       ...
103     }:
104     true;
106   # Pull out specific solution keys to build ENV=val pairs
107   phraseEnvs =
108     solution: value: spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value));
110   # Pull out specific solution keys to build CLI argstring
111   phraseArgs =
112     {
113       flags ? [ ],
114       scripts,
115       ...
116     }:
117     spaces (flags ++ scripts);
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     };
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
138   injectUnresholved =
139     solutions: unresholved:
140     (builtins.mapAttrs (name: value: value // { inherit unresholved; }) solutions);
142   # Build resholve invocation for each solution.
143   phraseCommands =
144     solutions: unresholved:
145     builtins.concatStringsSep "\n" (
146       lib.mapAttrsToList phraseInvocation (injectUnresholved solutions unresholved)
147     );
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; };
176   phraseSolution = name: solution: (phraseContextForOut (phraseInvocation name solution));
177   phraseSolutions =
178     solutions: unresholved: phraseContextForOut (phraseCommands solutions unresholved);
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;
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 ];
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         };
271         # do these imply that we should use NoCC or something?
272         dontConfigure = true;
273         dontBuild = true;
275         installPhase = ''
276           cp -R $src $out
277         '';
279         # enable below for verbose debug info if needed
280         # supports default python.logging levels
281         # LOGLEVEL="INFO";
282         preFixup = phraseSolutions solutions unresholved;
284         # don't break the metadata...
285         meta = unresholved.meta;
286       }
287     );