.github/workflows/ci.yml

name: CI

# Runs full Plasma coverage except for proven editorial-only PR/main changes; on version tags
# (`vX.Y.Z`) additionally builds the `.plasmoid` and publishes a GitHub Release.
#
# Runtime checks and smoke tests use the prebuilt Ubuntu/Neon CI image.

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: '23 7 * * 1'

# Only superseded PR runs are cancelled. Each main push and release keeps its run.
concurrency:
  group: ci-${{ github.event.pull_request.number || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
  contents: read

env:
  DEBIAN_FRONTEND: noninteractive
  LANG: C.UTF-8
  # Explicit import warnings prevent the local missing-module fallback.
  QMLLINT_FLAGS: --import warning --unqualified disable
  QML_TEST_REQUIRE_NO_SKIPS: '1'

jobs:
  lint:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Install lightweight check tooling
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            make shellcheck jq libxml2-utils python3-pyflakes curl ca-certificates gettext zip
          sudo bash scripts/install-actionlint.sh
      - name: Run fast checks
        run: |
          command -v actionlint
          python3 -c 'import pyflakes'
          make check-shellcheck check-workflows check-python-lint check-changelog \
            check-python-test_documentation check-python-test_ci_scope check-config-schema check-metadata
          make package

  scope:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      run_runtime: ${{ steps.scope.outputs.run_runtime }}
    steps:
      - name: Checkout comparison history
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Select Plasma coverage
        id: scope
        run: python3 scripts/ci_scope.py

  check-runtime:
    needs: scope
    if: needs.scope.outputs.run_runtime == 'true'
    runs-on: ubuntu-latest
    timeout-minutes: 20
    container:
      image: ghcr.io/lucenx9/codexbar-plasma-ci-runtime@sha256:581a1870084038e88c12808e1cac44dee715259567de11abe67eb3d4e9694b14
      credentials:
        username: ${{ github.actor }}
        password: ${{ github.token }}
      options: --user 0
    permissions:
      contents: read
      packages: read
    steps:
      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - name: Verify image matches its checked-in recipe
        run: sha256sum --check --strict /usr/local/share/ci-inputs.sha256

      - name: make check
        run: make check

      - name: make package
        run: make package

  smoke-runtime:
    needs: scope
    if: needs.scope.outputs.run_runtime == 'true'
    runs-on: ubuntu-latest
    timeout-minutes: 20
    container:
      image: ghcr.io/lucenx9/codexbar-plasma-ci-runtime@sha256:581a1870084038e88c12808e1cac44dee715259567de11abe67eb3d4e9694b14
      credentials:
        username: ${{ github.actor }}
        password: ${{ github.token }}
      options: --user 0
    permissions:
      contents: read
      packages: read
    steps:
      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - name: Verify image matches its checked-in recipe
        run: sha256sum --check --strict /usr/local/share/ci-inputs.sha256

      - name: Exercise the popup on a virtual display
        run: xvfb-run -a -s '-screen 0 1280x1024x24' make smoke SMOKE_ARGS='--output dist/smoke/ci'

      - name: Upload popup screenshots and logs
        if: always()
        uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: popup-smoke
          path: dist/smoke/ci
          if-no-files-found: warn
          retention-days: 7

      - name: Capture panel content combinations with OpenGL
        run: xvfb-run -a -s '-screen 0 1280x1024x24' python3 scripts/panel_matrix.py --output dist/smoke/panel-matrix

      - name: Upload panel matrix gallery and measurements
        if: always()
        uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: panel-matrix
          path: dist/smoke/panel-matrix
          if-no-files-found: warn
          retention-days: 7

  check:
    if: always()
    needs: [scope, lint, check-runtime]
    permissions:
      contents: read
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout coverage validator
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Report required check result
        env:
          SCOPE_RESULT: ${{ needs.scope.result }}
          RUN_RUNTIME: ${{ needs.scope.outputs.run_runtime }}
          RUNTIME_RESULT: ${{ needs.check-runtime.result }}
          LINT_RESULT: ${{ needs.lint.result }}
        run: python3 scripts/ci_scope.py --gate check

  smoke:
    if: always()
    needs: [scope, smoke-runtime]
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout smoke result validator
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Report required smoke result
        env:
          SCOPE_RESULT: ${{ needs.scope.result }}
          RUN_RUNTIME: ${{ needs.scope.outputs.run_runtime }}
          RUNTIME_RESULT: ${{ needs.smoke-runtime.result }}
        run: python3 scripts/ci_scope.py --gate smoke

  release:
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
    needs: [check, smoke]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write
    steps:
      - name: Install build-only tooling
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            make git ca-certificates jq gettext python3 zip

      - name: Checkout
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - name: Verify release tag matches metadata
        shell: bash
        run: |
          if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "release tag must use vX.Y.Z: $GITHUB_REF_NAME" >&2
            exit 1
          fi
          metadata_version="$(jq -r '.KPlugin.Version // empty' metadata.json)"
          if [[ -z "$metadata_version" || "v${metadata_version}" != "$GITHUB_REF_NAME" ]]; then
            echo "release tag $GITHUB_REF_NAME does not match metadata version $metadata_version" >&2
            exit 1
          fi

      - name: Prepare release notes from changelog
        shell: bash
        run: |
          mkdir -p dist
          python3 scripts/changelog.py --tag "$GITHUB_REF_NAME" > dist/release-notes.md

      - name: make package
        run: make package

      - name: Publish release
        # v3.0.2 creates a draft, uploads every asset, and only then publishes.
        # Keep repository release immutability enabled so the updater can trust
        # the published tag and asset digests.
        uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
        with:
          files: |
            dist/codexbar-plasma.plasmoid
            dist/codexbar-plasma.plasmoid.sha256
          body_path: dist/release-notes.md
          generate_release_notes: false