#!/usr/bin/env bash
#**************************************************************************
#*                                                                        *
#*                                 OCaml                                  *
#*                                                                        *
#*                         Christophe Troestler                           *
#*                                                                        *
#*   Copyright 2015 Christophe Troestler                                  *
#*                                                                        *
#*   All rights reserved.  This file is distributed under the terms of    *
#*   the GNU Lesser General Public License version 2.1, with the          *
#*   special exception on linking described in the file LICENSE.          *
#*                                                                        *
#**************************************************************************

set -e

BUILD_PID=0

# This must correspond with the entry in appveyor.yml
CACHE_DIRECTORY=/cygdrive/c/projects/cache

MAKE=make

# The environment is too large for xargs!
unset ORIGINAL_PATH
unset __VSCMD_PREINIT_PATH

# There are some utilities on the AppVeyor runner which include mingw-w64
# runtime DLLs which we don't want to be available in the build.
export PATH="$(tr ':' '\n' <<<"$PATH" |
               grep -vxFf <(which -a libwinpthread-1.dll |
               xargs -r dirname) |
               paste -sd:)"
if which 'libwinpthread-1.dll' 2>/dev/null; then
  echo 'Failed to remove libwinpthread-1.dll from PATH'
  exit 1
fi

git config --global --add safe.directory '*'

function run {
  if [[ $1 = "--show" ]] ; then SHOW_CMD='true'; shift; else SHOW_CMD=''; fi
  NAME=$1
  shift
  echo "-=-=- $NAME -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
  if [[ -n $SHOW_CMD ]]; then (set -x; "$@"); else "$@"; fi
  CODE=$?
  if [[ $CODE -ne 0 ]] ; then
    echo "-=-=- $NAME failed! -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
    if [[ $BUILD_PID -ne 0 ]] ; then
      kill -KILL $BUILD_PID 2>/dev/null
      wait $BUILD_PID 2>/dev/null
    fi
    exit $CODE
  else
    echo "-=-=- End of $NAME -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
  fi
}

# Function: set_configuration
# Takes 3 arguments
# $1:the Windows port. Recognized values: mingw, msvc and msvc64
# $2: the prefix to use to install
function set_configuration {
  mkdir -p "$CACHE_DIRECTORY"

  local CACHE_KEY CACHE_FILE_PREFIX CACHE_FILE
  CACHE_KEY=$({ cat configure; uname; } | sha1sum | cut -c 1-7)
  CACHE_FILE_PREFIX="$CACHE_DIRECTORY/config.cache-$1"
  CACHE_FILE="$CACHE_FILE_PREFIX-$CACHE_KEY"

  args=('--cache-file' "$CACHE_FILE" \
        '--prefix' "$2/_opam" \
        '--docdir' "$2/_opam/doc/ocaml" \
        '--enable-ocamltest')

  case "$1" in
    cygwin*)
      args+=('--enable-native-toplevel');;
    mingw32)
      args+=('--host=i686-w64-mingw32');;
    mingw64)
      args+=('--host=x86_64-w64-mingw32' '--disable-stdlib-manpages' \
             '--enable-native-toplevel');;
    msvc32)
      args+=('--host=i686-pc-windows');;
    msvc64)
      args+=('--host=x86_64-pc-windows' '--enable-native-toplevel');;
  esac
  if [[ $RELOCATABLE = 'true' ]]; then
    args+=('--with-relative-libdir' \
           '--enable-runtime-search' '--enable-runtime-search-target=fallback')
  fi

  # Remove old configure cache if the configure script or the OS
  # have changed
  if [[ ! -f "$CACHE_FILE" ]] ; then
      rm -f -- "$CACHE_FILE_PREFIX"*
  fi

  echo './configure' "${args[@]@Q}"
  if ! ./configure "${args[@]}"; then
    # Remove configure cache if the script has failed
    rm -f -- "$CACHE_FILE"
    local failed
    ./configure "${args[@]}" || failed=$?
    if ((failed)) ; then cat config.log ; exit $failed ; fi
  fi

  cp "$CACHE_FILE" config.cache

#  FILE=$(pwd | cygpath -f - -m)/Makefile.config
#  run "Content of $FILE" cat Makefile.config
}

PARALLEL_URL='https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel'
APPVEYOR_BUILD_FOLDER=$(echo "$APPVEYOR_BUILD_FOLDER" | cygpath -f -)
FLEXDLLROOT="$PROGRAMFILES/flexdll"
export OPAMSWITCH="$OCAMLROOT"

if [[ $BOOTSTRAP_FLEXDLL = 'false' ]] ; then
  case "$PORT" in
    cygwin*)
      install_flexdll='false';;
    *)
      export PATH="$FLEXDLLROOT:$PATH"
      install_flexdll='true';;
  esac
else
  install_flexdll='false'
fi

case "$1" in
  install)
    mkdir -p "$CACHE_DIRECTORY"
    if [ ! -e "$CACHE_DIRECTORY/parallel-source" ] || \
       [ "$PARALLEL_URL" != "$(cat "$CACHE_DIRECTORY/parallel-source")" ] ; then
      # Download latest version directly from the repo
      curl -Ls $PARALLEL_URL -o "$CACHE_DIRECTORY/parallel"
      echo "$PARALLEL_URL" > "$CACHE_DIRECTORY/parallel-source"
    fi
    cp "$CACHE_DIRECTORY/parallel" /usr/bin
    chmod +x /usr/bin/parallel
    parallel --version
    if [[ $install_flexdll = 'true' ]] ; then
      mkdir -p "$FLEXDLLROOT"
      cd "$APPVEYOR_BUILD_FOLDER/../flexdll"
      # The objects are always built from the sources
      for f in flexdll.h flexlink.exe default*.manifest ; do
        cp "$f" "$FLEXDLLROOT/"
      done
    fi
    case "$PORT" in
      msvc*)
        echo 'eval $($APPVEYOR_BUILD_FOLDER/tools/msvs-promote-path)' \
          >> ~/.bash_profile
        ;;
    esac
    ;;
  test)
    FULL_BUILD_PREFIX="$APPVEYOR_BUILD_FOLDER/../$BUILD_PREFIX"
    run 'ocamlc.opt -version' "$FULL_BUILD_PREFIX-$PORT/ocamlc.opt" -version
    if [[ $PORT =~ mingw* ]] ; then
      run "Check runtime symbols" \
          "$FULL_BUILD_PREFIX-$PORT/tools/check-symbol-names" \
          $FULL_BUILD_PREFIX-$PORT/runtime/*.a \
          $FULL_BUILD_PREFIX-$PORT/otherlibs/*/lib*.a
    fi
    # Check that libwinpthread-1.dll is not linked
    cd "$FULL_BUILD_PREFIX-$PORT"
    find . -name \*.exe | xargs ldd > results
    winpthreads='^[[:blank:]]libwinpthread-[^.]\+\.dll =>'
    if grep -q "$winpthreads" results; then
      echo 'winpthreads is not being linked statically:'
      grep ':$\|'"$winpthreads" results | grep -B 1 "$winpthreads"
      exit 1
    fi
    rm -f results
    run_testsuite=true
    if [[ -n $APPVEYOR_PULL_REQUEST_NUMBER ]]; then
      API_URL="https://api.github.com/repos/$APPVEYOR_REPO_NAME/issues/$APPVEYOR_PULL_REQUEST_NUMBER"
      if curl --silent "$API_URL/labels" | grep -q 'CI: Skip testsuite'; then
        run_testsuite=false
      fi
    fi
    if $run_testsuite; then
      # The testsuite is too slow to run on AppVeyor in full. Run the dynlink
      # tests now (to include natdynlink)
      # GNU Parallel 20250122 introduced a somewhat dubious check on the
      # characters in $PWD and $OLDPWD - --unsafe disables these "checks"
      # (as would sed -i -e 's/PWD OLDPWD//' /usr/bin/parallel)
      export PARALLEL='--unsafe'
      run "test dynlink $PORT" \
          $MAKE -C "$FULL_BUILD_PREFIX-$PORT/testsuite" parallel-lib-dynlink
      # Now reconfigure ocamltest to run in bytecode-only mode
      sed -i '/native_/s/true/false/' \
             "$FULL_BUILD_PREFIX-$PORT/ocamltest/ocamltest_config.ml"
      $MAKE -C "$FULL_BUILD_PREFIX-$PORT" -j ocamltest ocamltest.opt
      # And run the entire testsuite, skipping all the native-code tests
      run "test $PORT" \
          make -C "$FULL_BUILD_PREFIX-$PORT/testsuite" SHOW_TIMINGS=1 all
    fi
    run "install $PORT" $MAKE -C "$FULL_BUILD_PREFIX-$PORT" install
    make -C "$FULL_BUILD_PREFIX-$PORT" INSTALL_MODE=clone install
    (
      cd "$OCAMLROOT"
      mv _opam destdir
      #ret="$PWD"
      #script="$PWD/ocaml-compiler-clone.sh"
      #cd "$(find $PWD/install -name _opam -type d)"
      mkdir -p "destdir/share/ocaml"
      cp "$FULL_BUILD_PREFIX-$PORT/config."{cache,status} 'destdir/share/ocaml/'
      cp "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" \
           'destdir/share/ocaml/clone'
      cd destdir
      sh "$FULL_BUILD_PREFIX-$PORT/ocaml-compiler-clone.sh" "$OCAMLROOT/_opam"
    )
    rm -rf "$OCAMLROOT"
    $MAKE -C "$FULL_BUILD_PREFIX-$PORT" OPAM_PACKAGE_NAME=ocaml-variants \
      INSTALL_MODE=opam install
    (
      cd "$FULL_BUILD_PREFIX-$PORT"
      export PATH="$FLEXDLLROOT:$PATH"
      opam init --cli=2.4 --bare --yes --disable-sandboxing --auto-setup \
                --cygwin-local-install
      # These commands intentionally run using opam's "default" CLI
      opam switch create "$OPAMSWITCH" --empty
      opam pin add --no-action --kind=path ocaml-variants .
      opam pin add --no-action flexdll flexdll
      opam install --yes flexdll winpthreads
      opam install --yes --assume-built ocaml-variants
      git checkout -- ocaml-variants.install
      rm -f config.cache ocaml-variants-fixup.sh ocaml-compiler-clone.sh
      opam exec -- ocamlc -v
    )
    run "test $PORT in prefix" \
      $MAKE -f Makefile.test -C "$FULL_BUILD_PREFIX-$PORT/testsuite/in_prefix" \
            -j test-in-prefix
    if [[ $PORT = 'msvc64' ]] ; then
      run "$MAKE check_all_arches" \
           $MAKE -C "$FULL_BUILD_PREFIX-$PORT" check_all_arches
      cd "$FULL_BUILD_PREFIX-$PORT"
      # Ensure that .gitignore is up-to-date - this will fail if any untracked
      # or altered files exist. We revert the change from the bootstrap (that
      # would have failed the build earlier if necessary)
      git checkout -- boot/ocamlc boot/ocamllex
      # Remove the FlexDLL sources placed earlier in the process
      rm -rf "flexdll-$FLEXDLL_VERSION"
      run --show "Check tree is tracked" test -z "$(git status --porcelain)"
      # check that the `distclean` target definitely cleans the tree
      run "$MAKE distclean" $MAKE distclean
      # Check the working tree is clean
      run --show "Check tree is tracked" test -z "$(git status --porcelain)"
      # Check that there are no ignored files
      run --show "Check tree is clean" \
        test -z "$(git ls-files --others -i --exclude-standard)"
    fi
    ;;
  *)
    cd "$APPVEYOR_BUILD_FOLDER/../$BUILD_PREFIX-$PORT"

    if [[ $PORT = 'msvc64' ]] ; then
      # Ensure that make distclean can be run from an empty tree
      run "$MAKE distclean" $MAKE distclean
    fi

    if [[ $install_flexdll = 'true' ]] ; then
      tar -xzf "$APPVEYOR_BUILD_FOLDER/flexdll.tar.gz"
      cd "flexdll-$FLEXDLL_VERSION"
      $MAKE MSVC_DETECT=0 CHAINS=${PORT%32} support
      cp -f *.obj "$FLEXDLLROOT/" 2>/dev/null || \
      cp -f *.o "$FLEXDLLROOT/"
      cd ..
    fi

    set_configuration "$PORT" "$OCAMLROOT"

    export TERM=ansi

    case "$BUILD_MODE" in
      world.opt)
        set -o pipefail
        # For an explanation of the sed command, see
        # https://github.com/appveyor/ci/issues/1824
        build="-C ../$BUILD_PREFIX-$PORT"
        script --quiet --return --command \
          "if ! $MAKE -j $build; then $MAKE $build; exit 1; fi" \
          "../$BUILD_PREFIX-$PORT/build.log" |
            sed --unbuffered \
                -e 's/\d027\[K//g' \
                -e 's/\d027\[m/\d027[0m/g' \
                -e 's/\d027\[01\([m;]\)/\d027[1\1/g'
        rm -f build.log;;
    steps)
      run "C deps: runtime" make -j64 -C runtime setup-depend
      run "C deps: win32unix" make -j64 -C otherlibs/win32unix setup-depend
      run "$MAKE world" $MAKE world
      run "$MAKE bootstrap" $MAKE bootstrap
      run "$MAKE opt" $MAKE opt
      run "$MAKE opt.opt" $MAKE opt.opt;;
    C)
      run "$MAKE world" $MAKE world
      run "$MAKE runtimeopt" $MAKE runtimeopt
      run "$MAKE -C otherlibs/systhreads libthreadsnat.lib" \
           $MAKE -C otherlibs/systhreads libthreadsnat.lib;;
    *)
      echo "Unrecognised build: $BUILD_MODE"
      exit 1
    esac

    echo DLL base addresses
    case "$PORT" in
      *32)
        ARG='-4';;
      *64)
        ARG='-8';;
    esac
    find "../$BUILD_PREFIX-$PORT" -type f \( -name \*.dll -o -name \*.so \) | \
      xargs rebase -i "$ARG"
    find "../$BUILD_PREFIX-$PORT" -type f \( -name \*.dll -o -name \*.so \) | \
      xargs ldd

    ;;
esac
