name: Build and Release KytyPS5

on:
  workflow_dispatch:
  push:
    branches: [ main, master ]
  pull_request:

jobs:
  windows:
    name: Build KytyPS5 (Windows)
    runs-on: windows-2022

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

      - name: Setup Visual Studio environment
        uses: ilammy/msvc-dev-cmd@v1

      - name: Setup Ninja
        uses: seanmiddleditch/gha-setup-ninja@v5

      - name: Locate vcpkg
        id: vcpkg
        shell: pwsh
        run: |
          $vcpkgRoot = Split-Path (Get-Command vcpkg).Source
          $portHash = (Get-FileHash "$vcpkgRoot\ports\glslang\vcpkg.json" -Algorithm SHA256).Hash
          "root=$vcpkgRoot" |
            Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
          "port_hash=$($portHash.ToLowerInvariant())" |
            Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append

      - name: Cache glslang
        id: glslang_cache
        uses: actions/cache@v4
        with:
          path: ${{ steps.vcpkg.outputs.root }}\installed\x64-windows
          key: glslang-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkg.outputs.port_hash }}-tools-opt

      - name: Install glslang
        shell: pwsh
        run: |
          $vcpkgRoot = "${{ steps.vcpkg.outputs.root }}"
          if ("${{ steps.glslang_cache.outputs.cache-hit }}" -ne "true") {
            vcpkg install glslang[tools,opt]:x64-windows
          }
          "$vcpkgRoot\installed\x64-windows\tools\glslang" |
            Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

      - name: Install Qt 6.10.3
        uses: jurplel/install-qt-action@v4
        with:
          version: "6.10.3"
          host: windows
          target: desktop
          arch: win64_msvc2022_64
          cache: true

      - name: Verify toolchain
        shell: cmd
        run: |
          git --version
          cmake --version
          ninja --version
          clang-cl --version
          glslangValidator --version
          echo Qt6_DIR=%Qt6_DIR%

      - name: Configure
        shell: cmd
        run: |
          cmake -S src -B _Build/windows ^
            -G Ninja ^
            -DCMAKE_BUILD_TYPE=Release ^
            -DCMAKE_C_COMPILER=clang-cl ^
            -DCMAKE_CXX_COMPILER=clang-cl ^
            -DCMAKE_PREFIX_PATH="%Qt6_DIR%"

      - name: Build
        shell: cmd
        run: |
          cmake --build _Build/windows --target launcher virtual_memory_allocation_tests --parallel

      - name: Test
        shell: cmd
        run: |
          ctest --test-dir _Build/windows --output-on-failure -R "^virtual_memory_allocation$"

      - name: Install
        shell: cmd
        run: |
          cmake --install _Build/windows --prefix _Build/windows/install

      - name: Upload Windows artifacts
        uses: actions/upload-artifact@v4
        with:
          name: KytyPS5-Windows-x64
          path: _Build/windows/install/**
          if-no-files-found: error

  macos:
    name: Build KytyPS5 (macOS)
    runs-on: macos-15

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

      - name: Select Xcode 26
        shell: bash
        run: |
          xcode_path="$(ls -d /Applications/Xcode_26*.app | tail -n 1)"
          test -n "$xcode_path"
          sudo xcode-select --switch "$xcode_path/Contents/Developer"
          xcodebuild -version

      - name: Install build dependencies
        shell: bash
        run: brew install glslang ninja

      - name: Install Qt 6.10.3
        uses: jurplel/install-qt-action@v4
        with:
          version: "6.10.3"
          host: mac
          target: desktop
          arch: clang_64
          cache: true

      - name: Verify toolchain
        shell: bash
        run: |
          git --version
          cmake --version
          ninja --version
          clang++ --version
          glslangValidator --version
          echo "Host architecture: $(uname -m)"
          echo "QT_ROOT_DIR=$QT_ROOT_DIR"

      - name: Configure
        shell: bash
        run: |
          cmake -S src -B _Build/macos \
            -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_OSX_ARCHITECTURES=x86_64 \
            -DCMAKE_C_COMPILER=clang \
            -DCMAKE_CXX_COMPILER=clang++ \
            -DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"

      - name: Build
        shell: bash
        run: |
          cmake --build _Build/macos \
            --target launcher virtual_memory_allocation_tests \
            --parallel

      - name: Test
        shell: bash
        run: |
          ctest --test-dir _Build/macos --output-on-failure \
            -R '^virtual_memory_allocation$'

      - name: Install
        shell: bash
        run: |
          cmake --install _Build/macos --prefix _Build/macos/install

      - name: Bundle MoltenVK
        shell: bash
        env:
          MOLTENVK_VERSION: v1.4.2
          MOLTENVK_SHA256: f95765a6229cb7b915990a2890ce12ebe36a730b021545d3d52ae69ce4c4024e
        run: |
          archive="$RUNNER_TEMP/MoltenVK-macos.tar"
          package="$RUNNER_TEMP/MoltenVK"
          curl --fail --location --retry 3 \
            --output "$archive" \
            "https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
          echo "$MOLTENVK_SHA256  $archive" | shasum -a 256 --check
          tar -xf "$archive" -C "$RUNNER_TEMP"
          install -m 755 \
            "$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
            _Build/macos/install/libMoltenVK.dylib
          install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
          codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib

      - name: Verify artifacts
        shell: bash
        run: |
          file _Build/macos/install/launcher
          file _Build/macos/install/kyty_emulator
          file _Build/macos/install/libMoltenVK.dylib
          lipo _Build/macos/install/launcher -verify_arch x86_64
          lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
          lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
          codesign --verify --strict _Build/macos/install/kyty_emulator
          codesign --verify --strict _Build/macos/install/libMoltenVK.dylib

      - name: Upload macOS artifacts
        uses: actions/upload-artifact@v4
        with:
          name: KytyPS5-macOS-x86_64
          path: _Build/macos/install/**
          if-no-files-found: error

  linux:
    name: Build KytyPS5 (Linux)
    runs-on: ubuntu-24.04

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

      - name: Install build dependencies
        shell: bash
        run: |
          sudo apt-get update
          sudo apt-get install --no-install-recommends --yes \
            clang \
            glslang-tools \
            libasound2-dev \
            libdbus-1-dev \
            libgl1-mesa-dev \
            libpulse-dev \
            libudev-dev \
            libwayland-dev \
            libx11-dev \
            libxcursor-dev \
            libxext-dev \
            libxfixes-dev \
            libxi-dev \
            libxkbcommon-dev \
            libxrandr-dev \
            libxss-dev \
            lld \
            ninja-build \
            wayland-protocols

      - name: Install Qt 6.10.3
        uses: jurplel/install-qt-action@v4
        with:
          version: "6.10.3"
          host: linux
          target: desktop
          arch: linux_gcc_64
          cache: true

      - name: Verify toolchain
        shell: bash
        run: |
          git --version
          cmake --version
          ninja --version
          clang++ --version
          ld.lld --version
          glslangValidator --version
          echo "Qt6_DIR=$Qt6_DIR"

      - name: Configure
        shell: bash
        run: |
          mkdir -p _Build
          cmake -S src -B _Build/linux \
            -G Ninja \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_C_COMPILER=clang \
            -DCMAKE_CXX_COMPILER=clang++ \
            -DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
          exit "${PIPESTATUS[0]}"

      - name: Verify SDL2 backends
        shell: bash
        run: |
          status=0
          for feature in SDL_ALSA SDL_PULSEAUDIO SDL_WAYLAND SDL_X11 SDL_LIBUDEV SDL_DBUS; do
            if grep -qE "^--   ${feature} +\\(Wanted: ON\\): ON" _Build/configure.log; then
              echo "ok   ${feature}"
            else
              echo "FAIL ${feature} is not enabled"
              status=1
            fi
          done
          exit "$status"

      - name: Build
        shell: bash
        run: |
          cmake --build _Build/linux \
            --target launcher page_manager_tests memory_tracker_tests \
              virtual_memory_allocation_tests \
            --parallel

      - name: Test
        shell: bash
        run: |
          ctest --test-dir _Build/linux --output-on-failure \
            -R '^(page_manager|memory_tracker|virtual_memory_allocation)$'

      - name: Install
        shell: bash
        run: |
          cmake --install _Build/linux --prefix _Build/linux/install

      - name: Verify artifacts
        shell: bash
        run: |
          file _Build/linux/install/launcher
          file _Build/linux/install/kyty_emulator
          file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
          ldd _Build/linux/install/kyty_emulator > /dev/null
          readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
          while IFS= read -r -d '' binary; do
            while read -r dependency; do
              test -e "_Build/linux/install/lib/$dependency"
            done < <(
              readelf -d "$binary" |
                sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
            )
          done < <(
            find _Build/linux/install/launcher _Build/linux/install/plugins \
              -type f \( -name launcher -o -name '*.so' \) -print0
          )
          _Build/linux/install/kyty_emulator --help > /dev/null

      - name: Upload Linux artifacts
        uses: actions/upload-artifact@v4
        with:
          name: KytyPS5-Linux-x86_64
          path: _Build/linux/install/**
          if-no-files-found: error

  release:
    name: Release KytyPS5
    if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
    needs: [windows, macos, linux]
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download builds
        uses: actions/download-artifact@v4
        with:
          path: artifacts

      - name: Set release name
        shell: bash
        run: |
          echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"

      - name: Package builds
        shell: bash
        run: |
          windows_dir="artifacts/KytyPS5-Windows-x64"
          macos_dir="artifacts/KytyPS5-macOS-x86_64"
          linux_dir="artifacts/KytyPS5-Linux-x86_64"
          test -d "$windows_dir"
          test -d "$macos_dir"
          test -d "$linux_dir"
          chmod a+x \
            "$macos_dir/launcher" \
            "$macos_dir/kyty_emulator" \
            "$macos_dir/libMoltenVK.dylib" \
            "$linux_dir/launcher" \
            "$linux_dir/kyty_emulator"
          (
            cd "$windows_dir"
            zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
          )
          (
            cd "$macos_dir"
            zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
          )
          tar -C "$linux_dir" -czf \
            "$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .

      - name: Create release
        shell: bash
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          assets=(
            "$RELEASE_NAME-Windows-x64.zip"
            "$RELEASE_NAME-macOS-x86_64.zip"
            "$RELEASE_NAME-Linux-x86_64.tar.gz"
          )
          if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
            gh release upload "$RELEASE_NAME" "${assets[@]}" \
              --clobber \
              --repo "$GITHUB_REPOSITORY"
          else
            gh release create "$RELEASE_NAME" "${assets[@]}" \
              --generate-notes \
              --repo "$GITHUB_REPOSITORY" \
              --target "$GITHUB_SHA" \
              --title "$RELEASE_NAME"
          fi
