Refactor PR check workflow for efficiency #63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Checks | ||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.issue.number }} | ||
| cancel-in-progress: true | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| issues: write | ||
| statuses: write | ||
| jobs: | ||
| slash-command: | ||
| name: Parse /run-checks | ||
| # Only wake up if it's a PR comment and contains the exact command | ||
| if: >- | ||
| github.event.issue.pull_request != null && | ||
| contains(github.event.comment.body, '/run-checks') | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| pr-sha: ${{ steps.get-sha.outputs.sha }} | ||
| steps: | ||
| - name: Check commenter permission | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| username: context.actor, | ||
| }); | ||
| if (!['admin', 'write'].includes(data.permission)) { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: `@${context.actor} ❌ Only maintainers can trigger checks.`, | ||
| }); | ||
| core.setFailed('Unauthorized execution attempt.'); | ||
| } | ||
| - name: React with rocket | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.reactions.createForIssueComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: ${{ github.event.comment.id }}, | ||
| content: 'rocket', | ||
| }); | ||
| - name: Get PR head SHA | ||
| id: get-sha | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const { data: pr } = await github.rest.pulls.get({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: context.issue.number, | ||
| }); | ||
| core.setOutput('sha', pr.head.sha); | ||
| - name: Set checks to pending | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const sha = '${{ steps.get-sha.outputs.sha }}'; | ||
| const checks = [ | ||
| 'Lint & Code Quality', | ||
| 'Build C++ (ubuntu-22.04)', | ||
| 'Build C++ (ubuntu-24.04)', | ||
| 'Build C++ (macos-14)', | ||
| 'PyTorch & Python Tests', | ||
| 'Validate Configurations', | ||
| 'Docker Verification (Dockerfile)', | ||
| 'Docker Verification (Dockerfile.cpp)', | ||
| 'Docker Verification (Dockerfile.backend)' | ||
| ]; | ||
| for (const check of checks) { | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha, | ||
| state: 'pending', | ||
| context: check, | ||
| description: 'Waiting in queue...', | ||
| }); | ||
| } | ||
| lint: | ||
| name: Lint & Code Quality | ||
| needs: slash-command | ||
| # This explicit check forces the job to run when the slash-command job completes successfully | ||
| if: ${{ always() && needs.slash-command.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.slash-command.outputs.pr-sha }} | ||
| fetch-depth: 1 | ||
| - name: C++ format check (Advisory) | ||
| run: | | ||
| sudo apt-get update && sudo apt-get install -y clang-format | ||
| find . -name "*.cpp" -o -name "*.h" | grep -v "build/" | \ | ||
| xargs -I {} clang-format --dry-run {} || true | ||
| - name: Python lint (ruff) | ||
| uses: chartboost/ruff-action@v1 | ||
| with: | ||
| args: "check . --exit-zero" | ||
| - name: Report status | ||
| if: always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: '${{ needs.slash-command.outputs.pr-sha }}', | ||
| state: 'success', | ||
| context: 'Lint & Code Quality', | ||
| description: 'Completed with warnings ignored.', | ||
| }); | ||
| build-cpp: | ||
| name: Build C++ (${{ matrix.os }}) | ||
| needs: slash-command | ||
| if: ${{ always() && needs.slash-command.result == 'success' }} | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-22.04, ubuntu-24.04, macos-14] | ||
| include: | ||
| - os: ubuntu-22.04 | ||
| artifact: quadtrix-linux-x64 | ||
| - os: ubuntu-24.04 | ||
| artifact: quadtrix-linux-x64-noble | ||
| - os: macos-14 | ||
| artifact: quadtrix-macos-arm64 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.slash-command.outputs.pr-sha }} | ||
| - name: Install GCC (Linux) | ||
| if: runner.os == 'Linux' | ||
| run: sudo apt-get update && sudo apt-get install -y g++ ccache | ||
| - name: Cache ccache | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.ccache | ||
| key: ccache-${{ matrix.os }}-${{ hashFiles('**/*.cpp', '**/*.h') }} | ||
| restore-keys: ccache-${{ matrix.os }}- | ||
| - name: Compile main.cpp | ||
| run: | | ||
| g++ -std=c++17 -O3 -march=native \ | ||
| -I. -Iinclude \ | ||
| -o quadtrix main.cpp | ||
| - name: Smoke test | ||
| run: ./quadtrix --help || echo "No execution hooks configured yet." | ||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: ${{ matrix.artifact }} | ||
| path: quadtrix | ||
| retention-days: 7 | ||
| if-no-files-found: warn | ||
| - name: Report status | ||
| if: always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: '${{ needs.slash-command.outputs.pr-sha }}', | ||
| state: '${{ job.status }}' === 'success' ? 'success' : 'failure', | ||
| context: 'Build C++ (${{ matrix.os }})', | ||
| description: 'Completed with status: ${{ job.status }}', | ||
| }); | ||
| python-tests: | ||
| name: PyTorch & Python Tests | ||
| needs: slash-command | ||
| if: ${{ always() && needs.slash-command.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.slash-command.outputs.pr-sha }} | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.10' | ||
| cache: 'pip' | ||
| - name: Install Dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install torch --index-url https://download.pytorch.org/whl/cpu | ||
| if [ -f requirements.txt ]; then pip install -r requirements.txt; fi | ||
| pip install pytest | ||
| - name: Run Python Suite | ||
| run: | | ||
| if [ -d "tests" ]; then pytest tests/; else echo "No tests folder found yet. Skipping."; fi | ||
| - name: Report status | ||
| if: always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: '${{ needs.slash-command.outputs.pr-sha }}', | ||
| state: '${{ job.status }}' === 'success' ? 'success' : 'failure', | ||
| context: 'PyTorch & Python Tests', | ||
| description: 'Completed with status: ${{ job.status }}', | ||
| }); | ||
| validate: | ||
| name: Validate Configurations | ||
| needs: slash-command | ||
| if: ${{ always() && needs.slash-command.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.slash-command.outputs.pr-sha }} | ||
| - name: Check required files exist | ||
| run: | | ||
| files=( | ||
| "main.cpp" | ||
| "requirements.txt" | ||
| ) | ||
| failed=0 | ||
| for f in "${files[@]}"; do | ||
| if [ -f "$f" ]; then | ||
| echo "✅ $f" | ||
| else | ||
| echo "❌ $f — MISSING" | ||
| failed=1 | ||
| fi | ||
| done | ||
| exit $failed | ||
| - name: Report status | ||
| if: always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: '${{ needs.slash-command.outputs.pr-sha }}', | ||
| state: '${{ job.status }}' === 'success' ? 'success' : 'failure', | ||
| context: 'Validate Configurations', | ||
| description: 'Completed with status: ${{ job.status }}', | ||
| }); | ||
| docker-builds: | ||
| name: Docker Verification (${{ matrix.dockerfile }}) | ||
| needs: slash-command | ||
| if: ${{ always() && needs.slash-command.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| dockerfile: [Dockerfile, Dockerfile.cpp, Dockerfile.backend] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.slash-command.outputs.pr-sha }} | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
| - name: Build Verification Image | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: . | ||
| file: .devops/${{ matrix.dockerfile }} | ||
| push: false | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| - name: Report status | ||
| if: always() | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.repos.createCommitStatus({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: '${{ needs.slash-command.outputs.pr-sha }}', | ||
| state: '${{ job.status }}' === 'success' ? 'success' : 'failure', | ||
| context: 'Docker Verification (${{ matrix.dockerfile }})', | ||
| description: 'Completed with status: ${{ job.status }}', | ||
| }); | ||
| post-result: | ||
| name: Post result | ||
| needs: [slash-command, lint, build-cpp, python-tests, validate, docker-builds] | ||
| runs-on: ubuntu-latest | ||
| if: always() && needs.slash-command.result == 'success' | ||
| steps: | ||
| - uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const jobs = ${{ toJSON(needs) }}; | ||
| const failed = Object.values(jobs).some(j => j.result === 'failure' || j.result === 'cancelled'); | ||
| const message = failed | ||
| ? '❌ **Some verification runs failed.** Please review the [Actions Tab](https://github.com/${{ context.repo.owner }}/${{ context.repo.repo }}/actions/runs/${{ github.run_id }}) for debugging details.' | ||
| : '✅ **All system workflows finalized cleanly!** Ready for integration.'; | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: message | ||
| }); | ||