aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--default.nix427
-rw-r--r--evaluators.nix401
2 files changed, 828 insertions, 0 deletions
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..d6ed821
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,427 @@
+{ pkgs }:
+
+with pkgs;
+with lib;
+
+rec {
+ qemu = (pkgs.qemu.override {
+ sdlSupport = false;
+ vncSupport = false;
+ spiceSupport = false;
+ pulseSupport = false;
+ smbdSupport = true;
+ hostCpuOnly = true;
+ }).overrideAttrs (old: rec {
+ name = "qemu-${version}";
+ version = "3.0.0";
+ src = fetchurl {
+ url = "http://wiki.qemu.org/download/qemu-${version}.tar.bz2";
+ sha256 = "1s7bm2xhcxbc9is0rg8xzwijx7azv67skq7mjc58spsgc2nn4glk";
+ };
+ });
+
+ baseKernelPackages = linuxPackages;
+
+ kconfig = kernel-config.override {
+ linux = baseKernelPackages.kernel;
+ } {
+ config = {
+ X86 = true;
+ "64BIT" = true;
+ #"PRINTK" "BUG" # extra debugging
+
+ LOCAL_VERSION = "qeval";
+ DEFAULT_HOSTNAME = "qeval";
+
+ SWAP = false;
+
+ TTY = true;
+ SERIAL_8250 = true;
+ SERIAL_8250_CONSOLE = true;
+
+ # execute elf and #! scripts
+ BINFMT_ELF = true;
+ BINFMT_SCRIPT = true;
+
+ # enable ramdisk with gzip
+ BLK_DEV_INITRD = true;
+ RD_GZIP = true;
+
+ # allow for userspace to shut kernel down
+ PROC_FS = true;
+ MAGIC_SYSRQ = true;
+
+ # needed for guest to tell qemu to shutdown
+ PCI = true;
+ ACPI = true;
+
+ # allow unix domain sockets
+ NET = true;
+ UNIX = true;
+
+ # enable block layer
+ BLOCK = true;
+ BLK_DEV = true;
+ BLK_DEV_LOOP = true;
+
+ MISC_FILESYSTEMS = true;
+ SQUASHFS = true;
+ SQUASHFS_LZ4 = true;
+ LZ4_DECOMPRESS = true;
+ SQUASHFS_DECOMP_SINGLE = true;
+ # SQUASHFS_DECOMP_MULTI = true;
+ # SQUASHFS_FILE_DIRECT = true;
+ SQUASHFS_FILE_CACHE = true;
+
+ PROC_SYSCTL = true;
+ KERNFS = true;
+ SYSFS = true;
+ DEVTMPFS = true;
+ TMPFS = true;
+
+ OVERLAY_FS = true;
+
+ # support passing in various things
+ VIRTIO_PCI = true;
+ VIRTIO_BLK = true;
+ VIRTIO_INPUT = true;
+ VIRTIO_CONSOLE = true;
+
+ FUTEX = true;
+
+ # stop on kernel panic
+ PVPANIC = true;
+ X86_PLATFORM_DEVICES = true;
+
+ # enable timers (ghc needs them)
+ POSIX_TIMERS = true;
+ TIMERFD = true;
+ EVENTFD = true;
+ EPOLL = true;
+
+ # tsc scaling, maybe
+ # "X86_TSC"
+
+ ADVISE_SYSCALLS = true;
+
+ # "FSCACHE"
+ # "CACHEFILES"
+
+ # TODO: disable IR_SANYO_DECODER, etc.
+ RC_CORE = false;
+
+ # required for guest to gather entropy, some applications
+ # will otherwise block forever (e.g. rustc)
+ HW_RANDOM = true;
+ HW_RANDOM_VIRTIO = true;
+ };
+ };
+
+ kernelPackages = linuxPackages_custom {
+ inherit (baseKernelPackages.kernel) version src;
+ configfile = kconfig;
+ };
+ inherit (kernelPackages) kernel;
+
+ initrdUtils = runCommand "initrd-utils"
+ { buildInputs = [ nukeReferences ];
+ allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
+ }
+ ''
+ mkdir -p $out/bin $out/lib
+
+ # Copy what we need from Glibc.
+ cp -p ${stdenv.glibc.out}/lib/ld-linux*.so.? $out/lib
+ cp -p ${stdenv.glibc.out}/lib/libc.so.* $out/lib
+ cp -p ${stdenv.glibc.out}/lib/libm.so.* $out/lib
+ cp -p ${stdenv.glibc.out}/lib/libresolv.so.* $out/lib
+
+ # Copy BusyBox.
+ cp -pd ${busybox}/bin/* $out/bin
+
+ # Run patchelf to make the programs refer to the copied libraries.
+ for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done
+
+ for i in $out/bin/*; do
+ if [ -f "$i" -a ! -L "$i" ]; then
+ echo "patching $i..."
+ patchelf --set-interpreter $out/lib/ld-linux*.so.? --set-rpath $out/lib $i || true
+ fi
+ done
+ '';
+
+ closurePaths = path:
+ let closure = closureInfo { rootPaths = path; };
+ text = lib.fileContents "${closure}/store-paths";
+ in lib.splitString "\n" text;
+
+ stage1 = writeScript "vm-run-stage1" ''
+ #! ${initrdUtils}/bin/ash -e
+ export PATH=${initrdUtils}/bin
+
+ mkdir /etc
+ echo -n > /etc/fstab
+
+ mount -t proc none /proc
+ mount -t sysfs none /sys
+
+ # Does this even work with the current config?
+ echo 2 > /proc/sys/vm/panic_on_oom
+
+ for o in $(cat /proc/cmdline); do
+ case $o in
+ jobDesc=*)
+ set -- $(IFS==; echo $o)
+ jobDesc=$2
+ ;;
+ mountVirtfs=*)
+ set -- $(IFS==; echo $o)
+ mountVirtfs=$2
+ ;;
+ esac
+ done
+
+ mount -t devtmpfs devtmpfs /dev
+
+ # ifconfig lo up
+ stty -icrnl -igncr # necessary?
+
+ mkdir -p /dev/shm /dev/pts
+ mount -t tmpfs -o "mode=1777" none /dev/shm
+ mount -t devpts none /dev/pts
+
+ mkdir -p /tmp /run /var
+ mount -t tmpfs -o "mode=1777" none /tmp
+ mount -t tmpfs -o "mode=755" none /run
+ ln -sfn /run /var/run
+
+ mkdir -p /etc
+ ln -sf /proc/mounts /etc/mtab
+ echo "127.0.0.1 localhost" > /etc/hosts
+ echo "root:x:0:0:root:/:/bin/sh" > /etc/passwd
+
+ mkdir -p /bin
+ ln -s ${initrdUtils}/bin/ash /bin/sh
+
+ for store in /dev/vd*; do
+ if [ -e "$store" ]; then
+ name=$(basename $store)
+ mkdir -p /mnt/store/$name
+ mount -o ro,loop /dev/$name /mnt/store/$name
+ fi
+ done
+
+ stores="$( ( find /mnt/store -mindepth 1 -maxdepth 1; echo /nix/store ) | paste -sd :)"
+ echo stores: $stores
+ mount -t overlay overlay -o "ro,lowerdir=$stores" /nix/store
+
+ if [ -n "$jobDesc" ]; then
+ . "$jobDesc"
+ fi
+
+ "$preCmd"
+
+ echo ready > /dev/vport2p1
+ read -r input < /dev/vport2p1
+ echo "$input" > /input
+
+ "$cmd" /input
+
+ exec poweroff -f
+ '';
+
+ initrd = initrdPath: makeInitrd {
+ contents = [
+ { object = stage1;
+ symlink = "/init"; }
+ { object = linkFarm "extra"
+ (map (p: { name = p.name; path = toString p; }) initrdPath);
+ symlink = "/tmp/extra"; }
+ ];
+ };
+
+ squashfsTools = pkgs.squashfsTools.override { lz4Support = true; };
+ mkSquashFs = settings: contents: bitflip.flipTwice (stdenv.mkDerivation {
+ name = "squashfs.img";
+ nativeBuildInputs = [ squashfsTools ];
+ buildCommand = ''
+ closureInfo=${closureInfo { rootPaths = contents; }}
+ mksquashfs $(cat $closureInfo/store-paths) $out \
+ -keep-as-directory -all-root -b 1048576 ${settings}
+ '';
+ });
+
+ mkSquashFsXz = mkSquashFs "-comp xz -Xdict-size 100%";
+ mkSquashFsLz4 = mkSquashFs "-comp lz4 -Xhc";
+ mkSquashFsGz = mkSquashFs "-comp gzip -Xcompression-level 9";
+
+ prepareJob = args@{
+ name, aliases ? [], initrdPath ? [ initrdUtils ], storeDrives ? {}, mem ? 50, command, preCommand ? "",
+ doCheck ? true, testInput ? "", testOutput ? "success" }:
+ let
+ fullPath = (concatLists (builtins.attrValues storeDrives)) ++ initrdPath;
+ mkScript = cmd: writeScript "run" ''
+ #!/bin/sh -e
+ PATH=${lib.makeBinPath (map builtins.unsafeDiscardStringContext fullPath)}
+ ${cmd}
+ '';
+
+ desc = writeText "desc" ''
+ cmd=${mkScript command}
+ preCmd=${mkScript preCommand}
+ '';
+ run' = run {
+ inherit name initrdPath fullPath mem desc;
+ storeDrives = (mapAttrs (k: mkSquashFsLz4) storeDrives) // {
+ desc = mkSquashFsLz4 [ desc initrdUtils ];
+ };
+ };
+
+ description = writeText "desc" (builtins.toJSON {
+ inherit name aliases mem;
+ available = map (p: p.name) fullPath;
+ });
+
+ self = stdenv.mkDerivation rec {
+ inherit name aliases;
+
+ src = writeShellScriptBin "run" ''
+ set -e
+ PATH=${coreutils}/bin
+ job=$(mktemp -d)
+ ${run'}/bin/run-qemu "$job" "$@"
+ rm -rf "$job"
+ '';
+
+ installPhase = ''
+ mkdir -p $out/bin $out/desc
+ for n in $name $aliases; do
+ ln -s $src/bin/run "$out/bin/$n"
+ done
+
+ ln -s ${description} "$out/desc/$name"
+ '';
+
+ inherit doCheck;
+ checkPhase = ''
+ EXPECTED="$(printf ${escapeShellArg testOutput})"
+ ${xxd}/bin/xxd <<<"$EXPECTED"
+ RESULT="$($src/bin/run ${escapeShellArg testInput})"
+ ${xxd}/bin/xxd <<<"$RESULT"
+ [ "$RESULT" = "$EXPECTED" ]
+ '';
+ };
+ in self // {
+ inherit desc;
+ run = run';
+
+ # Nix itself can't do it, because it can't check if something
+ # is a file or a directory (exportReferencesGraph doesn't tell),
+ # but apparmor rules differ based on that distinction
+ apparmor = stdenv.mkDerivation rec {
+ name = "apparmor.profile";
+
+ closureItems = [
+ self self.src run'
+ ];
+
+ buildCommand = ''
+ (
+ echo '${self.src}/bin/run flags=(complain) {'
+ echo ' signal, ptrace,'
+ echo ' /dev/kvm' wr,
+ echo ' /tmp/**' wr,
+ echo ' /proc/** r,'
+ echo ' /sys/devices/system/** r,'
+
+ closure=${closureInfo { rootPaths = closureItems; }}
+ while read -r path; do
+ if [ -f "$path" ]; then
+ echo " $path mkrix,"
+ elif [ -d "$path" ]; then
+ echo " $path** mkrix,"
+ fi
+ done < $closure/store-paths
+ echo }
+ ) > $out
+ '';
+ };
+ };
+
+ # -drive if=virtio,readonly,format=qcow2,file="$disk" \
+
+ # -enable-kvm -cpu Haswell-noTSX-IBRS,vmx=on \
+ # -cpu IvyBridge \
+ # -net none -m "$mem" \
+ # -virtfs local,readonly,path=/nix/store,security_model=none,mount_tag=store \
+ commonQemuOptions = ''
+ -only-migratable \
+ -nographic -no-reboot \
+ -cpu IvyBridge \
+ -enable-kvm \
+ -net none -m "$mem" \
+ -device virtio-rng-pci,max-bytes=1024,period=1000 \
+ -device virtio-serial-pci \
+ -device virtio-serial \
+ -device pvpanic \
+ -chardev pipe,path="$job"/control,id=control \
+ -device virtserialport,chardev=control,id=control \
+ -qmp-pretty unix:"$job"/qmp,nowait \'';
+
+ qemuDriveOptions = lib.concatMapStringsSep " " (d: "-drive if=virtio,readonly,format=raw,file=${d}");
+
+ run = args@{ name, fullPath, initrdPath, storeDrives, mem, desc, ... }: writeShellScriptBin "run-qemu" ''
+ # ${name}
+ # needs ''${concatStringsSep ", " fullPath}
+ job="$1"
+ shift
+ mkfifo "$job"/control
+ mem="${toString mem}"
+
+ ( echo '{ "execute": "qmp_capabilities" }'
+ ) | ${netcat}/bin/nc -lU "$job"/qmp >/dev/null &
+
+ ( echo "$@"
+ echo ". /input"
+ ) > "$job"/control &
+
+ timeout --foreground 10 \
+ ${qemu}/bin/qemu-system-x86_64 \
+ ${commonQemuOptions}
+ ${qemuDriveOptions (builtins.attrValues storeDrives)} \
+ -incoming 'exec:${lz4}/bin/lz4 -d ${suspension args}' | ${dos2unix}/bin/dos2unix -f | head -c 500
+
+ # ^ qemu incorrectly does crlf conversion, check in the future if still necessary
+ '' // args;
+
+ # if this doesn't build, and just silently sits there, try increasing memory
+ suspension = { name, initrdPath, fullPath, storeDrives, mem, desc }: bitflip.flipTwice (stdenv.mkDerivation {
+ name = "${name}-suspension";
+ requiredSystemFeatures = [ "kvm" ];
+ nativeBuildInputs = [ qemu netcat lz4 ];
+
+ inherit fullPath mem desc;
+
+ buildCommand = ''
+ mkdir job
+ job=$PWD/job
+ mkfifo job/control
+
+ ( read ready < job/control
+ echo '{ "execute": "qmp_capabilities" }'
+ echo '{ "execute": "migrate", "arguments": { "uri": "exec:lz4 -9 - '$out'" } }'
+ sleep 15 # FIXME
+ echo '{ "execute": "quit" }'
+ ) | ${netcat}/bin/nc -lU job/qmp &
+
+ qemu-system-x86_64 \
+ ${commonQemuOptions}
+ ${qemuDriveOptions (builtins.attrValues storeDrives)} \
+ -kernel ${kernel}/bzImage \
+ -initrd ${initrd initrdPath}/initrd \
+ -append "console=ttyS0,38400 tsc=unstable jobDesc=${desc}"
+ '';
+ });
+
+ evaluators = callPackage ./evaluators.nix { inherit prepareJob; };
+}
diff --git a/evaluators.nix b/evaluators.nix
new file mode 100644
index 0000000..1d07c28
--- /dev/null
+++ b/evaluators.nix
@@ -0,0 +1,401 @@
+{ pkgs, prepareJob }:
+with pkgs;
+
+let
+ self = {
+ perl = prepareJob {
+ name = "perl";
+ mem = 50;
+ aliases = [ "pl" ];
+ storeDrives.perl = [ pkgsCross.musl64.perl ];
+ preCommand = ''
+ perl -e 'print "Hello world!"'
+ '';
+
+ command = ''
+ perl "$1"
+ '';
+
+ testInput = "print \"success\"";
+ };
+
+ rust =
+ let
+ opts = lib.concatStringsSep " " [
+ "--color never"
+ "-C opt-level=0"
+ "-C prefer-dynamic"
+ "-C debuginfo=0"
+ "-v" "--error-format short"
+ "-C codegen-units=1"
+ ];
+ in prepareJob {
+ name = "rust";
+ aliases = [ "rs" ];
+ mem = 200;
+ storeDrives.rust = [
+ (rustChannelOf {
+ channel = "nightly";
+ date = "2018-08-06";
+ }).rust
+ gcc
+ ];
+
+ preCommand = ''
+ echo 'fn main() {}' > /tmp/sample
+ rustc ${opts} -o /tmp/sample.out /tmp/sample
+ /tmp/sample.out
+ rm /tmp/sample /tmp/sample.out
+ '';
+
+ command = ''
+ mv "$1" /input.raw
+ cat > /input <<EOF
+ #![allow(unreachable_code, dead_code)]
+ fn main() {
+ println!("{:?}", {
+ $(cat /input.raw)
+ })
+ }
+ EOF
+ rustc ${opts} -o /input.out /input && /input.out
+ '';
+
+ testInput = "\"success\"";
+ testOutput = "\"success\"";
+ };
+
+ go = prepareJob {
+ name = "go";
+ mem = 100;
+ storeDrives.go = [ go ];
+
+ command = ''
+ mv "$1" /input.raw
+ cat > /input.go <<EOF
+ package main
+ import "fmt"
+ func main() {
+ fmt.Println($(cat /input.raw))
+ }
+ EOF
+ go run /input.go
+ '';
+
+ testInput = ''"success"'';
+ };
+
+ c = prepareJob {
+ name = "c";
+ mem = 100;
+ aliases = [ "gcc" ];
+ storeDrives.gcc = [ gcc ];
+ preCommand = ''
+ echo 'int main() { return 0; }' > /tmp/sample
+ gcc -x c -o /tmp/sample.out /tmp/sample
+ /tmp/sample.out
+ '';
+
+ command = ''
+ gcc -x c -o /input.out -w -fdiagnostics-color=never "$1" && /input.out
+ '';
+
+ testInput = ''
+ void main() { printf("success\n"); }
+ '';
+ };
+
+ cpp = prepareJob {
+ name = "cpp";
+ mem = 100;
+ storeDrives.gcc = [ gcc ];
+
+ command = ''
+ mv "$1" /input.raw
+ cat > /input <<EOF
+ #include <iostream>
+ using namespace std;
+ int main() {
+ $(cat /input.raw)
+ return 0;
+ };
+ EOF
+ g++ -x 'c++' -o /input.out /input && /input.out
+ '';
+
+ testInput = "cout << \"success\" << endl;";
+ };
+
+ tcc = prepareJob {
+ name = "tcc";
+ mem = 50;
+ storeDrives.tcc = [ tinycc ];
+ preCommand = ''
+ echo 'int main() { return 0; }' > /tmp/sample
+ tcc -run /tmp/sample
+ '';
+
+ command = ''
+ tcc -run "$1" 2>/dev/null
+ '';
+
+ testInput = ''
+ void main() { printf("success\n"); }
+ '';
+ };
+
+ java = prepareJob rec {
+ name = "java";
+ mem = 150;
+
+ storeDrives.jdk = [ openjdk ];
+
+ preCommand = ''
+ cat > /tmp/Main.java <<EOF
+ ${testInput}
+ EOF
+ javac /tmp/Main.java
+ cd /tmp && java Main
+ '';
+
+ command = ''
+ mv "$1" /Main.java
+ javac Main.java && java Main
+ '';
+
+ # TODO: multi-line tests/input
+ testInput = ''
+ public class Main { public static void main(String... args) { System.out.println("success"); } }
+ '';
+ };
+
+ kotlin = prepareJob rec {
+ name = "kotlin";
+ mem = 300;
+ storeDrives.kotlin = [ kotlin ];
+
+ preCommand = ''
+ cat > /tmp/Main.kts <<EOF
+ ${testInput}
+ EOF
+ kotlinc -script /tmp/Main.kts
+ '';
+
+ command = ''
+ mv "$1" /input.kts
+ kotlinc -script /input.kts
+ '';
+
+ testInput = ''println("success")'';
+ };
+
+ python = prepareJob {
+ name = "python";
+ aliases = [ "python3" "py" "py3" ];
+ mem = 75;
+ storeDrives.python = [ python3 ];
+ preCommand = ''
+ ${python3}/bin/python3 -c "print(42)"
+ '';
+
+ command = ''
+ ${python3}/bin/python3 "$1"
+ '';
+
+ testInput = "print(\"success\")";
+ };
+
+ ruby = prepareJob {
+ name = "ruby";
+ aliases = [ "rb" ];
+ mem = 100;
+ storeDrives.ruby = [ ruby_2_5 ];
+
+ preCommand = ''
+ echo 42 | ruby
+ '';
+
+ command = ''
+ ruby "$1"
+ '';
+
+ testInput = "puts \"success\"";
+ };
+
+ sh = prepareJob {
+ name = "bash";
+ aliases = [ "shell" "sh" ];
+ mem = 60;
+ storeDrives.bash = [ bash coreutils gnused gnugrep gawk file bsdgames ];
+
+ command = ''
+ bash "$1"
+ '';
+
+ testInput = "echo success";
+ };
+
+ ash = prepareJob {
+ name = "ash";
+ command = ''
+ /bin/sh "$1"
+ '';
+
+ testInput = "echo success";
+ };
+
+ nodejs = prepareJob {
+ name = "nodejs";
+ aliases = [ "node" "js" ];
+ mem = 100;
+ storeDrives.node = [ nodejs ];
+
+ preCommand = ''
+ node -e "console.log(42)"
+ '';
+
+ command = ''
+ node -p "$(cat "$1")" | tr -s '\n ' ' '
+ '';
+ /*
+ mv "$1" /input.raw
+ cat > /input <<EOF
+ function debug(val) {
+ return require("util").inspect(val, { depth: 1, colors: false })
+ .replace(/\s+/g, ' ')
+ }
+ console.log(debug($(cat /input.raw)))
+ EOF
+
+ node /input
+ '';*/
+
+ testInput = "'success'";
+ testOutput = "success ";
+ };
+
+ lua = prepareJob {
+ name = "lua";
+ mem = 50;
+ storeDrives.lua = [ lua5_3 ];
+
+ command = ''
+ lua "$1"
+ '';
+
+ testInput = "print(\"success\")";
+ };
+
+ brainfuck = prepareJob {
+ name = "brainfuck";
+ aliases = [ "bf" ];
+ storeDrives.brainfuck = [
+ (runCommand "just-brainfuck" {} ''
+ mkdir -p $out/bin
+ cp ${haskellPackages.brainfuck}/bin/bf $out/bin/
+ '')
+ ];
+
+ preCommand = ''
+ echo '+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-.' > /tmp/sample
+ bf < /tmp/sample
+ '';
+
+ command = ''
+ bf < "$1"
+ '';
+
+ testInput = "+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-.";
+ testOutput = "hello world";
+ };
+
+ php = prepareJob {
+ name = "php";
+ mem = 100;
+ storeDrives.php = [ php ];
+
+ command = ''
+ php -r "$(cat "$1")"
+ '';
+
+ testInput = ''echo "success";'';
+ };
+
+ racket = prepareJob {
+ name = "racket";
+ aliases = [ "rkt" "r" ];
+ mem = 200;
+ storeDrives.racket = [ racket ];
+
+ preCommand = ''
+ racket -e '(+ 40 2)'
+ '';
+
+ command = ''
+ ( echo "#lang racket"
+ cat "$1"
+ ) | racket /proc/self/fd/0
+ '';
+
+ testInput = "(displayln 'success)";
+ };
+
+ guile = prepareJob {
+ name = "guile";
+ mem = 100;
+ storeDrives.guile = [ guile ];
+
+ command = ''
+ guile --no-auto-compile -s "$1"
+ '';
+
+ testInput = ''(display "success")'';
+ };
+
+ haskell = prepareJob {
+ name = "haskell";
+ aliases = [ "hask" "hs" "h" ];
+ mem = 200;
+ storeDrives.ghc = [ ghc ];
+
+ preCommand = ''
+ echo '"foo":[]:[]' > /tmp/sample
+ ghci -v0 < /tmp/sample
+ '';
+
+ command = ''
+ ghci -v0 < "$1"
+ '';
+
+ testInput = "putStrLn \"success\"";
+ };
+
+ listAll = with self; [
+ ash
+ sh
+ python
+ ruby
+ perl
+ lua
+ nodejs
+ haskell
+ rust
+ c tcc
+ cpp
+ java
+ # kotlin
+ racket
+ guile
+ brainfuck
+ php
+ go
+ ];
+
+ all = symlinkJoin {
+ name = "all-evaluators";
+ paths = self.listAll;
+ };
+
+ apparmorAll = map (p: p.apparmor) self.listAll;
+ };
+in self