beojan checking C++ code with clang-tidy #1514
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: Clang-Tidy Check | |
| 'run-name': "${{ github.actor }} checking C++ code with clang-tidy" | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| on: | |
| pull_request: | |
| issue_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: "The branch, ref, or SHA to checkout. Defaults to the repository's default branch." | |
| required: false | |
| type: string | |
| jobs: | |
| pre-check: | |
| if: > | |
| github.event_name == 'workflow_dispatch' || | |
| github.event_name == 'pull_request' || | |
| ( | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(fromJSON('["OWNER", "COLLABORATOR", "MEMBER"]'), github.event.comment.author_association) && | |
| startsWith(github.event.comment.body, '@phlexbot tidy-check') | |
| ) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is_act: ${{ steps.detect_act.outputs.is_act }} | |
| ref: ${{ (github.event_name == 'workflow_dispatch' && (github.event.inputs.ref || github.ref)) || steps.pr.outputs.ref || github.sha }} | |
| repo: ${{ steps.pr.outputs.repo || github.repository }} | |
| base_sha: ${{ steps.pr.outputs.base_sha || github.event.pull_request.base.sha || github.event.before }} | |
| pr_number: ${{ github.event.pull_request.number || github.event.issue.number }} | |
| steps: | |
| - name: Get PR Info | |
| if: github.event_name == 'issue_comment' | |
| id: pr | |
| uses: Framework-R-D/phlex/.github/actions/get-pr-info@main | |
| - name: Detect act environment | |
| id: detect_act | |
| uses: Framework-R-D/phlex/.github/actions/detect-act-env@main | |
| detect-changes: | |
| needs: pre-check | |
| if: > | |
| needs.pre-check.result == 'success' && | |
| github.event_name != 'workflow_dispatch' && | |
| needs.pre-check.outputs.is_act != 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| outputs: | |
| has_changes: ${{ steps.filter.outputs.matched }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| path: phlex-src | |
| ref: ${{ needs.pre-check.outputs.ref }} | |
| repository: ${{ needs.pre-check.outputs.repo }} | |
| - name: Detect relevant changes | |
| id: filter | |
| uses: Framework-R-D/phlex/.github/actions/detect-relevant-changes@main | |
| with: | |
| repo-path: phlex-src | |
| base-ref: ${{ needs.pre-check.outputs.base_sha }} | |
| head-ref: ${{ needs.pre-check.outputs.ref }} | |
| file-type: | | |
| cpp | |
| cmake | |
| - name: Report detection outcome | |
| run: | | |
| if [ "${{ steps.filter.outputs.matched }}" != "true" ]; then | |
| echo "::notice::No clang-tidy relevant changes detected; job will be skipped." | |
| else | |
| echo "::group::Clang-tidy relevant files" | |
| printf '%s\n' "${{ steps.filter.outputs.matched_files }}" | |
| echo "::endgroup::" | |
| fi | |
| clang-tidy-check: | |
| needs: [pre-check, detect-changes] | |
| if: > | |
| always() && | |
| ( | |
| needs.detect-changes.result == 'skipped' || | |
| ( | |
| needs.detect-changes.result == 'success' && | |
| needs.detect-changes.outputs.has_changes == 'true' | |
| ) | |
| ) | |
| runs-on: ubuntu-24.04 | |
| container: | |
| image: ghcr.io/framework-r-d/phlex-ci:latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ needs.pre-check.outputs.ref }} | |
| path: phlex-src | |
| repository: ${{ needs.pre-check.outputs.repo }} | |
| - name: Setup build environment | |
| uses: Framework-R-D/phlex/.github/actions/setup-build-env@main | |
| - name: Configure CMake (Debug) | |
| uses: Framework-R-D/phlex/.github/actions/configure-cmake@main | |
| with: | |
| build-type: Debug | |
| extra-options: "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_SCAN_FOR_MODULES=OFF -DCMAKE_CXX_CLANG_TIDY='clang-tidy;--export-fixes=clang-tidy-fixes.yaml'" | |
| - name: Run clang-tidy using CMake | |
| id: tidy | |
| shell: bash | |
| run: | | |
| . /entrypoint.sh | |
| cd "$GITHUB_WORKSPACE/phlex-build" | |
| echo "➡️ Running clang-tidy checks..." | |
| cmake_status=0 | |
| cmake --build . -j "$(nproc)" > clang-tidy.log 2>&1 || cmake_status=$? | |
| # Distinguish tooling failures from issue detection by checking log content | |
| if [ "$cmake_status" -ne 0 ]; then | |
| if ! grep -qE '^/.+\.(cpp|hpp|c|h):[0-9]+:[0-9]+: (warning|error):' clang-tidy.log; then | |
| echo "::error::clang-tidy failed without producing diagnostic output (exit code $cmake_status)" | |
| echo "::group::clang-tidy log output" | |
| cat clang-tidy.log | |
| echo "::endgroup::" | |
| exit "$cmake_status" | |
| fi | |
| fi | |
| if grep -qE '^/.+\.(cpp|hpp|c|h):[0-9]+:[0-9]+: (warning|error):' clang-tidy.log; then | |
| echo "has_issues=true" >> "$GITHUB_OUTPUT" | |
| echo "::warning::Clang-tidy found issues in the code" | |
| echo "Error count by check (full details in clang-tidy-log artifact):" | |
| sed -nEe '\&^/& s&^.*\[([^][:space:]]+)\]$&\1&p' clang-tidy.log | sort | uniq -c | sort -n -k 1 -r | |
| echo "Comment '@${{ github.event.repository.name }}bot tidy-fix [<check>...]' on the PR to attempt auto-fix" | |
| else | |
| echo "has_issues=false" >> "$GITHUB_OUTPUT" | |
| echo "✅ clang-tidy check passed" | |
| fi | |
| - name: Upload clang-tidy report | |
| if: always() | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: clang-tidy-report | |
| path: phlex-build/clang-tidy-fixes.yaml | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| - name: Upload clang-tidy log | |
| if: always() | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: clang-tidy-log | |
| path: phlex-build/clang-tidy.log | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| clang-tidy-pr-comments: | |
| needs: [pre-check, clang-tidy-check] | |
| if: always() && needs.clang-tidy-check.result == 'success' && (github.event_name == 'pull_request' || github.event_name == 'issue_comment') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Download clang-tidy report | |
| uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| with: | |
| name: clang-tidy-report | |
| path: phlex-build | |
| continue-on-error: true | |
| - name: Download clang-tidy log | |
| uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 | |
| with: | |
| name: clang-tidy-log | |
| path: phlex-build | |
| continue-on-error: true | |
| - name: Setup Node.js | |
| if: github.event_name == 'issue_comment' | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: '20.x' | |
| - name: Install js-yaml | |
| if: github.event_name == 'issue_comment' | |
| run: npm install js-yaml | |
| - name: Check if artifacts exist | |
| id: check_artifacts | |
| run: | | |
| if [ -f phlex-build/clang-tidy-fixes.yaml ]; then | |
| echo "has_fixes=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_fixes=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Post clang-tidy comments | |
| if: steps.check_artifacts.outputs.has_fixes == 'true' | |
| uses: platisd/clang-tidy-pr-comments@28cfb84edafa771c044bde7e4a2a3fae57463818 # v1.8.0 | |
| with: | |
| clang_tidy_fixes: phlex-build/clang-tidy-fixes.yaml | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| pull_request_id: ${{ github.event.pull_request.number || needs.pre-check.outputs.pr_number }} | |
| - name: Post summary comment | |
| if: steps.check_artifacts.outputs.has_fixes == 'true' && github.event_name == 'issue_comment' | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const yaml = require('js-yaml'); | |
| const fixesFile = 'phlex-build/clang-tidy-fixes.yaml'; | |
| const checkCounts = {}; | |
| let totalIssues = 0; | |
| if (fs.existsSync(fixesFile)) { | |
| try { | |
| const content = yaml.load(fs.readFileSync(fixesFile, 'utf8')); | |
| if (content?.Diagnostics) { | |
| content.Diagnostics.forEach(diag => { | |
| if (diag.DiagnosticName) { | |
| checkCounts[diag.DiagnosticName] = (checkCounts[diag.DiagnosticName] || 0) + 1; | |
| totalIssues++; | |
| } | |
| }); | |
| } | |
| } catch (e) { | |
| console.log(`Error reading fixes file: ${e.message}`); | |
| } | |
| } | |
| const sorted = Object.entries(checkCounts).sort((a, b) => b[1] - a[1]); | |
| let body = '## Clang-Tidy Check Results\n\n'; | |
| body += `Found ${totalIssues} issue(s) in the code.\n\n`; | |
| if (sorted.length > 0) { | |
| body += '### Issues by check:\n\n'; | |
| sorted.forEach(([check, count]) => { | |
| body += `- **${check}**: ${count}\n`; | |
| }); | |
| body += '\n'; | |
| } | |
| body += 'See inline comments for details. '; | |
| body += 'Comment `@phlexbot tidy-fix [<check>...]` to attempt auto-fix.'; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ${{ needs.pre-check.outputs.pr_number }}, | |
| body: body | |
| }); | |
| clang-tidy-check-skipped: | |
| needs: [pre-check, detect-changes] | |
| if: > | |
| needs.pre-check.result == 'success' && | |
| github.event_name != 'workflow_dispatch' && | |
| needs.pre-check.outputs.is_act != 'true' && | |
| (needs.detect-changes.result == 'success' && needs.detect-changes.outputs.has_changes != 'true') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: No relevant clang-tidy changes detected | |
| run: echo "::notice::No clang-tidy relevant changes detected; check skipped." |