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

288 lines
7.5 KiB
Nix

{
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;
}
);
}