diff options
Diffstat (limited to 'nix/runExecline')
-rw-r--r-- | nix/runExecline/default.nix | 30 | ||||
-rw-r--r-- | nix/runExecline/runExecline.nix | 122 | ||||
-rw-r--r-- | nix/runExecline/tests.nix | 80 |
3 files changed, 232 insertions, 0 deletions
diff --git a/nix/runExecline/default.nix b/nix/runExecline/default.nix new file mode 100644 index 000000000000..fd92203d0146 --- /dev/null +++ b/nix/runExecline/default.nix @@ -0,0 +1,30 @@ +{ depot, pkgs, lib, ... }: +let + runExecline = import ./runExecline.nix { + inherit (pkgs) stdenv; + inherit (depot.nix) escapeExecline getBins; + inherit pkgs lib; + }; + + runExeclineLocal = name: args: execline: + runExecline name + (args // { + derivationArgs = args.derivationArgs or {} // { + preferLocalBuild = true; + allowSubstitutes = false; + }; + }) + execline; + + tests = import ./tests.nix { + inherit runExecline runExeclineLocal; + inherit (depot.nix) getBins writeScript; + inherit (pkgs) stdenv coreutils; + inherit pkgs; + }; + +in { + __functor = _: runExecline; + local = runExeclineLocal; + inherit tests; +} diff --git a/nix/runExecline/runExecline.nix b/nix/runExecline/runExecline.nix new file mode 100644 index 000000000000..0e45080735bb --- /dev/null +++ b/nix/runExecline/runExecline.nix @@ -0,0 +1,122 @@ +{ 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 + ]; +}) diff --git a/nix/runExecline/tests.nix b/nix/runExecline/tests.nix new file mode 100644 index 000000000000..d2f5a1780c16 --- /dev/null +++ b/nix/runExecline/tests.nix @@ -0,0 +1,80 @@ +{ stdenv, pkgs, runExecline, runExeclineLocal, getBins, writeScript +# https://www.mail-archive.com/skaware@list.skarnet.org/msg01256.html +, coreutils }: + +let + + bins = getBins coreutils [ "mv" ] + // getBins pkgs.execline [ + "execlineb" + { use = "if"; as = "execlineIf"; } + "redirfd" + "importas" + ] + // getBins pkgs.s6-portable-utils [ + "s6-chmod" + "s6-grep" + "s6-touch" + "s6-cat" + "s6-test" + ]; + + # execline block of depth 1 + block = args: builtins.map (arg: " ${arg}") args ++ [ "" ]; + + # derivation that tests whether a given line exists + # in the given file. Does not use runExecline, because + # that should be tested after all. + fileHasLine = line: file: derivation { + name = "run-execline-test-file-${file.name}-has-line"; + inherit (stdenv) system; + builder = bins.execlineIf; + args = + (block [ + bins.redirfd "-r" "0" file # read file to stdin + bins.s6-grep "-F" "-q" line # and grep for the line + ]) + ++ [ + # if the block succeeded, touch $out + bins.importas "-ui" "out" "out" + bins.s6-touch "$out" + ]; + preferLocalBuild = true; + allowSubstitutes = false; + }; + + # basic test that touches out + basic = runExeclineLocal "run-execline-test-basic" { + } [ + "importas" "-ui" "out" "out" + "${bins.s6-touch}" "$out" + ]; + + # whether the stdin argument works as intended + stdin = fileHasLine "foo" (runExeclineLocal "run-execline-test-stdin" { + stdin = "foo\nbar\nfoo"; + } [ + "importas" "-ui" "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 + ]); + + + wrapWithVar = runExeclineLocal "run-execline-test-wrap-with-var" { + builderWrapper = writeScript "var-wrapper" '' + #!${bins.execlineb} -S0 + export myvar myvalue $@ + ''; + } [ + "importas" "-ui" "v" "myvar" + "if" [ bins.s6-test "myvalue" "=" "$v" ] + "importas" "out" "out" + bins.s6-touch "$out" + ]; + +in [ + basic + stdin + wrapWithVar +] |