#!/usr/bin/env bash
#===- llvm/utils/docker/scripts/build_install_llvm.sh ---------------------===//
#
#                     The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
#===-----------------------------------------------------------------------===//

set -e

function show_usage() {
  cat << EOF
Usage: build_install_llvm.sh [options] -- [cmake-args]

Checkout svn sources and run cmake with the specified arguments. Used
inside docker container.
Passes additional -DCMAKE_INSTALL_PREFIX and archives the contents of
the directory to /tmp/clang.tar.gz.

Available options:
  -h|--help           show this help message
  -b|--branch         svn branch to checkout, i.e. 'trunk',
                      'branches/release_40'
                      (default: 'trunk')
  -r|--revision       svn revision to checkout
  -c|--cherrypick     revision to cherry-pick. Can be specified multiple times.
                      Cherry-picks are performed in the sorted order using the
                      following command:
                      'svn patch <(svn diff -c \$rev)'.
  -p|--llvm-project   name of an svn project to checkout. Will also add the
                      project to a list LLVM_ENABLE_PROJECTS, passed to CMake.
                      For clang, please use 'clang', not 'cfe'.
                      Project 'llvm' is always included and ignored, if
                      specified.
                      Can be specified multiple times.
  -i|--install-target name of a cmake install target to build and include in
                      the resulting archive. Can be specified multiple times.
Required options: At least one --install-target.

All options after '--' are passed to CMake invocation.
EOF
}

LLVM_SVN_REV=""
CHERRYPICKS=""
LLVM_BRANCH=""
CMAKE_ARGS=""
CMAKE_INSTALL_TARGETS=""
# We always checkout llvm
LLVM_PROJECTS="llvm"
CMAKE_LLVM_ENABLE_PROJECTS=""
CLANG_TOOLS_EXTRA_ENABLED=0

function contains_project() {
  local TARGET_PROJ="$1"
  local PROJ
  for PROJ in $LLVM_PROJECTS; do
    if [ "$PROJ" == "$TARGET_PROJ" ]; then
      return 0
    fi
  done
  return 1
}

function append_project() {
  local PROJ="$1"

  LLVM_PROJECTS="$LLVM_PROJECTS $PROJ"
  if [ "$CMAKE_LLVM_ENABLE_PROJECTS" != "" ]; then
    CMAKE_LLVM_ENABLE_PROJECTS="$CMAKE_LLVM_ENABLE_PROJECTS;$PROJ"
  else
    CMAKE_LLVM_ENABLE_PROJECTS="$PROJ"
  fi
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -r|--revision)
      shift
      LLVM_SVN_REV="$1"
      shift
      ;;
    -c|--cherrypick)
      shift
      CHERRYPICKS="$CHERRYPICKS $1"
      shift
      ;;
    -b|--branch)
      shift
      LLVM_BRANCH="$1"
      shift
      ;;
    -p|--llvm-project)
      shift
      PROJ="$1"
      shift

      if [ "$PROJ" == "cfe" ]; then
        PROJ="clang"
      fi

      if [ "$PROJ" == "clang-tools-extra" ]; then
        if [ $CLANG_TOOLS_EXTRA_ENABLED -ne 0 ]; then
          echo "Project 'clang-tools-extra' is already enabled, ignoring extra occurences."
        else
          CLANG_TOOLS_EXTRA_ENABLED=1
        fi

        continue
      fi

      if ! contains_project "$PROJ" ; then
        append_project "$PROJ"
      else
        echo "Project '$PROJ' is already enabled, ignoring extra occurences."
      fi
      ;;
    -i|--install-target)
      shift
      CMAKE_INSTALL_TARGETS="$CMAKE_INSTALL_TARGETS $1"
      shift
      ;;
    --)
      shift
      CMAKE_ARGS="$*"
      shift $#
      ;;
    -h|--help)
      show_usage
      exit 0
      ;;
    *)
      echo "Unknown option: $1"
      exit 1
  esac
done

if [ "$CMAKE_INSTALL_TARGETS" == "" ]; then
  echo "No install targets. Please pass one or more --install-target."
  exit 1
fi

if [ $CLANG_TOOLS_EXTRA_ENABLED -ne 0 ]; then
  if ! contains_project "clang"; then
    echo "Project 'clang-tools-extra' was enabled without 'clang'."
    echo "Adding 'clang' to a list of projects."

    append_project "clang"
  fi
fi

if [ "$LLVM_BRANCH" == "" ]; then
  LLVM_BRANCH="trunk"
fi

if [ "$LLVM_SVN_REV" != "" ]; then
  SVN_REV_ARG="-r$LLVM_SVN_REV"
  echo "Checking out svn revision r$LLVM_SVN_REV."
else
  SVN_REV_ARG=""
  echo "Checking out latest svn revision."
fi

# Sort cherrypicks and remove duplicates.
CHERRYPICKS="$(echo "$CHERRYPICKS" | xargs -n1 | sort | uniq | xargs)"

function apply_cherrypicks() {
  local CHECKOUT_DIR="$1"

  [ "$CHERRYPICKS" == "" ] || echo "Applying cherrypicks"
  pushd "$CHECKOUT_DIR"

  # This function is always called on a sorted list of cherrypicks.
  for CHERRY_REV in $CHERRYPICKS; do
    echo "Cherry-picking r$CHERRY_REV into $CHECKOUT_DIR"

    local PATCH_FILE="$(mktemp)"
    svn diff -c $CHERRY_REV > "$PATCH_FILE"
    svn patch "$PATCH_FILE"
    rm "$PATCH_FILE"
  done

  popd
}

CLANG_BUILD_DIR=/tmp/clang-build
CLANG_INSTALL_DIR=/tmp/clang-install

mkdir "$CLANG_BUILD_DIR"

# Get the sources from svn.
echo "Checking out sources from svn"
mkdir "$CLANG_BUILD_DIR/src"
for LLVM_PROJECT in $LLVM_PROJECTS; do
  if [ "$LLVM_PROJECT" == "clang" ]; then
    SVN_PROJECT="cfe"
  else
    SVN_PROJECT="$LLVM_PROJECT"
  fi

  echo "Checking out https://llvm.org/svn/llvm-project/$SVN_PROJECT to $CLANG_BUILD_DIR/src/$LLVM_PROJECT"
  svn co -q $SVN_REV_ARG \
    "https://llvm.org/svn/llvm-project/$SVN_PROJECT/$LLVM_BRANCH" \
    "$CLANG_BUILD_DIR/src/$LLVM_PROJECT"

  # We apply cherrypicks to all repositories regardless of whether the revision
  # changes this repository or not. For repositories not affected by the
  # cherrypick, applying the cherrypick is a no-op.
  apply_cherrypicks "$CLANG_BUILD_DIR/src/$LLVM_PROJECT"
done

if [ $CLANG_TOOLS_EXTRA_ENABLED -ne 0 ]; then
  echo "Checking out https://llvm.org/svn/llvm-project/clang-tools-extra to $CLANG_BUILD_DIR/src/clang/tools/extra"
  svn co -q $SVN_REV_ARG \
    "https://llvm.org/svn/llvm-project/clang-tools-extra/$LLVM_BRANCH" \
    "$CLANG_BUILD_DIR/src/clang/tools/extra"

  apply_cherrypicks "$CLANG_BUILD_DIR/src/clang/tools/extra"
fi

CHECKSUMS_FILE="/tmp/checksums/checksums.txt"

if [ -f "$CHECKSUMS_FILE" ]; then
  echo "Validating checksums for LLVM checkout..."
  python "$(dirname $0)/llvm_checksum/llvm_checksum.py" -c "$CHECKSUMS_FILE" \
    --partial --multi_dir "$CLANG_BUILD_DIR/src"
else
  echo "Skipping checksumming checks..."
fi

mkdir "$CLANG_BUILD_DIR/build"
pushd "$CLANG_BUILD_DIR/build"

# Run the build as specified in the build arguments.
echo "Running build"
cmake -GNinja \
  -DCMAKE_INSTALL_PREFIX="$CLANG_INSTALL_DIR" \
  -DLLVM_ENABLE_PROJECTS="$CMAKE_LLVM_ENABLE_PROJECTS" \
  $CMAKE_ARGS \
  "$CLANG_BUILD_DIR/src/llvm"
ninja $CMAKE_INSTALL_TARGETS

popd

# Pack the installed clang into an archive.
echo "Archiving clang installation to /tmp/clang.tar.gz"
cd "$CLANG_INSTALL_DIR"
tar -czf /tmp/clang.tar.gz *

# Cleanup.
rm -rf "$CLANG_BUILD_DIR" "$CLANG_INSTALL_DIR"

echo "Done"