name: CI/CD on: push: branches: [main, develop] tags: ["v*"] pull_request: branches: [main, develop] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: PYTHON_LATEST: "3.12" PYTHON_MINIMUM: "3.11" jobs: # =========================================================================== # Code Quality Checks # =========================================================================== lint: name: Lint and Format Check runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install black flake8 isort mypy - name: Check code formatting with black run: black --check --diff . - name: Check import sorting with isort run: isort --check-only --diff . - name: Lint with flake8 run: | # Stop on syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # Treat warnings as errors for CI flake8 . --count --max-complexity=10 --max-line-length=100 --statistics - name: Type check with mypy run: mypy core plugins --ignore-missing-imports # =========================================================================== # Security Analysis # =========================================================================== security: name: Security Analysis runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install bandit[toml] safety - name: Run Bandit security linter run: bandit -r core plugins -f json -o bandit-report.json || true - name: Upload Bandit report uses: actions/upload-artifact@v4 if: always() with: name: bandit-report path: bandit-report.json - name: Run Safety check run: safety check || true # =========================================================================== # Tests # =========================================================================== test: name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] python-version: ["3.11", "3.12"] include: - os: ubuntu-latest python-version: "3.11" coverage: true steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install system dependencies (Ubuntu) if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install -e ".[dev]" - name: Run tests with coverage if: matrix.coverage run: | pytest tests/ -v --cov=core --cov=plugins --cov-report=xml --cov-report=html --cov-fail-under=80 env: QT_QPA_PLATFORM: offscreen - name: Run tests without coverage if: '!matrix.coverage' run: | pytest tests/ -v -m "not slow and not ui" env: QT_QPA_PLATFORM: offscreen - name: Upload coverage to Codecov if: matrix.coverage uses: codecov/codecov-action@v3 with: files: ./coverage.xml fail_ci_if_error: true verbose: true - name: Upload coverage report if: matrix.coverage uses: actions/upload-artifact@v4 with: name: coverage-report path: htmlcov/ # =========================================================================== # Build and Package # =========================================================================== build: name: Build Package runs-on: ubuntu-latest needs: [lint, test] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build twine - name: Build package run: python -m build - name: Check package run: twine check dist/* - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/ # =========================================================================== # Test Installation # =========================================================================== test-install: name: Test Installation runs-on: ${{ matrix.os }} needs: build strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_LATEST }} - name: Download build artifacts uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Install from wheel run: pip install dist/*.whl - name: Test CLI entry point run: | eu-utility --help || true python -c "from core import __version__; print(f'Version: {__version__}')" # =========================================================================== # Release # =========================================================================== release: name: Create Release runs-on: ubuntu-latest needs: [build, test-install] if: startsWith(github.ref, 'refs/tags/v') permissions: contents: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Download build artifacts uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: files: dist/* generate_release_notes: true draft: false prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }} # =========================================================================== # Publish to PyPI (on tagged releases) # =========================================================================== publish: name: Publish to PyPI runs-on: ubuntu-latest needs: release if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'dev') environment: name: pypi url: https://pypi.org/p/eu-utility permissions: id-token: write steps: - name: Download build artifacts uses: actions/download-artifact@v4 with: name: dist path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1