name: Windows MSYS2 builds

on:
  push:
    paths-ignore:
      - '.clang-format'
      - '.mdl-styles'
      - '*.md'
      - 'docs/**'
      - 'licenses/**'
      - 'website/**'

  pull_request:
    paths-ignore:
      - '.clang-format'
      - '.mdl-styles'
      - '*.md'
      - 'docs/**'
      - 'licenses/**'
      - 'website/**'

  workflow_dispatch:

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

env:
  BUILD_RELEASE_DIR: build/release
  BUILD_DEBUGGER_DIR: build/debugger

jobs:
  build_msys2:
    name: ${{ matrix.conf.name }}
    runs-on: windows-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    strategy:
      matrix:
        conf:
          - name: GCC (MinGW) x86 +tests
            toolchain: ""
            arch: i686
            sys: mingw32
            cc: gcc
            max_warnings: 2
            run_tests: true
            build_flags: -Dbuildtype=debug -Dfluidsynth:tests=false

          - name: GCC (UCRT) x86_64 +debugger
            toolchain: ucrt-
            arch: x86_64
            sys: ucrt64
            cc: gcc
            max_warnings: 2
            build_flags: -Denable_debugger=normal

          - name: Clang x86_64 +debugger
            toolchain: clang-
            arch: x86_64
            sys: clang64
            cc: clang
            max_warnings: 2
            build_flags: -Denable_debugger=normal

          - name: Clang x86_64 +tests
            toolchain: clang-
            arch: x86_64
            sys: clang64
            cc: clang
            max_warnings: 2
            run_tests: true
            build_flags: -Dbuildtype=debug -Dfluidsynth:tests=false

    defaults:
      run:
        shell: msys2 {0}

    steps:
      - name: Disable git line ending conversion
        shell: powershell
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

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

      - name: Setup MSYS2 and install packages
        uses: msys2/setup-msys2@v2
        with:
          msystem: ${{matrix.conf.sys}}
          update: false
          install: >-
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-${{matrix.conf.cc}}
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-meson
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-pkgconf
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-ntldd
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-ncurses
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-glib2
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-iir
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-libpng
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-opusfile
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-libslirp
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-SDL2_net
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-zlib
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-speexdsp
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-python-pip
            mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-fluidsynth

      - shell: msys2 {0}
        run: |
            pacman --noconfirm -U https://repo.msys2.org/mingw/${{matrix.conf.sys}}/mingw-w64-${{matrix.conf.toolchain}}${{matrix.conf.arch}}-SDL2-2.26.5-1-any.pkg.tar.zst

      - name: Cache subprojects
        id:   cache-subprojects
        uses: actions/cache@v4
        with:
          path: subprojects.tar
          key: subprojects-${{ hashFiles('subprojects/*.wrap') }}-1
          enableCrossOsArchive: true

      - name: Update SSL certificates
        shell: pwsh
        run: |
          pip3 install --upgrade pip-system-certs certifi
          $certifiPath = & 'python3' -c 'import certifi; print(certifi.where())' | Out-String
          $env:SSL_CERT_FILE = $certifiPath
          "SSL_CERT_FILE=$certifiPath" >> $env:GITHUB_ENV
          $webclient = New-Object System.Net.WebClient
          $webclient.DownloadString("https://github.com") >$null

      - if:   steps.cache-subprojects.outputs.cache-hit != 'true'
        name: Generate subprojects cache
        run:  scripts/fetch-and-tar-subprojects.sh

      - name: Extract subprojects cache
        run:  scripts/extract-subprojects-tar.sh
        continue-on-error: true
        #
        # As of 2023-06-22, MSYS2's tar warns about being unable to apply
        # directory permission to files, after which it returns a failure code.
        # This is a work-around until tar is fixed.

      - name: Log environment
        run:  ./scripts/log-env.sh

      - name: Setup build
        run: |
          meson setup ${{ matrix.conf.build_flags }} build

      - name: Build
        run: |
          set -xo pipefail
          scripts/retry_command.sh 10 meson compile -C build |& tee build.log
          #
          # Reason for retry is due to intermittent Python symbol extractor failure
          # under MSYS2 reported in bug: https://github.com/msys2/MINGW-packages/issues/11864

      - name: Run tests
        if:   matrix.conf.run_tests
        run:  meson test --num-processes 128 -t 0 -C build --print-errorlogs

      - name: Summarize warnings
        if:   matrix.conf.run_tests != true
        env:
          MAX_WARNINGS: ${{ matrix.conf.max_warnings }}
        run:  python ./scripts/count-warnings.py -lf build.log

  build_msys2_release:
    name: ${{ matrix.conf.name }} Release
    runs-on: windows-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository

    strategy:
      matrix:
        conf:
          - name: Clang x86_64
            arch: x86_64
            sys: clang64

    defaults:
      run:
        shell: msys2 {0}

    steps:
      - name: Disable git line ending conversion
        shell: powershell
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

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

      - name: Setup MSYS2
        uses: msys2/setup-msys2@v2
        with:
          msystem: ${{matrix.conf.sys}}
          update: true
          install: |
            git
            mingw-w64-clang-${{matrix.conf.arch}}-clang
            mingw-w64-clang-${{matrix.conf.arch}}-cmake
            mingw-w64-clang-${{matrix.conf.arch}}-meson
            mingw-w64-clang-${{matrix.conf.arch}}-pkgconf
            mingw-w64-clang-${{matrix.conf.arch}}-ntldd
            mingw-w64-clang-${{matrix.conf.arch}}-ncurses
            mingw-w64-clang-${{matrix.conf.arch}}-glib2
            mingw-w64-clang-${{matrix.conf.arch}}-iir
            mingw-w64-clang-${{matrix.conf.arch}}-libpng
            mingw-w64-clang-${{matrix.conf.arch}}-opusfile
            mingw-w64-clang-${{matrix.conf.arch}}-libslirp
            mingw-w64-clang-${{matrix.conf.arch}}-SDL2_net
            mingw-w64-clang-${{matrix.conf.arch}}-speexdsp
            mingw-w64-clang-${{matrix.conf.arch}}-python-pip
            mingw-w64-clang-${{matrix.conf.arch}}-fluidsynth

      - shell: msys2 {0}
        run: |
            pacman --noconfirm -U https://repo.msys2.org/mingw/${{matrix.conf.sys}}/mingw-w64-clang-${{matrix.conf.arch}}-SDL2-2.26.5-1-any.pkg.tar.zst

      - name: Cache subprojects
        id:   cache-subprojects
        uses: actions/cache@v4
        with:
          path: subprojects.tar
          key: subprojects-${{ hashFiles('subprojects/*.wrap') }}-1
          enableCrossOsArchive: true

      - name: Update SSL certificates
        shell: pwsh
        run: |
          pip3 install --upgrade pip-system-certs certifi
          $certifiPath = & 'python3' -c 'import certifi; print(certifi.where())' | Out-String
          $env:SSL_CERT_FILE = $certifiPath
          "SSL_CERT_FILE=$certifiPath" >> $env:GITHUB_ENV
          $webclient = New-Object System.Net.WebClient
          $webclient.DownloadString("https://github.com") >$null

      - if:   steps.cache-subprojects.outputs.cache-hit != 'true'
        name: Generate subprojects cache
        run:  scripts/fetch-and-tar-subprojects.sh

      - name: Extract subprojects cache
        run:  scripts/extract-subprojects-tar.sh
        continue-on-error: true
        #
        # As of 2023-06-22, MSYS2's tar warns about being unable to apply
        # directory permission to files, after which it returns a failure code.
        # This is a work-around until tar is fixed.

      - name: Log environment
        run:  ./scripts/log-env.sh

      - name: Inject version string
        run: |
          set -x
          VERSION=$(./scripts/get-version.sh version-and-hash)
          echo "VERSION=$VERSION" >> $GITHUB_ENV

      - name: Inject package name
        run: |
          set -x
          echo "PKG_RELEASE=dosbox-staging-windows-msys2-${{ matrix.conf.arch }}-${{ env.VERSION }}" >> $GITHUB_ENV

      - name: Setup release build
        run: |
          meson setup \
            -Db_lto=true \
            -Db_lto_threads=$(nproc) \
            -Dtry_static_libs=mt32emu \
            -Duse_zlib_ng=sse2,ssse3 \
            $BUILD_RELEASE_DIR

      - name: Setup debugger build
        run: |
          meson setup \
            -Db_lto=true \
            -Db_lto_threads=$(nproc) \
            -Denable_debugger=heavy \
            -Dtry_static_libs=mt32emu \
            -Duse_zlib_ng=sse2,ssse3 \
            $BUILD_DEBUGGER_DIR

      - name: Build release
        run: |
          set -euo pipefail
          scripts/retry_command.sh 2 meson compile -C $BUILD_RELEASE_DIR
          #
          # Reason for retry is due to intermittent Python symbol extractor failure
          # under MSYS2 reported in bug: https://github.com/msys2/MINGW-packages/issues/11864

      - name: Build debugger
        run: |
          set -euo pipefail
          scripts/retry_command.sh 2 meson compile -C $BUILD_DEBUGGER_DIR
          #
          # Reason for retry is due to intermittent Python symbol extractor failure
          # under MSYS2 reported in bug: https://github.com/msys2/MINGW-packages/issues/11864

      - name: Package
        run: |
          # Package the debug build to bring in required dependencies such as ncurses
          ./scripts/create-package.sh \
            -p msys2 \
            "$BUILD_DEBUGGER_DIR" \
            "$PKG_RELEASE"
          mv "${PKG_RELEASE}/dosbox.exe" "${PKG_RELEASE}/dosbox_with_debugger.exe"
          install -DT "${BUILD_RELEASE_DIR}/dosbox.exe" "${PKG_RELEASE}/dosbox.exe"
          echo "artifact_name=dosbox-staging-windows-x64-`git describe --abbrev=4`" >> $GITHUB_ENV
          echo "artifact_path=${{ github.workspace }}\msvc" >> $GITHUB_ENV

      - name: Windows Defender AV Scan
        shell: powershell
        run: |
          $ErrorActionPreference = 'Stop'
          $dosboxDir = "${{ github.workspace }}\${{ env.PKG_RELEASE }}"
          & 'C:\Program Files\Windows Defender\MpCmdRun.exe' -Scan -ScanType 3 -DisableRemediation -File $dosboxDir
          if( $LASTEXITCODE -ne 0 ) {
              Get-Content -Path $env:TEMP\MpCmdRun.log
              Throw "Exit $LASTEXITCODE : Windows Defender found an issue"
          }

      - name: Wait for MSVC builds to complete
        uses: fountainhead/action-wait-for-check@v1.2.0
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          checkName: Publish additional Windows artifacts
          ref: ${{ github.event.pull_request.head.sha || github.sha }}

      - name: Download MSVC artifact
        uses: dawidd6/action-download-artifact@v6
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          workflow: windows-msvc.yml
          name: ${{ env.artifact_name }}
          path: ${{ env.artifact_path }}
          if_no_artifact_found: warn

      - name: Prepare Windows installer
        shell: bash
        run: |
          if [[ "${{ github.ref }}" == "refs/tags/"* ]] && [[ "${{ github.ref }}" != *"-"* ]]; then
            version_tag=`echo ${{ github.ref }} | sed -r 's/([^\/]*\/){2}//'`
            packageinfo="release $version_tag"
          elif [[ "${{ github.ref }}" == "refs/heads/release/"* ]]; then
            version_tag=`git describe --tags | cut -f1 -d"-"`
            packageinfo="release $version_tag"
          elif [ -n "${{ github.ref }}" ] && [ -n "${{ github.sha }}" ]; then
            git_branch=`echo ${{ github.ref }} | sed -r 's/([^\/]*\/){2}//'`
            git_commit=`echo ${{ github.sha }} | awk '{print substr($0,1,9);exit}'`
            packageinfo="a development branch named $git_branch with commit $git_commit"
          else
            packageinfo="a development branch"
          fi
          mkdir out
          echo "version_number=`git describe --tags --abbrev=4`" >> $GITHUB_ENV
          sed -e "s|%PACKAGE_INFORMATION%|${packageinfo}|;s|%GITHUB_REPO%|${{ github.repository }}|" docs/README.template >out/setup_preamble.txt
          sed -i "s|DOSBOX-STAGING-VERSION|`git describe --tags --abbrev=4`|" contrib/windows_installer/DOSBox-Staging-setup.iss
          echo @echo off >out/dosbox_with_console.bat
          echo \"%~dp0\\dosbox.exe\" %* >>out/dosbox_with_console.bat
          echo if errorlevel 1 pause >>out/dosbox_with_console.bat
          if [ -d msvc ]; then
            cp msvc/dosbox*.exe out
            mv out/dosbox.exe out/dosbox_msvc.exe
            mv out/dosbox_with_debugger.exe out/dosbox_msvc_with_debugger.exe
          else
            echo Note: MSVC build will not be included in the Windows installer.
            sed -i -e 's|^\(Name: \"defaultmsvc\"\)|;\1|;s|^\(Source: \"{#DOSBoxAppExeMSVC\)|;\1|;s|; Tasks: not defaultmsvc$||' contrib/windows_installer/DOSBox-Staging-setup.iss
          fi

      - name: Build Windows installer
        shell: pwsh
        run: |
          cd out
          mkdir program
          copy ${{ github.workspace }}\contrib\windows_installer\* .
          copy ${{ github.workspace }}\contrib\icons\windows\dosbox-staging.ico .
          copy ${{ github.workspace }}\contrib\icons\windows\dosbox-staging.bmp .
          copy ${{ github.workspace }}\contrib\icons\windows\dosbox-staging-side.bmp .
          xcopy /s ${{ github.workspace }}\${{ env.PKG_RELEASE }}\* program
          replace /a ..\msvc\*.dll program
          move program\dosbox.exe .
          move program\dosbox_with_debugger.exe .
          C:\PROGRA~2\INNOSE~1\ISCC.exe DOSBox-Staging-setup.iss

      - name: Upload package
        uses: actions/upload-artifact@v4.3.1
        with:
          name: ${{ env.PKG_RELEASE }}
          path: ${{ env.PKG_RELEASE }}
          overwrite: true

      - name: Upload installer
        uses: actions/upload-artifact@v4.3.1
        with:
          name: dosbox-staging-${{ env.version_number }}-setup.exe
          path: ${{ github.workspace }}\out\dosbox-staging-${{ env.version_number }}-setup.exe
          overwrite: true

  # This job exists only to publish an artifact with version info when building
  # from main branch, so snapshot build version will be visible on:
  # https://www.dosbox-staging.org/releases/development-builds/
  #
  publish_additional_artifacts:
    name: Publish additional artifacts
    needs: build_msys2_release
    runs-on: windows-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: false

      - name: Generate changelog
        shell: bash
        run: |
          set +x
          git fetch --unshallow
          git fetch --all --tags --force
          VERSION=$(git describe --abbrev=4)
          echo "VERSION=$VERSION" >> $GITHUB_ENV
          NEWEST_TAG=$(git describe --abbrev=0)
          git log "$NEWEST_TAG..HEAD" > changelog-$VERSION.txt

      - uses: actions/upload-artifact@v4.3.1
        with:
          # Keep exactly this artifact name; it's being used to propagate
          # version info via GitHub REST API
          name: changelog-${{ env.VERSION }}.txt
          path: changelog-${{ env.VERSION }}.txt
          overwrite: true
