diff options
Diffstat (limited to 'pkgs/development/misc/resholve/resholve-utils.nix')
-rw-r--r-- | pkgs/development/misc/resholve/resholve-utils.nix | 169 |
1 files changed, 149 insertions, 20 deletions
diff --git a/pkgs/development/misc/resholve/resholve-utils.nix b/pkgs/development/misc/resholve/resholve-utils.nix index 2d3c55b875631..27e347e7c4aa3 100644 --- a/pkgs/development/misc/resholve/resholve-utils.nix +++ b/pkgs/development/misc/resholve/resholve-utils.nix @@ -1,4 +1,4 @@ -{ lib, resholve, binlore }: +{ lib, stdenv, resholve, binlore, writeTextFile }: rec { /* These functions break up the work of partially validating the @@ -10,6 +10,7 @@ rec { # for brevity / line length spaces = l: builtins.concatStringsSep " " l; + colons = l: builtins.concatStringsSep ":" l; semicolons = l: builtins.concatStringsSep ";" l; /* Throw a fit with dotted attr path context */ @@ -17,58 +18,186 @@ rec { throw "${builtins.concatStringsSep "." path}: ${msg}"; /* Special-case directive value representations by type */ - makeDirective = solution: env: name: val: + phraseDirective = solution: env: name: val: if builtins.isInt val then builtins.toString val else if builtins.isString val then name else if true == val then name else if false == val then "" # omit! else if null == val then "" # omit! - else if builtins.isList val then "${name}:${semicolons val}" + else if builtins.isList val then "${name}:${semicolons (map lib.escapeShellArg val)}" else nope [ solution env name ] "unexpected type: ${builtins.typeOf val}"; /* Build fake/fix/keep directives from Nix types */ - makeDirectives = solution: env: val: - lib.mapAttrsToList (makeDirective solution env) val; + phraseDirectives = solution: env: val: + lib.mapAttrsToList (phraseDirective solution env) val; + + /* Custom ~search-path routine to handle relative path strings */ + relSafeBinPath = input: + if lib.isDerivation input then ((lib.getOutput "bin" input) + "/bin") + else if builtins.isString input then input + else throw "unexpected type for input: ${builtins.typeOf input}"; /* Special-case value representation by type/name */ - makeEnvVal = solution: env: val: - if env == "inputs" then lib.makeBinPath val + phraseEnvVal = solution: env: val: + if env == "inputs" then (colons (map relSafeBinPath val)) else if builtins.isString val then val else if builtins.isList val then spaces val - else if builtins.isAttrs val then spaces (makeDirectives solution env val) + else if builtins.isAttrs val then spaces (phraseDirectives solution env val) else nope [ solution env ] "unexpected type: ${builtins.typeOf val}"; /* Shell-format each env value */ shellEnv = solution: env: value: - lib.escapeShellArg (makeEnvVal solution env value); + lib.escapeShellArg (phraseEnvVal solution env value); /* Build a single ENV=val pair */ - makeEnv = solution: env: value: + phraseEnv = solution: env: value: "RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}"; - /* Discard attrs claimed by makeArgs */ - removeCliArgs = value: - removeAttrs value [ "scripts" "flags" ]; + /* Discard attrs: + - claimed by phraseArgs + - only needed for binlore.collect + */ + removeUnneededArgs = value: + removeAttrs value [ "scripts" "flags" "unresholved" ]; /* Verify required arguments are present */ validateSolution = { scripts, inputs, interpreter, ... }: true; /* Pull out specific solution keys to build ENV=val pairs */ - makeEnvs = solution: value: - spaces (lib.mapAttrsToList (makeEnv solution) (removeCliArgs value)); + phraseEnvs = solution: value: + spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value)); /* Pull out specific solution keys to build CLI argstring */ - makeArgs = { flags ? [ ], scripts, ... }: + phraseArgs = { flags ? [ ], scripts, ... }: spaces (flags ++ scripts); + phraseBinloreArgs = value: + let + hasUnresholved = builtins.hasAttr "unresholved" value; + in { + drvs = value.inputs ++ + lib.optionals hasUnresholved [ value.unresholved ]; + strip = if hasUnresholved then [ value.unresholved ] else [ ]; + }; + /* Build a single resholve invocation */ - makeInvocation = solution: value: + phraseInvocation = solution: value: if validateSolution value then # we pass resholve a directory - "RESHOLVE_LORE=${binlore.collect { drvs = value.inputs; } } ${makeEnvs solution value} ${resholve}/bin/resholve --overwrite ${makeArgs value}" + "RESHOLVE_LORE=${binlore.collect (phraseBinloreArgs value) } ${phraseEnvs solution value} ${resholve}/bin/resholve --overwrite ${phraseArgs value}" else throw "invalid solution"; # shouldn't trigger for now + injectUnresholved = solutions: unresholved: (builtins.mapAttrs (name: value: value // { inherit unresholved; } ) solutions); + /* Build resholve invocation for each solution. */ - makeCommands = solutions: - lib.mapAttrsToList makeInvocation solutions; + phraseCommands = solutions: unresholved: + builtins.concatStringsSep "\n" ( + lib.mapAttrsToList phraseInvocation (injectUnresholved solutions unresholved) + ); + + /* + subshell/PS4/set -x and : command to output resholve envs + and invocation. Extra context makes it clearer what the + Nix API is doing, makes nix-shell debugging easier, etc. + */ + phraseContext = { invokable, prep ? ''cd "$out"'' }: '' + ( + ${prep} + PS4=$'\x1f'"\033[33m[resholve context]\033[0m " + set -x + : invoking resholve with PWD=$PWD + ${invokable} + ) + ''; + phraseContextForPWD = invokable: phraseContext { inherit invokable; prep = ""; }; + phraseContextForOut = invokable: phraseContext { inherit invokable; }; + + phraseSolution = name: solution: (phraseContextForOut (phraseInvocation name solution)); + phraseSolutions = solutions: unresholved: + phraseContextForOut (phraseCommands solutions unresholved); + + writeScript = name: partialSolution: text: + writeTextFile { + inherit name text; + executable = true; + checkPhase = '' + ${(phraseContextForPWD ( + phraseInvocation name ( + partialSolution // { + scripts = [ "${placeholder "out"}" ]; + } + ) + ) + )} + ${partialSolution.interpreter} -n $out + ''; + }; + writeScriptBin = name: partialSolution: text: + writeTextFile rec { + inherit name text; + executable = true; + destination = "/bin/${name}"; + checkPhase = '' + ${phraseContextForOut ( + phraseInvocation name ( + partialSolution // { + scripts = [ "bin/${name}" ]; + } + ) + ) + } + ${partialSolution.interpreter} -n $out/bin/${name} + ''; + }; + mkDerivation = { pname + , src + , version + , passthru ? { } + , solutions + , ... + }@attrs: + let + inherit stdenv; + + /* + Knock out our special solutions arg, but otherwise + just build what the caller is giving us. We'll + actually resholve it separately below (after we + generate binlore for it). + */ + unresholved = (stdenv.mkDerivation ((removeAttrs attrs [ "solutions" ]) + // { + inherit pname version src; + })); + in + /* + resholve in a separate derivation; some concerns: + - we aren't keeping many of the user's args, so they + can't readily set LOGLEVEL and such... + - not sure how this affects multiple outputs + */ + lib.extendDerivation true passthru (stdenv.mkDerivation { + src = unresholved; + version = unresholved.version; + pname = "resholved-${unresholved.pname}"; + buildInputs = [ resholve ]; + + # retain a reference to the base + passthru = unresholved.passthru // { + unresholved = unresholved; + }; + + # do these imply that we should use NoCC or something? + dontConfigure = true; + dontBuild = true; + + installPhase = '' + cp -R $src $out + ''; + + # enable below for verbose debug info if needed + # supports default python.logging levels + # LOGLEVEL="INFO"; + preFixup = phraseSolutions solutions unresholved; + }); } |