import "fail.just"

BUILD_DIR := "build"

# Load environment variables set by "nix develop"-shell

FAIL_SHARE := env("FAIL_SHARE")
WASI_ROOT := env("WASI_ROOT")
WAMR_ROOT := env("WAMR_ROOT")
LIBIWASM_DEBUG := env("LIBIWASM_DEBUG")
LIBIWASM_RELEASE := env("LIBIWASM_RELEASE")
LIBIWASM_LINUX_DEBUG := env("LIBIWASM_LINUX_DEBUG")
LIBIWASM_LINUX_RELEASE := env("LIBIWASM_LINUX_RELEASE")
CROSS_CC := env("CROSS_CC")
LINUX_CC := env("LINUX_CC")

# FAIL* variables

FAIL_SERVER_PORT := "1111"
RESULTBROWSER_PORT := "5000"
BOCHS_RUNNER := "bochs-experiment-runner.py"
FAIL_TRACE := "fail-x86-tracing"
FAIL_DUMP := "dump-trace"
FAIL_IMPORT := "import-trace"
FAIL_PRUNE := "prune-trace"
FAIL_SERVER := "generic-experiment-server"
FAIL_INJECT := "generic-experiment-client"
RESULT_BROWSER := "resultbrowser.py"

# =================================================================================================================== #
# Helper recipes
# =================================================================================================================== #

[default]
[private]
list:
    @just --list --unsorted

[private]
create-build-dir module:
    mkdir -p {{ BUILD_DIR }}-{{ module }}

[doc("Delete the build directory")]
clean module:
    rm -rf {{ BUILD_DIR }}-{{ module }}

# =================================================================================================================== #
# Build WASM module recipes
# =================================================================================================================== #

WASI_CC := f"{{WASI_ROOT}}/bin/clang"
WASI_CFLAGS := "\
--target=wasm32 \
--sysroot={{WASI_ROOT}}/share/wasi-sysroot \
-z stack-size=4096 \
-O0 \
-nostdlib \
-Wl,--no-entry \
-Wl,--export-all \
-Wl,--no-gc-sections \
-Wl,--initial-memory=65536 \
-Wl,--export=__heap_base \
-Wl,--export=__data_end \
"
WAMRC := "wamrc"
WAMRCFLAGS := "\
--target=i386 \
--cpu=generic \
--opt-level=0 \
"
XXD := "xxd"

[doc("C -> WASM: Compile a C function to a WASM module using WASI-SDK")]
[group("build-module")]
build-wasm-module module: (create-build-dir module)
    {{ WASI_CC }} {{ WASI_CFLAGS }} targets/wasm-module/{{ module }}.c -o {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm

[doc("WASM -> AOT: Compile a WASM module ahead-of-time using WAMR")]
[group("build-module")]
build-wasm-aot module: (build-wasm-module module)
    {{ WAMRC }} {{ WAMRCFLAGS }} -o {{ BUILD_DIR }}-{{ module }}/wasm_module.aot {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm

[doc("AOT -> C-Array: Dump a WASM module compiled ahead-of-time to a binary array")]
[group("build-module")]
build-wasm-aot-array module: (build-wasm-aot module)
    {{ XXD }} -i {{ BUILD_DIR }}-{{ module }}/wasm_module.aot > {{ BUILD_DIR }}-{{ module }}/wasm_module_array.c

# =================================================================================================================== #
# Host program recipes
# =================================================================================================================== #
# FAIL*

CROSS_CFLAGS := "\
-I./targets/wasm-host \
-O2 \
-m32 \
-ffunction-sections \
-fdata-sections \
-ffreestanding \
-fomit-frame-pointer \
-ggdb \
"
CROSS_LDFLAGS := f"\
-Wl,--build-id=none \
-static \
-nostdlib \
-m32 \
-L{{LIBIWASM_RELEASE}} \
-liwasm \
-lc \
-lgcc \
-lm \
"
CROSS_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/baremetal \
"

# LINUX-POSIX

LINUX_CFLAGS := "\
-I./targets/wasm-host \
-O0 \
-m32 \
-ffunction-sections \
-fdata-sections \
-ggdb \
"
LINUX_LDFLAGS := f"\
-Wl,--build-id=none \
-m32 \
-Wl,-rpath,{{LIBIWASM_LINUX_DEBUG}} \
-L{{LIBIWASM_LINUX_DEBUG}} \
-liwasm \
-lm \
"
LINUX_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/linux \
"

# LINUX-Baremetal

LINUX_BAREMETAL_CFLAGS := "\
-I./targets/wasm-host \
-O0 \
-m32 \
-ffunction-sections \
-fdata-sections \
-ffreestanding \
-ggdb \
"
LINUX_BAREMETAL_LDFLAGS := f"\
-Wl,--build-id=none \
-static \
-nostdlib \
-m32 \
-L{{LIBIWASM_DEBUG}} \
-liwasm \
-lc \
-lgcc \
-lm \
--entry main \
"
LINUX_BAREMETAL_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/baremetal \
"

[private]
build-wasm-host-fail module:
    {{ CROSS_CC }} {{ CROSS_CFLAGS }} {{ CROSS_INCLUDES }} \
      -c {{ BUILD_DIR }}-{{ module }}/module_host.c \
      -o {{ BUILD_DIR }}-{{ module }}/system.o

[private]
build-wasm-host-linux module:
    {{ LINUX_CC }} {{ LINUX_CFLAGS }} {{ LINUX_INCLUDES }} \
      -c {{ BUILD_DIR }}-{{ module }}/module_host.c \
      -o {{ BUILD_DIR }}-{{ module }}/system.o

[private]
build-wasm-host-linux-baremetal module:
    {{ CROSS_CC }} {{ LINUX_BAREMETAL_CFLAGS }} {{ LINUX_BAREMETAL_INCLUDES }} \
      -c {{ BUILD_DIR }}-{{ module }}/module_host.c \
      -o {{ BUILD_DIR }}-{{ module }}/system.o

[private]
build-wasm-host-dispatch module target="fail":
    #!/usr/bin/env sh
    if [ "{{ target }}" = "fail" ]; then
        just build-wasm-host-fail "{{ module }}"
    elif [ "{{ target }}" = "linux" ]; then
        just build-wasm-host-linux "{{ module }}"
    elif [ "{{ target }}" = "linux-baremetal" ]; then
        just build-wasm-host-linux-baremetal "{{ module }}"
    else
        echo "unknown target: {{ target }}" >&2
        exit 1
    fi

[doc("Compile C-Host: The host uses WAMR to load the AOT module")]
[group("build-host")]
build-wasm-host module target="fail": (build-wasm-aot-array module) && (build-wasm-host-dispatch module target)
    cp targets/wasm-host/{{ target }}.c {{ BUILD_DIR }}-{{ module }}/module_host.c
    sed -i \
        -e "s/__WASM_ARRAY_FILE__/wasm_module_array.c/g" \
        -e "s/__WASM_ARRAY__/build_{{ module }}_wasm_module_aot/g" \
        -e "s/__WASM_ARRAY_LEN__/build_{{ module }}_wasm_module_aot_len/g" \
        {{ BUILD_DIR }}-{{ module }}/module_host.c

[private]
build-system-startup-fail module:
    {{ CROSS_CC }} targets/startup.s {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/startup.o

[doc("Compile bootloader")]
[group("build-host")]
build-system-startup module target="fail":
    #!/usr/bin/env sh
    if [ "{{ target }}" = "fail" ]; then
        just build-system-startup-fail "{{ module }}"
    else
        echo "{{ target }} doesn't need bootloader"
    fi

[private]
build-system-syscalls-fail module:
    {{ CROSS_CC }} targets/syscalls.c {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/syscalls.o

[private]
build-system-syscalls-linux-baremetal module:
    {{ CROSS_CC }} targets/syscalls.c {{ LINUX_BAREMETAL_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/syscalls.o

[doc("Compile newlib syscall stubs")]
[group("build-host")]
build-system-syscalls module target="fail":
    #!/usr/bin/env sh
    if [ "{{ target }}" = "fail" ]; then
        just build-system-syscalls-fail "{{ module }}"
    elif [ "{{ target }}" = "linux-baremetal" ]; then
        just build-system-syscalls-linux-baremetal "{{ module }}"
    else
        echo "{{ target }} doesn't require syscall stubs"
    fi

[private]
link-system-fail module:
    {{ CROSS_CC }} \
        -Wl,-T targets/linker.ld \
        {{ BUILD_DIR }}-{{ module }}/system.o \
        {{ BUILD_DIR }}-{{ module }}/startup.o \
        {{ BUILD_DIR }}-{{ module }}/syscalls.o \
        {{ CROSS_LDFLAGS }} \
        -o {{ BUILD_DIR }}-{{ module }}/system.elf

[private]
link-system-linux module:
    {{ LINUX_CC }} \
        {{ BUILD_DIR }}-{{ module }}/system.o \
        {{ LINUX_LDFLAGS }} \
        -o {{ BUILD_DIR }}-{{ module }}/system.elf

[private]
link-system-linux-baremetal module:
    {{ CROSS_CC }} \
        {{ BUILD_DIR }}-{{ module }}/system.o \
        {{ BUILD_DIR }}-{{ module }}/syscalls.o \
        {{ LINUX_BAREMETAL_LDFLAGS }} \
        -o {{ BUILD_DIR }}-{{ module }}/system.elf

[doc("Link C-Host, syscall stubs and bootloader")]
[group("build-host")]
link-system module target="fail": (build-wasm-host module target) (build-system-syscalls module target) (build-system-startup module target)
    #!/usr/bin/env sh
    if [ "{{ target }}" = "fail" ]; then
        just link-system-fail "{{ module }}"
    elif [ "{{ target }}" = "linux" ]; then
        just link-system-linux "{{ module }}"
    elif [ "{{ target }}" = "linux-baremetal" ]; then
        just link-system-linux-baremetal "{{ module }}"
    else
        echo "unknown target: {{ target }}" >&2
        exit 1
    fi

[doc("Create bootdisk")]
[group("build-host")]
build-iso module target="fail": (link-system module target)
    mkdir -p {{ BUILD_DIR }}-{{ module }}/grub/boot/grub
    cp targets/grub.cfg {{ BUILD_DIR }}-{{ module }}/grub/boot/grub/
    cp {{ BUILD_DIR }}-{{ module }}/system.elf {{ BUILD_DIR }}-{{ module }}/grub/boot/
    grub-mkrescue -o {{ BUILD_DIR }}-{{ module }}/system.iso {{ BUILD_DIR }}-{{ module }}/grub

# =================================================================================================================== #
# MySQL recipes
# =================================================================================================================== #

[doc("Start MySQL container to receive FAIL* trace/campaign results")]
[group("fail-db")]
start-db:
    docker run -d \
        --name fail-db \
        -e MYSQL_ROOT_PASSWORD=fail \
        -e MYSQL_USER=fail \
        -e MYSQL_PASSWORD=fail \
        -e MYSQL_DATABASE=fail \
        -p 3306:3306 \
        mysql

[doc("Connect to MySQL database using DBeaver")]
[group("fail-db")]
connect-db:
    dbeaver -con "name=fail|driver=mysql|host=localhost|port=3306|database=fail|user=fail|password=fail"

[doc("Stop MySQL container")]
[group("fail-db")]
stop-db:
    docker stop fail-db

[doc("Remove MySQL container")]
[group("fail-db")]
remove-db:
    docker container rm fail-db

# =================================================================================================================== #
# Debugging recipes
# =================================================================================================================== #

[doc("Run binary")]
[group("debug")]
run module:
    {{ BUILD_DIR }}-{{ module }}/system.elf

[doc("Launch gdb")]
[group("debug")]
gdb module:
    gdb --tui {{ BUILD_DIR }}-{{ module }}/system.elf

[doc("Launch radare2 at os_main")]
[group("debug")]
radare module:
    radare2 -AA -c "s dbg.os_main; pdf" {{ BUILD_DIR }}-{{ module }}/system.elf

# =================================================================================================================== #
# AIO
# =================================================================================================================== #

[group("just do it")]
upload module:
    scp -r {{ BUILD_DIR }}-{{ module }} mars:~/Documents/failnix/{{ BUILD_DIR }}-{{ module }}

[group("just do it")]
build module target="fail": (build-iso module target)

[group("just do it")]
inject module:
    just clean {{ module }}
    just build {{ module }} "fail"

    just start-db
    @echo "Waiting for database..."
    sleep 20

    just trace {{ module }}
    just import {{ module }}
    just server {{ module }}
    just client {{ module }}
    just result {{ module }}
    just resultbrowser
