name: General build matrix

on:
  push:
    branches:
    - master
  pull_request:
    branches:
    - master

# We only care about the latest revision of a PR, so cancel all previous instances.
# Allow running master builds to complete to help with ccache refreshes.
concurrency:
  group: general-build-${{ github.event.pull_request.number || github.ref_name }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

# Overall strategy for what sorts of builds to include:
# We want a build for the newest and oldest supported version of each compiler.
# We want a build for each platform.
# We also want to ensure that each of the following tweaks are covered in at
# least one PR build:
# - TILES=1
# - SOUND=1
# - RELEASE=1
# - CMAKE=1
# - LOCALIZE=0
# - LTO=1
# - LIBBACKTRACE=1 (on Linux and Windows)
# - Tests with important mods enabled (Magiclysm)
# - A clang-tidy run
# - SANITIZE=address,undefined
# We try to minimize the number of builds subject to those constraints.
#
# To see what toolchains are available, the following may be useful:
# https://launchpad.net/%7Eubuntu-toolchain-r/+archive/ubuntu/test/+index
# https://apt.llvm.org/

jobs:
  skip-duplicates-code:
    continue-on-error: true
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      should_skip_code: ${{ steps.skip_check.outputs.should_skip }}
    steps:
      - id: skip_check
        uses: fkirc/skip-duplicate-actions@master
        with:
          paths_ignore: '["android/**", "build-data/osx/**", "doc/**", "doxygen_doc/**", "lgtm/**", "msvc-**", "object_creator/**", "tools/**", "utilities/**", "data/**", "lang/**"]'
  skip-duplicates-data:
    continue-on-error: true
    runs-on: ubuntu-latest
    # Map a step output to a job output
    outputs:
      should_skip_data: ${{ steps.skip_check.outputs.should_skip }}
    steps:
      - id: skip_check
        uses: fkirc/skip-duplicate-actions@master
        with:
          paths_ignore: '["android/**", "build-data/osx/**", "doc/**", "doxygen_doc/**", "lgtm/**", "msvc-**", "object_creator/**", "tools/**", "utilities/**"]'
  matrix-variables:
    continue-on-error: true
    runs-on: ubuntu-latest
    outputs:
      fail_fast: ${{ steps.matrix_vars.outputs.fail_fast }}
      skip_tests: ${{ steps.matrix_vars.outputs.skip_tests }}
      max_parallel: ${{ steps.matrix_vars.outputs.max_parallel }}
    steps:
      - id: matrix_vars
        run: |
          echo "fail_fast=$([ "$GITHUB_REF_NAME" = "master" ] && echo false || echo true)" >> $GITHUB_OUTPUT
          echo "skip_tests=$([ "$GITHUB_REF_NAME" = "master" ] && echo true || echo false)" >> $GITHUB_OUTPUT
          echo "max_parallel=$([ "$GITHUB_REF_NAME" = "master" ] && echo 20 || echo 1)" >> $GITHUB_OUTPUT
  varied_builds:
    needs: [ skip-duplicates-code, skip-duplicates-data, matrix-variables ]
    strategy:
      fail-fast: ${{ fromJSON(needs.matrix-variables.outputs.fail_fast) }}
      max-parallel: ${{ fromJSON(needs.matrix-variables.outputs.max_parallel) }}
      matrix:
        include:
          - compiler: clang++-6.0
            os: ubuntu-18.04
            tiles: 0
            sound: 0
            release: 1
            cmake: 0
            localize: 1
            test-stage: 1
            native: linux64
            archive-success: basic-build
            dont_skip_data_only_changes: 1
            title: Basic Build and Test (Clang 6, Ubuntu, Curses)
            # ~190MB in a clean build
            # ~30MB compressed
            # observed usage: 3.0GB -> 300MB
            ccache_limit: 2G

          - compiler: g++-9
            os: ubuntu-latest
            release: 1
            cmake: 0
            tiles: 0
            sound: 0
            localize: 1
            gold: 1
            lto: 1
            libbacktrace: 1
            native: linux64
            title: GCC 9, Curses, LTO
            # ~850MB in a clean build
            # ~370MB compressed
            # observed usage: 4.0GB -> 1.5GB
            ccache_limit: 3G

          - compiler: clang++-12
            os: ubuntu-22.04
            release: 0
            cmake: 0
            tiles: 1
            sound: 0
            localize: 1
            native: linux64
            sanitize: address
            cxxflags: --gcc-toolchain=/opt/mock-gcc-11
            mods: --mods=magiclysm
            dont_skip_data_only_changes: 1
            title: Clang 12, Ubuntu, Tiles, ASan
            # ~390MB in a clean build
            # ~50MB compressed
            # observed usage: 4.0GB -> 400MB
            ccache_limit: 3G

          - compiler: g++-11
            os: ubuntu-latest
            release: 0
            cmake: 0
            tiles: 0
            sound: 0
            localize: 1
            native: linux64
            sanitize: address
            title: GCC 11, Ubuntu, Curses, ASan
            # ~480MB in a clean build
            # ~50MB compressed
            # observed usage: 4.4GB -> 400MB
            ccache_limit: 4G

          - compiler: clang++
            os: macos-10.15
            release: 0
            cmake: 0
            native: osx
            sanitize: undefined
            tiles: 1
            sound: 1
            localize: 1
            title: Clang 12, macOS 10.15, Tiles, Sound, UBSan
            # ~880MB ccache-compressed in a clean build
            # ~100 compressed
            # observed usage: 3.85GB -> 500MB
            ccache_limit: 4G

          - compiler: g++
            os: ubuntu-latest
            release: 0
            cmake: 0
            native:
            tiles: 1
            sound: 1
            localize: 1
            libbacktrace: 1
            title: GCC 11, Ubuntu cross-compile to MinGW-Win64, Tiles, Sound
            ldflags: -static-libgcc -static-libstdc++
            mxe_target: x86_64-w64-mingw32.static.gcc11
            wine: wine
            # ~285MB in a clean build
            # ~36MB compressed
            # observed usage: 3.0GB -> 350MB
            ccache_limit: 2G

          - compiler: g++-7
            os: ubuntu-18.04
            release: 1
            cmake: 1
            tiles: 1
            sound: 1
            localize: 0
            native: linux64
            title: GCC 7, Ubuntu, Tiles, Sound, CMake
            # ~180MB in a clean build
            # ~25MB compressed
            # observed usage: 3.0GB -> 300MB
            ccache_limit: 2G

          # Reserving space for msvc ccache & vcpkg cache
            # observed usage:
            # vcpkg: ??? -> 500MB
            # ccache: 8GB -> 1.0GB

    name: ${{ matrix.title }}
    runs-on: ${{ matrix.os }}
    env:
        ZSTD_CLEVEL: 17
        CMAKE: ${{ matrix.cmake }}
        COMPILER: ${{ matrix.compiler }}
        MXE_TARGET: ${{ matrix.mxe_target }}
        WINE: ${{ matrix.wine }}
        OS: ${{ matrix.os }}
        TILES: ${{ matrix.tiles }}
        SOUND: ${{ matrix.sound }}
        LOCALIZE: ${{ matrix.localize }}
        MODS: ${{ matrix.mods }}
        SANITIZE: ${{ matrix.sanitize }}
        TEST_STAGE: ${{ matrix.test-stage }}
        CXXFLAGS: ${{ matrix.cxxflags }}
        LDFLAGS: ${{ matrix.ldflags }}
        EXTRA_TEST_OPTS: --error-format=github-action
        NUM_TEST_JOBS: ${{ matrix.num_test_jobs }}
        NATIVE: ${{ matrix.native }}
        GOLD: ${{ matrix.gold }}
        LTO: ${{ matrix.lto }}
        LIBBACKTRACE: ${{ matrix.libbacktrace }}
        RELEASE: ${{ matrix.release }}
        ARCHIVE_SUCCESS: ${{ matrix.archive-success }}
        CCACHE_LIMIT: ${{ matrix.ccache_limit }}
        CCACHE_FILECLONE: true
        CCACHE_HARDLINK: true
        CCACHE_NOCOMPRESS: true
        SKIP: ${{ ( github.event.pull_request.draft == true && matrix.title != 'Basic Build and Test (Clang 6, Ubuntu, Curses)' ) || ( matrix.dont_skip_data_only_changes == 0 && needs.skip-duplicates.outputs.should_skip_code == 'true' ) || ( matrix.dont_skip_data_only_changes != 0 && needs.skip-duplicates-mods.outputs.should_skip_data == 'true' ) }}
        SKIP_TESTS: ${{ needs.matrix-variables.outputs.skip_tests }}
    steps:
    - name: checkout repository
      if: ${{ env.SKIP == 'false' }}
      uses: actions/checkout@v2
      with:
        fetch-depth: 1
    - name: install dependencies (ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' }}
      run: |
          sudo apt-get update
          sudo apt-get install libncursesw5-dev ccache gettext parallel
    - name: install SDL2 dependencies (ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' && matrix.tiles == 1 }}
      run: |
          sudo apt-get install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev libpulse-dev
    - name: install old GCC (Ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' && (matrix.compiler == 'g++-7' || matrix.compiler == 'g++-8') }}
      run: |
          sudo apt-get install g++-7 g++-8
    - name: install new GCC (Ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' && (matrix.compiler == 'g++-11') }}
      run: |
          sudo add-apt-repository ppa:ubuntu-toolchain-r/test
          sudo apt-get update
          sudo apt-get install g++-11
    - name: install old Clang (Ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' && (matrix.compiler == 'clang++-6.0') }}
      run: |
          sudo apt-get install clang-6.0
    - name: set up a mock GCC toolchain root for Clang (Ubuntu)
      if: ${{ env.SKIP == 'false' && runner.os == 'Linux' && (matrix.compiler == 'clang++-12') }}
      run: |
        sudo mkdir /opt/mock-gcc-11
        sudo ln -s /usr/include /opt/mock-gcc-11/include
        sudo ln -s /usr/bin /opt/mock-gcc-11/bin
        sudo mkdir -p /opt/mock-gcc-11/lib/gcc/x86_64-linux-gnu
        sudo ln -s /usr/lib/gcc/x86_64-linux-gnu/11 /opt/mock-gcc-11/lib/gcc/x86_64-linux-gnu/11
    - name: install dependencies (mac)
      if: ${{ env.SKIP == 'false' && runner.os == 'macOS' }}
      run: |
          HOMEBREW_NO_AUTO_UPDATE=yes HOMEBREW_NO_INSTALL_CLEANUP=yes brew install sdl2 sdl2_image sdl2_ttf sdl2_mixer gettext ccache parallel
    - name: prepare
      if: ${{ env.SKIP == 'false' }}
      run: bash ./build-scripts/requirements.sh
    - name: Get ccache vars
      id: get-vars
      if: ${{ env.SKIP == 'false' }}
      run: |
        echo "datetime=$(/bin/date -u "+%Y%m%d%H%M")" >> $GITHUB_OUTPUT
        echo "ccache-path=$([ "$RUNNER_OS" = "macOS" ] && echo '/Users/runner/Library/Caches/ccache' || echo '~/.ccache')" >> $GITHUB_OUTPUT
      shell: bash
    - name: ccache cache files
      if: ${{ env.SKIP == 'false' && ( runner.os == 'Linux' || runner.os == 'macOS' ) }}
      uses: actions/cache@v2
      with:
        path: ${{ steps.get-vars.outputs.ccache-path }}
        # double-dash after compiler is not a typo, it is to disambiguate between g++-<date> and g++-11-<date> for restore key prefix matching
        key: ccache-${{ github.ref_name }}-${{ runner.os }}-${{ matrix.compiler }}--${{ steps.get-vars.outputs.datetime }}
        restore-keys: |
          ccache-master-${{ runner.os }}-${{ matrix.compiler }}--
    - uses: ammaraskar/gcc-problem-matcher@master
    - name: build
      if: ${{ env.SKIP == 'false' }}
      run: bash ./build-scripts/gha_compile_only.sh
    - name: post-build ccache manipulation
      if: ${{ env.SKIP == 'false' && !failure() && (runner.os == 'Linux' || runner.os == 'macOS') }}
      run: |
        ccache --show-stats
        ccache -M ${{ env.CCACHE_LIMIT }}
        ccache -c
        ccache --show-stats
    - name: clear ccache on PRs
      if: ${{ github.ref_name != 'master' && env.SKIP == 'false' && !failure() && (runner.os == 'Linux' || runner.os == 'macOS') }}
      run: |
        ccache -C
    # TODO: post ccache here, however actions/cache@v2 does not support manual upload step
    - name: run tests
      if: ${{ env.SKIP == 'false' && env.SKIP_TESTS == 'false' }}
      run: bash ./build-scripts/gha_test_only.sh
    - run: |
        echo ${{ github.event.number }} > pull_request_id
        echo "true" > ${{ env.ARCHIVE_SUCCESS }}
      if: ${{ env.ARCHIVE_SUCCESS && success() }}
    - run: |
        echo ${{ github.event.number }} > pull_request_id
        echo "false" > ${{ env.ARCHIVE_SUCCESS }}
      if: ${{ env.ARCHIVE_SUCCESS && failure() }}
    - uses: actions/upload-artifact@v2
      if: ${{ always() && env.ARCHIVE_SUCCESS }}
      with:
        name: pull_request_id
        path: pull_request_id
    - uses: actions/upload-artifact@v2
      if: ${{ always() && env.ARCHIVE_SUCCESS }}
      with:
        name: ${{ env.ARCHIVE_SUCCESS }}
        path: ${{ env.ARCHIVE_SUCCESS }}
    - name: upload artifacts if failed
      uses: actions/upload-artifact@v2
      if: failure()
      with:
        name: cata_test
        path: tests/cata_test*
        if-no-files-found: ignore
