# Syntax reference https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
# Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners
name: CI-windows

on:
  push:
    branches:
      - 'main'
      - 'releases/**'
      - '2.*'
    tags:
      - '2.*'
  pull_request:

permissions:
  contents: read

defaults:
  run:
    shell: cmd

# TODO: choose/add a step to bail out on compiler warnings (maybe even the release build)

jobs:

  build_qt:
    strategy:
      matrix:
        os: [windows-2022, windows-2025]
        qt_ver: [5.15.2, 6.9.1]
      fail-fast: false

    runs-on: ${{ matrix.os }}

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

      - name: Set up Visual Studio environment
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: x64

      - name: Install Qt ${{ matrix.qt_ver }}
        uses: jurplel/install-qt-action@v4
        with:
          version: ${{ matrix.qt_ver }}
          modules: 'qtcharts'
          setup-python: 'false'
          cache: true
          aqtversion: '==3.1.*'  # TODO: remove when aqtinstall 3.2.2 is available

      - name: Run CMake for GUI release (Qt 5)
        if: startsWith(matrix.qt_ver, '5')
        run: |
          ; TODO: enable rules?
          ; specify Release build so matchcompiler is used
          cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DBUILD_ONLINE_HELP=On -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install || exit /b !errorlevel!

      - name: Run CMake for GUI release (Qt 6)
        if: startsWith(matrix.qt_ver, '6')
        run: |
          ; TODO: enable rules?
          ; specify Release build so matchcompiler is used
          cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DBUILD_GUI=On -DUSE_QT6=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DBUILD_ONLINE_HELP=On -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install || exit /b !errorlevel!

      - name: Run CMake build
        run: |
          cmake --build build --target cppcheck-gui --config Release || exit /b !errorlevel!

      - name: Deploy GUI
        run: |
          windeployqt build\bin\Release || exit /b !errorlevel!
          del build\bin\Release\cppcheck-gui.ilk || exit /b !errorlevel!
          del build\bin\Release\cppcheck-gui.pdb || exit /b !errorlevel!

      # TODO: run GUI tests

      - name: Run CMake install
        run: |
          cmake --build build --target install

  build:
    strategy:
      matrix:
        os: [windows-2022, windows-2025]
        config: [debug, release]
      fail-fast: false

    runs-on: ${{ matrix.os }}

    env:
      # see https://www.pcre.org/original/changelog.txt
      PCRE_VERSION: 8.45

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

      - name: Set up Python 3.13
        if: matrix.config == 'release'
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'
          check-latest: true

      - name: Set up Visual Studio environment
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: x64

      - name: Cache PCRE
        id: cache-pcre
        uses: actions/cache@v4
        with:
          path: |
            externals\pcre.h
            externals\pcre.lib
            externals\pcre64.lib
          key: pcre-${{ env.PCRE_VERSION }}-x64-bin-win

      - name: Download PCRE
        if: steps.cache-pcre.outputs.cache-hit != 'true'
        run: |
          curl -fsSL https://github.com/pfultz2/pcre/archive/refs/tags/%PCRE_VERSION%.zip -o pcre-%PCRE_VERSION%.zip || exit /b !errorlevel!

      - name: Install PCRE
        if: steps.cache-pcre.outputs.cache-hit != 'true'
        run: |
          @echo on
          7z x pcre-%PCRE_VERSION%.zip || exit /b !errorlevel!
          cd pcre-%PCRE_VERSION% || exit /b !errorlevel!
          git apply --ignore-space-change ..\externals\pcre.patch || exit /b !errorlevel!
          cmake . -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DPCRE_BUILD_PCRECPP=Off -DPCRE_BUILD_TESTS=Off -DPCRE_BUILD_PCREGREP=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 || exit /b !errorlevel!
          nmake || exit /b !errorlevel!
          copy pcre.h ..\externals || exit /b !errorlevel!
          copy pcre.lib ..\externals\pcre64.lib || exit /b !errorlevel!
        env:
          CL: /MP

      - name: Install missing Python packages
        if: matrix.config == 'release'
        run: |
          python -m pip install pip --upgrade || exit /b !errorlevel!
          python -m pip install pytest || exit /b !errorlevel!
          python -m pip install pytest-custom_exit_code || exit /b !errorlevel!
          python -m pip install pytest-timeout || exit /b !errorlevel!
          python -m pip install pytest-xdist || exit /b !errorlevel!
          python -m pip install psutil || exit /b !errorlevel!

      # TODO: build with CMake

      - name: Build CLI debug configuration using MSBuild
        if: matrix.config == 'debug'
        run: |
          :: cmake --build build --target check --config Debug || exit /b !errorlevel!
          msbuild -m cppcheck.sln /p:Configuration=Debug-PCRE;Platform=x64 -maxcpucount || exit /b !errorlevel!
        env:
          _CL_: /WX

      - name: Run Debug test
        if: matrix.config == 'debug'
        run: .\bin\debug\testrunner.exe || exit /b !errorlevel!

      - name: Build CLI release configuration using MSBuild
        if: matrix.config == 'release'
        run: |
          :: cmake --build build --target check --config Release || exit /b !errorlevel!
          msbuild -m cppcheck.sln /p:Configuration=Release-PCRE;Platform=x64 -maxcpucount || exit /b !errorlevel!
        env:
          _CL_: /WX

      - name: Run Release test
        if: matrix.config == 'release'
        run: .\bin\testrunner.exe || exit /b !errorlevel!

      - name: Prepare test/cli
        if: matrix.config == 'release'
        run: |
          :: since FILESDIR is not set copy the binary to the root so the addons are found
          :: copy .\build\bin\Release\cppcheck.exe .\cppcheck.exe || exit /b !errorlevel!
          copy .\bin\cppcheck.exe .\cppcheck.exe || exit /b !errorlevel!
          copy .\bin\cppcheck-core.dll .\cppcheck-core.dll || exit /b !errorlevel!

      - name: Run test/cli
        if: matrix.config == 'release'
        run: |
          python -m pytest -Werror --strict-markers -vv -n auto test/cli || exit /b !errorlevel!

      - name: Run test/cli (-j2)
        if: matrix.config == 'release'
        run: |
          python -m pytest -Werror --strict-markers -vv -n auto test/cli || exit /b !errorlevel!
        env:
          TEST_CPPCHECK_INJECT_J: 2

      # TODO: install clang
      - name: Run test/cli (--clang)
        if: false # matrix.config == 'release'
        run: |
          python -m pytest -Werror --strict-markers -vv -n auto test/cli || exit /b !errorlevel!
        env:
          TEST_CPPCHECK_INJECT_CLANG: clang

      - name: Run test/cli (--cppcheck-build-dir)
        if: matrix.config == 'release'
        run: |
          python -m pytest -Werror --strict-markers -vv -n auto test/cli || exit /b !errorlevel!
        env:
          TEST_CPPCHECK_INJECT_BUILDDIR: injected

      # TODO: test with Release configuration?
      - name: Test SEH wrapper
        if: matrix.config == 'release'
        run: |
          cmake -S . -B build.cmake.seh -DBUILD_TESTS=On || exit /b !errorlevel!
          cmake --build build.cmake.seh --target test-sehwrapper || exit /b !errorlevel!
          :: TODO: how to run this without copying the file?
          copy build.cmake.seh\bin\Debug\test-sehwrapper.exe . || exit /b !errorlevel!
          python3 -m pytest -Werror --strict-markers -vv test/seh/test-sehwrapper.py || exit /b !errorlevel!
          del test-sehwrapper.exe || exit /b !errorlevel!

      - name: Test addons
        if: matrix.config == 'release'
        run: |
          echo on
          .\cppcheck --addon=threadsafety addons\test\threadsafety || exit /b !errorlevel!
          .\cppcheck --addon=threadsafety --std=c++03 addons\test\threadsafety || exit /b !errorlevel!
          .\cppcheck --addon=misra --enable=style --inline-suppr --enable=information --error-exitcode=1 addons\test\misra\misra-ctu-*-test.c || exit /b !errorlevel!
          cd addons\test
          rem We'll force C89 standard to enable an additional verification for
          rem rules 5.4 and 5.5 which have standard-dependent options.
          ..\..\cppcheck --dump -DDUMMY --suppress=uninitvar --inline-suppr misra\misra-test.c --std=c89 --platform=unix64 || exit /b !errorlevel!
          python3 ..\misra.py -verify misra\misra-test.c.dump || exit /b !errorlevel!
          rem Test slight MISRA differences in C11 standard
          ..\..\cppcheck --dump -DDUMMY --suppress=uninitvar --inline-suppr misra\misra-test-c11.c --std=c11 --platform=unix64 || exit /b !errorlevel!
          python3 ..\misra.py -verify misra\misra-test-c11.c.dump || exit /b !errorlevel!
          rem TODO: do we need to verify something here?
          ..\..\cppcheck --dump -DDUMMY --suppress=uninitvar --suppress=uninitStructMember --std=c89 misra\misra-test.h || exit /b !errorlevel!
          ..\..\cppcheck --dump misra\misra-test.cpp || exit /b !errorlevel!
          python3 ..\misra.py -verify misra\misra-test.cpp.dump || exit /b !errorlevel!
          python3 ..\misra.py --rule-texts=misra\misra2012_rules_dummy_ascii.txt -verify misra\misra-test.cpp.dump || exit /b !errorlevel!
          python3 ..\misra.py --rule-texts=misra\misra2012_rules_dummy_utf8.txt -verify misra\misra-test.cpp.dump || exit /b !errorlevel!
          python3 ..\misra.py --rule-texts=misra\misra2012_rules_dummy_windows1250.txt -verify misra\misra-test.cpp.dump || exit /b !errorlevel!
          ..\..\cppcheck --addon=misra --enable=style --platform=avr8 --error-exitcode=1 misra\misra-test-avr8.c || exit /b !errorlevel!
          ..\..\cppcheck --dump misc-test.cpp || exit /b !errorlevel!
          python3 ..\misc.py -verify misc-test.cpp.dump || exit /b !errorlevel!
          ..\..\cppcheck --dump naming_test.c || exit /b !errorlevel!
          rem TODO: fix this - does not fail on Linux
          rem python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.c.dump || exit /b !errorlevel!
          ..\..\cppcheck --dump naming_test.cpp || exit /b !errorlevel!
          python3 ..\naming.py --var='[a-z].*' --function='[a-z].*' naming_test.cpp.dump || exit /b !errorlevel!

      # TODO: run with "-n auto" when misra_test.py can be run in parallel
      - name: test addons (Python)
        if: matrix.config == 'release'
        run: |
          python -m pytest -Werror --strict-markers -vv -n 1 addons/test || exit /b !errorlevel!
        env:
          PYTHONPATH: ./addons

      - name: Check Windows test syntax
        if: matrix.config == 'debug'
        run: |
          cd test\cfg
          cl.exe windows.cpp -DUNICODE=1 -D_UNICODE=1 /Zs || exit /b !errorlevel!
          cl.exe mfc.cpp /EHsc /Zs || exit /b !errorlevel!

      - name: Show all ignored files
        if: false  # TODO: currently lists all the contents of ignored folders - we only need what actually matched
        run: |
          git ls-files --others --ignored --exclude-standard || exit /b !errorlevel!

      - name: Check for changed and unversioned files
        run: |
          :: TODO: how to do this with a single command?
          git status --ignored=no
          :: TODO: make this work
          :: git status --ignored=no | grep -q 'working tree clean'
