{ pkgs, stdenv, lib, getBins, escapeExecline }:
# runExecline is a primitive building block
# for writing non-kitchen sink builders.
#
# It’s conceptually similar to `runCommand`,
# but instead of concatenating bash scripts left
# and right, it actually *uses* the features of
# `derivation`, passing things to `args`
# and making it possible to overwrite the `builder`
# in a sensible manner.
#
# Additionally, it provides a way to pass a nix string
# to `stdin` of the build script.
#
# Similar to //nix/writeExecline, the passed script is
# not a string, but a nested list of nix lists
# representing execline blocks. Escaping is
# done by the implementation, the user can just use
# normal nix strings.
#
# Example:
#
# runExecline "my-drv" { stdin = "hi!"; } [
# "importas" "out" "out"
# # this pipes stdout of s6-cat to $out
# # and s6-cat redirects from stdin to stdout
# "redirfd" "-w" "1" "$out" bins.s6-cat
# ]
#
# which creates a derivation with "hi!" in $out.
#
# See ./tests.nix for more examples.
let
bins = getBins pkgs.execline [
"execlineb"
{ use = "if"; as = "execlineIf"; }
"redirfd"
"importas"
"exec"
]
// getBins pkgs.s6-portable-utils [
"s6-cat"
"s6-grep"
"s6-touch"
"s6-test"
"s6-chmod"
];
in
# TODO: move name into the attrset
name:
{
# a string to pass as stdin to the execline script
stdin ? ""
# a program wrapping the acutal execline invocation;
# should be in Bernstein-chaining style
, builderWrapper ? bins.exec
# additional arguments to pass to the derivation
, derivationArgs ? { }
}:
# the execline script as a nested list of string,
# representing the blocks;
# see docs of `escapeExecline`.
execline:
# those arguments can’t be overwritten
assert !derivationArgs ? system;
assert !derivationArgs ? name;
assert !derivationArgs ? builder;
assert !derivationArgs ? args;
derivation (derivationArgs // {
# TODO(Profpatsch): what about cross?
inherit (stdenv) system;
inherit name;
# okay, `builtins.toFile` does not accept strings
# that reference drv outputs. This means we need
# to pass the script and stdin as envvar;
# this might clash with another passed envar,
# so we give it a long & unique name
_runExeclineScript =
let
in escapeExecline execline;
_runExeclineStdin = stdin;
passAsFile = [
"_runExeclineScript"
"_runExeclineStdin"
] ++ derivationArgs.passAsFile or [ ];
# the default, exec acts as identity executable
builder = builderWrapper;
args = [
bins.importas # import script file as $script
"-ui" # drop the envvar afterwards
"script" # substitution name
"_runExeclineScriptPath" # passed script file
bins.importas # do the same for $stdin
"-ui"
"stdin"
"_runExeclineStdinPath"
bins.redirfd # now we
"-r" # read the file
"0" # into the stdin of execlineb
"$stdin" # that was given via stdin
bins.execlineb # the actual invocation
# TODO(Profpatsch): depending on the use-case, -S0 might not be enough
# in all use-cases, then a wrapper for execlineb arguments
# should be added (-P, -S, -s).
"-S0" # set $@ inside the execline script
"-W" # die on syntax error
"$script" # substituted by importas
];
})