288 lines
7.5 KiB
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;
|
|
}
|
|
);
|
|
}
|