at master 7.7 kB view raw
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}