name: GFortran CI

# WARNING: When updating the OS version of the Github Actions runner,
# e.g. from Ubuntu 20.04 to 22.04, you need to manually update the cache keys!
# This is because there's no easy way to get the OS version from within this workflow file.

on:
  push:
    branches: [ master ]
  pull_request:

env:
  # FFLAGS for building ABIN, applicable for most jobs
  ABIN_FFLAGS: -O0 -fopenmp --coverage -fprofile-abs-path -ffpe-trap=invalid,zero,overflow,denormal -fimplicit-none -Wall -Wno-integer-division -Wno-maybe-uninitialized
  ABIN_LDLIBS: --coverage
  OPTIMIZED_FFLAGS: -O3 -fopenmp -fimplicit-none -Wall -Wno-integer-division

jobs:

  format:
    name: Format check
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Install fprettify dependencies
      run: pip install --user configargparse

    - name: Run fprettify
      run: |
        ./autoformat.py
        f=`git ls-files -m`; if [[ -n $f ]];then echo -e "ERROR: Detected unformatted files:\n$f";exit 1;fi

  basic_build:
    name: Basic build
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
      matrix:
         gcc_v: [7, 9, 10]
    env:
      FC: gfortran
      GCC_V: ${{ matrix.gcc_v}}
      CODECOV_NAME: ${{format('{0} GCC-{1}', github.job, matrix.gcc_v)}}

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Install GCC-7
      if: matrix.gcc_v == 7
      run: |
        sudo apt install gcc-7 gfortran-7 g++-7
        echo "ABIN_FFLAGS=${ABIN_FFLAGS/-fprofile-abs-path/}" >> $GITHUB_ENV

    - name: Enable runtime checks
      # It looks like this messes up the code coverage, so enabling only some builds
      if: matrix.gcc_v != 7
      run: |
        echo "ABIN_FFLAGS=${ABIN_FFLAGS} -fcheck=all -fsanitize=address,undefined,leak" >> $GITHUB_ENV
        echo "ABIN_LDLIBS=${ABIN_LDLIBS} -fsanitize=address,undefined,leak" >> $GITHUB_ENV

    - name: Set GFortran version
      run: |
        sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC_V} 100 \
        --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-${GCC_V} \
        --slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_V}

    # pFUnit library is used to build and run unit tests
    - name: pFUnit build Cache
      id: pfunit-cache
      uses: actions/cache@v3
      with:
        path: ~/pfunit/build/installed
        # To force a pFUnit rebuild (bust the cache), make a change to install_pfunit.sh
        key: ${{ runner.os }}-ubuntu20.04-pfunit-gfortran${{ env.GCC_V }}-${{ hashFiles('dev_scripts/install_pfunit.sh') }}

    - name: Download and build pFUnit
      if: steps.pfunit-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_pfunit.sh ${HOME}/pfunit

    - name: Build ABIN
      run: ./configure --pfunit ${HOME}/pfunit/build/installed/ && make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }}
        LDLIBS: ${{ env.ABIN_LDLIBS }}

    - name: Run Unit tests
      run: make unittest

    - name: Codecov upload unit tests
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        flags: unittests
        gcov: true

    - name: Run End-to-End tests
      if: always()
      run: make e2etest

    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        fail_ci_if_error: true
        gcov: true

  intel_build:
    name: Intel OneAPI build
    runs-on: ubuntu-20.04
    env:
      FC: mpiifort
      # Use GCC for C++ code to speed up the build
      #CC: icc
      #CXX: icpc
      APT_PACKAGES: >-
        intel-oneapi-compiler-fortran
        intel-oneapi-mpi
        intel-oneapi-mpi-devel
      # intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Add Intel repository
      run: |
        wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
        sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
        rm GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
        echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list
        sudo apt-get update

    - name: Install Intel oneAPI compiler
      run: |
        sudo apt-get install ${{ env.APT_PACKAGES }}
        source /opt/intel/oneapi/setvars.sh
        printenv >> $GITHUB_ENV

    - name: Build ABIN
      run: ./configure --mpi "" && make
      env:
        FFLAGS: -O0 -fopenmp -warn all,noextern

    - name: Run End-to-End tests
      run: make e2etest

  tcpb_build:
    name: TCPB build
    runs-on: ubuntu-20.04
    needs: basic_build
    env:
      FC: gfortran
      CODECOV_NAME: ${{format('{0}', github.job)}}
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Install protocol buffers library
      run: sudo apt install protobuf-compiler

    - name: TCBP build cache
      id: tcpb-cache
      uses: actions/cache@v3
      with:
        path: ~/tcpb-cpp/lib
        # To force a rebuild (bust the cache), make a change to install_tcpb.sh
        key: ${{ runner.os }}-ubuntu20.04-tcpb-gfortran-default-${{ hashFiles('dev_scripts/install_tcpb.sh') }}

    - name: Download and build tcpb_cpp
      if: steps.tcpb-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_tcpb.sh ${HOME}/tcpb-cpp

    - name: Build ABIN
      run: ./configure --tcpb ${HOME}/tcpb-cpp/lib && make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }}
        LDLIBS: ${{ env.ABIN_LDLIBS }}

    - name: Run End-to-End tests
      run: make e2etest

    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: tcpb
        fail_ci_if_error: true
        gcov: true


  optimized_build:
    name: Optimized build
    # NOTE: We use a more recent Ubuntu version here
    # so we can test more recent GCC versions.
    runs-on: ubuntu-22.04
    needs: basic_build
    strategy:
      fail-fast: false
      matrix:
         gcc_v: [9, 11, 12]
    env:
      FC: gfortran
      GCC_V: ${{ matrix.gcc_v}}

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2
    - name: Set GFortran version
      run: |
        sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC_V} 100 \
        --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-${GCC_V}

    - name: pFUnit build Cache
      id: pfunit-cache
      uses: actions/cache@v3
      with:
        path: ~/pfunit/build/installed
        key: ${{ runner.os }}-ubuntu22.04-pfunit-gfortran${{ env.GCC_V }}-${{ hashFiles('dev_scripts/install_pfunit.sh') }}

    - name: Download and build pFUnit
      if: steps.pfunit-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_pfunit.sh ${HOME}/pfunit

    - name: build ABIN
      run: ./configure --pfunit ${HOME}/pfunit/build/installed/ && make
      env:
        FFLAGS: ${{ env.OPTIMIZED_FFLAGS }}

    - name: Run Unit tests
      run: make unittest

    - name: Run End-to-End tests
      run: make e2etest

  # Here we just take the defaults everywhere, except installing libfftw via apt
  # To use FFTW with other Gfortran versions, we would need to build it.
  fftw_build:
    runs-on: ubuntu-22.04
    name: FFTW build
    needs: basic_build
    env:
      CODECOV_NAME: ${{format('{0}', github.job)}}
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Install FFTW libraries
      run: sudo apt-get install libfftw3-dev

    - name: pFUnit build Cache
      id: pfunit-cache
      uses: actions/cache@v3
      with:
        path: ~/pfunit/build/installed
        key: ${{ runner.os }}-ubuntu22.04-pfunit-gfortran-default-${{ hashFiles('dev_scripts/install_pfunit.sh') }}

    - name: Download and build pFUnit
      if: steps.pfunit-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_pfunit.sh ${HOME}/pfunit

    - name: Build ABIN
      run: ./configure --pfunit ${HOME}/pfunit/build/installed/ --fftw && make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }}
        LDLIBS: ${{ env.ABIN_LDLIBS }}

    - name: Run Unit tests
      run: make unittest

    - name: Codecov upload unit tests
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        flags: unittests
        gcov: true

    - name: Run End-to-End tests
      run: make e2etest

    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        fail_ci_if_error: true
        gcov: true

  mpich_build:
    name: MPICH build
    runs-on: ubuntu-20.04
    timeout-minutes: 25
    needs: basic_build
    strategy:
      fail-fast: false
      matrix:
         gcc_v: [7, 10]
         mpich_v: ["3.4.3", "4.0.2"]
    env:
      # To speed-up MPICH build
      CFLAGS: -O0
      GCC_V: ${{ matrix.gcc_v}}
      MPICH_V: ${{matrix.mpich_v}}
      CODECOV_NAME: ${{format('{0} GCC-{1} MPICH-{2}', github.job, matrix.gcc_v, matrix.mpich_v)}}

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2

    - name: Install GCC-7
      if: matrix.gcc_v == 7
      run: |
        sudo apt install gcc-7 gfortran-7 g++-7
        echo "ABIN_FFLAGS=${ABIN_FFLAGS/-fprofile-abs-path/}" >> $GITHUB_ENV

    - name: Set GFortran version
      run: |
        sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC_V} 100 \
        --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-${GCC_V} \
        --slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_V}
    - name: MPICH build Cache
      id: mpich-cache
      uses: actions/cache@v3
      with:
        path: ~/mpich/${{ env.MPICH_V }}/install
        key: ${{runner.os}}-ubuntu20.04-mpich${{ env.MPICH_V }}-gfortran${{ env.GCC_V }}-${{hashFiles('dev_scripts/install_mpich.sh')}}

    - name: Build and Install MPICH
      if: steps.mpich-cache.outputs.cache-hit != 'true'
      # Without the extra "-fallow-argument-mismatch" FFLAG, configure with GFortran-10 fails with:
      # "The Fortran compiler gfortran does not accept programs
      # that call the same routine with arguments of different types"
      # Unfortunately, previous GCC versions do not have this flag
      # so we need to set it conditionally.
      # MPICH 4.0 also needs FCFLAGS set.
      # We also need to set it for ABIN compilation below.
      run: |
        if [ $GCC_V -ge 10 ];then export FFLAGS="-fallow-argument-mismatch";export FCFLAGS="-fallow-argument-mismatch";fi && \
        ./dev_scripts/install_mpich.sh ${HOME}/mpich ${MPICH_V}

    - name: build ABIN
      run: |
        if [ $GCC_V -ge 10 ];then export FFLAGS="-fallow-argument-mismatch $FFLAGS";fi && \
        ./configure --mpi ${HOME}/mpich/${MPICH_V}/install && make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }} -g
        LDLIBS: ${{ env.ABIN_LDLIBS }}
    - name: test ABIN
      run: make test
    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        fail_ci_if_error: true
        gcov: true

  openmpi_build:
    name: OpenMPI build
    runs-on: ubuntu-20.04
    timeout-minutes: 25
    needs: basic_build
    strategy:
      fail-fast: false
    env:
      # To speed-up OpenMPI build
      CFLAGS: -O0
      CODECOV_NAME: ${{format('{0} OpenMPI-4.0', github.job)}}
      OPENMPI_V: "4.1"
      OPENMPI_PATCH: "2"

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2
    - name: OpenMPI build cache
      id: openmpi-cache
      uses: actions/cache@v3
      with:
        path: ~/openmpi/${{ env.OPENMPI_V }}/install
        key: ${{runner.os}}-ubuntu20.04-openmpi${{ env.OPENMPI_V }}-gfortran-default-${{hashFiles('dev_scripts/install_openmpi.sh')}}

    - name: Build and Install OpenMPI
      if: steps.openmpi-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_openmpi.sh ${HOME}/openmpi ${OPENMPI_V} ${OPENMPI_PATCH}

    - name: build ABIN
      run: ./configure --mpi "${HOME}/openmpi/${OPENMPI_V}/install" && make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }}
        LDLIBS: ${{ env.ABIN_LDLIBS }}
    - name: test ABIN
      run: make test
    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        fail_ci_if_error: true
        gcov: true

  plumed_build:
    name: PLUMED build
    runs-on: ubuntu-20.04
    needs: basic_build
    strategy:
      fail-fast: false
      matrix:
         plumed_v: [2.7.6, 2.8.2]

    env:
      PLUMED_V: ${{ matrix.plumed_v}}
      # Speeding up the Plumed build
      CFLAGS: -O0
      CXXLAGS: -O0
      CODECOV_NAME: ${{format('{0} PLUMED-{1}', github.job, matrix.plumed_v)}}

    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 2
    - name: Plumed build cache
      id: plumed-cache
      uses: actions/cache@v3
      with:
        path: ~/plumed/${{ env.PLUMED_V }}/install
        key: ${{runner.os}}-ubuntu20.04-plumed${{env.PLUMED_V}}-gcc-default-${{hashFiles('dev_scripts/install_plumed.sh')}}

    - name: Build and Install PLUMED
      if: steps.plumed-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_plumed.sh ${HOME}/plumed ${PLUMED_V}

    - name: pFUnit build Cache
      id: pfunit-cache
      uses: actions/cache@v3
      with:
        path: ~/pfunit/build/installed
        key: ${{ runner.os }}-ubuntu20.04-pfunit-gfortran-default-${{ hashFiles('dev_scripts/install_pfunit.sh') }}

    - name: Download and build pFUnit
      if: steps.pfunit-cache.outputs.cache-hit != 'true'
      run: ./dev_scripts/install_pfunit.sh ${HOME}/pfunit

    - name: build ABIN
      run: |
        ./configure --plumed "${HOME}/plumed/${PLUMED_V}/install"\
                    --pfunit ~/pfunit/build/installed/ &&\
        make
      env:
        FFLAGS: ${{ env.ABIN_FFLAGS }}
        LDLIBS: ${{ env.ABIN_LDLIBS }}

    - name: Run Unit tests
      run: make unittest

    - name: Codecov upload unit tests
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        flags: unittests
        gcov: true

    - name: Run End-to-End tests
      run: make e2etest

    - name: Codecov upload
      uses: codecov/codecov-action@v3
      with:
        name: ${{env.CODECOV_NAME}}
        fail_ci_if_error: true
        gcov: true