nixpkgs/pkgs/development/misc/resholve/resholve-utils.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

212 lines
7.1 KiB
Nix
Raw Normal View History

{ lib, stdenv, resholve, binlore, writeTextFile }:
rec {
/* These functions break up the work of partially validating the
'solutions' attrset and massaging it into env/cli args.
Note: some of the left-most args do not *have* to be passed as
deep as they are, but I've done so to provide more error context
*/
# 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 */
nope = path: msg:
throw "${builtins.concatStringsSep "." path}: ${msg}";
/* Special-case directive value representations by type */
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 (map lib.escapeShellArg val)}"
else nope [ solution env name ] "unexpected type: ${builtins.typeOf val}";
/* Build fake/fix/keep directives from Nix types */
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 */
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 (phraseDirectives solution env val)
else nope [ solution env ] "unexpected type: ${builtins.typeOf val}";
/* Shell-format each env value */
shellEnv = solution: env: value:
lib.escapeShellArg (phraseEnvVal solution env value);
/* Build a single ENV=val pair */
phraseEnv = solution: env: value:
"RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}";
/* 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 */
phraseEnvs = solution: value:
spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value));
/* Pull out specific solution keys to build CLI argstring */
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 */
phraseInvocation = solution: value:
if validateSolution value then
# we pass resholve a directory
"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. */
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"}" ];
}
)
)
)}
'' + lib.optionalString (partialSolution.interpreter != "none") ''
${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}" ];
}
)
)
}
'' + lib.optionalString (partialSolution.interpreter != "none") ''
${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 version src;
pname = "${pname}-unresholved";
}));
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;
inherit version pname;
buildInputs = [ resholve ];
disallowedReferences = [ resholve ];
# retain a reference to the base
passthru = unresholved.passthru // {
unresholved = unresholved;
# fallback attr for update bot to query our src
originalSrc = unresholved.src;
};
# 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;
# don't break the metadata...
meta = unresholved.meta;
});
}