name: Build
on:
  push:
  pull_request:

env:
  CTEST_OUTPUT_ON_FAILURE: ON
  VERBOSE: 1

defaults:
  run:
    shell: bash

jobs:
  build_and_test:
    env:
      CMAKE_GENERATOR: Ninja

    name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-${{ matrix.config.version }}
    runs-on: ${{ matrix.config.os }}
    strategy:
      fail-fast: false
      matrix:
        config:
          - os: ubuntu-16.04
            compiler: gcc
            version: "4.8" # results in 4.8.5

          - os: ubuntu-16.04
            compiler: gcc
            version: "5"

          - os: ubuntu-18.04
            compiler: gcc
            version: "6"

          - os: ubuntu-18.04
            compiler: gcc
            version: "7"

          - os: ubuntu-18.04
            compiler: gcc
            version: "8"

          - os: ubuntu-18.04
            compiler: gcc
            version: "9"

          - os: ubuntu-20.04
            compiler: gcc
            version: "10"

          - os: ubuntu-16.04
            compiler: clang
            version: "5.0"

          - os: ubuntu-16.04
            compiler: clang
            version: "6.0"

          - os: ubuntu-18.04
            compiler: clang
            version: "7"

          - os: ubuntu-18.04
            compiler: clang
            version: "8"

          - os: ubuntu-20.04
            compiler: clang
            version: "9"

          - os: ubuntu-20.04
            compiler: clang
            version: "10"

          - os: macOS-latest
            compiler: xcode
            version: "10.3"

          - os: macOS-latest
            compiler: xcode
            version: "11.7"

          - os: macOS-latest
            compiler: xcode
            version: "12.2"
    steps:
      - name: Install dependencies
        run: |
          if [ "${{ runner.os }}" = "Linux" ]; then
            sudo apt-get update

            # Install ld.gold (binutils) and ld.lld on different runs.
            if [ "${{ matrix.config.os }}" = "ubuntu-16.04" ]; then
              sudo apt-get install -y ninja-build elfutils libzstd1-dev binutils
            else
              sudo apt-get install -y ninja-build elfutils libzstd-dev lld
            fi

            if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
              echo "CC=gcc-${{ matrix.config.version }}" >> $GITHUB_ENV
              echo "CXX=g++-${{ matrix.config.version }}" >> $GITHUB_ENV

              sudo apt install -y g++-${{ matrix.config.version }} g++-${{ matrix.config.version }}-multilib
            else
              echo "CC=clang-${{ matrix.config.version }}" >> $GITHUB_ENV
              echo "CXX=clang++-${{ matrix.config.version }}" >> $GITHUB_ENV

              sudo apt install -y clang-${{ matrix.config.version }} g++-multilib
            fi
          elif [ "${{ runner.os }}" = "macOS" ]; then
            HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 \
              brew install ninja

            if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
              brew install gcc@${{ matrix.config.version }}
              echo "CC=gcc-${{ matrix.config.version }}" >> $GITHUB_ENV
              echo "CXX=g++-${{ matrix.config.version }}" >> $GITHUB_ENV
            else
              sudo xcode-select -switch /Applications/Xcode_${{ matrix.config.version }}.app
              echo "CC=clang" >> $GITHUB_ENV
              echo "CXX=clang++" >> $GITHUB_ENV
            fi
          fi

      - name: Get source
        uses: actions/checkout@v2

      - name: Build and test
        run: ci/build
        env:
          ENABLE_CACHE_CLEANUP_TESTS: true
          CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI

      - name: Collect testdir from failed tests
        if: failure()
        run: ci/collect-testdir

      - name: Upload testdir from failed tests
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-${{ matrix.config.version }}-testdir.tar.xz
          path: testdir.tar.xz

  specific_tests:
    name: ${{ matrix.config.name }}
    runs-on: ${{ matrix.config.os }}
    strategy:
      fail-fast: false
      matrix:
        config:
          - name: Linux GCC debug + C++14 + in source + tracing
            os: ubuntu-18.04
            CC: gcc
            CXX: g++
            ENABLE_CACHE_CLEANUP_TESTS: 1
            BUILDDIR: .
            CCACHE_LOC: .
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1 -DCMAKE_CXX_STANDARD=14
            apt_get: elfutils libzstd-dev

          - name: Linux GCC 32-bit
            os: ubuntu-18.04
            CC: gcc
            CXX: g++
            CFLAGS: -m32 -g -O2
            CXXFLAGS: -m32 -g -O2
            LDFLAGS: -m32
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
            ENABLE_CACHE_CLEANUP_TESTS: 1
            apt_get: elfutils gcc-multilib g++-multilib lib32stdc++-5-dev

          - name: Linux GCC CUDA
            os: ubuntu-18.04
            CC: gcc
            CXX: g++
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CUDA: 10.1.243-1
            apt_get: elfutils libzstd-dev

          - name: Linux MinGW 32-bit
            os: ubuntu-18.04
            CC: i686-w64-mingw32-gcc-posix
            CXX: i686-w64-mingw32-g++-posix
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DCMAKE_SYSTEM_NAME=Windows -DZSTD_FROM_INTERNET=ON
            RUN_TESTS: none
            apt_get: elfutils mingw-w64

          - name: Linux MinGW 64-bit
            os: ubuntu-20.04
            CC: x86_64-w64-mingw32-gcc-posix
            CXX: x86_64-w64-mingw32-g++-posix
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DCMAKE_SYSTEM_NAME=Windows -DZSTD_FROM_INTERNET=ON
            RUN_TESTS: unittest-in-wine
            apt_get: elfutils mingw-w64 wine

          - name: Windows VS2019 32-bit
            os: windows-2019
            msvc_arch: x64_x86
            allow_test_failures: true  # For now, don't fail the build on failure
            CC: cl
            CXX: cl
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CMAKE_GENERATOR: Ninja
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
            TEST_CC: clang -target i686-pc-windows-msvc

          - name: Windows VS2019 64-bit
            os: windows-2019
            msvc_arch: x64
            allow_test_failures: true  # For now, don't fail the build on failure
            CC: cl
            CXX: cl
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CMAKE_GENERATOR: Ninja
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
            TEST_CC: clang -target x86_64-pc-windows-msvc

          - name: Clang address & UB sanitizer
            os: ubuntu-20.04
            CC: clang
            CXX: clang++
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DENABLE_SANITIZER_ADDRESS=ON -DENABLE_SANITIZER_UNDEFINED_BEHAVIOR=ON
            ASAN_OPTIONS: detect_leaks=0
            apt_get: elfutils libzstd-dev

          - name: Clang static analyzer
            os: ubuntu-20.04
            CC: clang
            CXX: clang++
            ENABLE_CACHE_CLEANUP_TESTS: 1
            CMAKE_PREFIX: scan-build
            RUN_TESTS: none
            apt_get: libzstd-dev

          - name: Linux binary
            os: ubuntu-20.04
            CC: gcc
            CXX: g++
            SPECIAL: build-and-verify-package
            CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Release
            apt_get: elfutils libzstd-dev ninja-build

          - name: Source package
            os: ubuntu-20.04
            CC: gcc
            CXX: g++
            SPECIAL: build-and-verify-source-package
            apt_get: elfutils libzstd-dev ninja-build asciidoc xsltproc docbook-xml docbook-xsl

          - name: HTML documentation
            os: ubuntu-18.04
            EXTRA_CMAKE_BUILD_FLAGS: --target doc-html
            RUN_TESTS: none
            apt_get: libzstd-dev asciidoc docbook-xml docbook-xsl

          - name: Manual page
            os: ubuntu-18.04
            EXTRA_CMAKE_BUILD_FLAGS: --target doc-man-page
            RUN_TESTS: none
            apt_get: libzstd-dev asciidoc xsltproc docbook-xml docbook-xsl

          - name: Clang-Tidy
            os: ubuntu-18.04
            CC: clang-9
            CXX: clang++-9
            RUN_TESTS: none
            CMAKE_PARAMS: -DENABLE_CLANG_TIDY=ON -DCLANGTIDY=/usr/bin/clang-tidy-9
            apt_get: libzstd-dev clang-9 clang-tidy-9

    steps:
      - name: Get source
        uses: actions/checkout@v2

      - name: Install CUDA
        if: matrix.config.CUDA != ''
        run: sudo --preserve-env=CUDA,GITHUB_PATH ci/install-cuda
        env:
          CUDA: ${{ matrix.config.CUDA }}

      - name: Run apt-get
        if: matrix.config.apt_get != ''
        run: sudo apt-get update && sudo apt-get install ${{ matrix.config.apt_get }}

      - name: Prepare Windows environment (Visual Studio)
        if: runner.os == 'Windows'
        uses: ilammy/msvc-dev-cmd@v1.5.0
        with:
          arch: ${{ matrix.config.msvc_arch }}

      - name: Prepare Windows environment (Clang)
        if: runner.os == 'Windows'
        shell: powershell
        run: |
          $ErrorActionPreference = 'Stop'

          # The test suite currently requires that the compiler specified by the
          # "CC" environment variable is on a path without spaces. Provide that
          # by creating a junction from ~/opt/llvm to the Visual Studio path.
          $null = New-Item `
            -Path "${HOME}\opt\llvm" `
            -ItemType Junction `
            -Target "${env:VCINSTALLDIR}\Tools\Llvm\x64" `
            -Force
          "Path=${HOME}\opt\llvm\bin;${env:Path}" | `
            Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

      - name: Build and test
        id: build-and-test
        env:
          ASAN_OPTIONS: ${{ matrix.config.ASAN_OPTIONS }}
          BUILDDIR: ${{ matrix.config.BUILDDIR }}
          CC: ${{ matrix.config.CC }}
          CCACHE_LOC: ${{ matrix.config.CCACHE_LOC }}
          CFLAGS: ${{ matrix.config.CFLAGS }}
          CMAKE_GENERATOR: ${{ matrix.config.CMAKE_GENERATOR }}
          CMAKE_PARAMS: ${{ matrix.config.CMAKE_PARAMS }}
          CXX: ${{ matrix.config.CXX }}
          CXXFLAGS: ${{ matrix.config.CXXFLAGS }}
          ENABLE_CACHE_CLEANUP_TESTS: ${{ matrix.config.ENABLE_CACHE_CLEANUP_TESTS }}
          EXTRA_CMAKE_BUILD_FLAGS: ${{ matrix.config.EXTRA_CMAKE_BUILD_FLAGS }}
          LDFLAGS: ${{ matrix.config.LDFLAGS }}
          RUN_TESTS: ${{ matrix.config.RUN_TESTS }}
          SPECIAL: ${{ matrix.config.SPECIAL }}
          TEST_CC: ${{ matrix.config.TEST_CC }}
        run: |
          rc=0
          ci/build || rc=$?
          echo "::set-output name=exit_status::$rc"
          exit $rc
        # CTest exits with return code 8 on test failure.
        continue-on-error: ${{ matrix.config.allow_test_failures == true &&
          steps.build-and-test.outputs.exit_status == 8 }}

      - name: Collect testdir from failed tests
        if: failure() || steps.build-and-test.outcome == 'failure'
        run: ci/collect-testdir
        # TODO: in case of build-and-verify-*package the BUILDDIR is set within those scripts.

      - name: Upload testdir from failed tests
        if: failure() || steps.build-and-test.outcome == 'failure'
        uses: actions/upload-artifact@v2
        with:
          name: ${{ matrix.config.name }} - testdir.tar.xz
          path: testdir.tar.xz

  check_format:
    name: Code formatting
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
    steps:
      - name: Get source
        uses: actions/checkout@v2

      - name: Run Clang-Format in check mode
        run: misc/format-files --all --check

  codespell:
    name: Spelling
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
    steps:
      - name: Get source
        uses: actions/checkout@v2

      - name: Install codespell
        run: sudo apt-get update && sudo apt-get install codespell

      - name: Run codespell
        run: codespell -q 7 -S ".git,LICENSE.adoc,./src/third_party/*" -I misc/codespell-allowlist.txt
