name: Build

on:
  push:
    branches-ignore:
      - gh-readonly-queue/**
  pull_request:
  merge_group:

jobs:
  build-cmake:
    runs-on: ${{ matrix.os }}
    env:
      CARGO_HTTP_MULTIPLEXING: false
    strategy:
      fail-fast: false
      matrix:
        name: [ubuntu-latest-fancy, macOS-latest, windows-latest, ubuntu-22.04, windows-latest-mingw]
        include:
        - name: ubuntu-latest-fancy
          os: ubuntu-latest
          cmake-args: -G Ninja
          cmake-init-env: CXXFLAGS=-Werror
          package-file: "*-linux_x86_64.tar.xz"
          workaround-issue10203-run-after-configure:
        - name: ubuntu-22.04
          os: ubuntu-22.04
          cmake-path: /usr/bin/
          cmake-args: -G Ninja -DTEST_MYSQL=ON
          cmake-init-env: CXXFLAGS=-Werror
          gtest-env: GTEST_FILTER=-*SQLite*
          package-file: "*-linux_x86_64.tar.xz"
          workaround-issue10203-run-after-configure:
        - name: macOS-latest
          os: macOS-latest
          cmake-args: -G Ninja
          cmake-init-env: CXXFLAGS=-Werror
          package-file: "*-macos.dmg"
          workaround-issue10203-run-after-configure:
        - name: windows-latest
          os: windows-latest
          cmake-args: -A x64 -DEXCEPTION_HANDLING=ON
          cmake-init-env: CXXFLAGS=/WX LDFLAGS=/WX
          package-file: "*-win64.zip"
          workaround-issue10203-run-after-configure:
        - name: windows-latest-mingw
          os: windows-latest
          cmake-args: -G Ninja -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DEXCEPTION_HANDLING=ON -DCMAKE_RC_COMPILER=windres -DCMAKE_AR=ar
          cmake-init-env: CXXFLAGS=-Werror LDFLAGS=-Werror
          package-file: "*-win64.zip"
          # Delete outdated libwinpthread-1.dll after configuring to workaround https://github.com/ddnet/ddnet/issues/10203 when using MinGW.
          # Adding this condition into the individual steps is not possible without using bash shell consistently, which does not work for windows-latest.
          workaround-issue10203-run-after-configure: rm libwinpthread-1.dll

    steps:
    - uses: actions/checkout@v4
      with:
        submodules: recursive

    - name: Prepare Linux
      if: contains(matrix.os, 'ubuntu')
      run: |
        sudo apt-get update -y
        # mount: /var/lib/grub/esp: special device /dev/disk/by-id/scsi-... does not exist.
        # sudo apt-get upgrade -y
        sudo apt-get install pkg-config ninja-build libfreetype6-dev libnotify-dev libsdl2-dev libsqlite3-dev libvulkan-dev glslang-tools spirv-tools libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libx264-dev libpng-dev valgrind gcovr libglew-dev -y

    - name: Prepare Linux (non-fancy)
      if: contains(matrix.os, 'ubuntu') && !contains(matrix.name, 'fancy')
      run: |
        curl -LO https://github.com/Kitware/CMake/releases/download/v3.13.4/cmake-3.13.4-Linux-x86_64.tar.gz
        sudo tar --strip-components 1 -C /usr -xf cmake-3.13.4-Linux-x86_64.tar.gz
        # Our minimum supported Rust version (MSRV)
        rustup default 1.63.0
        sudo rm -rf /var/lib/mysql/ /var/run/mysqld
        sudo mkdir /var/lib/mysql/ /var/run/mysqld/
        sudo chown mysql:mysql /var/lib/mysql/ /var/run/mysqld/
        sudo mysqld --initialize-insecure --user=mysql --basedir=/usr --datadir=/var/lib/mysql/
        sudo /usr/bin/mysqld_safe --basedir=/usr --datadir='/var/lib/mysql/' &
        sleep 10
        sudo mysql <<EOF
        CREATE DATABASE ddnet;
        CREATE USER 'ddnet'@'localhost' IDENTIFIED BY 'thebestpassword';
        GRANT ALL PRIVILEGES ON ddnet.* TO 'ddnet'@'localhost';
        FLUSH PRIVILEGES;
        EOF

    - name: Prepare Linux (fancy)
      if: contains(matrix.os, 'ubuntu') && contains(matrix.name, 'fancy')
      run: |
        sudo apt-get install cmake libmariadb-dev libwebsockets-dev mariadb-server -y
        sudo systemctl stop mysql
        sudo rm -rf /var/lib/mysql/
        sudo mysql_install_db --user=mysql --datadir=/var/lib/mysql/
        cd /usr; sudo mysqld_safe --datadir='/var/lib/mysql/' --no-watch
        sleep 10
        sudo mysql <<EOF
        CREATE DATABASE ddnet;
        CREATE USER 'ddnet'@'localhost' IDENTIFIED BY 'thebestpassword';
        GRANT ALL PRIVILEGES ON ddnet.* TO 'ddnet'@'localhost';
        FLUSH PRIVILEGES;
        EOF

    - name: Prepare macOS
      if: contains(matrix.os, 'macOS')
      run: |
        brew update || true
        brew install pkg-config sdl2 ffmpeg ninja molten-vk vulkan-headers glslang spirv-tools rust || true
        brew upgrade freetype
        pip3 install --break-system-packages dmgbuild
        echo /Library/Frameworks/Python.framework/Versions/3.12/bin >> $GITHUB_PATH
        sudo rm -rf /Library/Developer/CommandLineTools

    - name: Prepare Windows MinGW
      if: contains(matrix.os, 'windows') && contains(matrix.name, 'mingw')
      run: |
        rustup toolchain install stable-x86_64-pc-windows-gnu
        rustup target add x86_64-pc-windows-gnu
        rustup default stable-x86_64-pc-windows-gnu
        rustup set default-host x86_64-pc-windows-gnu

    - name: Cache Rust dependencies
      uses: Swatinem/rust-cache@v2
      with:
          workspaces: |
              src/mastersrv
              src/masterping
              ./

    - name: Build mastersrv
      if: ${{ !contains(matrix.os, 'ubuntu-22.04') }}
      run: |
        cd src/mastersrv
        cargo build

    - name: Build masterping
      if: ${{ !contains(matrix.os, 'ubuntu-22.04') }}
      run: |
        cd src/masterping
        cargo build

    - name: Build in debug mode
      run: |
        mkdir debug
        cd debug
        ${{ matrix.cmake-path }}cmake --version
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.cmake-init-env }} ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Debug -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. ..
        ${{ matrix.workaround-issue10203-run-after-configure }}
        ${{ matrix.cmake-path }}cmake --build . --config Debug --target everything

    - name: Test debug
      run: |
        cd debug
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests

    - name: Run debug server
      run: |
        cd debug
        ./DDNet-Server shutdown

    - name: Build in release mode
      run: |
        mkdir release
        cd release
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.cmake-init-env }} ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Release -Werror=dev -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. ..
        ${{ matrix.workaround-issue10203-run-after-configure }}
        ${{ matrix.cmake-path }}cmake --build . --config Release --target everything

    - name: Test release
      run: |
        cd release
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests

    - name: Run release server
      run: |
        cd release
        ./DDNet-Server shutdown

    - name: Build headless client
      if: contains(matrix.os, 'ubuntu-latest')
      run: |
        mkdir headless
        cd headless
        ${{ matrix.cmake-path }}cmake -E env CXXFLAGS="--coverage -Werror" ${{ matrix.cmake-path }}cmake -E env LDFLAGS="--coverage -Werror" ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DHEADLESS_CLIENT=ON -DCMAKE_BUILD_TYPE=Debug -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. ..
        ${{ matrix.workaround-issue10203-run-after-configure }}
        ${{ matrix.cmake-path }}cmake -E env RUSTFLAGS="-Clink-arg=--coverage" ${{ matrix.cmake-path }}cmake --build . --config Debug

    - name: Test headless client (unit tests)
      if: contains(matrix.os, 'ubuntu-latest')
      run: |
        cd headless
        ${{ matrix.cmake-path }}cmake -E env RUSTFLAGS="-Clink-arg=--coverage" RUSTDOCFLAGS="-Clink-arg=--coverage" ${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests

    - name: Upload Codecov report (unit tests)
      if: contains(matrix.os, 'ubuntu-latest')
      uses: codecov/codecov-action@v4
      with:
        flags: unittests

    - name: Build in release mode with debug info and all features on
      if: contains(matrix.name, 'fancy')
      run: |
        mkdir fancy
        cd fancy
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.cmake-init-env }} ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. -DANTIBOT=ON -DWEBSOCKETS=ON ..
        ${{ matrix.workaround-issue10203-run-after-configure }}
        ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target everything

    - name: Test fancy
      if: contains(matrix.name, 'fancy')
      run: |
        cd fancy
        ${{ matrix.cmake-path }}cmake -E env ${{ matrix.gtest-env }} ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests

    - name: Run fancy server
      if: contains(matrix.name, 'fancy')
      run: |
        cd fancy
        ./DDNet-Server shutdown

    - name: Run integration tests with Valgrind's Memcheck
      if: contains(matrix.os, 'ubuntu-latest')
      run: |
        # Remove old coverage data:
        find headless -name '*.gcno' -o -name '*.gcda' -delete
        cp src/mastersrv/target/debug/mastersrv headless
        python scripts/integration_test.py --test-mastersrv --valgrind-memcheck headless

    - name: Upload Codecov report (integration tests)
      if: contains(matrix.os, 'ubuntu-latest')
      uses: codecov/codecov-action@v4
      with:
        flags: integrationtests

    - name: Package
      run: |
        cd release
        ${{ matrix.cmake-path }}cmake --build . --config Release --target package_default
        mkdir artifacts
        mv ${{ matrix.package-file }} artifacts

    - name: Upload Artifacts
      uses: actions/upload-artifact@v4
      with:
        name: ddnet-${{ matrix.name }}
        path: release/artifacts

  build-android:
    runs-on: ubuntu-24.04
    env:
      CARGO_HTTP_MULTIPLEXING: false
    steps:
    - uses: actions/checkout@v4
      with:
        submodules: recursive

    - name: Validate Gradle Wrapper
      uses: gradle/actions/wrapper-validation@v4

    - name: Prepare Linux
      run: |
        sudo apt-get update -y
        sudo apt-get install cmake ninja-build openjdk-21-jdk p7zip-full curl glslang-tools openssl
        cargo install cargo-ndk
        rustup target add armv7-linux-androideabi
        rustup target add i686-linux-android
        rustup target add aarch64-linux-android
        rustup target add x86_64-linux-android
        mkdir ~/Android
        cd ~/Android
        mkdir Sdk
        cd Sdk
        mkdir ndk
        cd ndk
        wget --quiet https://dl.google.com/android/repository/android-ndk-r26d-linux.zip
        unzip android-ndk-r26d-linux.zip
        rm android-ndk-r26d-linux.zip
        cd ~/Android/Sdk
        mkdir build-tools
        cd build-tools
        wget --quiet https://dl.google.com/android/repository/build-tools_r30.0.3-linux.zip
        unzip build-tools_r30.0.3-linux.zip
        rm build-tools_r30.0.3-linux.zip
        mv android-11 30.0.3
        cd ~/Android/Sdk
        mkdir cmdline-tools
        cd cmdline-tools
        wget --quiet https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
        unzip commandlinetools-linux-11076708_latest.zip
        rm commandlinetools-linux-11076708_latest.zip
        mv cmdline-tools latest
        yes | latest/bin/sdkmanager --licenses

    - name: Cache Rust dependencies
      uses: Swatinem/rust-cache@v2
      with:
          workspaces: |
              src/mastersrv
              src/masterping
              ./

    - name: Build Android app
      env:
        TW_KEY_NAME: /home/runner/DDNet.jks
        TW_KEY_ALIAS: DDNet-Key
      run: |
        export TW_KEY_PW="$(openssl rand -base64 32)"
        keytool -genkey -v -keystore "$TW_KEY_NAME" -keyalg RSA -keysize 2048 -validity 10000 -alias "$TW_KEY_ALIAS" -storepass "$TW_KEY_PW" -dname "CN=DDNet CI, OU=DDNet, O=DDNet"
        mkdir build-android
        scripts/android/cmake_android.sh all DDNet org.ddnet.client Release build-android
        mkdir artifacts
        mv build-android/DDNet.apk artifacts

    - name: Upload Artifacts
      uses: actions/upload-artifact@v4
      with:
        name: ddnet-android
        path: artifacts
