set shell := ["bash", "-euo", "pipefail", "-c"]

meson_bin := env_var_or_default("MESON_BIN", "meson")
python_bin := env_var_or_default("PYTHON", "python3")
jobs := env_var_or_default("MFEM_JOBS", "4")
allow_preinstalled := env_var_or_default("MFEM_ALLOW_PREINSTALLED", "false")
dependency_prefix := env_var_or_default("MFEM_DEPENDENCY_PREFIX", "")
cuda_home := env_var_or_default("CUDA_HOME", "")
cuda_arch := env_var_or_default("MFEM_CUDA_ARCH", "native")
web_port := env_var_or_default("MFEM_WEB_PORT", "8000")
macos_target := env_var_or_default("MACOSX_DEPLOYMENT_TARGET", "11.0")

project_root := justfile_directory()
build_root := project_root + "/" + env_var_or_default("MFEM_BUILD_ROOT", "build")
install_root := project_root + "/" + env_var_or_default("MFEM_INSTALL_ROOT", "install")
basic_dir := build_root + "/basic"
serial_dir := build_root + "/serial"
parallel_dir := build_root + "/parallel"
debug_dir := build_root + "/debug"
cuda_dir := build_root + "/cuda"
hip_dir := build_root + "/hip"
wasm_dir := build_root + "/emscripten"
wasm_cache := build_root + "/emscripten-cache"
basic_install := install_root + "/basic"
serial_install := install_root + "/serial"
parallel_install := install_root + "/parallel"
debug_install := install_root + "/debug"
cuda_install := install_root + "/cuda"
hip_install := install_root + "/hip"
benchmark_trials := env_var_or_default("MFEM_BENCH_TRIALS", "5")
benchmark_mesh_n := env_var_or_default("MFEM_BENCH_MESH_N", "48")
benchmark_order := env_var_or_default("MFEM_BENCH_ORDER", "3")
benchmark_applications := env_var_or_default("MFEM_BENCH_APPLICATIONS", "50")
benchmark_minimum_apply_seconds := env_var_or_default("MFEM_BENCH_MIN_SECONDS", "1.0")
benchmark_max_applications := env_var_or_default("MFEM_BENCH_MAX_APPLICATIONS", "1000000")
benchmark_omp_threads := env_var_or_default("MFEM_BENCH_OMP_THREADS", "8")
benchmark_mpi_ranks := env_var_or_default("MFEM_BENCH_MPI_RANKS", "4")

default:
    @just --list

[private]
configure dir install_dir buildtype profile allow *extra:
    #!/usr/bin/env bash
    set -euo pipefail
    setup_args=(
      "{{ dir }}"
      "--prefix={{ install_dir }}"
      "--buildtype={{ buildtype }}"
      "-Dfeature_profile={{ profile }}"
      "-Dallow_preinstalled={{ allow }}"
      "-Ddependency_prefix={{ dependency_prefix }}"
      "-Djobs={{ jobs }}"
      "-Dbuild_examples=true"
      "-Dbuild_tests=true"
    )
    if [[ -f "{{ dir }}/meson-private/coredata.dat" ]]; then
      setup_args=(--reconfigure "${setup_args[@]}")
    fi
    "{{ meson_bin }}" setup "${setup_args[@]}" {{ extra }}

[private]
build-and-test dir:
    "{{ meson_bin }}" compile -C "{{ dir }}"
    "{{ meson_bin }}" test -C "{{ dir }}" --print-errorlogs

basic: (configure basic_dir basic_install "release" "minimal" allow_preinstalled "-Dmfem_mpi=disabled" "-Dmfem_cuda=disabled" "-Dmfem_hip=disabled") (build-and-test basic_dir)

serial: (configure serial_dir serial_install "release" "portable" allow_preinstalled "-Dmfem_mpi=disabled" "-Dmfem_metis=disabled" "-Dmfem_cuda=disabled" "-Dmfem_hip=disabled") (build-and-test serial_dir)

parallel: (configure parallel_dir parallel_install "release" "portable" allow_preinstalled "-Dmfem_mpi=enabled" "-Dmfem_sundials=enabled" "-Dmfem_cuda=disabled" "-Dmfem_hip=disabled" "-Dbuild_benchmarks=true") (build-and-test parallel_dir)

debug: (configure debug_dir debug_install "debug" "portable" allow_preinstalled) (build-and-test debug_dir)

cuda:
    #!/usr/bin/env bash
    set -euo pipefail
    cuda_root="{{ cuda_home }}"
    if [[ -z "$cuda_root" ]]; then
      if command -v nvcc >/dev/null 2>&1; then
        nvcc_path="$(command -v nvcc)"
        while [[ -L "$nvcc_path" ]]; do
          nvcc_target="$(readlink "$nvcc_path")"
          if [[ "$nvcc_target" == /* ]]; then
            nvcc_path="$nvcc_target"
          else
            nvcc_path="$(dirname "$nvcc_path")/$nvcc_target"
          fi
        done
        cuda_root="$(cd "$(dirname "$nvcc_path")/.." && pwd -P)"
      elif [[ -x /usr/local/cuda/bin/nvcc ]]; then
        cuda_root=/usr/local/cuda
      fi
    fi
    if [[ -n "$cuda_root" ]]; then
      export CUDA_HOME="$cuda_root"
      export PATH="$cuda_root/bin:$PATH"
    fi
    if ! command -v nvcc >/dev/null 2>&1; then
      echo "CUDA was requested, but nvcc was not found; set CUDA_HOME or PATH." >&2
      exit 2
    fi
    selected_arch="{{ cuda_arch }}"
    if [[ "$selected_arch" == native ]] && command -v nvidia-smi >/dev/null 2>&1; then
      if detected_arch="$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | sed -n '1{s/[[:space:].]//g;p;}')" && [[ "$detected_arch" =~ ^[0-9]+$ ]]; then
        selected_arch="$detected_arch"
      fi
    fi
    setup_args=(
      "{{ cuda_dir }}"
      "--prefix={{ cuda_install }}"
      "--buildtype=release"
      "-Dfeature_profile=portable"
      "-Dallow_preinstalled={{ allow_preinstalled }}"
      "-Ddependency_prefix={{ dependency_prefix }}"
      "-Djobs={{ jobs }}"
      "-Dbuild_examples=true"
      "-Dbuild_benchmarks=true"
      "-Dbuild_tests=true"
      "-Dmfem_cuda=enabled"
      "-Dmfem_hip=disabled"
      "-Dmfem_cuda_arch=${selected_arch}"
    )
    if [[ -f "{{ cuda_dir }}/meson-private/coredata.dat" ]]; then
      setup_args=(--reconfigure "${setup_args[@]}")
    fi
    "{{ meson_bin }}" setup "${setup_args[@]}"
    "{{ meson_bin }}" compile -C "{{ cuda_dir }}"
    "{{ meson_bin }}" test -C "{{ cuda_dir }}" --print-errorlogs

hip: (configure hip_dir hip_install "release" "portable" allow_preinstalled "-Dmfem_hip=enabled" "-Dmfem_cuda=disabled") (build-and-test hip_dir)

emscripten:
    #!/usr/bin/env bash
    set -euo pipefail
    mkdir -p "{{ wasm_cache }}"
    setup_args=(
      "{{ wasm_dir }}"
      "--cross-file={{ project_root }}/cross/wasm32.ini"
      "--prefix={{ install_root }}/emscripten"
      "--buildtype=release"
      "-Dfeature_profile=portable"
      "-Dallow_preinstalled=false"
      "-Djobs={{ jobs }}"
      "-Dbuild_python=false"
      "-Dbuild_examples=true"
      "-Dbuild_tests=true"
    )
    if [[ -f "{{ wasm_dir }}/meson-private/coredata.dat" ]]; then
      setup_args=(--reconfigure "${setup_args[@]}")
    fi
    EM_CACHE="{{ wasm_cache }}" "{{ meson_bin }}" setup "${setup_args[@]}"
    EM_CACHE="{{ wasm_cache }}" "{{ meson_bin }}" compile -C "{{ wasm_dir }}"
    EM_CACHE="{{ wasm_cache }}" "{{ meson_bin }}" test -C "{{ wasm_dir }}" --print-errorlogs

serve-emscripten port=web_port: emscripten
    @echo "MFEM browser demo: http://0.0.0.0:{{ port }}"
    "{{ python_bin }}" -m http.server "{{ port }}" --bind 0.0.0.0 --directory "{{ wasm_dir }}/web"

python:
    mkdir -p "{{ project_root }}/dist"
    MACOSX_DEPLOYMENT_TARGET="{{ macos_target }}" "{{ python_bin }}" -m build --wheel --no-isolation --outdir "{{ project_root }}/dist"

run-serial: serial
    "{{ serial_dir }}/examples/mfem-serial-poisson"

run-parallel ranks="4": parallel
    "{{ python_bin }}" "{{ project_root }}/tools/run_mpi_test.py" --launcher "{{ parallel_dir }}/build-config/mfem/mfem-prefix/bin/mpiexec" --processes "{{ ranks }}" "{{ parallel_dir }}/examples/mfem-parallel-hypre"

run-cuda ranks="2": cuda
    "{{ python_bin }}" "{{ project_root }}/tools/run_mpi_test.py" --launcher "{{ cuda_dir }}/build-config/mfem/mfem-prefix/bin/mpiexec" --processes "{{ ranks }}" "{{ cuda_dir }}/examples/mfem-parallel-hypre" cuda

benchmark: benchmark-cpu

benchmark-cpu: parallel
    #!/usr/bin/env bash
    set -euo pipefail
    launcher="{{ parallel_dir }}/build-config/mfem/mfem-prefix/bin/mpiexec"
    dep_prefix="{{ dependency_prefix }}"
    if [[ ! -x "$launcher" && -n "$dep_prefix" ]]; then
      for candidate in "$dep_prefix/bin/mpiexec" "$dep_prefix/bin/mpirun"; do
        [[ -x "$candidate" ]] && launcher="$candidate" && break
      done
    fi
    if [[ ! -x "$launcher" ]]; then
      launcher="$(command -v mpiexec || command -v mpirun || true)"
    fi
    [[ -n "$launcher" ]] || { echo "No MPI launcher was found." >&2; exit 2; }
    if [[ -n "$dep_prefix" ]]; then
      export PATH="$dep_prefix/bin:$PATH"
      if [[ "$(uname -s)" == Darwin ]]; then
        export DYLD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${DYLD_LIBRARY_PATH:-}"
      else
        export LD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${LD_LIBRARY_PATH:-}"
      fi
    fi
    "{{ python_bin }}" "{{ project_root }}/tools/run_backend_benchmarks.py" \
      --executable "{{ parallel_dir }}/benchmarks/mfem-backend-benchmark" \
      --launcher "$launcher" \
      --cases cpu,ceed-cpu,omp,mpi \
      --omp-threads "{{ benchmark_omp_threads }}" \
      --mpi-ranks "{{ benchmark_mpi_ranks }}" \
      --trials "{{ benchmark_trials }}" \
      --mesh-n "{{ benchmark_mesh_n }}" \
      --order "{{ benchmark_order }}" \
      --applications "{{ benchmark_applications }}" \
      --minimum-apply-seconds "{{ benchmark_minimum_apply_seconds }}" \
      --max-applications "{{ benchmark_max_applications }}" \
      --output-dir "{{ parallel_dir }}/benchmarks/results"

benchmark-cuda: cuda
    #!/usr/bin/env bash
    set -euo pipefail
    launcher="{{ cuda_dir }}/build-config/mfem/mfem-prefix/bin/mpiexec"
    dep_prefix="{{ dependency_prefix }}"
    if [[ ! -x "$launcher" && -n "$dep_prefix" ]]; then
      for candidate in "$dep_prefix/bin/mpiexec" "$dep_prefix/bin/mpirun"; do
        [[ -x "$candidate" ]] && launcher="$candidate" && break
      done
    fi
    if [[ ! -x "$launcher" ]]; then
      launcher="$(command -v mpiexec || command -v mpirun || true)"
    fi
    [[ -n "$launcher" ]] || { echo "No MPI launcher was found." >&2; exit 2; }
    if [[ -n "$dep_prefix" ]]; then
      export PATH="$dep_prefix/bin:$PATH"
      if [[ "$(uname -s)" == Darwin ]]; then
        export DYLD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${DYLD_LIBRARY_PATH:-}"
      else
        export LD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${LD_LIBRARY_PATH:-}"
      fi
    fi
    "{{ python_bin }}" "{{ project_root }}/tools/run_backend_benchmarks.py" \
      --executable "{{ cuda_dir }}/benchmarks/mfem-backend-benchmark" \
      --launcher "$launcher" \
      --cases cpu,ceed-cpu,omp,mpi,cuda,ceed-cuda \
      --omp-threads "{{ benchmark_omp_threads }}" \
      --mpi-ranks "{{ benchmark_mpi_ranks }}" \
      --trials "{{ benchmark_trials }}" \
      --mesh-n "{{ benchmark_mesh_n }}" \
      --order "{{ benchmark_order }}" \
      --applications "{{ benchmark_applications }}" \
      --minimum-apply-seconds "{{ benchmark_minimum_apply_seconds }}" \
      --max-applications "{{ benchmark_max_applications }}" \
      --output-dir "{{ cuda_dir }}/benchmarks/results"

benchmark-quick: parallel
    #!/usr/bin/env bash
    set -euo pipefail
    launcher="{{ parallel_dir }}/build-config/mfem/mfem-prefix/bin/mpiexec"
    dep_prefix="{{ dependency_prefix }}"
    if [[ ! -x "$launcher" && -n "$dep_prefix" ]]; then
      for candidate in "$dep_prefix/bin/mpiexec" "$dep_prefix/bin/mpirun"; do
        [[ -x "$candidate" ]] && launcher="$candidate" && break
      done
    fi
    if [[ ! -x "$launcher" ]]; then
      launcher="$(command -v mpiexec || command -v mpirun || true)"
    fi
    [[ -n "$launcher" ]] || { echo "No MPI launcher was found." >&2; exit 2; }
    if [[ -n "$dep_prefix" ]]; then
      export PATH="$dep_prefix/bin:$PATH"
      if [[ "$(uname -s)" == Darwin ]]; then
        export DYLD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${DYLD_LIBRARY_PATH:-}"
      else
        export LD_LIBRARY_PATH="$dep_prefix/lib:$dep_prefix/lib64:${LD_LIBRARY_PATH:-}"
      fi
    fi
    "{{ python_bin }}" "{{ project_root }}/tools/run_backend_benchmarks.py" \
      --executable "{{ parallel_dir }}/benchmarks/mfem-backend-benchmark" \
      --launcher "$launcher" \
      --cases cpu,ceed-cpu,omp,mpi \
      --omp-threads "{{ benchmark_omp_threads }}" \
      --mpi-ranks "{{ benchmark_mpi_ranks }}" \
      --trials 1 --mesh-n 16 --order 2 --applications 5 \
      --minimum-apply-seconds 0 \
      --output-dir "{{ parallel_dir }}/benchmarks/quick-results"

install-basic: basic
    "{{ meson_bin }}" install -C "{{ basic_dir }}"

install-serial: serial
    "{{ meson_bin }}" install -C "{{ serial_dir }}"

install-parallel: parallel
    "{{ meson_bin }}" install -C "{{ parallel_dir }}"

fetch:
    "{{ meson_bin }}" subprojects download
