#!/bin/bash
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

set -o errexit
set -o pipefail
set -o nounset

# meson_init.sh configures OpenTitan's Meson build system. Rather than calling
# Meson directly, this script should be used instead, since it handles things
# that Meson does not out of the box, such as:
# - The RISC-V toolchain.
# - Idempotence.
# - Building for multiple device platforms.
#
# This script's semantics for creating build directories are as follows:
# - By default, this script will create any missing build directories and
#   configure them. It is idempotent: if there is no work to be done, nothing
#   will change and this script will exit successfully.
# - Passing -r will reconfigure existing build directories. This should be used
#   when editing meson.build files.
# - Passing -f will erase existing build directories. This should be used when
#   existing configuration is completely broken, or a clean build is desired
#   (neither of these should be necessary).
# - Passing -A will explicitly disable idempotence, and cause the script to exit
#   if a build directory already exists. This should be used only in CI, where
#   such error-checking is desireable.
#
# Note that only the first option above is actually guaranteed to be idempotent.

. util/build_consts.sh

echo "Detected \$REPO_TOP at $REPO_TOP."
echo "Object directory set at $OBJ_DIR."
echo "Binary directory set at $BIN_DIR."
echo "OpenTitan version: $OT_VERSION"
echo

function usage() {
  cat << USAGE
Configure Meson build targets.

Usage: $0 [-r|-f|-A|-K]

  -f: Force a reconfiguration by removing existing build dirs.
  -r: Reconfigure build dirs, if they exist.
  -A: Assert that no build dirs exist when running this command.
  -K: Keep include search paths as generated by Meson.

USAGE
}

FLAGS_assert=false
FLAGS_force=false
FLAGS_reconfigure=""
FLAGS_keep_includes=false
while getopts 'r?:f?:K?:A?' flag; do
  case "${flag}" in
    f) FLAGS_force=true;;
    r) FLAGS_reconfigure="--reconfigure";;
    A) FLAGS_assert=true;;
    K) FLAGS_keep_includes=true;;
    ?) usage && exit 1;;
    *) usage
       error "Unexpected option ${flag}"
       ;;
  esac
done

if [[ ! -n "$(command -v meson)" ]]; then
  echo "Unable to find meson. Please install meson before running this command." >&2
  exit 1
fi

if [[ ! -n "$(command -v ninja)" ]]; then
  echo "Unable to find ninja. Please install ninja before running this command." >&2
  exit 1
fi

if [[ "${FLAGS_force}" == true ]]; then
  rm -rf "$OBJ_DIR"
  rm -rf "$BIN_DIR"
fi

if [[ "${FLAGS_assert}" == true ]]; then
  if [[ -e "$OBJ_DIR" ]]; then
    echo "Object directory at $OBJ_DIR already exists. Aborting." >&2
    exit 1
  fi
  if [[ -e "$BIN_DIR" ]]; then
    echo "Binary directory at $BIN_DIR already exists. Aborting." >&2
    exit 1
  fi
fi

readonly DEFAULT_RISCV_TOOLS=/tools/riscv
TOOLCHAIN_PATH="${TOOLCHAIN_PATH:-$DEFAULT_RISCV_TOOLS}"
CROSS_FILE=$(mktemp /tmp/toolchain.XXXXXX.txt)
cp toolchain.txt "$CROSS_FILE"
perl -pi -e "s#$DEFAULT_RISCV_TOOLS#$TOOLCHAIN_PATH#g" "$CROSS_FILE"
echo "Set up toolchain file at $CROSS_FILE." >&2

# purge_includes $build_dir deletes any -I command line arguments that are not
# - Absolute paths.
# - Ephemeral build directories.
#
# This function is necessary because Meson does not give adequate
# control over what directories are passed in as -I search directories
# to the C compiler. While Meson does provide |implicit_include_directories|,
# support for this option is poor: empirically, Meson ignores this option for
# some targerts. Doing it as a post-processing step ensures that Meson does
# not allow improper #includes to compile successfully.
function purge_includes() {
  if [[ "${FLAGS_keep_includes}" == "true" ]]; then
    return
  fi
  echo "Purging superfluous -I arguments from $1."
  local ninja_file="$1/build.ninja"
  perl -pi -e 's#-I[^/][^@ ]+ # #g' -- "$ninja_file"
}

for platform in ${PLATFORMS[@]}; do
  obj_dir="$(sw_obj_dir "$platform")"
  reconf="${FLAGS_reconfigure}"

  if [[ ! -d "$obj_dir" ]]; then
    echo "Output directory for $platform does not exist at $obj_dir; creating." >&2
    mkdir -p "$obj_dir"
    reconf=""
  elif [[ -z "$reconf" ]]; then
    echo "Output directory for $platform already exists at $obj_dir; skipping." >&2
    continue
  fi

  bin_dir="$(sw_bin_dir "$platform")"
  mkdir -p "$bin_dir"

  set -x
  meson $reconf \
    -Dtarget="$platform" \
    -Dot_version="$OT_VERSION" \
    -Ddev_bin_dir="$bin_dir" \
    -Dhost_bin_dir="$HOST_BIN_DIR" \
    --cross-file="$CROSS_FILE" \
    "$obj_dir"
  { set +x; } 2>/dev/null
  purge_includes "$obj_dir"
done
