#
# DO NOT MODIFY! AUTO-GENERATED FROM:
# .github/workflows/ci/workflow_template.yml
#

name: CI

on:
  push:
    branches:
      - 'main'
      - 'maintenance/*'
    tags:
      - 'v*'
  pull_request:
    types:
        - opened
        - reopened
        - synchronize

defaults:
  run:
    # Powershell on Windows? No thank you...
    shell: bash --noprofile --norc -eo pipefail .github/workflows/ci/helpers.sh . {0}

jobs:

  # Job: Analyze {{{
  analyze:

    name: Analyze
    runs-on: ubuntu-latest

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Calculate skip cache keys
        id: set_cache
        run: |
          job_cache_extra_deps=(); job_id=test_linux; job_name='Test (Linux)'; job_needs=(); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_linux_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_test_linux; job_skiplists=(job_test os_linux); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=Linux; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(osx/deps.sh); job_id=test_macos; job_name='Test (macOS)'; job_needs=(); job_os=macOS; job_platform=macos-15; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_macos_py-3.13.3_macos-15; job_skip_cache_path=.skip_cache_test_macos; job_skiplists=(job_test os_macos); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=macOS; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_windows; job_name='Test (Windows)'; job_needs=(); job_os=Windows; job_platform=windows-2022; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_windows_py-3.13.3_windows-2022; job_skip_cache_path=.skip_cache_test_windows; job_skiplists=(job_test os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=Windows; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_python_latest_supported; job_name='Test (Python Latest Supported)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_latest_supported_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_latest_supported; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python Latest Supported'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_python_312; job_name='Test (Python 3.12)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.12; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_312_py-3.12_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_312; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.12'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_python_311; job_name='Test (Python 3.11)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.11; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_311_py-3.11_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_311; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.11'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_python_310; job_name='Test (Python 3.10)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.10; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_310_py-3.10_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_310; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.10'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_qt_gui; job_name='Test (Qt GUI)'; job_needs=(); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/dist_extra_gui_qt.txt reqs/test.txt); job_skip_cache_name=skip_test_qt_gui_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_test_qt_gui; job_skiplists=(job_test_gui_qt); job_test_args=test/gui_qt; job_type=test_gui_qt; job_variant='Qt GUI'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_packaging; job_name='Test (Packaging)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/packaging.txt reqs/setup.txt); job_skip_cache_name=skip_test_packaging_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_packaging; job_skiplists=(job_test_packaging); job_type=test_packaging; job_variant=Packaging; analyze_set_job_skip_cache_key
          job_cache_extra_deps=(); job_id=test_code_quality; job_name='Test (Code Quality)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/code_quality.txt); job_skip_cache_name=skip_test_code_quality_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_code_quality; job_type=test_code_quality; job_variant='Code Quality'; analyze_set_job_skip_cache_key
          job_cache_extra_deps=('reqs/dist_*.txt' linux/appimage/deps.sh); job_id=build_linux; job_name='Build (Linux)'; job_needs=(test_linux); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_linux_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_build_linux; job_skiplists=(job_build os_linux); job_type=build; job_variant=Linux; analyze_set_job_skip_cache_key
          job_cache_extra_deps=('reqs/dist_*.txt' osx/deps.sh); job_id=build_macos; job_name='Build (macOS)'; job_needs=(test_macos); job_os=macOS; job_platform=macos-15; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_macos_py-3.13.3_macos-15; job_skip_cache_path=.skip_cache_build_macos; job_skiplists=(job_build os_macos); job_type=build; job_variant=macOS; analyze_set_job_skip_cache_key
          job_cache_extra_deps=('reqs/dist_*.txt' windows/dist_deps.sh); job_id=build_windows; job_name='Build (Windows)'; job_needs=(test_windows); job_os=Windows; job_platform=windows-2022; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_windows_py-3.13.3_windows-2022; job_skip_cache_path=.skip_cache_build_windows; job_skiplists=(job_build os_windows); job_type=build; job_variant=Windows; analyze_set_job_skip_cache_key

      - name: Check skip cache for Test (Linux)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_linux
          key: 0_check_${{ steps.set_cache.outputs.test_linux_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_linux_skip_cache_key }}

      - name: Check skip cache for Test (macOS)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_macos
          key: 0_check_${{ steps.set_cache.outputs.test_macos_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_macos_skip_cache_key }}

      - name: Check skip cache for Test (Windows)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_windows
          key: 0_check_${{ steps.set_cache.outputs.test_windows_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_windows_skip_cache_key }}

      - name: Check skip cache for Test (Python Latest Supported)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_python_latest_supported
          key: 0_check_${{ steps.set_cache.outputs.test_python_latest_supported_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_python_latest_supported_skip_cache_key }}

      - name: Check skip cache for Test (Python 3.12)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_python_312
          key: 0_check_${{ steps.set_cache.outputs.test_python_312_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_python_312_skip_cache_key }}

      - name: Check skip cache for Test (Python 3.11)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_python_311
          key: 0_check_${{ steps.set_cache.outputs.test_python_311_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_python_311_skip_cache_key }}

      - name: Check skip cache for Test (Python 3.10)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_python_310
          key: 0_check_${{ steps.set_cache.outputs.test_python_310_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_python_310_skip_cache_key }}

      - name: Check skip cache for Test (Qt GUI)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_qt_gui
          key: 0_check_${{ steps.set_cache.outputs.test_qt_gui_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_qt_gui_skip_cache_key }}

      - name: Check skip cache for Test (Packaging)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_packaging
          key: 0_check_${{ steps.set_cache.outputs.test_packaging_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_packaging_skip_cache_key }}

      - name: Check skip cache for Test (Code Quality)
        uses: actions/cache@v3
        with:
          path: .skip_cache_test_code_quality
          key: 0_check_${{ steps.set_cache.outputs.test_code_quality_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.test_code_quality_skip_cache_key }}

      - name: Check skip cache for Build (Linux)
        uses: actions/cache@v3
        with:
          path: .skip_cache_build_linux
          key: 0_check_${{ steps.set_cache.outputs.build_linux_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.build_linux_skip_cache_key }}

      - name: Check skip cache for Build (macOS)
        uses: actions/cache@v3
        with:
          path: .skip_cache_build_macos
          key: 0_check_${{ steps.set_cache.outputs.build_macos_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.build_macos_skip_cache_key }}

      - name: Check skip cache for Build (Windows)
        uses: actions/cache@v3
        with:
          path: .skip_cache_build_windows
          key: 0_check_${{ steps.set_cache.outputs.build_windows_skip_cache_key }}_${{ github.run_id }}
          restore-keys:
            0_${{ steps.set_cache.outputs.build_windows_skip_cache_key }}

      - name: Set outputs
        id: set_ouputs
        run: |
          analyze_set_release_info
          job_cache_extra_deps=(); job_id=test_linux; job_name='Test (Linux)'; job_needs=(); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_linux_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_test_linux; job_skiplists=(job_test os_linux); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=Linux; analyze_set_job_skip_job
          job_cache_extra_deps=(osx/deps.sh); job_id=test_macos; job_name='Test (macOS)'; job_needs=(); job_os=macOS; job_platform=macos-15; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_macos_py-3.13.3_macos-15; job_skip_cache_path=.skip_cache_test_macos; job_skiplists=(job_test os_macos); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=macOS; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_windows; job_name='Test (Windows)'; job_needs=(); job_os=Windows; job_platform=windows-2022; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_windows_py-3.13.3_windows-2022; job_skip_cache_path=.skip_cache_test_windows; job_skiplists=(job_test os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant=Windows; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_python_latest_supported; job_name='Test (Python Latest Supported)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_latest_supported_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_latest_supported; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python Latest Supported'; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_python_312; job_name='Test (Python 3.12)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.12; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_312_py-3.12_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_312; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.12'; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_python_311; job_name='Test (Python 3.11)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.11; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_311_py-3.11_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_311; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.11'; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_python_310; job_name='Test (Python 3.10)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.10; job_reqs=(reqs/dist.txt reqs/test.txt); job_skip_cache_name=skip_test_python_310_py-3.10_ubuntu-latest; job_skip_cache_path=.skip_cache_test_python_310; job_skiplists=(job_test os_linux os_macos os_windows); job_test_args='-p no:pytest-qt --ignore=test/gui_qt'; job_type=test; job_variant='Python 3.10'; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_qt_gui; job_name='Test (Qt GUI)'; job_needs=(); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/dist.txt reqs/dist_extra_gui_qt.txt reqs/test.txt); job_skip_cache_name=skip_test_qt_gui_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_test_qt_gui; job_skiplists=(job_test_gui_qt); job_test_args=test/gui_qt; job_type=test_gui_qt; job_variant='Qt GUI'; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_packaging; job_name='Test (Packaging)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/packaging.txt reqs/setup.txt); job_skip_cache_name=skip_test_packaging_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_packaging; job_skiplists=(job_test_packaging); job_type=test_packaging; job_variant=Packaging; analyze_set_job_skip_job
          job_cache_extra_deps=(); job_id=test_code_quality; job_name='Test (Code Quality)'; job_needs=(); job_os=Linux; job_platform=ubuntu-latest; job_python=3.13.3; job_reqs=(reqs/code_quality.txt); job_skip_cache_name=skip_test_code_quality_py-3.13.3_ubuntu-latest; job_skip_cache_path=.skip_cache_test_code_quality; job_type=test_code_quality; job_variant='Code Quality'; analyze_set_job_skip_job
          job_cache_extra_deps=('reqs/dist_*.txt' linux/appimage/deps.sh); job_id=build_linux; job_name='Build (Linux)'; job_needs=(test_linux); job_os=Linux; job_platform=ubuntu-22.04; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_linux_py-3.13.3_ubuntu-22.04; job_skip_cache_path=.skip_cache_build_linux; job_skiplists=(job_build os_linux); job_type=build; job_variant=Linux; analyze_set_job_skip_job
          job_cache_extra_deps=('reqs/dist_*.txt' osx/deps.sh); job_id=build_macos; job_name='Build (macOS)'; job_needs=(test_macos); job_os=macOS; job_platform=macos-15; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_macos_py-3.13.3_macos-15; job_skip_cache_path=.skip_cache_build_macos; job_skiplists=(job_build os_macos); job_type=build; job_variant=macOS; analyze_set_job_skip_job
          job_cache_extra_deps=('reqs/dist_*.txt' windows/dist_deps.sh); job_id=build_windows; job_name='Build (Windows)'; job_needs=(test_windows); job_os=Windows; job_platform=windows-2022; job_python=3.13.3; job_reqs=(reqs/build.txt reqs/setup.txt); job_skip_cache_name=skip_build_windows_py-3.13.3_windows-2022; job_skip_cache_path=.skip_cache_build_windows; job_skiplists=(job_build os_windows); job_type=build; job_variant=Windows; analyze_set_job_skip_job

    outputs:
      is_release: ${{ steps.set_ouputs.outputs.is_release }}
      release_type: ${{ steps.set_ouputs.outputs.release_type }}
      test_linux_skip_job: ${{ steps.set_ouputs.outputs.test_linux_skip_job }}
      test_linux_skip_cache_key: ${{ steps.set_cache.outputs.test_linux_skip_cache_key }}
      test_macos_skip_job: ${{ steps.set_ouputs.outputs.test_macos_skip_job }}
      test_macos_skip_cache_key: ${{ steps.set_cache.outputs.test_macos_skip_cache_key }}
      test_windows_skip_job: ${{ steps.set_ouputs.outputs.test_windows_skip_job }}
      test_windows_skip_cache_key: ${{ steps.set_cache.outputs.test_windows_skip_cache_key }}
      test_python_latest_supported_skip_job: ${{ steps.set_ouputs.outputs.test_python_latest_supported_skip_job }}
      test_python_latest_supported_skip_cache_key: ${{ steps.set_cache.outputs.test_python_latest_supported_skip_cache_key }}
      test_python_312_skip_job: ${{ steps.set_ouputs.outputs.test_python_312_skip_job }}
      test_python_312_skip_cache_key: ${{ steps.set_cache.outputs.test_python_312_skip_cache_key }}
      test_python_311_skip_job: ${{ steps.set_ouputs.outputs.test_python_311_skip_job }}
      test_python_311_skip_cache_key: ${{ steps.set_cache.outputs.test_python_311_skip_cache_key }}
      test_python_310_skip_job: ${{ steps.set_ouputs.outputs.test_python_310_skip_job }}
      test_python_310_skip_cache_key: ${{ steps.set_cache.outputs.test_python_310_skip_cache_key }}
      test_qt_gui_skip_job: ${{ steps.set_ouputs.outputs.test_qt_gui_skip_job }}
      test_qt_gui_skip_cache_key: ${{ steps.set_cache.outputs.test_qt_gui_skip_cache_key }}
      test_packaging_skip_job: ${{ steps.set_ouputs.outputs.test_packaging_skip_job }}
      test_packaging_skip_cache_key: ${{ steps.set_cache.outputs.test_packaging_skip_cache_key }}
      test_code_quality_skip_job: ${{ steps.set_ouputs.outputs.test_code_quality_skip_job }}
      test_code_quality_skip_cache_key: ${{ steps.set_cache.outputs.test_code_quality_skip_cache_key }}
      build_linux_skip_job: ${{ steps.set_ouputs.outputs.build_linux_skip_job }}
      build_linux_skip_cache_key: ${{ steps.set_cache.outputs.build_linux_skip_cache_key }}
      build_macos_skip_job: ${{ steps.set_ouputs.outputs.build_macos_skip_job }}
      build_macos_skip_cache_key: ${{ steps.set_cache.outputs.build_macos_skip_cache_key }}
      build_windows_skip_job: ${{ steps.set_ouputs.outputs.build_windows_skip_job }}
      build_windows_skip_cache_key: ${{ steps.set_cache.outputs.build_windows_skip_cache_key }}
      release_skip_job: ${{ steps.set_ouputs.outputs.release_skip_job }}
  # }}}

  # Job: Test (Linux) {{{
  test_linux:

    name: Test (Linux)
    runs-on: ubuntu-22.04
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_linux_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-22.04'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_linux
          key: 0_${{ needs.analyze.outputs.test_linux_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_linux'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (macOS) {{{
  test_macos:

    name: Test (macOS)
    runs-on: macos-15
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_macos_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'macos-15'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt', 'osx/deps.sh') }}

      # To support older macOS versions, setup Python from an official installer.
      - name: Setup Python
        run: setup_osx_python '3.13.3'

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_macos
          key: 0_${{ needs.analyze.outputs.test_macos_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_macos'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Windows) {{{
  test_windows:

    name: Test (Windows)
    runs-on: windows-2022
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_windows_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      # On Windows, ensure we use a tar compatible
      # with the one used in the analyze job.
      - name: Fix tar
        run: rm C:/Windows/System32/tar

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'windows-2022'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_windows
          key: 0_${{ needs.analyze.outputs.test_windows_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_windows'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Python Latest Supported) {{{
  test_python_latest_supported:

    name: Test (Python Latest Supported)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_python_latest_supported_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_python_latest_supported
          key: 0_${{ needs.analyze.outputs.test_python_latest_supported_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_python_latest_supported'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Python 3.12) {{{
  test_python_312:

    name: Test (Python 3.12)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_python_312_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.12' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_python_312
          key: 0_${{ needs.analyze.outputs.test_python_312_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_python_312'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Python 3.11) {{{
  test_python_311:

    name: Test (Python 3.11)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_python_311_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

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

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.11' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_python_311
          key: 0_${{ needs.analyze.outputs.test_python_311_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_python_311'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Python 3.10) {{{
  test_python_310:

    name: Test (Python 3.10)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_python_310_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.10' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/test.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/test.txt        

      # Test {{{

      - name: Run tests
        run: run_tests -p no:pytest-qt --ignore=test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_python_310
          key: 0_${{ needs.analyze.outputs.test_python_310_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_python_310'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Qt GUI) {{{
  test_qt_gui:

    name: Test (Qt GUI)
    runs-on: ubuntu-22.04
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_qt_gui_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-22.04'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/dist.txt', 'reqs/dist_extra_gui_qt.txt', 'reqs/test.txt') }}

      - name: Install system dependencies
        run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/dist.txt -r reqs/dist_extra_gui_qt.txt -r reqs/test.txt        

      - name: Build UI
        run: python setup.py build_ui

      # Test {{{

      - name: Run tests
        run: run_tests test/gui_qt


      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_qt_gui
          key: 0_${{ needs.analyze.outputs.test_qt_gui_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_qt_gui'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Test (Packaging) {{{
  test_packaging:

    name: Test (Packaging)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_packaging_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4
        with:
          # We need the whole history for patching the version.
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/packaging.txt', 'reqs/setup.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/packaging.txt -r reqs/setup.txt        

      - name: Patch version
        id: set_version
        run: |
          python setup.py patch_version
          echo "version=$(python setup.py --version)" >> $GITHUB_OUTPUT

      # Test packaging {{{

      - name: Run packaging sanity checks
        run: packaging_checks

      - name: Archive artifact (sdist)
        if: needs.analyze.outputs.is_release == 'yes'
        uses: actions/upload-artifact@v4
        with:
          name: Source
          path: dist/*.tar.gz

      - name: Archive artifact (wheel)
        if: needs.analyze.outputs.is_release == 'yes'
        uses: actions/upload-artifact@v4
        with:
          name: Wheel
          path: dist/*.whl

      - name: Generate translations catalogs archive
        if: needs.analyze.outputs.is_release == 'yes'
        run: generate_translations_catalogs_archive

      - name: Archive artifact (translations catalogs)
        if: needs.analyze.outputs.is_release == 'yes'
        uses: actions/upload-artifact@v4
        with:
          name: Translations Catalogs
          path: dist/*-messages.zip

      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_packaging
          key: 0_${{ needs.analyze.outputs.test_packaging_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_packaging'"

      - name: List cache contents
        run: list_cache

    outputs:
      version: ${{ steps.set_version.outputs.version }}
  # }}}

  # Job: Test (Code Quality) {{{
  test_code_quality:

    name: Test (Code Quality)
    runs-on: ubuntu-latest
    needs: [analyze, ]
    if: >-
      !cancelled()
      && needs.analyze.outputs.test_code_quality_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-latest'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/code_quality.txt') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/code_quality.txt        

      - name: Run Ruff (format check)
        run: ruff format --check .

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_test_code_quality
          key: 0_${{ needs.analyze.outputs.test_code_quality_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_test_code_quality'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Build (Linux) {{{
  build_linux:

    name: Build (Linux)
    runs-on: ubuntu-22.04
    needs: [analyze, test_linux]
    if: >-
      !cancelled()
      && (needs.test_linux.result == 'success' || needs.test_linux.result == 'skipped')
      && needs.analyze.outputs.build_linux_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4
        with:
          # We need the whole history for patching the version.
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'ubuntu-22.04'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/build.txt', 'reqs/setup.txt', 'reqs/dist_*.txt', 'linux/appimage/deps.sh') }}

      - name: Install system dependencies
        run: apt_get_install libdbus-1-dev libdbus-glib-1-dev libudev-dev libusb-1.0-0-dev libegl-dev libxkbcommon-x11-0

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/build.txt -r reqs/setup.txt        

      - name: Patch version
        id: set_version
        run: |
          python setup.py patch_version

      # Build {{{

      - name: Build distribution (Linux AppImage)
        run: python setup.py -q bdist_appimage --no-update-tools

      - name: Archive artifact (Linux AppImage)
        uses: actions/upload-artifact@v4
        with:
          name: Linux AppImage
          path: dist/*.AppImage

      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_build_linux
          key: 0_${{ needs.analyze.outputs.build_linux_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_build_linux'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Build (macOS) {{{
  build_macos:

    name: Build (macOS)
    runs-on: macos-15
    needs: [analyze, test_macos]
    if: >-
      !cancelled()
      && (needs.test_macos.result == 'success' || needs.test_macos.result == 'skipped')
      && needs.analyze.outputs.build_macos_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4
        with:
          # We need the whole history for patching the version.
          fetch-depth: 0

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'macos-15'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/build.txt', 'reqs/setup.txt', 'reqs/dist_*.txt', 'osx/deps.sh') }}

      # To support older macOS versions, setup Python from an official installer.
      - name: Setup Python
        run: setup_osx_python '3.13.3'

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/build.txt -r reqs/setup.txt        

      - name: Patch version
        id: set_version
        run: |
          python setup.py patch_version

      # Build {{{

      - name: Build distribution (macOS DMG)
        run: python setup.py -q bdist_dmg

      - name: Archive artifact (macOS DMG)
        uses: actions/upload-artifact@v4
        with:
          name: macOS DMG
          path: dist/*.dmg

      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_build_macos
          key: 0_${{ needs.analyze.outputs.build_macos_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_build_macos'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Build (Windows) {{{
  build_windows:

    name: Build (Windows)
    runs-on: windows-2022
    needs: [analyze, test_windows]
    if: >-
      !cancelled()
      && (needs.test_windows.result == 'success' || needs.test_windows.result == 'skipped')
      && needs.analyze.outputs.build_windows_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4
        with:
          # We need the whole history for patching the version.
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13.3'

      # On Windows, ensure we use a tar compatible
      # with the one used in the analyze job.
      - name: Fix tar
        run: rm C:/Windows/System32/tar

      - name: Set cache name
        id: set_cache
        run: setup_cache_name '3.13.3' 'windows-2022'

      - name: Setup cache
        uses: actions/cache@v4
        with:
          path: .cache
          key: 0_${{ steps.set_cache.outputs.cache_name }}_${{ hashFiles('reqs/constraints.txt', 'reqs/build.txt', 'reqs/setup.txt', 'reqs/dist_*.txt', 'windows/dist_deps.sh') }}

      - name: Setup Python environment
        run: setup_python_env -c reqs/constraints.txt -r reqs/build.txt -r reqs/setup.txt        

      - name: Patch version
        id: set_version
        run: |
          python setup.py patch_version

      # Build {{{

      - name: Build distributions (Windows)
        run: |
          # Note: we avoid using the WSL bash (which does not work).
          bash="$(cygpath -w -a "$(which bash)")"
          python setup.py -q bdist_win -t -z -i --bash="$bash"

      - name: Archive artifact (Windows Installer)
        uses: actions/upload-artifact@v4
        with:
          name: Windows Installer
          path: dist/*.exe

      - name: Archive artifact (Windows ZIP)
        uses: actions/upload-artifact@v4
        with:
          name: Windows ZIP
          path: dist/*.zip

      # }}}

      - name: Update skip cache 1
        uses: actions/cache@v4
        with:
          path: .skip_cache_build_windows
          key: 0_${{ needs.analyze.outputs.build_windows_skip_cache_key }}

      - name: Update skip cache 2
        run: run_eval "echo 'https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID' >'.skip_cache_build_windows'"

      - name: List cache contents
        run: list_cache
  # }}}

  # Job: Release {{{
  release:

    name: Release
    environment: release
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    needs: [analyze, test_linux, test_macos, test_windows, test_python_latest_supported, test_python_312, test_python_311, test_python_310, test_qt_gui, test_packaging, test_code_quality, build_linux, build_macos, build_windows]
    if: >-
      !cancelled()
      && needs.test_linux.result == 'success'
      && needs.test_macos.result == 'success'
      && needs.test_windows.result == 'success'
      && needs.test_python_latest_supported.result == 'success'
      && needs.test_python_312.result == 'success'
      && needs.test_python_311.result == 'success'
      && needs.test_python_310.result == 'success'
      && needs.test_qt_gui.result == 'success'
      && needs.test_packaging.result == 'success'
      && needs.test_code_quality.result == 'success'
      && needs.build_linux.result == 'success'
      && needs.build_macos.result == 'success'
      && needs.build_windows.result == 'success'
      && needs.analyze.outputs.release_skip_job == 'no'

    steps:

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: 3.13.3

      - name: Install dependencies
        run: |
          run apt_get_install -qq pandoc
          run "$python" -m pip install -c reqs/constraints.txt -r reqs/release.txt

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

      - name: Publish GitHub release (${{ needs.analyze.outputs.release_type }})
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_TYPE: ${{ needs.analyze.outputs.release_type }}
          RELEASE_VERSION: ${{ needs.test_packaging.outputs.version }}
        run: publish_github_release

      - name: Collect Python distributions for PyPI
        run: |
          mkdir -p dist/pypi
          cp dist/Source/*.tar.gz dist/pypi/
          cp dist/Wheel/*.whl dist/pypi/

      - name: Publish release to PyPI (Trusted Publishing)
        if: needs.analyze.outputs.release_type == 'tagged'
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/pypi
          skip-existing: true
  # }}}
