#!/usr/bin/env bash
#===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===----------------------------------------------------------------------===##

set -ex
set -o pipefail

PROGNAME="$(basename "${0}")"

function usage() {
cat <<EOF
Usage:
${PROGNAME} [options] <BUILDER>

[-h|--help]         Display this help and exit.

--llvm-root <DIR>   Path to the root of the LLVM monorepo. By default, we try
                    to figure it out based on the current working directory.

--build-dir <DIR>   The directory to use for building the library. By default,
                    this is '<llvm-root>/build/<builder>'.

--osx-roots <DIR>   Path to pre-downloaded macOS dylibs. By default, we download
                    them from Green Dragon. This is only relevant at all when
                    running back-deployment testing if one wants to override
                    the old dylibs we use to run the tests with different ones.
EOF
}

while [[ $# -gt 0 ]]; do
    case ${1} in
        -h|--help)
            usage
            exit 0
            ;;
        --llvm-root)
            MONOREPO_ROOT="${2}"
            shift; shift
            ;;
        --build-dir)
            BUILD_DIR="${2}"
            shift; shift
            ;;
        --osx-roots)
            OSX_ROOTS="${2}"
            shift; shift
            ;;
        *)
            BUILDER="${1}"
            shift
            ;;
    esac
done

MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}"
BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build/${BUILDER}}"
INSTALL_DIR="${BUILD_DIR}/install"

# On macOS, fall back to using the Ninja provided with Xcode if no other Ninja can be found.
if which ninja &>/dev/null; then
    NINJA="$(which ninja)"
else
    NINJA="$(xcrun --find ninja)"
fi

function clean() {
    rm -rf "${BUILD_DIR}"
}

function generate-cmake() {
    echo "--- Generating CMake"
    cmake -S "${MONOREPO_ROOT}/llvm" \
          -B "${BUILD_DIR}" \
          -GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}" \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
          -DLLVM_ENABLE_PROJECTS="libcxx;libunwind;libcxxabi" \
          -DLLVM_LIT_ARGS="-sv --show-unsupported --xunit-xml-output test-results.xml" \
          -DLIBCXX_CXX_ABI=libcxxabi \
          "${@}"
}

function check-cxx-cxxabi() {
    echo "--- Installing libc++ and libc++abi to a fake location"
    ${NINJA} -vC "${BUILD_DIR}" install-cxx install-cxxabi

    echo "+++ Running the libc++ tests"
    ${NINJA} -vC "${BUILD_DIR}" check-cxx

    echo "+++ Running the libc++abi tests"
    ${NINJA} -vC "${BUILD_DIR}" check-cxxabi
}

# TODO: The goal is to test this against all configurations. We should also move
#       this to the Lit test suite instead of being a separate CMake target.
function check-abi-list() {
    echo "+++ Running the libc++ ABI list test"
    ${NINJA} -vC "${BUILD_DIR}" check-cxx-abilist || (
        echo "+++ Generating the libc++ ABI list after failed check"
        ${NINJA} -vC "${BUILD_DIR}" generate-cxx-abilist
        false
    )
}

function check-cxx-benchmarks() {
    echo "--- Running the benchmarks"
    ${NINJA} -vC "${BUILD_DIR}" check-cxx-benchmarks
}

# Print the version of a few tools to aid diagnostics in some cases
cmake --version
${NINJA} --version

case "${BUILDER}" in
check-format)
    clean
    echo "+++ Checking formatting"
    # We need to set --extensions so that clang-format checks extensionless files.
    mkdir -p ${BUILD_DIR}
    git-clang-format \
        --binary /usr/bin/clang-format --diff \
        --extensions ',h,hh,hpp,hxx,c,cc,cxx,cpp' HEAD~1 \
        -- \
            libcxx/{benchmarks,include,src,test} \
            libcxxabi/{fuzz,include,src,test} \
        | tee ${BUILD_DIR}/clang-format.patch
    # Check if the diff is empty, fail otherwise.
    ! grep -q '^--- a' ${BUILD_DIR}/clang-format.patch
;;
check-generated-output)
    clean
    echo "+++ Checking the output of the generated scripts"
    mkdir -p ${BUILD_DIR}
    python3 libcxx/utils/generate_feature_test_macro_components.py
    python3 libcxx/utils/generate_header_inclusion_tests.py
    python3 libcxx/utils/generate_header_tests.py
    git diff | tee ${BUILD_DIR}/generated_output.patch
    # Check if the diffs are empty, fail otherwise.
    ! grep -q '^--- a' ${BUILD_DIR}/generated_output.patch
;;
generic-cxx03)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx03.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-cxx11)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx11.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-cxx14)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx14.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-cxx17)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx17.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-cxx20)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx20.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-cxx2b)
    export CC=clang-tot
    export CXX=clang++-tot
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-cxx2b.cmake"
    check-cxx-cxxabi
    check-abi-list
;;
generic-noexceptions)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-noexceptions.cmake"
    check-cxx-cxxabi
;;
generic-static)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-static.cmake"
    check-cxx-cxxabi
;;
generic-32bit)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-32bits.cmake"
    check-cxx-cxxabi
;;
generic-gcc)
    export CC=gcc
    export CXX=g++
    clean
    generate-cmake
    check-cxx-cxxabi
;;
generic-asan)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-asan.cmake"
    check-cxx-cxxabi
;;
generic-msan)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-msan.cmake"
    check-cxx-cxxabi
;;
generic-tsan)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-tsan.cmake"
    check-cxx-cxxabi
;;
generic-ubsan)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-ubsan.cmake"
    check-cxx-cxxabi
;;
generic-with_llvm_unwinder)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -DLIBCXXABI_USE_LLVM_UNWINDER=ON
    check-cxx-cxxabi
;;
generic-singlethreaded)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-singlethreaded.cmake"
    check-cxx-cxxabi
;;
generic-no-debug)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-no-debug.cmake"
    check-cxx-cxxabi
;;
generic-no-filesystem)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-no-filesystem.cmake"
    check-cxx-cxxabi
;;
generic-no-random_device)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-no-random_device.cmake"
    check-cxx-cxxabi
;;
generic-no-localization)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-no-localization.cmake"
    check-cxx-cxxabi
;;
x86_64-apple-system)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Apple.cmake"
    check-cxx-cxxabi
;;
x86_64-apple-system-noexceptions)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Apple.cmake" \
                   -DLIBCXX_ENABLE_EXCEPTIONS=OFF \
                   -DLIBCXXABI_ENABLE_EXCEPTIONS=OFF
    check-cxx-cxxabi
;;
x86_64-apple-system-backdeployment-*)
    clean

    if [[ "${OSX_ROOTS}" == "" ]]; then
        echo "--- Downloading previous macOS dylibs"
        PREVIOUS_DYLIBS_URL="https://dl.dropboxusercontent.com/s/liu4fmc53qzlfly/libcxx-roots.tar.gz"
        OSX_ROOTS="${BUILD_DIR}/macos-roots"
        mkdir -p "${OSX_ROOTS}"
        curl "${PREVIOUS_DYLIBS_URL}" | tar -xz --strip-components=1 -C "${OSX_ROOTS}"
    fi

    DEPLOYMENT_TARGET="${BUILDER#x86_64-apple-system-backdeployment-}"

    # TODO: On Apple platforms, we never produce libc++abi.1.dylib, always libc++abi.dylib.
    #       Fix that in the build so that the tests stop searching for @rpath/libc++abi.1.dylib.
    cp "${OSX_ROOTS}/macOS/libc++abi/${DEPLOYMENT_TARGET}/libc++abi.dylib" \
       "${OSX_ROOTS}/macOS/libc++abi/${DEPLOYMENT_TARGET}/libc++abi.1.dylib"

    PARAMS="target_triple=x86_64-apple-macosx${DEPLOYMENT_TARGET}"
    PARAMS+=";cxx_runtime_root=${OSX_ROOTS}/macOS/libc++/${DEPLOYMENT_TARGET}"
    PARAMS+=";abi_runtime_root=${OSX_ROOTS}/macOS/libc++abi/${DEPLOYMENT_TARGET}"
    PARAMS+=";use_system_cxx_lib=True"

    export CC=clang
    export CXX=clang++
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Apple.cmake" \
                   -DLIBCXX_TEST_PARAMS="${PARAMS}" \
                   -DLIBCXXABI_TEST_PARAMS="${PARAMS}"

    check-cxx-cxxabi
;;
benchmarks)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake
    check-cxx-benchmarks
;;
documentation)
    export CC=clang
    export CXX=clang++
    clean
    generate-cmake -DLLVM_ENABLE_SPHINX=ON

    echo "+++ Generating documentation"
    ${NINJA} -vC "${BUILD_DIR}" docs-libcxx-html
;;
unified-standalone)
    export CC=clang
    export CXX=clang++

    clean

    echo "--- Generating CMake"
    cmake -S "${MONOREPO_ROOT}/libcxx/utils/ci/runtimes" \
          -B "${BUILD_DIR}" \
          -GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}" \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
          -DLLVM_ENABLE_PROJECTS="libcxx;libcxxabi;libunwind"

    check-cxx-cxxabi
;;
runtimes-build)
    export CC=clang
    export CXX=clang++

    clean

    echo "--- Generating CMake"
    cmake -S "${MONOREPO_ROOT}/llvm" \
          -B "${BUILD_DIR}" \
          -GNinja \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
          -DLLVM_ENABLE_PROJECTS="clang" \
          -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
          -DLLVM_RUNTIME_TARGETS="x86_64-unknown-linux-gnu"

    echo "+++ Running the libc++ and libc++abi tests"
    ${NINJA} -C "${BUILD_DIR}" check-runtimes

    echo "--- Installing libc++ and libc++abi to a fake location"
    ${NINJA} -C "${BUILD_DIR}" install-cxx install-cxxabi
;;
legacy-standalone)
    export CC=clang
    export CXX=clang++

    clean

    echo "--- Generating CMake"
    cmake -S "${MONOREPO_ROOT}/libcxx" \
          -B "${BUILD_DIR}/libcxx" \
          -GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}" \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
          -DLLVM_PATH="${MONOREPO_ROOT}/llvm" \
          -DLIBCXX_CXX_ABI=libcxxabi \
          -DLIBCXX_CXX_ABI_INCLUDE_PATHS="${MONOREPO_ROOT}/libcxxabi/include" \
          -DLIBCXX_CXX_ABI_LIBRARY_PATH="${BUILD_DIR}/libcxxabi/lib"

    cmake -S "${MONOREPO_ROOT}/libcxxabi" \
          -B "${BUILD_DIR}/libcxxabi" \
          -GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}" \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
          -DLLVM_PATH="${MONOREPO_ROOT}/llvm" \
          -DLIBCXXABI_LIBCXX_PATH="${MONOREPO_ROOT}/libcxx" \
          -DLIBCXXABI_LIBCXX_INCLUDES="${BUILD_DIR}/libcxx/include/c++/v1" \
          -DLIBCXXABI_LIBCXX_LIBRARY_PATH="${BUILD_DIR}/libcxx/lib"

    echo "+++ Generating libc++ headers"
    ${NINJA} -vC "${BUILD_DIR}/libcxx" generate-cxx-headers

    echo "+++ Building libc++abi"
    ${NINJA} -vC "${BUILD_DIR}/libcxxabi" cxxabi

    echo "+++ Building libc++"
    ${NINJA} -vC "${BUILD_DIR}/libcxx" cxx

    echo "+++ Running the libc++ tests"
    ${NINJA} -vC "${BUILD_DIR}/libcxx" check-cxx

    echo "+++ Running the libc++abi tests"
    ${NINJA} -vC "${BUILD_DIR}/libcxxabi" check-cxxabi
;;
aarch64)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/AArch64.cmake"
    check-cxx-cxxabi
;;
aarch64-noexceptions)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/AArch64.cmake" \
    -DLIBCXX_ENABLE_EXCEPTIONS=OFF \
    -DLIBCXXABI_ENABLE_EXCEPTIONS=OFF
    check-cxx-cxxabi
;;
# Aka Armv8 32 bit
armv8)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Armv8Arm.cmake"
    check-cxx-cxxabi
;;
armv8-noexceptions)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Armv8Thumb-noexceptions.cmake"
    check-cxx-cxxabi
;;
# Armv7 32 bit. One building Arm only one Thumb only code.
armv7)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Armv7Arm.cmake"
    check-cxx-cxxabi
;;
armv7-noexceptions)
    clean
    generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Armv7Thumb-noexceptions.cmake"
    check-cxx-cxxabi
;;
generic-win)
    clean
    # TODO: The CI runner doesn't have bash in the path currently, and it's
    # needed for running tests. Once it's available out of the box, remove this.
    export PATH="$PATH:/c/Program Files/Git/usr/bin"

    # TODO: Clang-cl in MSVC configurations don't have access to compiler_rt
    # builtins helpers for int128 division. See
    # https://reviews.llvm.org/D91139#2429595 for a comment about longterm
    # intent for handling the issue. In the meantime, define
    # -D_LIBCPP_HAS_NO_INT128 (both when building the library itself and
    # when building tests) to allow enabling filesystem for running tests,
    # even if it uses a non-permanent ABI.

    # TODO: The CI runner currently uses Clang 11, which doesn't implicitly
    # link in oldnames.lib (which is needed for some tests) when compiling
    # with the plain "clang" driver, as the tests do (as opposed to using
    # the "clang-cl" driver for compiling). When the CI runner runs
    # Clang 12, the "-loldnames" option can be dropped.

    # TODO: Currently, building with the experimental library breaks running
    # tests (the test linking look for the c++experimental library with the
    # wrong name, and the statically linked c++experimental can't be linked
    # correctly when libc++ visibility attributes indicate dllimport linkage
    # anyway), thus just disable the experimental library. Remove this
    # setting when cmake and the test driver does the right thing automatically.

    echo "--- Generating CMake"
    cmake -S "${MONOREPO_ROOT}/libcxx" \
          -B "${BUILD_DIR}" \
          -GNinja -DCMAKE_MAKE_PROGRAM="${NINJA}" \
          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
          -DCMAKE_C_COMPILER=clang-cl \
          -DCMAKE_CXX_COMPILER=clang-cl \
          -DLLVM_LIT_ARGS="-sv --show-unsupported --xunit-xml-output test-results.xml" \
          -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=NO \
          -DLIBCXX_ENABLE_FILESYSTEM=YES \
          -DCMAKE_CXX_FLAGS="-D_LIBCPP_HAS_NO_INT128" \
          -DLIBCXX_TEST_COMPILER_FLAGS="-D_LIBCPP_HAS_NO_INT128" \
          -DLIBCXX_TEST_LINKER_FLAGS="-loldnames"
    echo "+++ Running the libc++ tests"
    ${NINJA} -vC "${BUILD_DIR}" check-cxx
;;
*)
    echo "${BUILDER} is not a known configuration"
    exit 1
;;
esac
