#!/bin/bash

# Common code to build a host image on GCE

# INTERNAL_extra_source may be set to a directory containing the source for
# extra package to build.

# INTERNAL_IP can be set to --internal-ip run on a GCE instance
# The instance will need --scope compute-rw

source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
DIR="${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm"

# ARM-board options

DEFINE_boolean arm false "Build on an ARM board"
DEFINE_string arm_instance "" "IP address or DNS name of an ARM system to do the secondary build"
DEFINE_string arm_user "vsoc-01" "User to invoke on the ARM system"

# Docker options

DEFINE_boolean docker false "Build inside docker"
DEFINE_boolean docker_persistent true "Build inside a privileged, persistent container (faster for iterative development)"
DEFINE_string docker_arch "$(uname -m)" "Target architectre"
DEFINE_boolean docker_build_image true "When --noreuse is specified, this flag controls building the docker image (else we assume it was built and reuse it)"
DEFINE_string docker_image "docker_vmm" "Name of docker image to build"
DEFINE_string docker_container "docker_vmm" "Name of docker container to create"
DEFINE_string docker_source "" "Path to sources checked out using manifest"
DEFINE_string docker_working "" "Path to working directory"
DEFINE_string docker_output "" "Output directory (when --docker is specified)"
DEFINE_string docker_user "${USER}" "Docker-container user"
DEFINE_string docker_uid "${UID}" "Docker-container user ID"

# GCE options

DEFINE_boolean gce false "Build on a GCE instance"
DEFINE_string gce_project "$(gcloud config get-value project)" "Project to use" "p"
DEFINE_string gce_source_image_family debian-10 "Image familty to use as the base" "s"
DEFINE_string gce_source_image_project debian-cloud "Project holding the base image" "m"
DEFINE_string gce_instance "${USER}-build" "Instance name to create for the build" "i"
DEFINE_string gce_user cuttlefish_crosvm_builder "User name to use on GCE when doing the build"
DEFINE_string gce_zone "$(gcloud config get-value compute/zone)" "Zone to use" "z"

# Common options

DEFINE_string manifest "" "Path to custom manifest to use for the build"
DEFINE_boolean reuse false "Set to true to reuse a previously-set-up instance."
DEFINE_boolean reuse_resync false "Reuse a previously-set-up instance, but clean and re-sync the sources. Overrides --reuse if both are specified."

SSH_FLAGS=(${INTERNAL_IP})

wait_for_instance() {
  alive=""
  while [[ -z "${alive}" ]]; do
    sleep 5
    alive="$(gcloud compute ssh "${SSH_FLAGS[@]}" "$@" -- uptime || true)"
  done
}

check_common_docker_options() {
  if [[ -z "${FLAGS_docker_image}" ]]; then
    echo Option --docker_image must not be empty 1>&1
    fail=1
  fi
  if [[ -z "${FLAGS_docker_container}" ]]; then
    echo Options --docker_container must not be empty 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_docker_user}" ]]; then
    echo Options --docker_user must not be empty 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_docker_uid}" ]]; then
    echo Options --docker_uid must not be empty 1>&2
    fail=1
  fi
  # Volume mapping are specified only when a container is created.  With
  # --reuse, an already-created persistent container is reused, which implies
  # that we cannot change the volume maps.  For non-persistent containers, we
  # use docker run, which creates and runs the continer in one step; in that
  # case, we must pass the same values for --docker_source and --docker_output
  # that we passed when we ran the non-persistent continer the first time.
  if [[ ${_reuse} -eq 1 && ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
    if [ -n "${FLAGS_docker_source}" ]; then
      echo Option --docker_source may not be specified with --reuse and --docker_persistent 1>&2
      fail=1
    fi
    if [ -n "${FLAGS_docker_working}" ]; then
      echo Option --docker_working may not be specified with --reuse and --docker_persistent 1>&2
      fail=1
    fi
    if [ -n "${FLAGS_docker_output}" ]; then
      echo Option --docker_output may not be specified with --reuse and --docker_persistent 1>&2
      fail=1
    fi
  fi
  if [[ "${fail}" -ne 0 ]]; then
    exit "${fail}"
  fi
}

build_locally_using_docker() {
  check_common_docker_options
  case "${FLAGS_docker_arch}" in
    aarch64) ;;
    x86_64) ;;
    *) echo Invalid value ${FLAGS_docker_arch} for --docker_arch 1>&2
      fail=1
      ;;
  esac
  if [[ "${fail}" -ne 0 ]]; then
    exit "${fail}"
  fi
  local -i _persistent=0
  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
    _persistent=1
  fi

  local -i _build_image=0
  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
    _build_image=1
  fi

  local _docker_output=""
  if [ -z "${FLAGS_docker_output}" ]; then
    _docker_output="${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/${FLAGS_docker_arch}-linux-gnu"
  else
    _docker_output="${FLAGS_docker_output}"
  fi

  local _temp="$(mktemp -d)"
  rsync -avR "${relative_source_files[@]/#/${DIR}/./}" "${_temp}"
  if [ -n "${custom_manifest}" ]; then
    cp "${custom_manifest}" "${_temp}"/custom.xml
  else
    touch "${_temp}"/custom.xml
  fi

  ${DIR}/rebuild-docker.sh "${FLAGS_docker_image}" \
                     "${FLAGS_docker_container}" \
                     "${FLAGS_docker_arch}" \
                     "${FLAGS_docker_user}" \
                     "${FLAGS_docker_uid}" \
                     "${_persistent}" \
                     "x${FLAGS_docker_source}" \
                     "x${FLAGS_docker_working}" \
                     "x${_docker_output}" \
                     "${_reuse}" \
                     "${_build_image}" \
                     "${_temp}/Dockerfile" \
                     "${_temp}" \
                     "${#docker_flags[@]}" "${docker_flags[@]}" \
                     "${#_prepare_source[@]}" "${_prepare_source[@]}"

  rm -rf "${_temp}"
}

function build_on_gce() {
  check_common_docker_options
  if [[ -z "${FLAGS_gce_instance}" ]]; then
    echo Must specify instance 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_gce_project}" ]]; then
    echo Must specify project 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_gce_zone}" ]]; then
    echo Must specify zone 1>&2
    fail=1
  fi
  if [[ "${fail}" -ne 0 ]]; then
    exit "${fail}"
  fi
  project_zone_flags=(--project="${FLAGS_gce_project}" --zone="${FLAGS_gce_zone}")
  if [ ${_reuse} -eq 0 ]; then
    delete_instances=("${FLAGS_gce_instance}")
    gcloud compute instances delete -q \
      "${delete_instances[@]}" \
      "${project_zone_flags[@]}" || \
        echo Instance does not exist
    gcloud compute images delete -q \
      "${delete_instances[@]/%/-image}" \
      --project "${FLAGS_gce_project}" || \
        echo Image does not exist
    gcloud compute disks delete -q \
      "${delete_instances[@]/%/-disk}" \
      "${project_zone_flags[@]}" || \
        echo Disk does not exist

    gcloud compute disks create \
      "${delete_instances[@]/%/-disk}" \
      "${project_zone_flags[@]}" \
      --image-project="${FLAGS_gce_source_image_project}" \
      --image-family="${FLAGS_gce_source_image_family}"
    gcloud compute images create \
      "${delete_instances[@]/%/-image}" \
      --source-disk "${delete_instances[@]/%/-disk}" \
      --project "${FLAGS_gce_project}" --source-disk-zone "${FLAGS_gce_zone}" \
      --licenses "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"
    gcloud compute instances create \
      "${delete_instances[@]}" \
      "${project_zone_flags[@]}" \
      --image "${delete_instances[@]/%/-image}" \
      --boot-disk-size=200GB \
      --machine-type=n1-standard-8 \
      --min-cpu-platform "Intel Skylake"

    wait_for_instance "${FLAGS_gce_instance}" "${project_zone_flags[@]}"

    # install docker
    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
        "${project_zone_flags[@]}" \
        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
        'curl -fsSL https://get.docker.com | /bin/bash'
    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
        "${project_zone_flags[@]}" \
        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
      sudo usermod -aG docker "${FLAGS_gce_user}"

    # beta for the --internal-ip flag that may be passed via SSH_FLAGS

    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
        "${project_zone_flags[@]}" \
        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
        mkdir -p '$PWD/docker $PWD/docker/source $PWD/docker/working $PWD/docker/output'

    tar czv -C "${DIR}" -f - "${relative_source_files[@]}" | \
      gcloud beta compute ssh "${SSH_FLAGS[@]}" \
          "${project_zone_flags[@]}" \
          "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
          'tar xzv -C ~/docker -f -'
    if [ -n "${custom_manifest}" ]; then
      gcloud beta compute scp "${SSH_FLAGS[@]}" \
        "${project_zone_flags[@]}" \
        "${custom_manifest}" \
        "${FLAGS_gce_user}@${FLAGS_gce_instance}:~/docker/custom.xml"
    else
      gcloud beta compute ssh "${SSH_FLAGS[@]}" \
        "${project_zone_flags[@]}" \
        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
        "touch ~/docker/custom.xml"
    fi
  fi

  local _status=$(gcloud compute instances list \
                  --project="${FLAGS_gce_project}" \
                  --zones="${FLAGS_gce_zone}" \
                  --filter="name=('${FLAGS_gce_instance}')" \
                  --format=flattened | awk '/status:/ {print $2}')
  if [ "${_status}" != "RUNNING" ] ; then
    echo "Instance ${FLAGS_gce_instance} is not running."
    exit 1;
  fi

  local -i _persistent=0
  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
    _persistent=1
  fi
  local -i _build_image=0
  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
    _build_image=1
  fi
  gcloud beta compute ssh "${SSH_FLAGS[@]}" \
      "${project_zone_flags[@]}" \
      "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
      ./docker/rebuild-docker.sh "${FLAGS_docker_image}" \
                       "${FLAGS_docker_container}" \
                       "${FLAGS_docker_arch}" \
                       '${USER}' \
                       '${UID}' \
                       "${_persistent}" \
                       'x$PWD/docker/source' \
                       'x$PWD/docker/working' \
                       'x$PWD/docker/output' \
                       "${_reuse}" \
                       "${_build_image}" \
                       '~/docker/Dockerfile' \
                       '~/docker/' \
                       "${#docker_flags[@]}" "${docker_flags[@]}" \
                       "${#_prepare_source[@]}" "${_prepare_source[@]}"

  gcloud beta compute ssh "${SSH_FLAGS[@]}" \
      "${project_zone_flags[@]}" \
      "${FLAGS_gce_user}@${FLAGS_gce_instance}" --command \
      'tar czv -C $PWD/docker/output -f - $(find $PWD/docker/output -printf "%P\n")' | \
    tar xzv -C ${DIR}/${FLAGS_docker_arch}-linux-gnu -f -

  gcloud compute disks describe \
    "${project_zone_flags[@]}" "${FLAGS_gce_instance}" | \
      grep ^sourceImage: > "${DIR}"/x86_64-linux-gnu/builder_image.txt
}

function build_on_arm_board() {
  check_common_docker_options
  if [[ "${FLAGS_docker_arch}" != "aarch64" ]]; then
    echo ARM board supports building only aarch64 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_arm_instance}" ]]; then
    echo Must specify IP address of ARM board 1>&2
    fail=1
  fi
  if [[ -z "${FLAGS_arm_user}" ]]; then
    echo Must specify a user account on ARM board 1>&2
    fail=1
  fi
  if [[ "${fail}" -ne 0 ]]; then
    exit "${fail}"
  fi
  if [[ "${_reuse}" -eq 0 ]]; then
    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
      rm -rf '$PWD/docker'
  fi
  rsync -avR -e ssh \
    "${relative_source_files[@]/#/${DIR}/./}" \
    "${FLAGS_arm_user}@${FLAGS_arm_instance}:~/docker/"

  if [ -n "${custom_manifest}" ]; then
    scp "${custom_manifest}" "${FLAGS_arm_user}@${FLAGS_arm_instance}":~/docker/custom.xml
  else
    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
      "touch ~/docker/custom.xml"
  fi

  local -i _persistent=0
  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
    _persistent=1
  fi
  local -i _build_image=0
  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
    _build_image=1
  fi
  ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
    mkdir -p '$PWD/docker/source' '$PWD/docker/working' '$PWD/docker/output'
  ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
    ./docker/rebuild-docker.sh "${FLAGS_docker_image}" \
                     "${FLAGS_docker_container}" \
                     "${FLAGS_docker_arch}" \
                     '${USER}' \
                     '${UID}' \
                     "${_persistent}" \
                     'x$PWD/docker/source' \
                     'x$PWD/docker/working' \
                     'x$PWD/docker/output' \
                     "${_reuse}" \
                     "${_build_image}" \
                     '~/docker/Dockerfile' \
                     '~/docker/' \
                     "${#docker_flags[@]}" "${docker_flags[@]}" \
                     "${#_prepare_source[@]}" "${_prepare_source[@]}"

  rsync -avR -e ssh "${FLAGS_arm_user}@${FLAGS_arm_instance}":docker/output/./ \
    "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/${FLAGS_docker_arch}-linux-gnu"
}

main() {
  set -o errexit
  set -x
  fail=0
  relative_source_files=("rebuild-docker.sh"
     "rebuild-internal.sh"
     "Dockerfile"
     "x86_64-linux-gnu/manifest.xml"
     "aarch64-linux-gnu/manifest.xml"
     "policy-inliner.sh"
     ".dockerignore")
  # These must match the definitions in the Dockerfile
  docker_flags=("-eSOURCE_DIR=/source" "-eWORKING_DIR=/working" "-eOUTPUT_DIR=/output" "-eTOOLS_DIR=/static/tools")

  if [[ $(( $((${FLAGS_gce}==${FLAGS_TRUE})) + $((${FLAGS_arm}==${FLAGS_TRUE})) + $((${FLAGS_docker}==${FLAGS_TRUE})) )) > 1 ]]; then
    echo You may specify only one of --gce, --docker, or --arm 1>&2
    exit 2
  fi

  if [[ -n "${FLAGS_manifest}" ]]; then
    if [[ ! -f "${FLAGS_manifest}" ]]; then
      echo custom manifest not found: ${FLAGS_manifest} 1>&1
      exit 2
    fi
    custom_manifest="${FLAGS_manifest}"
    docker_flags+=("-eCUSTOM_MANIFEST=/static/custom.xml")
  else
    custom_manifest="${DIR}/${FLAGS_docker_arch}-linux-gnu/manifest.xml"
    docker_flags+=("-eCUSTOM_MANIFEST=/static/${FLAGS_docker_arch}-linux-gnu/manifest.xml")
  fi
  local -a _prepare_source=(setup_env fetch_source);
  local -i _reuse=0
  if [[ ${FLAGS_reuse} -eq ${FLAGS_TRUE} ]]; then
    # neither install packages, nor sync sources; skip to building them
    _prepare_source=(setup_env)
    # unless you're setting up a non-persistent container and --docker_source is
    # the empty string; in this case, --reuse implies --reuse_resync
    if [[ "${FLAGS_docker_persistent}" -eq ${FLAGS_FALSE} && \
          -z "${FLAGS_docker_source}" ]]; then
      _prepare_source+=(resync_source)
    fi
    _reuse=1
  fi
  if [[ ${FLAGS_reuse_resync} -eq ${FLAGS_TRUE} ]]; then
    # do not install packages but clean and sync sources afresh
    _prepare_source=(setup_env resync_source);
    _reuse=1
  fi
  if [[ ${FLAGS_gce} -eq ${FLAGS_TRUE} ]]; then
    build_on_gce
    exit 0
    gcloud compute instances delete -q \
      "${project_zone_flags[@]}" \
      "${FLAGS_gce_instance}"
  fi
  if [[ ${FLAGS_arm} -eq ${FLAGS_TRUE} ]]; then
    build_on_arm_board
    exit 0
  fi
  if [[ ${FLAGS_docker} -eq ${FLAGS_TRUE} ]]; then
    build_locally_using_docker
    exit 0
  fi
}

FLAGS "$@" || exit 1
main "${FLAGS_ARGV[@]}"