Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at 23.05 211 lines 7.2 kB view raw
1{ lib, stdenv, resholve, binlore, writeTextFile }: 2 3rec { 4 /* These functions break up the work of partially validating the 5 'solutions' attrset and massaging it into env/cli args. 6 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 */ 10 11 # for brevity / line length 12 spaces = l: builtins.concatStringsSep " " l; 13 colons = l: builtins.concatStringsSep ":" l; 14 semicolons = l: builtins.concatStringsSep ";" l; 15 16 /* Throw a fit with dotted attr path context */ 17 nope = path: msg: 18 throw "${builtins.concatStringsSep "." path}: ${msg}"; 19 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}"; 29 30 /* Build fake/fix/keep directives from Nix types */ 31 phraseDirectives = solution: env: val: 32 lib.mapAttrsToList (phraseDirective solution env) val; 33 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}"; 39 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}"; 47 48 /* Shell-format each env value */ 49 shellEnv = solution: env: value: 50 lib.escapeShellArg (phraseEnvVal solution env value); 51 52 /* Build a single ENV=val pair */ 53 phraseEnv = solution: env: value: 54 "RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}"; 55 56 /* Discard attrs: 57 - claimed by phraseArgs 58 - only needed for binlore.collect 59 */ 60 removeUnneededArgs = value: 61 removeAttrs value [ "scripts" "flags" "unresholved" ]; 62 63 /* Verify required arguments are present */ 64 validateSolution = { scripts, inputs, interpreter, ... }: true; 65 66 /* Pull out specific solution keys to build ENV=val pairs */ 67 phraseEnvs = solution: value: 68 spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value)); 69 70 /* Pull out specific solution keys to build CLI argstring */ 71 phraseArgs = { flags ? [ ], scripts, ... }: 72 spaces (flags ++ scripts); 73 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 }; 82 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 89 90 injectUnresholved = solutions: unresholved: (builtins.mapAttrs (name: value: value // { inherit unresholved; } ) solutions); 91 92 /* Build resholve invocation for each solution. */ 93 phraseCommands = solutions: unresholved: 94 builtins.concatStringsSep "\n" ( 95 lib.mapAttrsToList phraseInvocation (injectUnresholved solutions unresholved) 96 ); 97 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; }; 114 115 phraseSolution = name: solution: (phraseContextForOut (phraseInvocation name solution)); 116 phraseSolutions = solutions: unresholved: 117 phraseContextForOut (phraseCommands solutions unresholved); 118 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; 163 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 ]; 187 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 }; 194 195 # do these imply that we should use NoCC or something? 196 dontConfigure = true; 197 dontBuild = true; 198 199 installPhase = '' 200 cp -R $src $out 201 ''; 202 203 # enable below for verbose debug info if needed 204 # supports default python.logging levels 205 # LOGLEVEL="INFO"; 206 preFixup = phraseSolutions solutions unresholved; 207 208 # don't break the metadata... 209 meta = unresholved.meta; 210 }); 211}