name: Test Windows

on:
  push:
    branches:
      - "**"
    paths-ignore:
      - ".github/workflows/docs.yml"
      - ".github/workflows/wheels*"
      - ".gitmodules"
      - "docs/**"
      - "wheels/**"
  pull_request:
    paths-ignore:
      - ".github/workflows/docs.yml"
      - ".github/workflows/wheels*"
      - ".gitmodules"
      - "docs/**"
      - "wheels/**"
  workflow_dispatch:

permissions:
  contents: read

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

env:
  COVERAGE_CORE: sysmon

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        python-version: ["pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
        architecture: ["x64"]
        os: ["windows-latest"]
        include:
            # Test the oldest Python on 32-bit
            - { python-version: "3.9", architecture: "x86", os: "windows-2019" }

    timeout-minutes: 45

    name: Python ${{ matrix.python-version }} (${{ matrix.architecture }})

    steps:
    - name: Checkout Pillow
      uses: actions/checkout@v4
      with:
        persist-credentials: false

    - name: Checkout cached dependencies
      uses: actions/checkout@v4
      with:
        persist-credentials: false
        repository: python-pillow/pillow-depends
        path: winbuild\depends

    - name: Checkout extra test images
      uses: actions/checkout@v4
      with:
        persist-credentials: false
        repository: python-pillow/test-images
        path: Tests\test-images

    # sets env: pythonLocation
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        allow-prereleases: true
        architecture: ${{ matrix.architecture }}
        cache: pip
        cache-dependency-path: ".github/workflows/test-windows.yml"

    - name: Print build system information
      run: python3 .github/workflows/system-info.py

    - name: Upgrade pip
      run: |
        python3 -m pip install --upgrade pip

    - name: Install CPython dependencies
      if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
      run: |
        python3 -m pip install PyQt6

    - name: Install dependencies
      id: install
      run: |
        choco install nasm --no-progress
        echo "C:\Program Files\NASM" >> $env:GITHUB_PATH

        choco install ghostscript --version=10.4.0 --no-progress
        echo "C:\Program Files\gs\gs10.04.0\bin" >> $env:GITHUB_PATH

        # Install extra test images
        xcopy /S /Y Tests\test-images\* Tests\images

        # make cache key depend on VS version
        & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
          | find """catalog_buildVersion""" `
          | ForEach-Object { $a = $_.split(" ")[1]; echo "vs=$a" >> $env:GITHUB_OUTPUT }
      shell: pwsh

    - name: Cache build
      id: build-cache
      uses: actions/cache@v4
      with:
        path: winbuild\build
        key:
          ${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}-${{ steps.install.outputs.vs }}

    - name: Prepare build
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: |
        & python.exe winbuild\build_prepare.py -v
      shell: pwsh

    - name: Build dependencies / libjpeg-turbo
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_libjpeg.cmd"

    - name: Build dependencies / zlib
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_zlib.cmd"

    - name: Build dependencies / xz
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_xz.cmd"

    - name: Build dependencies / WebP
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_libwebp.cmd"

    - name: Build dependencies / LibTiff
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_libtiff.cmd"

    # for FreeType CBDT/SBIX font support
    - name: Build dependencies / libpng
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_libpng.cmd"

    - name: Build dependencies / libavif
      if: steps.build-cache.outputs.cache-hit != 'true' && matrix.architecture == 'x64'
      run: "& winbuild\\build\\build_dep_libavif.cmd"

    # for FreeType WOFF2 font support
    - name: Build dependencies / brotli
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_brotli.cmd"

    - name: Build dependencies / FreeType
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_freetype.cmd"

    - name: Build dependencies / LCMS2
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_lcms2.cmd"

    - name: Build dependencies / OpenJPEG
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_openjpeg.cmd"

    # GPL licensed
    - name: Build dependencies / libimagequant
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_libimagequant.cmd"

    # Raqm dependencies
    - name: Build dependencies / HarfBuzz
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_harfbuzz.cmd"

    # Raqm dependencies
    - name: Build dependencies / FriBidi
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: "& winbuild\\build\\build_dep_fribidi.cmd"

    # trim ~150MB for each job
    - name: Optimize build cache
      if: steps.build-cache.outputs.cache-hit != 'true'
      run: rmdir /S /Q winbuild\build\src
      shell: cmd

    - name: Build Pillow
      run: |
        $FLAGS="-C raqm=vendor -C fribidi=vendor"
        cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]"
        & $env:pythonLocation\python.exe selftest.py --installed
      shell: pwsh

    # skip PyPy for speed
    - name: Enable heap verification
      if: "!contains(matrix.python-version, 'pypy')"
      run: |
        & reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f

    - name: Test Pillow
      run: |
        path %GITHUB_WORKSPACE%\winbuild\build\bin;%PATH%
        .ci\test.cmd
      shell: cmd

    - name: Prepare to upload errors
      if: failure()
      run: |
        mkdir -p Tests/errors
      shell: bash

    - name: Upload errors
      uses: actions/upload-artifact@v4
      if: failure()
      with:
        name: errors
        path: Tests/errors

    - name: After success
      run: |
        .ci/after_success.sh
      shell: pwsh

    - name: Upload coverage
      uses: codecov/codecov-action@v5
      with:
        files: ./coverage.xml
        flags: GHA_Windows
        name: ${{ runner.os }} Python ${{ matrix.python-version }}
        token: ${{ secrets.CODECOV_ORG_TOKEN }}

  success:
    permissions:
      contents: none
    needs: build
    runs-on: ubuntu-latest
    name: Windows Test Successful
    steps:
      - name: Success
        run: echo Windows Test Successful
