Recipes¶
Here are some example workflows to get started quickly.
auto-fix clang-formatonly clang-tidyonly clang-formatonly PR commentsPR comments from forks
name: cpp-linter (auto-fix)
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
jobs:
cpp-linter:
runs-on: ubuntu-latest
permissions: # explicit permissions granted to the `secrets.GITHUB_TOKEN`
contents: write # needed for auto-fix commits
pull-requests: read # needed to list changed files on pull_request events
steps:
- uses: actions/checkout@v7
# Pushes made with the default GITHUB_TOKEN do not start new workflow
# runs. To have the auto-fix commit re-checked by CI, check out and run
# the action with a GitHub App token; see the permissions docs.
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file' # Use .clang-format config file. (2)
tidy-checks: '-*' # disable clang-tidy. (3)
auto-fix: 'true' # auto-apply clang-format fixes. (1)
- name: Fail fast?!
if: steps.linter.outputs.clang-format-checks-failed > 0
run: exit 1
- See also
auto-fix - See also
style - See also
tidy-checks
name: cpp-linter
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
push:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
jobs:
cpp-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# ... optionally setup build env to create a compilation database
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: '' # disable clang-format checks. (1)
tidy-checks: '' # Use .clang-tidy config file. (2)
- name: Fail fast?!
if: steps.linter.outputs.clang-tidy-checks-failed > 0
run: exit 1
- See also
style - See also
tidy-checks
name: cpp-linter
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
push:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
jobs:
cpp-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# ... optionally setup build env to create a compilation database
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file' # Use .clang-format config file. (1)
tidy-checks: '-*' # disable clang-tidy checks. (2)
- name: Fail fast?!
if: steps.linter.outputs.clang-format-checks-failed > 0
run: exit 1
- See also
style - See also
tidy-checks
name: cpp-linter
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
push:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
jobs:
cpp-linter:
runs-on: ubuntu-latest
permissions: # (1)!
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v7
# ... optionally setup build env to create a compilation database
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file' # Use .clang-format config file. (2)
tidy-checks: '' # Use .clang-tidy config file. (3)
# only 'update' a single comment in a pull request's thread. (4)
# Pull requests from forks get a read-only token, so skip the comment there.
thread-comments: ${{ github.event.pull_request.head.repo.full_name == github.repository && 'update' }}
- name: Fail fast?!
if: steps.linter.outputs.checks-failed > 0
run: exit 1
- See also our token permissions document
- See also
style - See also
tidy-checks - See also
thread-comments
The token of a pull request from a fork is read-only, so the action cannot comment
there. Split the work into two workflows: the first lints with a read-only token and
uploads the comment as an artifact; the second runs on workflow_run with
pull-requests: write and posts it. See pull requests from forks.
.github/workflows/cpp-linter.yml
name: cpp-linter # (1)!
on:
pull_request:
branches: [main, master, develop]
paths: ['**.c', '**.cpp', '**.h', '**.hpp', '**.cxx', '**.hxx', '**.cc', '**.hh', '**CMakeLists.txt', 'meson.build', '**.cmake']
permissions: {}
jobs:
cpp-linter:
runs-on: ubuntu-latest
permissions: # (2)!
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
# ... optionally setup build env to create a compilation database
- uses: cpp-linter/cpp-linter-action@v2
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
style: 'file' # Use .clang-format config file. (3)
tidy-checks: '' # Use .clang-tidy config file. (4)
# No thread-comments here: this job's token is read-only. (5)
summary-output-file: ${{ runner.temp }}/cpp-linter-comment/comment.md
- name: Save the pull request number
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
mkdir -p "$RUNNER_TEMP/cpp-linter-comment"
echo "$PR_NUMBER" > "$RUNNER_TEMP/cpp-linter-comment/pr-number.txt"
- uses: actions/upload-artifact@v7
with:
name: cpp-linter-comment
path: ${{ runner.temp }}/cpp-linter-comment/
retention-days: 7
- name: Fail fast?!
if: steps.linter.outputs.checks-failed > 0
run: exit 1
- The second workflow finds this one by its
name. - Read-only, as for any pull request from a fork.
- See also
style - See also
tidy-checks - See also
thread-commentsandsummary-output-file
.github/workflows/cpp-linter-comment.yml
name: cpp-linter comment
on:
workflow_run:
workflows: [cpp-linter] # (1)!
types: [completed]
permissions: {}
# Post one run at a time per branch, so two runs cannot both add a new comment.
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: false
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
permissions:
actions: read # (2)!
pull-requests: write
steps:
# No checkout: this job never runs code from the pull request. (3)
- uses: actions/download-artifact@v8
id: download
continue-on-error: true # The lint run may have stopped before uploading.
with:
name: cpp-linter-comment
path: ${{ runner.temp }}/cpp-linter-comment
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Post or update the comment
if: steps.download.outcome == 'success'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
run: |
dir="$RUNNER_TEMP/cpp-linter-comment"
# The artifact was made while linting the pull request: use it only as data.
if [ ! -f "$dir/pr-number.txt" ] || [ ! -f "$dir/comment.md" ]; then
echo "::warning::The artifact has no pull request number or no comment"
exit 0
fi
pr="$(tr -cd '0-9' < "$dir/pr-number.txt" | head -c 10)"
if [ -z "$pr" ]; then
echo "::warning::The artifact has no pull request number"
exit 0
fi
# Only comment on the pull request whose head the lint run checked. (4)
if [ "$(gh api "repos/$GH_REPO/pulls/$pr" --jq '.head.sha + " " + .head.repo.full_name')" != "$HEAD_SHA $HEAD_REPO" ]; then
echo "::notice::Pull request #$pr has new commits since this run; not commenting"
exit 0
fi
marker='<!-- cpp-linter-action workflow_run comment -->'
body="$RUNNER_TEMP/body.md"
if [ "$(wc -c < "$dir/comment.md")" -gt 60000 ]; then
# A comment holds at most 65536 characters.
printf '%s\nToo many findings to show here. See the [workflow run](%s).\n' "$marker" "$RUN_URL" > "$body"
else
{ echo "$marker"; cat "$dir/comment.md"; } > "$body"
fi
# Update the comment from an earlier run instead of adding another one.
id="$(gh api --paginate "repos/$GH_REPO/issues/$pr/comments" \
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id" \
| head -n 1)"
if [ -n "$id" ]; then
gh api -X PATCH "repos/$GH_REPO/issues/comments/$id" -F "body=@$body" > /dev/null
else
gh api "repos/$GH_REPO/issues/$pr/comments" -F "body=@$body" > /dev/null
fi
- Must match the
nameof the first workflow. GitHub runs this workflow from the default branch only, so it takes effect after it is merged there. - Needed to download the artifact of the first workflow's run.
- This workflow has a write token, so it must not check out or run anything from the pull request. It only reads two files from the artifact.
- Anyone who opens a pull request can change the first workflow and so the artifact. The pull request number in it is checked against the commit and the fork that the first workflow linted.