---
name: CI

on:
  pull_request:
    branches: [master]
    types: [opened, synchronize, reopened]
  push:
    branches: [master]
  workflow_dispatch:

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

jobs:
  github_env:
    name: GitHub Env Debug
    runs-on: ubuntu-latest

    steps:
      - name: Dump github context
        run: echo "$GITHUB_CONTEXT"
        shell: bash
        env:
          GITHUB_CONTEXT: ${{ toJson(github) }}

  setup_release:
    name: Setup Release
    outputs:
      publish_release: ${{ steps.setup_release.outputs.publish_release }}
      release_body: ${{ steps.setup_release.outputs.release_body }}
      release_commit: ${{ steps.setup_release.outputs.release_commit }}
      release_generate_release_notes: ${{ steps.setup_release.outputs.release_generate_release_notes }}
      release_tag: ${{ steps.setup_release.outputs.release_tag }}
      release_version: ${{ steps.setup_release.outputs.release_version }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Release
        id: setup_release
        uses: LizardByte/setup-release-action@v2024.608.210128
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

  setup_flatpak_matrix:
    name: Setup Flatpak Matrix
    runs-on: ubuntu-latest
    steps:
      - name: Set release details
        id: flatpak_matrix
        # https://www.cynkra.com/blog/2020-12-23-dynamic-gha
        run: |
          # determine which architectures to build
          if [[ "${{ github.event_name }}" == "push" ]]; then
            matrix=$((
              echo '{ "arch" : ["x86_64", "aarch64"] }'
            ) | jq -c .)
          else
            matrix=$((
              echo '{ "arch" : ["x86_64"] }'
            ) | jq -c .)
          fi

          echo $matrix
          echo $matrix | jq .
          echo "matrix=$matrix" >> $GITHUB_OUTPUT

    outputs:
      matrix: ${{ steps.flatpak_matrix.outputs.matrix }}

  build_linux_flatpak:
    name: Linux Flatpak
    runs-on: ubuntu-22.04
    needs: [setup_release, setup_flatpak_matrix]
    strategy:
      fail-fast: false  # false to test all, true to fail entire job if any fail
      matrix: ${{fromJson(needs.setup_flatpak_matrix.outputs.matrix)}}

    steps:
      - name: Maximize build space
        uses: easimon/maximize-build-space@v10
        with:
          root-reserve-mb: 10240
          remove-dotnet: 'true'
          remove-android: 'true'
          remove-haskell: 'true'
          remove-codeql: 'true'
          remove-docker-images: 'true'

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

      - name: Setup Dependencies Linux Flatpak
        run: |
          PLATFORM_VERSION=22.08

          sudo apt-get update -y
          sudo apt-get install -y \
            cmake \
            flatpak \
            qemu-user-static

          sudo su $(whoami) -c "flatpak --user remote-add --if-not-exists flathub \
            https://flathub.org/repo/flathub.flatpakrepo"

          sudo su $(whoami) -c "flatpak --user install -y flathub \
            org.flatpak.Builder \
            org.freedesktop.Platform/${{ matrix.arch }}/${PLATFORM_VERSION} \
            org.freedesktop.Sdk/${{ matrix.arch }}/${PLATFORM_VERSION} \
            org.freedesktop.Sdk.Extension.node18/${{ matrix.arch }}/${PLATFORM_VERSION} \
            org.freedesktop.Sdk.Extension.vala/${{ matrix.arch }}/${PLATFORM_VERSION} \
            "

      - name: Cache Flatpak build
        uses: actions/cache@v4
        with:
          path: ./build/.flatpak-builder
          key: flatpak-${{ matrix.arch }}-${{ github.sha }}
          restore-keys: |
            flatpak-${{ matrix.arch }}-

      - name: Configure Flatpak Manifest
        run: |
          # variables for manifest
          branch="${{ github.head_ref }}"
          commit=${{ needs.setup_release.outputs.release_commit }}

          # check the branch variable
          if [ -z "$branch" ]
          then
            echo "This is a PUSH event"
            branch=${{ github.ref_name }}
            build_version=${{ needs.setup_release.outputs.release_tag }}
            clone_url=${{ github.event.repository.clone_url }}
          else
            echo "This is a PR event"
            clone_url=${{ github.event.pull_request.head.repo.clone_url }}
          fi
          echo "Branch: ${branch}"
          echo "Commit: ${commit}"
          echo "Clone URL: ${clone_url}"

          mkdir -p build
          mkdir -p artifacts

          cd build
          cmake -DGITHUB_CLONE_URL=${clone_url} \
            -DBUILD_VERSION=${build_version} \
            -DGITHUB_BRANCH=${branch} \
            -DGITHUB_COMMIT=${commit} \
            -DSUNSHINE_CONFIGURE_FLATPAK_MAN=ON \
            -DSUNSHINE_CONFIGURE_ONLY=ON \
            ..

      - name: Build Linux Flatpak
        working-directory: build
        run: |
          sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \
            --stop-at=cuda build-sunshine dev.lizardbyte.sunshine.yml'
          cp -r .flatpak-builder copy-of-flatpak-builder
          sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --arch=${{ matrix.arch }} --repo=repo --force-clean \
            build-sunshine dev.lizardbyte.sunshine.yml'
          rm -rf .flatpak-builder
          mv copy-of-flatpak-builder .flatpak-builder
          sudo su $(whoami) -c 'flatpak build-bundle --arch=${{ matrix.arch }} ./repo \
            ../artifacts/sunshine_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine'
          sudo su $(whoami) -c 'flatpak build-bundle --runtime --arch=${{ matrix.arch }} ./repo \
            ../artifacts/sunshine_debug_${{ matrix.arch }}.flatpak dev.lizardbyte.sunshine.Debug'

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: sunshine-linux-flatpak-${{ matrix.arch }}
          path: artifacts/

      - name: Create/Update GitHub Release
        if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
        uses: LizardByte/create-release-action@v2024.614.221009
        with:
          allowUpdates: true
          body: ${{ needs.setup_release.outputs.release_body }}
          discussionCategory: announcements
          generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
          name: ${{ needs.setup_release.outputs.release_tag }}
          prerelease: true
          tag: ${{ needs.setup_release.outputs.release_tag }}
          token: ${{ secrets.GH_BOT_TOKEN }}

  build_linux:
    name: Linux ${{ matrix.type }}
    runs-on: ubuntu-${{ matrix.dist }}
    needs: [setup_release]
    strategy:
      fail-fast: false  # false to test all, true to fail entire job if any fail
      matrix:
        include:  # package these differently
          - type: AppImage
            EXTRA_ARGS: '-DSUNSHINE_BUILD_APPIMAGE=ON'
            dist: 22.04

    steps:
      - name: Maximize build space
        uses: easimon/maximize-build-space@v10
        with:
          root-reserve-mb: 30720
          remove-dotnet: 'true'
          remove-android: 'true'
          remove-haskell: 'true'
          remove-codeql: 'true'
          remove-docker-images: 'true'

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

      - name: Install wget
        run: |
          sudo apt-get update -y
          sudo apt-get install -y \
            wget

      - name: Install CUDA
        env:
          CUDA_VERSION: 11.8.0
          CUDA_BUILD: 520.61.05
        timeout-minutes: 4
        run: |
          url_base="https://developer.download.nvidia.com/compute/cuda/${CUDA_VERSION}/local_installers"
          url="${url_base}/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux.run"
          sudo wget -q -O /root/cuda.run ${url}
          sudo chmod a+x /root/cuda.run
          sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr/local/cuda --no-opengl-libs --no-man-page --no-drm
          sudo rm /root/cuda.run

      - name: Setup Dependencies Linux
        timeout-minutes: 5
        run: |
          # allow newer gcc
          sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y

          # allow libfuse2 for appimage on 22.04
          sudo add-apt-repository universe

          # libx11-xcb-dev and libxcb-dri3-dev are required for building libva
          sudo apt-get install -y \
            build-essential \
            cmake \
            gcc-10 \
            g++-10 \
            libayatana-appindicator3-dev \
            libavdevice-dev \
            libcap-dev \
            libcurl4-openssl-dev \
            libdrm-dev \
            libevdev-dev \
            libfuse2 \
            libminiupnpc-dev \
            libnotify-dev \
            libnuma-dev \
            libopus-dev \
            libpulse-dev \
            libssl-dev \
            libvdpau-dev \
            libwayland-dev \
            libx11-dev \
            libx11-xcb-dev \
            libxcb-dri3-dev \
            libxcb-shm0-dev \
            libxcb-xfixes0-dev \
            libxcb1-dev \
            libxfixes-dev \
            libxrandr-dev \
            libxtst-dev \
            python3

          # clean apt cache
          sudo apt-get clean
          sudo rm -rf /var/lib/apt/lists/*

          # Update gcc alias
          # https://stackoverflow.com/a/70653945/11214013
          sudo update-alternatives --install \
            /usr/bin/gcc gcc /usr/bin/gcc-10 100 \
            --slave /usr/bin/g++ g++ /usr/bin/g++-10 \
            --slave /usr/bin/gcov gcov /usr/bin/gcov-10 \
            --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
            --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10

      - name: Setup python
        id: python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Build latest libva
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        timeout-minutes: 5
        run: |
          gh release download --archive=tar.gz --repo=intel/libva
          tar xzf libva-*.tar.gz && rm libva-*.tar.gz
          cd libva-*
          ./autogen.sh --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
            --enable-drm \
            --enable-x11 \
            --enable-glx \
            --enable-wayland \
            --without-legacy # emgd, nvctrl, fglrx
          make -j $(nproc)
          sudo make install
          cd .. && rm -rf libva-*

      - name: Build Linux
        env:
          BRANCH: ${{ github.head_ref || github.ref_name }}
          BUILD_VERSION: ${{ needs.setup_release.outputs.release_tag }}
          COMMIT: ${{ needs.setup_release.outputs.release_commit }}
        timeout-minutes: 10
        run: |
          echo "nproc: $(nproc)"

          mkdir -p build
          mkdir -p artifacts

          cd build
          cmake \
            -DBUILD_WERROR=ON \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_CUDA_COMPILER:PATH=/usr/local/cuda/bin/nvcc \
            -DCMAKE_INSTALL_PREFIX=/usr \
            -DSUNSHINE_ASSETS_DIR=share/sunshine \
            -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
            -DSUNSHINE_ENABLE_WAYLAND=ON \
            -DSUNSHINE_ENABLE_X11=ON \
            -DSUNSHINE_ENABLE_DRM=ON \
            -DSUNSHINE_ENABLE_CUDA=ON \
            ${{ matrix.EXTRA_ARGS }} \
            ..
          make -j $(expr $(nproc) - 1)  # use all but one core

      - name: Set AppImage Version
        if: |
          matrix.type == 'AppImage'
        run: |
          version=${{ needs.setup_release.outputs.release_tag }}
          echo "VERSION=${version}" >> $GITHUB_ENV

      - name: Package Linux - AppImage
        if: ${{ matrix.type == 'AppImage' }}
        working-directory: build
        run: |
          # install sunshine to the DESTDIR
          make install DESTDIR=AppDir

          # custom AppRun file
          cp -f ../packaging/linux/AppImage/AppRun ./AppDir/
          chmod +x ./AppDir/AppRun

          # variables
          DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}"
          ICON_FILE="${ICON_FILE:-sunshine.png}"

          # AppImage
          # https://docs.appimage.org/packaging-guide/index.html
          wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
          chmod +x linuxdeploy-x86_64.AppImage

          # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk
          sudo apt-get install libgtk-3-dev librsvg2-dev -y
          wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh
          chmod +x linuxdeploy-plugin-gtk.sh
          export DEPLOY_GTK_VERSION=3

          ./linuxdeploy-x86_64.AppImage \
            --appdir ./AppDir \
            --plugin gtk \
            --executable ./sunshine \
            --icon-file "../$ICON_FILE" \
            --desktop-file "./$DESKTOP_FILE" \
            --output appimage

          # move
          mv Sunshine*.AppImage ../artifacts/sunshine.AppImage

          # permissions
          chmod +x ../artifacts/sunshine.AppImage

      - name: Delete CUDA
        # free up space on the runner
        run: |
          sudo rm -rf /usr/local/cuda

      - name: Verify AppImage
        if: ${{ matrix.type == 'AppImage' }}
        run: |
          wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
          chmod +x appimagelint-x86_64.AppImage

          ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: sunshine-linux-${{ matrix.type }}-${{ matrix.dist }}
          path: artifacts/

      - name: Install test deps
        run: |
          sudo apt-get update -y
          sudo apt-get install -y \
            doxygen \
            graphviz \
            python3-venv \
            x11-xserver-utils \
            xvfb

          # clean apt cache
          sudo apt-get clean
          sudo rm -rf /var/lib/apt/lists/*

      - name: Run tests
        id: test
        working-directory: build/tests
        run: |
          export DISPLAY=:1
          Xvfb ${DISPLAY} -screen 0 1024x768x24 &

          ./test_sunshine --gtest_color=yes

      - name: Generate gcov report
        # any except canceled or skipped
        if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
        id: test_report
        working-directory: build
        run: |
          ${{ steps.python.outputs.python-path }} -m pip install gcovr
          ${{ steps.python.outputs.python-path }} -m gcovr -r .. \
            --exclude-noncode-lines \
            --exclude-throw-branches \
            --exclude-unreachable-branches \
            --exclude '.*tests/.*' \
            --exclude '.*third-party/.*' \
            --xml-pretty \
            -o coverage.xml

      - name: Upload coverage
        # any except canceled or skipped
        if: >-
          always() &&
          (steps.test_report.outcome == 'success') &&
          startsWith(github.repository, 'LizardByte/')
        uses: codecov/codecov-action@v4
        with:
          disable_search: true
          fail_ci_if_error: true
          files: ./build/coverage.xml
          flags: ${{ runner.os }}
          token: ${{ secrets.CODECOV_TOKEN }}
          verbose: true

      - name: Create/Update GitHub Release
        if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
        uses: LizardByte/create-release-action@v2024.614.221009
        with:
          allowUpdates: true
          body: ${{ needs.setup_release.outputs.release_body }}
          discussionCategory: announcements
          generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
          name: ${{ needs.setup_release.outputs.release_tag }}
          prerelease: true
          tag: ${{ needs.setup_release.outputs.release_tag }}
          token: ${{ secrets.GH_BOT_TOKEN }}

  build_homebrew:
    needs: [setup_release]
    strategy:
      fail-fast: false  # false to test all, true to fail entire job if any fail
      matrix:
        include:
          # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
          # while GitHub has larger macOS runners, they are not available for our repos :(
          - os_version: "12"
            os_name: "macos"
          - os_version: "13"
            os_name: "macos"
          - os_version: "14"
            os_name: "macos"
          - os_version: "latest"
            os_name: "ubuntu"
          - os_version: "latest"  # this job will only configure the formula for release, no validation
            os_name: "ubuntu"
            release: true
    name: Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }})
    runs-on: ${{ matrix.os_name }}-${{ matrix.os_version }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure formula
        run: |
          # variables for formula
          branch="${{ github.head_ref }}"
          commit=${{ needs.setup_release.outputs.release_commit }}

          # check the branch variable
          if [ -z "$branch" ]
          then
            echo "This is a PUSH event"
            build_version=${{ needs.setup_release.outputs.release_tag }}
            clone_url=${{ github.event.repository.clone_url }}
            branch="${{ github.ref_name }}"
            default_branch="${{ github.event.repository.default_branch }}"

            if [ "${{ matrix.release }}" == "true" ]; then
              # we will publish the formula with the release tag
              tag="${{ needs.setup_release.outputs.release_tag }}"
              version="${{ needs.setup_release.outputs.release_version }}"
            else
              tag="${{ github.ref_name }}"
              version="0.0.${{ github.run_number }}"
            fi
          else
            echo "This is a PR event"
            build_version="0.0.${{ github.event.number }}"
            clone_url=${{ github.event.pull_request.head.repo.clone_url }}
            branch="${{ github.event.pull_request.head.ref }}"
            default_branch="${{ github.event.pull_request.head.repo.default_branch }}"
            tag="${{ github.event.pull_request.head.ref }}"
            version="0.0.${{ github.event.number }}"
          fi
          echo "Branch: ${branch}"
          echo "Clone URL: ${clone_url}"
          echo "Tag: ${tag}"

          mkdir build
          cd build
          cmake \
            -DBUILD_VERSION="${build_version}" \
            -DFORMULA_VERSION="${version}" \
            -DGITHUB_BRANCH="${branch}" \
            -DGITHUB_COMMIT="${commit}" \
            -DGITHUB_CLONE_URL="${clone_url}" \
            -DGITHUB_DEFAULT_BRANCH="${default_branch}" \
            -DGITHUB_TAG="${tag}" \
            -DSUNSHINE_CONFIGURE_HOMEBREW=ON \
            -DSUNSHINE_CONFIGURE_ONLY=ON \
            ..
          cd ..

          # copy formula to artifacts
          mkdir -p homebrew
          cp -f ./build/sunshine.rb ./homebrew/sunshine.rb

          # testing
          cat ./homebrew/sunshine.rb

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

      - name: Setup Xvfb
        if: |
          matrix.release != true &&
          runner.os == 'Linux'
        run: |
          sudo apt-get update -y
          sudo apt-get install -y \
            xvfb

          export DISPLAY=:1
          Xvfb ${DISPLAY} -screen 0 1024x768x24 &

          echo "DISPLAY=${DISPLAY}" >> $GITHUB_ENV

      - name: Validate Homebrew Formula
        if: |
          matrix.release != true
        uses: LizardByte/homebrew-release-action@v2024.612.21058
        with:
          formula_file: ${{ github.workspace }}/homebrew/sunshine.rb
          git_email: ${{ secrets.GH_BOT_EMAIL }}
          git_username: ${{ secrets.GH_BOT_NAME }}
          publish: false
          token: ${{ secrets.GH_BOT_TOKEN }}
          validate: true

      - name: Create/Update GitHub Release
        if: >-
          matrix.release &&
          needs.setup_release.outputs.publish_release == 'true'
        uses: LizardByte/create-release-action@v2024.614.221009
        with:
          allowUpdates: true
          artifacts: '${{ github.workspace }}/homebrew/*'
          body: ${{ needs.setup_release.outputs.release_body }}
          discussionCategory: announcements
          generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
          name: ${{ needs.setup_release.outputs.release_tag }}
          prerelease: true
          tag: ${{ needs.setup_release.outputs.release_tag }}
          token: ${{ secrets.GH_BOT_TOKEN }}

  build_mac_port:
    needs: [setup_release]
    strategy:
      fail-fast: false  # false to test all, true to fail entire job if any fail
      matrix:
        include:
          # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
          # while GitHub has larger macOS runners, they are not available for our repos :(
          - os_version: "12"
            release: true
          - os_version: "13"
          - os_version: "14"
    name: Macports (macOS-${{ matrix.os_version }})
    runs-on: macos-${{ matrix.os_version }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Checkout ports
        uses: actions/checkout@v4
        with:
          repository: macports/macports-ports
          fetch-depth: 64
          path: ports

      - name: Checkout mpbb
        uses: actions/checkout@v4
        with:
          repository: macports/mpbb
          path: mpbb

      - name: Setup Dependencies Macports
        run: |
          # install dependencies using homebrew
          brew install cmake

      - name: Setup python
        id: python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Configure Portfile
        run: |
          # variables for Portfile
          branch="${{ github.head_ref }}"
          commit=${{ needs.setup_release.outputs.release_commit }}

          # check the branch variable
          if [ -z "$branch" ]
          then
            echo "This is a PUSH event"
            branch="${{ github.ref_name }}"
            build_version=${{ needs.setup_release.outputs.release_tag }}
            clone_url=${{ github.event.repository.clone_url }}
          else
            echo "This is a PR event"
            clone_url=${{ github.event.pull_request.head.repo.clone_url }}
          fi
          echo "Commit: ${commit}"
          echo "Clone URL: ${clone_url}"

          mkdir build
          cd build
          cmake \
            -DBUILD_VERSION=${build_version} \
            -DGITHUB_BRANCH=${branch} \
            -DGITHUB_COMMIT=${commit} \
            -DGITHUB_CLONE_URL=${clone_url} \
            -DSUNSHINE_CONFIGURE_PORTFILE=ON \
            -DSUNSHINE_CONFIGURE_ONLY=ON \
            ..
          cd ..

          # copy Portfile to artifacts
          mkdir -p artifacts
          cp -f ./build/Portfile ./artifacts/

          # copy Portfile to ports
          mkdir -p ./ports/multimedia/Sunshine
          cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile

          # testing
          cat ./artifacts/Portfile

      - name: Bootstrap MacPorts
        run: |
          . ports/.github/workflows/bootstrap.sh

          # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps.
          echo "/opt/mports/bin" >> $GITHUB_PATH
          echo "${PWD}/mpbb" >> $GITHUB_PATH
          echo "/opt/local/bin" >> $GITHUB_PATH
          echo "/opt/local/sbin" >> $GITHUB_PATH

      - name: Run port lint
        run: |
          port -q lint "Sunshine"

      - name: Build port
        env:
          subportlist: ${{ steps.subportlist.outputs.subportlist }}
        id: build
        run: |
          subport="Sunshine"

          workdir="/tmp/mpbb/$subport"
          mkdir -p "$workdir/logs"

          echo "::group::Installing dependencies"
          sudo mpbb \
            --work-dir "$workdir" \
            install-dependencies \
            "$subport"
          echo "::endgroup::"

          echo "::group::Installing ${subport}"
          sudo mpbb \
            --work-dir "$workdir" \
            install-port \
            --source \
            "$subport"
          echo "::endgroup::"

      - name: Build Logs
        if: always()
        run: |
          logfile="/opt/local/var/macports/logs/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/main.log"
          cat "$logfile"
          sudo mv "${logfile}" "${logfile}.bak"

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

      - name: Fix permissions
        run: |
          # https://apple.stackexchange.com/questions/362865/macos-list-apps-authorized-for-full-disk-access
          # https://github.com/actions/runner-images/issues/9529
          # https://github.com/actions/runner-images/pull/9530

          # function to execute sql query for each value
          function execute_sql_query {
            local value=$1
            local dbPath=$2

            echo "Executing SQL query for value: $value"
            sudo sqlite3 "$dbPath" "INSERT OR IGNORE INTO access VALUES($value);"
          }

          # permissions
          declare -a values=(
            "'kTCCServiceAccessibility','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552"
            "'kTCCServiceScreenCapture','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159"
          )
          if [[ "${{ matrix.os_version }}" == "14" ]]; then
            # TCC access table in Sonoma has extra 4 columns: pid, pid_version, boot_uuid, last_reminded
            for i in "${!values[@]}"; do
              values[$i]="${values[$i]},NULL,NULL,'UNUSED',${values[$i]##*,}"
            done
          fi

          # system and user databases
          dbPaths=(
            "/Library/Application Support/com.apple.TCC/TCC.db"
            "$HOME/Library/Application Support/com.apple.TCC/TCC.db"
          )

          for value in "${values[@]}"; do
            for dbPath in "${dbPaths[@]}"; do
              echo "Column names for $dbPath"
              echo "-------------------"
              sudo sqlite3 "$dbPath" "PRAGMA table_info(access);"
              echo "Current permissions for $dbPath"
              echo "-------------------"
              sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
              execute_sql_query "$value" "$dbPath"
              echo "Updated permissions for $dbPath"
              echo "-------------------"
              sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
            done
          done

      - name: Run tests
        id: test
        timeout-minutes: 10
        working-directory:
          /opt/local/var/macports/build/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/work/build/tests
        run: |
          sudo port install \
            doxygen \
            graphviz
          sudo ./test_sunshine --gtest_color=yes

      - name: Generate gcov report
        # any except canceled or skipped
        if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
        id: test_report
        working-directory:
          /opt/local/var/macports/build/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/work
        run: |
          base_dir=$(pwd)
          build_dir=${base_dir}/build

          # get the directory name that starts with Sunshine-*
          dir=$(ls -d Sunshine-*)

          cd ${build_dir}
          ${{ steps.python.outputs.python-path }} -m pip install gcovr
          sudo ${{ steps.python.outputs.python-path }} -m gcovr -r ../${dir} \
            --exclude-noncode-lines \
            --exclude-throw-branches \
            --exclude-unreachable-branches \
            --exclude '.*${dir}/tests/.*' \
            --exclude '.*${dir}/third-party/.*' \
            --gcov-object-directory $(pwd) \
            --verbose \
            --xml-pretty \
            -o ${{ github.workspace }}/build/coverage.xml

      - name: Upload coverage
        # any except canceled or skipped
        if: >-
          always() &&
          (steps.test_report.outcome == 'success') &&
          startsWith(github.repository, 'LizardByte/')
        uses: codecov/codecov-action@v4
        with:
          disable_search: true
          fail_ci_if_error: false  # todo: re-enable this when action is fixed
          files: ./build/coverage.xml
          flags: ${{ runner.os }}-${{ matrix.os_version }}
          token: ${{ secrets.CODECOV_TOKEN }}
          verbose: true

      - name: Create/Update GitHub Release
        if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
        uses: LizardByte/create-release-action@v2024.614.221009
        with:
          allowUpdates: true
          body: ${{ needs.setup_release.outputs.release_body }}
          discussionCategory: announcements
          generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
          name: ${{ needs.setup_release.outputs.release_tag }}
          prerelease: true
          tag: ${{ needs.setup_release.outputs.release_tag }}
          token: ${{ secrets.GH_BOT_TOKEN }}

  build_win:
    name: Windows
    runs-on: windows-2019
    needs: [setup_release]

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

      - name: Prepare tests
        id: prepare-tests
        if: false  # todo: DirectX11 is not available, so even software encoder fails
        run: |
          # function to download and extract a zip file
          function DownloadAndExtract {
            param (
              [string]$Uri,
              [string]$OutFile
            )

            $maxRetries = 5
            $retryCount = 0
            $success = $false

            while (-not $success -and $retryCount -lt $maxRetries) {
              $retryCount++
              Write-Host "Downloading $Uri to $OutFile, attempt $retryCount of $maxRetries"
              try {
                Invoke-WebRequest -Uri $Uri -OutFile $OutFile
                $success = $true
              } catch {
                Write-Host "Attempt $retryCount of $maxRetries failed with error: $($_.Exception.Message). Retrying..."
                Start-Sleep -Seconds 5
              }
            }

            if (-not $success) {
              Write-Host "Failed to download the file after $maxRetries attempts."
              exit 1
            }

            # use .NET to get the base name of the file
            $baseName = (Get-Item $OutFile).BaseName

            # Extract the zip file
            Expand-Archive -Path $OutFile -DestinationPath $baseName
          }

          # virtual display driver
          DownloadAndExtract `
            -Uri "https://www.amyuni.com/downloads/usbmmidd_v2.zip" `
            -OutFile "usbmmidd_v2.zip"

          # install
          Set-Location -Path usbmmidd_v2/usbmmidd_v2
          ./deviceinstaller64 install usbmmidd.inf usbmmidd

          # create the virtual display
          ./deviceinstaller64 enableidd 1

          # move up a directory
          Set-Location -Path ../..

          # install devcon
          DownloadAndExtract `
            -Uri "https://github.com/Drawbackz/DevCon-Installer/releases/download/1.4-rc/Devcon.Installer.zip" `
            -OutFile "Devcon.Installer.zip"
          Set-Location -Path Devcon.Installer
          # hash needs to match OS version
          # https://github.com/Drawbackz/DevCon-Installer/blob/master/devcon_sources.json
          Start-Process -FilePath "./Devcon Installer.exe" -Wait -ArgumentList `
            'install', `
            '-hash', '54004C83EE34F6A55380528A8B29F4C400E61FBB947A19E0AB9E5A193D7D961E', `
            '-addpath', `
            '-update', `
            '-dir', 'C:\Windows\System32'

          # disable Hyper-V Video
          # https://stackoverflow.com/a/59490940
          C:\Windows\System32\devcon.exe disable "VMBUS\{da0a7802-e377-4aac-8e77-0558eb1073f8}"

          # move up a directory
          Set-Location -Path ..

          # multi monitor tool
          DownloadAndExtract `
            -Uri "http://www.nirsoft.net/utils/multimonitortool-x64.zip" `
            -OutFile "multimonitortool.zip"

          # enable the virtual display
          # http://www.nirsoft.net/utils/multi_monitor_tool.html
          Set-Location -Path multimonitortool

          # Original Hyper-V is \\.\DISPLAY1, it will recreate itself as \\.\DISPLAY6 (or something higher than 2)
          # USB Mobile Monitor Virtual Display is \\.\DISPLAY2

          # these don't seem to work if not using runAs
          # todo: do they work if not using runAs?
          Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /enable \\.\DISPLAY2'
          Start-Process powershell -Verb runAs -ArgumentList '-Command ./MultiMonitorTool.exe /SetPrimary \\.\DISPLAY2'

          # wait a few seconds
          Start-Sleep -s 5

          # list monitors
          ./MultiMonitorTool.exe /stext monitor_list.txt

          # wait a few seconds
          Start-Sleep -s 5

          # print the monitor list
          Get-Content -Path monitor_list.txt

      - name: Setup Dependencies Windows
        uses: msys2/setup-msys2@v2
        with:
          msystem: ucrt64
          update: true
          install: >-
            wget

      - name: Update Windows dependencies
        shell: msys2 {0}
        run: |
          # download working curl
          wget https://repo.msys2.org/mingw/ucrt64/mingw-w64-ucrt-x86_64-curl-8.8.0-1-any.pkg.tar.zst

          # install dependencies
          pacman -U --noconfirm mingw-w64-ucrt-x86_64-curl-8.8.0-1-any.pkg.tar.zst
          pacman -Syu --noconfirm --ignore=mingw-w64-ucrt-x86_64-curl \
            doxygen \
            git \
            mingw-w64-ucrt-x86_64-cmake \
            mingw-w64-ucrt-x86_64-cppwinrt \
            mingw-w64-ucrt-x86_64-graphviz \
            mingw-w64-ucrt-x86_64-miniupnpc \
            mingw-w64-ucrt-x86_64-nlohmann-json \
            mingw-w64-ucrt-x86_64-nodejs \
            mingw-w64-ucrt-x86_64-nsis \
            mingw-w64-ucrt-x86_64-onevpl \
            mingw-w64-ucrt-x86_64-openssl \
            mingw-w64-ucrt-x86_64-opus \
            mingw-w64-ucrt-x86_64-toolchain

      - name: Setup python
        # use this instead of msys2 python due to known issues using wheels, https://www.msys2.org/docs/python/
        id: setup-python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Python Path
        id: python-path
        shell: msys2 {0}
        run: |
          # replace backslashes with double backslashes
          python_path=$(echo "${{ steps.setup-python.outputs.python-path }}" | sed 's/\\/\\\\/g')

          # step output
          echo "python-path=${python_path}"
          echo "python-path=${python_path}" >> $GITHUB_OUTPUT

      - name: Build Windows
        shell: msys2 {0}
        env:
          BRANCH: ${{ github.head_ref || github.ref_name }}
          BUILD_VERSION: ${{ needs.setup_release.outputs.release_tag }}
          COMMIT: ${{ needs.setup_release.outputs.release_commit }}
        run: |
          mkdir build
          cd build
          cmake \
            -DBUILD_WERROR=ON \
            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
            -DSUNSHINE_ASSETS_DIR=assets \
            -DTESTS_PYTHON_EXECUTABLE='${{ steps.python-path.outputs.python-path }}' \
            -DTESTS_SOFTWARE_ENCODER_UNAVAILABLE='skip' \
            -G "MinGW Makefiles" \
            ..
          mingw32-make -j$(nproc)

      - name: Package Windows
        shell: msys2 {0}
        run: |
          mkdir -p artifacts
          cd build

          # package
          cpack -G NSIS
          cpack -G ZIP

          # move
          mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows-installer.exe
          mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows-portable.zip

      - name: Run tests
        id: test
        shell: msys2 {0}
        working-directory: build/tests
        run: |
          ./test_sunshine.exe --gtest_color=yes

      - name: Generate gcov report
        # any except canceled or skipped
        if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
        id: test_report
        shell: msys2 {0}
        working-directory: build
        run: |
          ${{ steps.python-path.outputs.python-path }} -m pip install gcovr
          ${{ steps.python-path.outputs.python-path }} -m gcovr -r .. \
            --exclude-noncode-lines \
            --exclude-throw-branches \
            --exclude-unreachable-branches \
            --exclude '.*tests/.*' \
            --exclude '.*third-party/.*' \
            --xml-pretty \
            -o coverage.xml

      - name: Upload coverage
        # any except canceled or skipped
        if: >-
          always() &&
          (steps.test_report.outcome == 'success') &&
          startsWith(github.repository, 'LizardByte/')
        uses: codecov/codecov-action@v4
        with:
          disable_search: true
          fail_ci_if_error: true
          files: ./build/coverage.xml
          flags: ${{ runner.os }}
          token: ${{ secrets.CODECOV_TOKEN }}
          verbose: true

      - name: Package Windows Debug Info
        working-directory: build
        run: |
          # use .dbg file extension for binaries to avoid confusion with real packages
          Get-ChildItem -File -Recurse | `
            % { Rename-Item -Path $_.PSPath -NewName $_.Name.Replace(".exe",".dbg") }

          # save the binaries with debug info
          7z -r `
            "-xr!CMakeFiles" `
            "-xr!cpack_artifacts" `
            a "../artifacts/sunshine-win32-debuginfo.7z" "*.dbg"

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: sunshine-windows
          path: artifacts/

      - name: Create/Update GitHub Release
        if: ${{ needs.setup_release.outputs.publish_release == 'true' }}
        uses: LizardByte/create-release-action@v2024.614.221009
        with:
          allowUpdates: true
          body: ${{ needs.setup_release.outputs.release_body }}
          discussionCategory: announcements
          generateReleaseNotes: ${{ needs.setup_release.outputs.release_generate_release_notes }}
          name: ${{ needs.setup_release.outputs.release_tag }}
          prerelease: true
          tag: ${{ needs.setup_release.outputs.release_tag }}
          token: ${{ secrets.GH_BOT_TOKEN }}
