GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • cpp-linter/cpp-linter-action
  • cpp-linter-action
  • Inputs and Outputs
  • Pull Request Review Caveats
  • Token Permissions
  • Required Tools
  • Recipes
  • Contributing
  • ← cpp-linter hub

This view

  • Original page
  • New address

GeezerVison is showing cpp-linter.github.io — Recipes - C/C++ Linter Action

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
  1. See also auto-fix
  2. See also style
  3. 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
  1. See also style
  2. 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
  1. See also style
  2. 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
  1. See also our token permissions document
  2. See also style
  3. See also tidy-checks
  4. 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
  1. The second workflow finds this one by its name.
  2. Read-only, as for any pull request from a fork.
  3. See also style
  4. See also tidy-checks
  5. See also thread-comments and summary-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
  1. Must match the name of the first workflow. GitHub runs this workflow from the default branch only, so it takes effect after it is merged there.
  2. Needed to download the artifact of the first workflow's run.
  3. 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.
  4. 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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.