Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [main]
paths:
- 'action.yml'
- 'format/**'
- '.python-version'
- 'uv.lock'
- 'docs/examples/demo/**'
Expand All @@ -13,6 +14,7 @@ on:
branches: [main]
paths:
- 'action.yml'
- 'format/**'
- '.python-version'
- 'uv.lock'
- 'docs/examples/demo/**'
Expand All @@ -21,6 +23,7 @@ on:
# branches: [main]
# paths:
# - 'action.yml'
# - 'format/**'
# - '.python-version'
# - 'uv.lock'
# - 'docs/examples/demo/**'
Expand Down Expand Up @@ -182,3 +185,67 @@ jobs:
exit 1
fi
echo "PR branch is untouched, as expected"

test-format:
# Runs the format/ sub-action. The changed-lines run skips the mis-formatted demo
# sources and passes; the full run checks them and must fail.
permissions:
contents: read
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
clang-version: ['21']
include:
- os: ubuntu-latest
clang-version: '12'
- os: ubuntu-latest
clang-version: '23'
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Check the changed lines (expected to pass)
uses: ./format
id: changed
with:
version: ${{ matrix.clang-version }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
# The demo is mis-formatted on purpose; the next step covers it.
ignore: docs/examples/demo

- name: Check every file (expected to fail)
uses: ./format
id: all
continue-on-error: true
with:
version: ${{ matrix.clang-version }}
files-changed-only: false

- name: Check every file except the demo (expected to pass)
uses: ./format
id: ignored
with:
version: ${{ matrix.clang-version }}
files-changed-only: false
ignore: docs/examples/demo

- name: Assert the results
shell: bash
env:
CHANGED_FAILED: ${{ steps.changed.outputs.checks-failed }}
ALL_OUTCOME: ${{ steps.all.outcome }}
ALL_FAILED: ${{ steps.all.outputs.checks-failed }}
IGNORED_FAILED: ${{ steps.ignored.outputs.checks-failed }}
USED_VERSION: ${{ steps.all.outputs.clang-format-version }}
WANTED_VERSION: ${{ matrix.clang-version }}
run: |
set -euo pipefail
[ "$CHANGED_FAILED" = "0" ] || { echo "::error::changed-lines run reported $CHANGED_FAILED file(s)"; exit 1; }
[ "$ALL_OUTCOME" = "failure" ] || { echo "::error::full run should fail, got $ALL_OUTCOME"; exit 1; }
[ "$ALL_FAILED" = "2" ] || { echo "::error::full run should report 2 files, got $ALL_FAILED"; exit 1; }
[ "$IGNORED_FAILED" = "0" ] || { echo "::error::ignore did not skip the demo"; exit 1; }
case "$USED_VERSION" in "$WANTED_VERSION".*) ;; *) echo "::error::used clang-format $USED_VERSION, wanted $WANTED_VERSION"; exit 1 ;; esac
echo "format sub-action OK with clang-format $USED_VERSION"
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[permissions-doc]: https://cpp-linter.github.io/cpp-linter-action/permissions
[app-token-doc]: https://cpp-linter.github.io/cpp-linter-action/permissions/#github-app-token
[tools-doc]: https://cpp-linter.github.io/cpp-linter-action/required-tools
[format-check-doc]: https://cpp-linter.github.io/cpp-linter-action/format-check

[format-annotations-preview]: https://raw.githubusercontent.com/cpp-linter/cpp-linter-action/main/docs/images/annotations-clang-format.png
[tidy-annotations-preview]: https://raw.githubusercontent.com/cpp-linter/cpp-linter-action/main/docs/images/annotations-clang-tidy.png
Expand Down Expand Up @@ -41,8 +42,39 @@ reviews (with [`tidy-review`][tidy-review] or [`format-review`][format-review]).

## Quick start

### Check formatting

Save this as `.github/workflows/cpp-linter.yml`:

```yaml
name: cpp-linter
on:
pull_request:
push:
branches: [main]

jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: cpp-linter/cpp-linter-action/format@v2
```

- Checks the lines the pull request or push changes against your `.clang-format`, with
clang-format 21. Set `version` for another major from 12 to 23.
- Fails the job when a line is not formatted, marks the line in the diff view, and puts the
change clang-format would make in the step summary.
- Needs no token or extra permissions, so pull requests from forks get the same result.
Linux, macOS and Windows runners work.
- [Format check][format-check-doc] lists the inputs and how to move over from other
clang-format actions.

### Add clang-tidy and pull request reviews

Use the main action to also run clang-tidy and to post review suggestions or a comment.
Save this as `.github/workflows/cpp-linter.yml` instead:

```yaml
name: cpp-linter
on: pull_request
Expand Down
113 changes: 113 additions & 0 deletions docs/format-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: Format check
description: A one-step GitHub Action that fails a pull request or push when the C/C++ lines it changes are not clang-formatted.
---

# Format check

`cpp-linter/cpp-linter-action/format` checks that the C/C++ lines a pull request or push
changes follow clang-format, and fails the job when they do not. It is a separate, smaller
action in this repository: one step, no token, no Python.

```yaml
name: cpp-linter
on:
pull_request:
push:
branches: [main]

jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: cpp-linter/cpp-linter-action/format@v2
```

## What it does

1. Downloads the newest patch release of the clang-format major you ask for (21 by default)
from [cpp-linter/clang-tools-static-binaries], and checks its SHA-512.
2. Works out what changed:
- `pull_request`: the merge commit that `actions/checkout` checks out, compared with its
first parent (the base branch).
- `push`: the pushed commits, compared with the commit before the push (for a new branch,
with the parent of the last commit).
- `pull_request_target`: the same, but only when the job checks out the pull request
(`ref: refs/pull/<number>/merge`); with the default checkout of the base branch the step
fails and asks for `pull_request` instead.
- `merge_group`: the merge queue's base commit.
- Any other event, such as `workflow_dispatch` or `schedule`: every file.

The base commit is fetched on its own, so the default `fetch-depth: 1` of
`actions/checkout` is enough.
3. Runs `clang-format --dry-run --Werror` on the changed lines of each changed file.
4. For every line that is not formatted, adds an error annotation, which shows in the pull
request's diff view. The step summary lists the files and shows the change clang-format
would make; the job log has the full diff.
5. Fails the step when any file needs formatting.

Lines the change does not touch are not checked, so an old code base can turn the check on
without reformatting everything first.

## Inputs

| Input | Default | Description |
|---|---|---|
| `version` | `21` | clang-format major version, from 12 to 23. |
| `style` | `file` | Passed to `clang-format --style`. `file` uses the closest `.clang-format`, and LLVM style when there is none. Any built-in style (`google`, `webkit`, ...) works too. |
| `extensions` | `c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx` | File extensions to check, comma-separated. |
| `ignore` | (none) | Paths to skip, separated by `\|` or new lines, as git pathspecs relative to the repository root. `build` skips the `build` directory, `*.pb.cc` skips matching files anywhere. |
| `files-changed-only` | `true` | Check only the files the pull request or push changes. `false` checks every tracked file with a matching extension. |
| `lines-changed-only` | `true` | Check only the added or changed lines. `false` checks the whole of each changed file. |

Files listed in a [`.clang-format-ignore`][clang-format-ignore] file are skipped too, with
clang-format 18 or later.

## Outputs

| Output | Description |
|---|---|
| `checks-failed` | The number of files with style issues. |
| `clang-format-version` | The full clang-format version used, for example `21.1.8`. |

The step fails when `checks-failed` is not 0, and also when clang-format itself fails, for
example on a broken `.clang-format` (those files are not counted in `checks-failed`). To
report without failing, set `continue-on-error: true` on the step.

## Moving from another clang-format action

Those actions check the whole repository by default. To get the same, set
`files-changed-only: false`; to check only what each pull request changes, keep the default.

| [DoozyX/clang-format-lint-action] | This action |
|---|---|
| `clangFormatVersion: 18` | `version: 18` (12 to 23) |
| `style: file` | `style: file` (the default) |
| `extensions: 'h,cpp'` | `extensions: 'h,cpp'` |
| `exclude` (paths to skip) | `ignore`, with paths separated by `\|` or new lines |
| `source: './src'` | not needed; use `ignore` for paths to skip |
| `inplace: True` | use the main action's [`auto-fix`](inputs-outputs.md#auto-fix) |

| [jidicula/clang-format-action] | This action |
|---|---|
| `clang-format-version: '22'` | `version: '22'` (12 to 23) |
| `check-path: 'src'` | not needed; use `ignore` for paths to skip |
| `exclude-regex: '^./third_party/.*$'` | `ignore: 'third_party'` (a pathspec, not a regex) |
| `include-regex` | `extensions` (a list of extensions, not a regex) |

## Runners and limits

- GitHub-hosted Linux (x64, arm64), macOS and Windows runners work. Self-hosted runners and
`container:` jobs need `bash`, `git`, `curl` and `jq`.
- It only reports. For review suggestions, a pull request comment, auto-fix or clang-tidy,
use the [main action](index.md).
- `--style=file` reads `.clang-format` files from the checked-out pull request, as
clang-format does locally.
- Symbolic links are skipped, so a pull request cannot point clang-format at a file outside
the checkout.

[cpp-linter/clang-tools-static-binaries]: https://github.com/cpp-linter/clang-tools-static-binaries
[clang-format-ignore]: https://clang.llvm.org/docs/ClangFormat.html#clang-format-ignore
[DoozyX/clang-format-lint-action]: https://github.com/DoozyX/clang-format-lint-action
[jidicula/clang-format-action]: https://github.com/jidicula/clang-format-action
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ description: A GitHub Action that runs clang-format and clang-tidy on the C/C++
[permissions-doc]: permissions.md
[app-token-doc]: permissions.md#github-app-token
[tools-doc]: required-tools.md
[format-check-doc]: format-check.md

[format-annotations-preview]: images/annotations-clang-format.png
[tidy-annotations-preview]: images/annotations-clang-tidy.png
Expand Down
70 changes: 70 additions & 0 deletions format/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: C/C++ format check
description: >-
Check that the C/C++ lines a pull request or push changes follow clang-format.
One line to set up, fails the job on style issues, no token needed.
author: cpp-linter
branding:
icon: "check-circle"
color: "green"
inputs:
version:
description: |
The clang-format major version to use, from 12 to 23.
The newest patch release of that major from
[cpp-linter/clang-tools-static-binaries](https://github.com/cpp-linter/clang-tools-static-binaries)
is downloaded and its SHA-512 checked.
required: false
default: '21'
style:
description: |
The style passed to `clang-format --style`.
The default `file` uses the closest `.clang-format` file, and LLVM style when there is none.
required: false
default: file
extensions:
description: The file extensions to check, comma-separated.
required: false
default: "c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx"
ignore:
description: |
Paths to skip, separated by `|` or new lines, as git pathspecs relative to the
repository root: `build` skips the build directory, `*.pb.cc` skips matching files
anywhere. Files listed in `.clang-format-ignore` are skipped too (clang-format 18+).
required: false
default: ''
files-changed-only:
description: |
Check only the files the pull request or push changes.
Set to `false` to check every tracked file with a matching extension.
Other events (for example `workflow_dispatch`) always check every file.
required: false
default: 'true'
lines-changed-only:
description: |
Check only the lines the pull request or push adds or changes.
Set to `false` to check the whole of each changed file.
required: false
default: 'true'
outputs:
checks-failed:
description: The number of files with style issues.
value: ${{ steps.check.outputs.checks-failed }}
clang-format-version:
description: The full clang-format version that was used, for example `21.1.8`.
value: ${{ steps.check.outputs.clang-format-version }}
runs:
using: composite
steps:
- name: Check C/C++ format
id: check
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_STYLE: ${{ inputs.style }}
INPUT_EXTENSIONS: ${{ inputs.extensions }}
INPUT_IGNORE: ${{ inputs.ignore }}
INPUT_FILES_CHANGED_ONLY: ${{ inputs.files-changed-only }}
INPUT_LINES_CHANGED_ONLY: ${{ inputs.lines-changed-only }}
# Only used to fetch the base commit when actions/checkout did not keep its credentials.
INPUT_TOKEN: ${{ github.token }}
run: bash "$GITHUB_ACTION_PATH/check.sh"
Loading
Loading