# vim: tabstop=4 shiftwidth=4 softtabstop=4

default:
    image: "archlinux/devel-tnl:latest"
    tags:
        - docker
    retry:
        max: 2
        when:
            - runner_system_failure

stages:
    - lint
    - build
    - build:cuda
    - build:gcc
    - build:clang
    - build:doc
    - deploy

# default flags for cmake
.default_cmake_flags_def: &default_cmake_flags
    # architectures
    WITH_OPENMP: "no"
    WITH_CUDA: "no"
    WITH_CUDA_ARCH: "auto"
    WITH_MPI: "no"
    # build targets
    BUILD_TESTS: "no"
    BUILD_MATRIX_TESTS: "no"
    WITH_COVERAGE: "yes"
    BUILD_DOC: "no"
    BUILD_BENCHMARKS: "no"
    BUILD_EXAMPLES: "no"
    BUILD_TOOLS: "no"
    BUILD_PYTHON: "no"

# base for OpenMP+MPI builds
.openmp_mpi:
    variables:
        WITH_OPENMP: "yes"
        WITH_MPI: "yes"

# base for Clang builds
.clang:
    stage: build:clang
    variables:
        CXX: clang++
        CC: clang
        CUDA_HOST_COMPILER: clang++

# template for build jobs
.build_template:
    stage: build:gcc
    # don't wait for jobs in previous stages to complete before starting this job
    needs: []
    script:
        # all cores including hyperthreading
#        - export NUM_CORES=$(grep "core id" /proc/cpuinfo | wc -l)
        # all pyhsical cores
        - export NUM_CORES=$(grep "core id" /proc/cpuinfo | sort -u | wc -l)
        # respect $NINJAFLAGS from the outer environment if it is set
        - if [[ ${NINJAFLAGS} == "" ]]; then
                export NINJAFLAGS="-j$NUM_CORES";
          fi
        - export CTEST_OUTPUT_ON_FAILURE=1
        - export CTEST_PARALLEL_LEVEL=4
        - export OMP_NUM_THREADS=4
        # running as root does not matter inside Docker containers
        - export OMPI_ALLOW_RUN_AS_ROOT=1
        - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1
        # fall back to arch 7.0 when the builder has no GPU to avoid building for all GPU architectures
        - if ! nvidia-smi --list-gpus > /dev/null; then
                WITH_CUDA_ARCH=7.0;
          fi
        - cmake -B "./builddir/$CI_JOB_NAME" -S . -G Ninja
                -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
                -DCMAKE_INSTALL_PREFIX="$(pwd)/install_prefix/$CI_JOB_NAME"
                -DWITH_OPENMP=${WITH_OPENMP}
                -DWITH_MPI=${WITH_MPI}
                -DWITH_CUDA=${WITH_CUDA}
                -DWITH_CUDA_ARCH=${WITH_CUDA_ARCH}
                -DBUILD_TESTS=${BUILD_TESTS}
                -DBUILD_MATRIX_TESTS=${BUILD_MATRIX_TESTS}
                -DBUILD_DOC=${BUILD_DOC}
                -DWITH_COVERAGE=${WITH_COVERAGE}
                -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS}
                -DBUILD_EXAMPLES=${BUILD_EXAMPLES}
                -DBUILD_TOOLS=${BUILD_TOOLS}
                -DBUILD_PYTHON=${BUILD_PYTHON}
                -DWITH_CI_FLAGS=yes
        # "install" implies the "all" target
        - ninja -C "./builddir/$CI_JOB_NAME" ${NINJAFLAGS} install
        - if [[ ${BUILD_TESTS} == "yes" ]] || [[ ${BUILD_MATRIX_TESTS} == "yes" ]]; then
                ninja -C "./builddir/$CI_JOB_NAME" test;
          fi
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Debug
    only:
        changes:
            - src/**/*.{h,hpp,cpp,cu}
            - "**/CMakeLists.txt"
            - cmake/*
            - .gitlab-ci.yml
    interruptible: true

# template for collecting code coverage
.coverage_template:
    after_script:
        - mkdir coverage_html
        - if [[ ${CXX} == "clang++" ]]; then
                GCOV_COMMAND="llvm-cov gcov";
          else
                GCOV_COMMAND="gcov";
          fi
        - gcovr --print-summary --html-details coverage_html/coverage.html --xml coverage.xml --xml-pretty --gcov-executable "${GCOV_COMMAND}" --exclude-unreachable-branches --root "${CI_PROJECT_DIR}" --filter "${CI_PROJECT_DIR}/src/TNL/"
    coverage: /^\s*lines:\s*\d+.\d+\%/
    artifacts:
        name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
        expire_in: 7 days
        reports:
            coverage_report:
                coverage_format: cobertura
                path: coverage.xml
            junit: "builddir/$CI_JOB_NAME/tests-report.xml"
        paths:
            - coverage_html/

# template for registering tests-report.xml as an artifact
.tests_report_template:
    artifacts:
        name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
        expire_in: 7 days
        reports:
            junit: "builddir/$CI_JOB_NAME/tests-report.xml"

# Dummy build job to ensure that a pipeline is created for a merge request, even
# when there were no changes.
dummy build job:
    stage: build
    script: echo "dummy"
    only:
        - merge_requests
    except:
        changes:
            # .build_template
            - src/**/*.{h,hpp,cpp,cu}
            - "**/CMakeLists.txt"
            - cmake/*
            - .gitlab-ci.yml
            # build documentation
            - Documentation/**/*

# Cuda builds are specified first because they take more time than host-only builds,
# which can be allocated on hosts whitout GPUs.
# Similarly, release builds are launched first to avoid the tail effect (they take
# significantly more time than debug builds).

cuda_tests_Debug:
    extends:
        - .build_template
        - .coverage_template
    stage: build:cuda
    tags:
        - docker
        - nvidia
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Debug
        BUILD_TESTS: "yes"

cuda_tests_Release:
    extends:
        - .build_template
        - .tests_report_template
    stage: build:cuda
    tags:
        - docker
        - nvidia
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Release
        BUILD_TESTS: "yes"


cuda_matrix_tests_Debug:
    extends:
        - .build_template
        - .coverage_template
    stage: build:cuda
    tags:
        - docker
        - nvidia
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Debug
        BUILD_MATRIX_TESTS: "yes"

cuda_matrix_tests_Release:
    extends:
        - .build_template
        - .tests_report_template
    stage: build:cuda
    tags:
        - docker
        - nvidia
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Release
        BUILD_MATRIX_TESTS: "yes"


cuda_nontests_Debug:
    extends: .build_template
    stage: build:cuda
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Debug
        BUILD_EXAMPLES: "yes"
        BUILD_BENCHMARKS: "yes"
        BUILD_TOOLS: "yes"
        BUILD_PYTHON: "yes"

cuda_nontests_Release:
    extends: .build_template
    stage: build:cuda
    variables:
        <<: *default_cmake_flags
        WITH_CUDA: "yes"
        BUILD_TYPE: Release
        BUILD_EXAMPLES: "yes"
        BUILD_BENCHMARKS: "yes"
        BUILD_TOOLS: "yes"
        BUILD_PYTHON: "yes"


cuda_mpi_tests_Debug:
    extends:
        - cuda_tests_Debug
        - .openmp_mpi

cuda_mpi_tests_Release:
    extends:
        - cuda_tests_Release
        - .openmp_mpi

cuda_mpi_matrix_tests_Debug:
    extends:
        - cuda_matrix_tests_Debug
        - .openmp_mpi

cuda_mpi_matrix_tests_Release:
    extends:
        - cuda_matrix_tests_Release
        - .openmp_mpi

cuda_mpi_nontests_Debug:
    extends:
        - cuda_nontests_Debug
        - .openmp_mpi

cuda_mpi_nontests_Release:
    extends:
        - cuda_nontests_Release
        - .openmp_mpi





default_tests_Debug:
    extends:
        - .build_template
        - .coverage_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Debug
        BUILD_TESTS: "yes"

default_tests_Release:
    extends:
        - .build_template
        - .tests_report_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Release
        BUILD_TESTS: "yes"

default_matrix_tests_Debug:
    extends:
        - .build_template
        - .coverage_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Debug
        BUILD_MATRIX_TESTS: "yes"

default_matrix_tests_Release:
    extends:
        - .build_template
        - .tests_report_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Release
        BUILD_MATRIX_TESTS: "yes"

default_nontests_Debug:
    extends: .build_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Debug
        BUILD_EXAMPLES: "yes"
        BUILD_BENCHMARKS: "yes"
        BUILD_TOOLS: "yes"
        BUILD_PYTHON: "yes"

default_nontests_Release:
    extends: .build_template
    variables:
        <<: *default_cmake_flags
        BUILD_TYPE: Release
        BUILD_EXAMPLES: "yes"
        BUILD_BENCHMARKS: "yes"
        BUILD_TOOLS: "yes"
        BUILD_PYTHON: "yes"


mpi_tests_Debug:
    extends:
        - default_tests_Debug
        - .openmp_mpi

mpi_tests_Release:
    extends:
        - default_tests_Release
        - .openmp_mpi

mpi_matrix_tests_Debug:
    extends:
        - default_matrix_tests_Debug
        - .openmp_mpi

mpi_matrix_tests_Release:
    extends:
        - default_matrix_tests_Release
        - .openmp_mpi

mpi_nontests_Debug:
    extends:
        - default_nontests_Debug
        - .openmp_mpi

mpi_nontests_Release:
    extends:
        - default_nontests_Release
        - .openmp_mpi


clang_tests_Debug:
    extends:
        - default_tests_Debug
        - .clang

clang_tests_Release:
    extends:
        - default_tests_Release
        - .clang

clang_matrix_tests_Debug:
    extends:
        - default_matrix_tests_Debug
        - .clang

clang_matrix_tests_Release:
    extends:
        - default_matrix_tests_Release
        - .clang

clang_nontests_Debug:
    extends:
        - default_nontests_Debug
        - .clang

clang_nontests_Release:
    extends:
        - default_nontests_Release
        - .clang


clang_mpi_tests_Debug:
    extends:
        - mpi_tests_Debug
        - .clang

clang_mpi_tests_Release:
    extends:
        - mpi_tests_Release
        - .clang

clang_mpi_matrix_tests_Debug:
    extends:
        - mpi_matrix_tests_Debug
        - .clang

clang_mpi_matrix_tests_Release:
    extends:
        - mpi_matrix_tests_Release
        - .clang

clang_mpi_nontests_Debug:
    extends:
        - mpi_nontests_Debug
        - .clang

clang_mpi_nontests_Release:
    extends:
        - mpi_nontests_Release
        - .clang




documentation examples:
    extends: .build_template
    stage: build:cuda
    tags:
        - docker
        - nvidia
    variables:
        <<: *default_cmake_flags
        WITH_OPENMP: "yes"
        WITH_CUDA: "yes"
        WITH_MPI: "yes"
        BUILD_TYPE: Debug
        # build output snippets for documentation
        BUILD_DOC: "yes"
    only:
        changes:
            - Documentation/**/*
            - src/TNL/**/*.{h,hpp}
            - .gitlab-ci.yml
    # store output snippets for documentation
    artifacts:
        paths:
            - Documentation/output_snippets/

build documentation:
    stage: build:doc
    only:
        changes:
            - Documentation/**/*
            - src/TNL/**/*.{h,hpp}
            - .gitlab-ci.yml
    # use "needs" instead of "dependencies" to allow out-of-order start of this job
    needs:
        - job: documentation examples
          artifacts: true
    script:
        - ./Documentation/build
    artifacts:
        paths:
            - ./Documentation/html/

deploy documentation:
    stage: deploy
    only:
        changes:
            - Documentation/**/*
            - src/TNL/**/*.{h,hpp}
            - .gitlab-ci.yml
        variables:
            - $TNL_DOC_DEPLOY_KEY
        refs:
            - develop
            - schedules
            - triggers
    # use "dependencies" instead of "needs" to deploy only when the entire pipeline succeeds
    dependencies:
        - build documentation
    image: "archlinux:latest"
    before_script:
        - pacman -Suy --noconfirm --needed rsync openssh
    script:
        - ./Documentation/deploy
    tags:
        - docker
        - deploy_key

clang-format:
    stage: lint
#    only:
#        changes:
#            - Documentation/**/*.{h,hpp,cpp,cu}
#            - src/**/*.{h,hpp,cpp,cu}
    script:
        # TODO: replace "src/TNL" with "src"
        - ./scripts/run-clang-format.py
                --color always
                --style file
                --exclude "src/3rdparty/*"
                --recursive
                src/TNL Documentation
    interruptible: true
    # TODO: remove to enforce formatting
    allow_failure: true

clang-tidy:
    stage: lint
#    only:
#        changes:
#            - Documentation/**/*.{h,hpp,cpp,cu}
#            - src/**/*.{h,hpp,cpp,cu}
    variables:
        CXX: clang++
        CC: clang
    script:
        # configure only to generate compile_commands.json
        # TODO: set BUILD_EXAMPLES=yes to modernize examples
        - cmake -B "./builddir/$CI_JOB_NAME" -S . -G Ninja
                -DCMAKE_BUILD_TYPE=Debug
                -DWITH_OPENMP=yes
                -DWITH_MPI=yes
                -DWITH_CUDA=no
                -DBUILD_TESTS=yes
                -DBUILD_MATRIX_TESTS=yes
                -DBUILD_DOC=yes
                -DBUILD_BENCHMARKS=yes
                -DBUILD_EXAMPLES=no
                -DBUILD_TOOLS=yes
                -DBUILD_PYTHON=yes
                -DWITH_CI_FLAGS=yes
        # cmake creates compile_commands.json only on the second run, WTF!?
        - cmake -B "./builddir/$CI_JOB_NAME" -S . -G Ninja
        - ls -lah "./builddir/$CI_JOB_NAME"
        # run-clang-tidy is weird compared to run-clang-format.py:
        # - clang-tidy is not executed on header files, but on source files
        # - the positional arguments are regexes filtering sources from the compile_commands.json
        # - the -header-filter option (or HeaderFilterRegex in the config) allows to filter header files
        - run-clang-tidy -p "./builddir/$CI_JOB_NAME" ".*"
    interruptible: true
    # TODO: remove to enforce
    allow_failure: true
