Skip to content

beojan checking C++ code with clang-tidy #1514

beojan checking C++ code with clang-tidy

beojan checking C++ code with clang-tidy #1514

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."