Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at flake-libs 288 lines 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 [ 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}