#! /bin/bash

scriptdir=`dirname $0`

if [ -n "$GITHUB_EVENT_PATH" ]; then
  export CI=true
fi

. "$scriptdir/ansi_color.sh"
disable_color

print_start() {
  COL="$ANSI_YELLOW"
  if [ "x$2" != "x" ]; then
    COL="$2"
  fi
  printf "${COL}${1}$ANSI_NOCOLOR\n"
}

gstart () {
  print_start "$@"
}
gend () {
  :
}

if [ -n "$TRAVIS" ]; then
  echo "INFO: set 'gstart' and 'gend' for TRAVIS"
  # This is a trimmed down copy of https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/bash/*
  travis_time_start() {
    # `date +%N` returns the date in nanoseconds. It is used as a replacement for $RANDOM, which is only available in bash.
    travis_timer_id=`date +%N`
    travis_start_time=`travis_nanoseconds`
    echo "travis_time:start:$travis_timer_id"
  }
  travis_time_finish() {
    travis_end_time=`travis_nanoseconds`
    local duration=$(($travis_end_time-$travis_start_time))
    echo "travis_time:end:$travis_timer_id:start=$travis_start_time,finish=$travis_end_time,duration=$duration"
  }

  if [ "$TRAVIS_OS_NAME" = "osx" ]; then
    travis_nanoseconds() {
      date -u '+%s000000000'
    }
  else
    travis_nanoseconds() {
      date -u '+%s%N'
    }
  fi

  gstart () {
    echo "travis_fold:start:group"
    travis_time_start
    print_start "$@"
  }

  gend () {
    travis_time_finish
    echo "travis_fold:end:group"
  }
else
  if [ -n "$CI" ]; then
    echo "INFO: set 'gstart' and 'gend' for CI"
    gstart () {
      printf '::group::'
      print_start "$@"
      SECONDS=0
    }

    gend () {
      duration=$SECONDS
      echo '::endgroup::'
      printf "${ANSI_GRAY}took $(($duration / 60)) min $(($duration % 60)) sec.${ANSI_NOCOLOR}\n"
    }
  fi
fi

echo "cliargs: $0 $@"

# Stop in case of error
set -e

ISGPL=false
ISSYNTH=true

# Transform long options to short ones
for arg in "$@"; do
  shift
  case "$arg" in
      "--color"|"-color")         set -- "$@" "-c";;
      "--backend"|"-backend")     set -- "$@" "-b";;
      "--pkg"|"-pkg")             set -- "$@" "-p";;
      "--gplcompat"|"-gplcompat") set -- "$@" "-g";;
      "--no-synth"|"-no-synth")   set -- "$@" "-s";;
    *) set -- "$@" "$arg"
  esac
done
# Parse args
while getopts ":b:p:cgs" opt; do
  case $opt in
    c) enable_color;;
    b) BACK=$OPTARG ;;
    p) PKG_NAME=$OPTARG;;
    g) ISGPL=true;;
    s) ISSYNTH=false;;
    \?) printf "$ANSI_RED[CI - args] Invalid option: -$OPTARG $ANSI_NOCOLOR\n" >&2
        exit 1 ;;
    :)  printf "$ANSI_RED[CI - args] Option -$OPTARG requires an argument. $ANSI_NOCOLOR\n" >&2
        exit 1 ;;
  esac
done
shift $((OPTIND -1))

#---

#
# Build command options
#

notag() {
  # No tag: use date + commit id
  echo "`git log -1 --date=short --pretty=format:%cd | sed 's/-//g'`-$PKG_SHA"
}

vertag() {
  if expr "$1" : 'v[0-9].*' > /dev/null; then
    # Check version defined in configure.
    cfgver=`grep "^ghdl_version=" configure | sed -e 's/.*"\(.*\)"/\1/'`
    if [ "x$1" != "xv$cfgver" ]; then
      printf "${ANSI_RED}Tag '$1' does not match configure 'ghdl_version' ($cfgver)!${ANSI_NOCOLOR}\n" 1>&2;
      exit 1
    fi
    # Remove leading 'v' in tags in the filenames.
    echo $1 | cut -c2-
  else
    # Regular tag (like snapshots), nothing to change.
    echo "$1"
  fi
}

buildCmdOpts () {
  BUILD_ARG="$1"

  # Get short commit SHA
  if [ -n "$TRAVIS_COMMIT" ]; then
    GIT_SHA="$TRAVIS_COMMIT"
  fi
  if [ -n "$GITHUB_SHA" ]; then
    GIT_SHA="$GITHUB_SHA"
  fi
  if [ -z "$GIT_SHA" ]; then
    GIT_SHA="`git rev-parse --verify HEAD`"
  fi
  PKG_SHA="`printf $GIT_SHA | cut -c1-10`"

  echo "TRAVIS_COMMIT: $TRAVIS_COMMIT"
  echo "TRAVIS_TAG: $TRAVIS_TAG"
  echo "GITHUB_SHA: $GITHUB_SHA"
  echo "GITHUB_REF: $GITHUB_REF"
  echo "GIT_SHA: $GIT_SHA"

  # Compute package name
  case "$GITHUB_REF" in
    *tags*)
      PKG_TAG="$(vertag "`echo "$GITHUB_REF" | sed 's#^refs/tags/\(.*\)#\1#g'`")"
    ;;
    *heads*|*pull*)
      PKG_TAG="`notag`"
    ;;
    "")
      if [ -z "$TRAVIS_TAG" ]; then
        PKG_TAG="`notag`"
      else
        PKG_TAG="`vertag "$TRAVIS_TAG"`"
      fi
    ;;
    *)
      PKG_TAG="$GITHUB_REF"
    ;;
  esac

  echo "PKG_SHA: $PKG_SHA"
  echo "PKG_TAG: $PKG_TAG"

  # Extract from BUILD_ARG
  IFS='+' read -ra REFS <<< "$BUILD_ARG"
  DDIST=${REFS[0]}  # Linux distro (eg: ubuntuXX, fedoraXX)
  DBACK=${REFS[1]}  # Build/backend (eg: mcode, llvm)

  PKG_NAME="ghdl-${PKG_TAG}-${DDIST}-${DBACK}"
  BUILD_CMD_OPTS="$ENABLECOLOR -b $DBACK"

  if [ "x$ISGPL" = "xtrue" ]; then
      BUILD_CMD_OPTS="$BUILD_CMD_OPTS --gplcompat"
      PKG_NAME="${PKG_NAME}-gplcompat"
      DEXT="-gplcompat"
  fi
  export BUILD_CMD_OPTS="${BUILD_CMD_OPTS} -p $PKG_NAME"

  GHDL_IMAGE_TAG="`echo $BUILD_ARG | sed -e 's/+/-/g'`"
  BUILD_IMAGE_TAG="$GHDL_IMAGE_TAG"

  case $BUILD_ARG in
    *gcc*)
      BUILD_IMAGE_TAG="`echo $GHDL_IMAGE_TAG | sed 's#\(.*\)-gcc.*#\1-gcc#g'`"
    ;;
  esac

  GHDL_IMAGE_TAG="${GHDL_IMAGE_TAG}$DEXT"
}

run_cmd() {
  echo "$@"
  "$@"
}

#
# Build ghdl
#

build () {
  rm -f build_ok

  #--- Env

  gstart "[GHDL - build] Environment"
  env
  gend

  #--- GPL: gpl-ize sources

  if [ "$ISGPL" = "true" ]; then
      GPLDIR="${PKG_NAME}.src"
      gstart "[GHDL - build] create GPL source package (${ANSI_CYAN}${GPLDIR}.tgz${ANSI_NOCOLOR})"
      files=`echo *`
      make -f Makefile.in srcdir=. clean-pure-gpl
      mkdir "$GPLDIR"
      cp -pdrl $files "$GPLDIR"
      tar -zcf "${GPLDIR}.tgz" "$GPLDIR"
      CONFIG_OPTS+=' --enable-gplcompat'
      gend
  fi

  #--- Configure

  CDIR=`pwd`
  INSTALL_DIR="$CDIR/install-$BACK"
  mkdir "$INSTALL_DIR"
  mkdir "build-$BACK"
  cd "build-$BACK"

  if [ "x$ISSYNTH" = "xfalse" ]; then
    CONFIG_OPTS+=" --disable-synth"
  fi

  case "$BACK" in
      gcc*)
          gstart "[GHDL - build] Get gcc sources"
          gccURL="https://codeload.github.com/gcc-mirror/gcc/tar.gz/releases/${BACK}"
          echo "$gccURL"
          mkdir gcc-srcs
          curl -L "$gccURL" | tar -xz -C gcc-srcs --strip-components=1
          cd gcc-srcs
          sed -i.bak s/ftp:/http:/g ./contrib/download_prerequisites
          ./contrib/download_prerequisites
          cd ..
          gend

          gstart "[GHDL - build] Configure ghdl"
          run_cmd ../configure --with-gcc=gcc-srcs $CONFIG_OPTS
          gend
          gstart "[GHDL - build] Copy sources"
          make copy-sources
          mkdir gcc-objs; cd gcc-objs
          gend
          gstart "[GHDL - build] Configure gcc"
          run_cmd ../gcc-srcs/configure --enable-languages=c,vhdl --disable-bootstrap --disable-lto --disable-multilib --disable-libssp --disable-libgomp --disable-libquadmath "`gcc -v 2>&1 | grep -o -- --enable-default-pie`"
          gend
      ;;
      mcode)
          CXX=""
      ;;
      llvm)
          CXX="clang++"
          CONFIG_OPTS+=" --with-llvm-config CXX=$CXX"
      ;;
      llvm-3.5)
          CXX="clang++"
          CONFIG_OPTS+=" --with-llvm-config=llvm-config-3.5 CXX=$CXX"
      ;;
      llvm-*)
          llvmver=`echo $BACK | sed -e "s/llvm-//"`
          CXX="clang++-$llvmver"
          CONFIG_OPTS+=" --with-llvm-config=llvm-config-$llvmver CXX=$CXX"
      ;;
      *)
          printf "$ANSI_RED[GHDL - build] Unknown build $BACK $ANSI_NOCOLOR\n"
          exit 1;;
  esac

  if [ ! "`echo $BACK | grep gcc`" ]; then
      gstart "[GHDL - build] Configure"
      run_cmd ../configure $CONFIG_OPTS
      gend
  fi

  #--- make

  gstart "[GHDL - build] Make"
  set +e
  make LIB_CFLAGS="$LIB_CFLAGS" OPT_FLAGS="$OPT_FLAGS" -j`nproc` 2>make_err.log
  tail -1000 make_err.log
  set -e
  gend

  gstart "[GHDL - build] Install"
  make DESTDIR="$INSTALL_DIR" install
  cd ..
  gend

  if [ "`echo $BACK | grep gcc`" ]; then
      gstart "[GHDL - build] Make ghdllib"
      make DESTDIR="$INSTALL_DIR" ghdllib
      gend

      gstart "[GHDL - build] Install ghdllib"
      make DESTDIR="$INSTALL_DIR" install
      cd ..
      gend
  fi

  #--- package

  gstart "[GHDL - build] Create package ${ANSI_DARKCYAN}${PKG_NAME}.tgz"
  tar -zcvf "${PKG_NAME}.tgz" -C "$INSTALL_DIR/usr/local" .
  gend

  #--- build tools versions

  {
      make --version | grep 'Make'
      gnatls --version | grep 'GNATLS'
      gcc --version | grep 'gcc'
      if [ "$CXX" != "" ]; then
          $CXX --version | grep 'clang'
      fi
  } > BUILD_TOOLS

  #---

  printf "$ANSI_GREEN[GHDL - build] SUCCESSFUL${ANSI_NOCOLOR}\n"
  touch build_ok
}

#
# Build ghdl/ghdl image
#

build_img_ghdl() {
    gstart "[DOCKER - build] ghdl/ghdl:${GHDL_IMAGE_TAG}" "$ANSI_BLUE"
    docker build -t ghdl/ghdl:$GHDL_IMAGE_TAG . -f-<<EOF
FROM ghdl/run:$BUILD_IMAGE_TAG
ADD `ls | grep -v '\.src\.' | grep '^ghdl.*\.tgz'` /usr/local
EOF
    gend
}

#
# Full CI run
#

ci_run () {
  if [ "x$TASK" = "x" ]; then
    if [ "x$1" = "x" ]; then
      printf "${ANSI_RED}TASK not defined${ANSI_NOCOLOR}\n"
      exit 1
    else
      TASK="$1"
    fi
  fi

  gstart "[CI] git fetch --unshallow" "$ANSI_BLUE"
  # The command 'git describe' (used for version) needs the history. Get it.
  # But the following command fails if the repository is complete.
  git fetch --unshallow || true
  gend

  # Get build command options
  gstart "[CI] Get build command options" "$ANSI_BLUE"
  buildCmdOpts "$TASK"
  echo "build cmd: $BUILD_CMD_OPTS"
  gend

  # Build

  RUN="docker run --rm -t -e CI -e TRAVIS -v `pwd`:/work -w /work"
  if [ "x$IS_MACOS" = "xtrue" ]; then
      export CPATH="$CPATH:$(xcrun --show-sdk-path)/usr/include"
      export PATH="$PATH:$(brew --prefix llvm)/bin"
      CC=clang bash -c "${scriptdir}/ci-run.sh $BUILD_CMD_OPTS build"
  else
      # Assume linux

      gstart "[CI] Docker pull ghdl/build:$BUILD_IMAGE_TAG" "$ANSI_BLUE"
      docker pull ghdl/build:$BUILD_IMAGE_TAG
      gend

      printf "$ANSI_BLUE[CI] Build ghdl in docker image ghdl/build:$BUILD_IMAGE_TAG\n"
      $RUN \
        -e GHDL_VER_DESC="$(git describe --dirty)" \
        -e GHDL_VER_REF="$(git rev-parse --abbrev-ref HEAD)@${BUILD_IMAGE_TAG}" \
        -e GHDL_VER_HASH="$(git rev-parse HEAD)" \
        -e CONFIG_OPTS="$CONFIG_OPTS" \
        ghdl/build:"$BUILD_IMAGE_TAG" \
        bash -c "${scriptdir}/ci-run.sh $BUILD_CMD_OPTS build"
  fi

  if [ ! -f build_ok ]; then
      printf "${ANSI_RED}[GHDL - build] FAILED${ANSI_NOCOLOR}\n"
      exit 1
  fi

  # Test

  if [ "x$IS_MACOS" = "xtrue" ]; then
      pip3 install -r testsuite/requirements.txt
      CC=clang \
      prefix="$(pwd)/install-$(echo "$TASK" | cut -d+ -f2)/usr/local" \
      ./testsuite/testsuite.sh
  else
      # Build ghdl/ghdl:$GHDL_IMAGE_TAG image
      build_img_ghdl

      tests="sanity"

      case "$GHDL_IMAGE_TAG" in
        *ubuntu20*|*buster*)
          GHDL_TEST_IMAGE="test:$GHDL_IMAGE_TAG-py"
          gstart "[CI] Docker build $GHDL_TEST_IMAGE" "$ANSI_BLUE"
          docker build -t "$GHDL_TEST_IMAGE" . -f- <<-EOF
# syntax=docker/dockerfile:experimental
FROM ghdl/ghdl:$GHDL_IMAGE_TAG
RUN apt update -qq && apt install -y python3 python3-pip
RUN --mount=type=bind,src=./,target=/tmp/ghdl/ \
  pip3 install -r /tmp/ghdl/testsuite/requirements.txt
EOF
          gend
          tests+=" pyunit"
        ;;
        *)
          GHDL_TEST_IMAGE="ghdl/ghdl:$GHDL_IMAGE_TAG"
        ;;
      esac

      if [ "x$ISGPL" != "xtrue" ]; then
        tests+=" gna"
      fi

      tests+=" vests"

      if [ "x$ISSYNTH" = "xtrue" ]; then
        tests+=" synth"
      fi

      tests+=" vpi"

      # Run tests in docker container
      $RUN "$GHDL_TEST_IMAGE" bash -c "GHDL=ghdl ./testsuite/testsuite.sh $tests"
  fi

  if [ ! -f testsuite/test_ok ]; then
      printf "${ANSI_RED}[GHDL - test] FAILED${ANSI_NOCOLOR}\n"
      exit 1
  fi
}

#---

echo "command: $0 $@"

unset IS_MACOS
if [ "$GITHUB_OS" = "macOS" ] || [ "$TRAVIS_OS_NAME" = "osx" ]; then
  IS_MACOS="true"
fi

case "$1" in
  build)
    build
  ;;
  *)
    ci_run
  ;;
esac
