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